增删查改是数据库最常用的功能,因此 WCDB Swift 对其进行了特别的封装,使其通过一行代码即可完成操作。
插入操作
插入操作有 "insert" 和 "insertOrReplace" 两个接口。故名思义,前者只是单纯的插入数据,当数据出现冲突时会失败,而后者在主键一致时,新数据会覆盖旧数据。
以已经完成模型绑定的类 Sample
为例:
- class Sample: TableCodable {
- var identifier: Int? = nil
- var description: String? = nil
- enum CodingKeys: String, CodingTableKey {
- typealias Root = Sample
- static let objectRelationalMapping = TableBinding(CodingKeys.self)
- case identifier
- case description
- static var columnConstraintBindings: [CodingKeys: ColumnConstraintBinding]? {
- return [
- identifier: ColumnConstraintBinding(isPrimary: true),
- ]
- }
- }
- }
- try database.create(table: "sampleTable", of: Sample.self)
- let object = Sample()
- sample.identifier = 1
- sample.description = "insert"
- try database.insert(objects: object, intoTable: "sampleTable") // 插入成功
- try database.insert(objects: object, intoTable: "sampleTable") // 插入失败,因为主键 identifier = 1 已经存在
- sample.description = "insertOrReplace"
- try database.insertOrReplace(objects: object, intoTable: "sampleTable") // 插入成功,且 description 的内容会被替换为 "insertOrReplace"
关于自增插入,可参考模型绑定 - 自增属性一章。
"insert" 函数的原型为:
- // insert 和 insertOrReplace 函数只有函数名不同,其他参数都一样。
- func insert<Object: TableEncodable>(
- objects: [Object], // 需要插入的对象。WCDB Swift 同时实现了可变参数的版本,因此可以传入一个数组,也可以传入一个或多个对象。
- on propertyConvertibleList: [PropertyConvertible]? = nil, // 需要插入的字段
- intoTable table: String // 表名
- ) throws
这里需要特别注意的是 propertyConvertibleList
参数,它是 遵循 PropertyConveritble
协议的对象的数组。我们会在语言集成查询进一步介绍。这里只需了解,它可以传入模型绑定中定义的字段,如 Sample.Properties.identifier
。
当不传入 propertyConvertibleList
参数时,"insert" 或 "insertOrReplace" 接口会使用所有定义的字段进行插入。而 propertyConvertibleList
不为空时,"insert" 或 "insertOrReplace" 只会插入指定的字段,这就构成了部分插入。
以下是一个部分插入的例子:
- let object = Sample()
- sample.identifier = 1
- sample.description = "insert"
- try database.insert(objects: object, on: Sample.Properties.identifier, intoTable: "sampleTable") // 部分插入,没有指定 description。
这个例子中,指定了只插入 identifier
字段,因此其他没有指定的字段,会使用 模型绑定中定义的默认值 或 空 来代替。这里 description
没有定义默认值,因此其数据为空。
插入是最常用且比较容易操作卡顿的操作,因此 WCDB Swift 对其进行了特殊处理。当插入的对象数大于 1 时,WCDB Swift 会自动开启事务,进行批量化地插入,以获得更新的性能。
删除操作
删除操作只有一个接口,其函数原型为:
- func delete(fromTable table: String, // 表名
- where condition: Condition? = nil, // 符合删除的条件
- orderBy orderList: [OrderBy]? = nil, // 排序的方式
- limit: Limit? = nil, // 删除的个数
- offset: Offset? = nil // 从第几个开始删除
- ) throws
删除接口会删除表内的数据,并通过 condition
、orderList
、limit
和 offset
参数来确定需要删除的数据的范围。
这四个组合起来可以理解为:将 table
表内,满足 condition
的数据,按照 orderList
的方式进行排序,然后从头开始第 offset
行数据后的 limit
行数据删除。
以下是删除接口的示例代码:
- // 删除 sampleTable 中所有 identifier 大于 1 的行的数据
- try database.delete(fromTable: "sampleTable",
- where: Sample.Properties.identifier > 1)
- // 删除 sampleTable 中 identifier 降序排列后的前 2 行数据
- try database.delete(fromTable: "sampleTable",
- orderBy: Sample.Properties.identifier.asOrder(by: .descending),
- limit: 2)
- // 删除 sampleTable 中 description 非空的数据,按 identifier 降序排列后的前 3 行的后 2 行数据
- try database.delete(fromTable: "sampleTable",
- where: Sample.Properties.description.isNotNull(),
- orderBy: Sample.Properties.identifier.asOrder(by: .descending),
- limit: 2,
- offset: 3)
- // 删除 sampleTable 中的所有数据
- try database.delete(fromTable: "sampleTable")
这里的 condition
、limit
和 offset
本质都是遵循 ExpressionConvertible
的对象,可以是数字、字符串、字段或其他更多的组合。同样地,我们会在语言集成查询进一步介绍。
删除接口不会删除表本身,开发者需要调用drop(table:)
接口删除表。
更新操作
更新操作有 "update with object" 和 "update with row" 两个接口。它们的原型分别
- func update<Object: TableEncodable>(
- table: String,
- on propertyConvertibleList: [PropertyConvertible],
- with object: Object,
- where condition: Condition? = nil,
- orderBy orderList: [OrderBy]? = nil,
- limit: Limit? = nil,
- offset: Offset? = nil) throws
- func update(
- table: String,
- on propertyConvertibleList: [PropertyConvertible],
- with row: [ColumnEncodableBase],
- where condition: Condition? = nil,
- orderBy orderList: [OrderBy]? = nil,
- limit: Limit? = nil,
- offset: Offset? = nil) throws
其中 propertyConvertibleList
、condition
、limit
和 offset
都在前文介绍过了,这里不再赘述。两个接口除了 with 之后的参数,其他都一致。
"with object" 故名思义,通过 object 对象进行更新。以下是更新操作的示例代码:
- let object = Sample()
- object.description = "update"
- // 将 sampleTable 中所有 identifier 大于 1 且 description 字段不为空 的行的 description 字段更新为 "update"
- try database.update(table: "sampleTable"
- on: Sample.Properties.description,
- with: object,
- where: Sample.Properites.identifier > 1 && Sample.Properties.description.isNotNull())
- // 将 sampleTable 中前三行的 description 字段更新为 "update"
- try database.update(table: "sampleTable"
- on: Sample.Properties.description,
- with: object,
- limit: 3)
而 "with row" 接口则是通过 row 来对数据进行更新。row 是遵循 ColumnEncodable
协议的类型的数组。ColumnEncodable
协议会在后续自定义字段映射类型中进一步介绍。这里只需了解,能够进行字段映射的类型基本都遵循 ColumnEncodable
协议。
因此,与 "with object" 对应的示例代码为:
- let row: [ColumnCodableBase] = ["update"]
- // 将 sampleTable 中所有 identifier 大于 1 且 description 字段不为空 的行的 description 字段更新为 "update"
- try database.update(table: "sampleTable"
- on: Sample.Properties.description,
- with: row,
- where: Sample.Properites.identifier > 1 && Sample.Properties.description.isNotNull())
- // 将 sampleTable 中前三行的 description 字段更新为 "update"
- try database.update(table: "sampleTable"
- on: Sample.Properties.description,
- with: row,
- limit: 3)
查找操作
查找接口对应的操作有 8 个,分别为
- getObjects
- getObject
- getRows
- getRow
- getColumn
- getDistinctColumn
- getValue
- getDistinctValue
虽然接口较多,但大部分都是为了简化操作而提供的便捷接口。实现上其实与update
类似,只有 "object" 和 "row" 两种方式。
对象查找操作
"getObjects" 和 "getObject" 都是对象查找的接口,他们直接返回已进行模型绑定的对象。它们的函数原型为:
- func getObjects<Object: TableDecodable>(
- on propertyConvertibleList: [PropertyConvertible],
- fromTable table: String,
- where condition: Condition? = nil,
- orderBy orderList: [OrderBy]? = nil,
- limit: Limit? = nil,
- offset: Offset? = nil) throws -> [Object]
- func getObject<Object: TableDecodable>(
- on propertyConvertibleList: [PropertyConvertible],
- fromTable table: String,
- where condition: Condition? = nil,
- orderBy orderList: [OrderBy]? = nil,
- offset: Offset? = nil) throws -> Object?
其中 propertyConvertibleList
、condition
、limit
和 offset
都在前文介绍过了,这里不再赘述。而 "getObject" 等价于 limit: 1
时的 "getObjects" 接口。不同的是,它直接返回 Object
对象,而不是一个数组,使用上更便捷。以下是对象查找操作的示例代码:
- // 返回 sampleTable 中的所有数据
- let allObjects: [Sample] = try database.getObjects(fromTable: "sampleTable")
- // 返回 sampleTable 中 identifier 小于 5 或 大于 10 的行的数据
- let objects: [Sample] = try database.getObjects(fromTable: "sampleTable",
- where: Sample.Properties.identifier < 5 || Sample.Properties.identifier > 10)
- // 返回 sampleTable 中 identifier 最大的行的数据
- let object: Sample? = try database.getObject(fromTable: "sampleTable",
- orderBy: Sample.Properties.identifier.asOrder(by: .descending))
由于对象查找操作使用了范型,因此需要显式声明返回值的类型以匹配范型。否则会报错let allObjects = try database.getObjects(fromTable: "sampleTable") // 没有显式声明 allObjects 类型,范型无法匹配,无法编译通过。
对象部分查询
与 "insert"、"update" 类似,对象查找操作也支持指定字段,例如:
- let objects: [Sample] = try database.getObjects(fromTable: "sampleTable",
- on: Sample.Properties.identifier)
这里只获取了 identifier
字段,而没有获取 description
的值。这就可能与 Swift 本身存在冲突。Swift 规定了对象创建时,必须初始化所有成员变量。而进行对象部分查询时,则可能出现某部分变量没有变查询,因此无法初始化的情况。因此,对于可能不被查询的成员变量,应将其类型定义为可选值。对于 Sample
类中,上述 "getObjects" 接口虽然没有获取 description
的值,但由于 description
是 String?
类型,因此不会出错。而以下则是会出错的例子:
- class PartialSample: TableCodable {
- var identifier: Int? = nil
- var description: String = ""
- enum CodingKeys: String, CodingTableKey {
- typealias Root = PartialSample
- static let objectRelationalMapping = TableBinding(CodingKeys.self)
- case identifier
- case description
- }
- }
- // 由于 description 是 String 类型,"getObject" 过程无法对其进行初始化,因此以下调用会出错。
- // 正确的方式应将 `var description: String` 改为 `var description: String?`
- let partialObjects: [PartialSample] = try database.getObjects(fromTable: "sampleTable", on: Sample.Properties.identifier)
倘若开发者不确定哪些字段可能会被进行对象部分查询,可以将所有字段都定义为可选。
值查询操作
其余的 6 个查询接口都是值查询操作,它们都属于 "getRows" 接口的简化接口。其接口声明如下:
- func getRows(on columnResultConvertibleList: [ColumnResultConvertible],
- fromTable table: String,
- where condition: Condition? = nil,
- orderBy orderList: [OrderBy]? = nil,
- limit: Limit? = nil,
- offset: Offset? = nil) throws -> FundamentalRowXColumn
其中 condition
、orderList
、limit
和 offset
,前文已经介绍,这里不再赘述。columnResultConvertibleList
是遵循 ColumnResultConvertible
协议的对象数组,我们会在语言集成查询进一步介绍。
这里只需了解 Sample.Properties.identifier.max()
是遵循 ColumnResultConvertible
协议的对象,用于查找 identifier
列的最大值。
试考虑,表中的数据可以想象为一个矩阵的存在,假设其数据如下:
identifier | description |
---|---|
1 | "sample1" |
2 | "sample1" |
3 | "sample2" |
4 | "sample2" |
5 | "sample2" |
在不考虑 condition
、orderList
、limit
和 offset
参数的情况下:
- "getRows" 接口获取整个矩阵的所有内容,即返回值为二维数组。
- "getRow" 接口获取某一横行的数据,即返回值为一维数组。
- "getColumn" 接口获取某一纵列的数据,即返回值为一维数组。
- "getDistinctColumn" 与 "getColumn" 类似,但它会过滤掉重复的值。
- "getValue" 接口获取矩阵中某一个格的内容。
- "getDistinctValue" 与 "getValue" 类似,但它会过滤掉重复的值。
以下是值查询操作的示例代码:
- // 获取所有内容
- let allRows = try database.getRows(fromTable: "sampleTable")
- print(allRows[row: 2, column: 0].int32Value) // 输出 3
- // 获取第二行
- let secondRow = try database.getRow(fromTable: "sampleTable", offset: 1)
- print(secondRow[0].int32Value) // 输出 2
- // 获取 description 列
- let descriptionColumn = try database.getColumn(on: Sample.Properties.description, fromTable: "sampleTable")
- print(descriptionColumn) // 输出 "sample1", "sample1", "sample1", "sample2", "sample2"
- // 获取不重复的 description 列的值
- let distinctDescriptionColumn = try database.getDistinctColumn(on: Sample.Properties.description, fromTable: "sampleTable")
- print(distinctDescriptionColumn) // 输出 "sample1", "sample2"
- // 获取第二行 description 列的值
- let value = try database.getValue(on: Sample.Properties.description, offset: 1)
- print(value.stringValue) // 输出 "sample1"
- // 获取 identifier 的最大值
- let maxIdentifier = try database.getValue(on: Sample.Properties.identifier.max(), fromTable: "sampleTable")
- // 获取不重复的 description 的值
- let distinctDescription = try database.getDistinctValue(on: Sample.Properties.description, fromTable: "sampleTable")
- print(distinctDescription.stringValue) // 输出 "sample1"