首先要知道 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。

