首先要知道 SQLite 只支持如下五种存储类型:
存储类 | 描述 |
---|---|
NULL | 值是一个 NULL 值。 |
INTEGER | 值是一个带符号的整数,根据值的大小存储在 1、2、3、4、6 或 8 字节中。 |
REAL | 值是一个浮点值,存储为 8 字节的 IEEE 浮点数字。 |
TEXT | 值是一个文本字符串,使用数据库编码格式(默认为 UTF-8)存储。 |
BLOB | 值是一个二进制数据,完全根据它的输入存储。 |
SQLite.swift 将这五种存储类型与 Swift 数据类型做了一一对应:
Swift Type | SQLite Type |
---|---|
Int64 | INTEGER |
Double | REAL |
String | TEXT |
nil | NULL |
SQLite.Blob | BLOB |
其中 SQLite.Blob 类型是 SQLite.swift 自己定义的类型。另外,对于 Swift 的 Bool 类型,SQLite.swift 会将其转换成 Int64 然后以 INTEGER 类型进行存储(0 表示 false, 1 表示 true);对于 Swift 的 Date 类型,SQLite.swift 会将其转换成 String 类型后,通过 TEXT 类型进行存储;Int 类型也会自动转换成 Int64 后进行存储。
如果我们想定义其他的 Swift 数据类型到 SQLite 存储类型的转换,就得实现扩展 Swift 数据类型,使其满足 SQLite.Value 协议。Value 协议的作用就是将 Swfit 数据类型 A 转换成上述五种基本的 Swfit Type 之一,然后在存储时自动转换成对应的 SQLite Type 进行存储。
以 Swift 的 UUID 类型为例,最简单的做法就是将其转化成字符串进行存储。更“困难”一点的,也是本文主要想描述的,就是将 UUID 类型转换成 SQLite.Blob 类进行存储。
import SQLite struct Book { var id: Int64 var uuid: UUID var title: String var readed: Bool var read_at: Date? } // 扩展 UUID,使其满足 SQLite.Value 协议 // 实现 UUID <==> SQLite.Blob 的转换 extension UUID: Value { // 它的目标类型是 SQLite.Blob public typealias Datatype = Blob public static var declaredDatatype: String { return Blob.declaredDatatype } // 将目标类型转换成 UUID // SQLite.Blob 类型有个 bytes: [UInt8] 的存储属性,存储着实际的二进制数据 public static func fromDatatypeValue(_ datatypeValue: Datatype) -> UUID { let bytes = datatypeValue.bytes return UUID(uuid: (bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15])) } // 将 UUID 类型转换成目标类型 // 可以通过 UUID.uuid 计算属性获取其二进制值的表示,包含 16 个 UInt8 数据的元组:(UInt8, UInt8, ...) public var datatypeValue: Datatype { var bytes = [UInt8](repeating: 0, count: 16) (bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]) = self.uuid return Datatype(bytes: bytes) } } /** * 以下是测试代码 */ #if DEBUG db.trace { print("execute sql: \($0)" ) } #endif // 声明表的字段类型 let id = Expression<Int64>("id") let uuid = Expression<UUID>("uuid") // 注意!这里用的是 UUID,不是 Blob! let title = Expression<String>("title") let readed = Expression<Bool>("readed") // 这里用 Bool,实际存储时候会被自动转换成整形 let read_at = Expression<Date?>("read_at") // 这里用 Date,实际存储时候会被自动转换成字符串。并且用可选型表示该字段可以为空 let books = Table("books") // 创建表 try! db.run(books.create { t in t.column(id, primaryKey: true) t.column(uuid, unique: true) t.column(title) t.column(readed) t.column(read_at) }) // 插入数据 try! db.run(books.insert( uuid <- UUID(), title <- "1984", readed <- true, read_at <- Date() )) try! db.run(books.insert( uuid <- UUID(), title <- "美丽新世界", readed <- false )) // 读取数据 for book in try! db.prepare(books) { let b = Book(id: book[id], uuid: book[uuid], title: book[title], readed: book[readed], read_at: book[read_at] ?? nil) print(b) }
执行完后,我们打开 SQLite 数据库文件看一下:
表结构如下:
一个题外话,SQLite 的主键如果是整形(以上面的主键 id 为例),如果插入新数据时没有给出 id 值,那么系统默认就会使用(当前最大 id + 1)来进行填充。这看起来就像默认自增一样,这个值其实是来自 SQLite 的一个隐藏字段 rowid。不过与强制定义该列为 AUTOINCREMENT 不同的是,自增字段会记录上次用到的自增值(由系统自动记录在额外的一张 sqlite_sequence 表中),下次新增时候就以该值 +1。而默认的非自增整形主键,是会查询当前表中最大的 id 值来进行 +1。举个例子,如果表中已有 id = 1、2、3、4、5 五行数据,然后删除了 id = 4、5 这两行后。对于非自增整形主键,下次插入的 id 会是 4;对于自增主键,下次插入的 id 会是 6。