增删查改是数据库最常用的功能,因此 WCDB Swift 对其进行了特别的封装,使其通过一行代码即可完成操作。

插入操作

插入操作有 "insert" 和 "insertOrReplace" 两个接口。故名思义,前者只是单纯的插入数据,当数据出现冲突时会失败,而后者在主键一致时,新数据会覆盖旧数据。

以已经完成模型绑定的类 Sample 为例:

  1. class Sample: TableCodable {
  2. var identifier: Int? = nil
  3. var description: String? = nil
  4.  
  5. enum CodingKeys: String, CodingTableKey {
  6. typealias Root = Sample
  7. static let objectRelationalMapping = TableBinding(CodingKeys.self)
  8. case identifier
  9. case description
  10.  
  11. static var columnConstraintBindings: [CodingKeys: ColumnConstraintBinding]? {
  12. return [
  13. identifier: ColumnConstraintBinding(isPrimary: true),
  14. ]
  15. }
  16. }
  17. }
  18.  
  19. try database.create(table: "sampleTable", of: Sample.self)
  20.  
  21. let object = Sample()
  22. sample.identifier = 1
  23. sample.description = "insert"
  24. try database.insert(objects: object, intoTable: "sampleTable") // 插入成功
  25.  
  26. try database.insert(objects: object, intoTable: "sampleTable") // 插入失败,因为主键 identifier = 1 已经存在
  27.  
  28. sample.description = "insertOrReplace"
  29. try database.insertOrReplace(objects: object, intoTable: "sampleTable") // 插入成功,且 description 的内容会被替换为 "insertOrReplace"
关于自增插入,可参考模型绑定 - 自增属性一章。

"insert" 函数的原型为:

  1. // insert 和 insertOrReplace 函数只有函数名不同,其他参数都一样。
  2. func insert<Object: TableEncodable>(
  3. objects: [Object], // 需要插入的对象。WCDB Swift 同时实现了可变参数的版本,因此可以传入一个数组,也可以传入一个或多个对象。
  4. on propertyConvertibleList: [PropertyConvertible]? = nil, // 需要插入的字段
  5. intoTable table: String // 表名
  6. ) throws

这里需要特别注意的是 propertyConvertibleList 参数,它是 遵循 PropertyConveritble 协议的对象的数组。我们会在语言集成查询进一步介绍。这里只需了解,它可以传入模型绑定中定义的字段,如 Sample.Properties.identifier

当不传入 propertyConvertibleList 参数时,"insert" 或 "insertOrReplace" 接口会使用所有定义的字段进行插入。而 propertyConvertibleList 不为空时,"insert" 或 "insertOrReplace" 只会插入指定的字段,这就构成了部分插入。

以下是一个部分插入的例子:

  1. let object = Sample()
  2. sample.identifier = 1
  3. sample.description = "insert"
  4. try database.insert(objects: object, on: Sample.Properties.identifier, intoTable: "sampleTable") // 部分插入,没有指定 description。

这个例子中,指定了只插入 identifier 字段,因此其他没有指定的字段,会使用 模型绑定中定义的默认值 或 空 来代替。这里 description 没有定义默认值,因此其数据为空。

插入是最常用且比较容易操作卡顿的操作,因此 WCDB Swift 对其进行了特殊处理。当插入的对象数大于 1 时,WCDB Swift 会自动开启事务,进行批量化地插入,以获得更新的性能。

删除操作

删除操作只有一个接口,其函数原型为:

  1. func delete(fromTable table: String, // 表名
  2. where condition: Condition? = nil, // 符合删除的条件
  3. orderBy orderList: [OrderBy]? = nil, // 排序的方式
  4. limit: Limit? = nil, // 删除的个数
  5. offset: Offset? = nil // 从第几个开始删除
  6. ) throws

删除接口会删除表内的数据,并通过 conditionorderListlimitoffset 参数来确定需要删除的数据的范围。

这四个组合起来可以理解为:将 table 表内,满足 condition 的数据,按照 orderList 的方式进行排序,然后从头开始第 offset 行数据后的 limit 行数据删除。

以下是删除接口的示例代码:

  1. // 删除 sampleTable 中所有 identifier 大于 1 的行的数据
  2. try database.delete(fromTable: "sampleTable",
  3. where: Sample.Properties.identifier > 1)
  4.  
  5. // 删除 sampleTable 中 identifier 降序排列后的前 2 行数据
  6. try database.delete(fromTable: "sampleTable",
  7. orderBy: Sample.Properties.identifier.asOrder(by: .descending),
  8. limit: 2)
  9.  
  10. // 删除 sampleTable 中 description 非空的数据,按 identifier 降序排列后的前 3 行的后 2 行数据
  11. try database.delete(fromTable: "sampleTable",
  12. where: Sample.Properties.description.isNotNull(),
  13. orderBy: Sample.Properties.identifier.asOrder(by: .descending),
  14. limit: 2,
  15. offset: 3)
  16.  
  17. // 删除 sampleTable 中的所有数据
  18. try database.delete(fromTable: "sampleTable")

这里的 conditionlimitoffset 本质都是遵循 ExpressionConvertible 的对象,可以是数字、字符串、字段或其他更多的组合。同样地,我们会在语言集成查询进一步介绍。

删除接口不会删除表本身,开发者需要调用 drop(table:) 接口删除表。

更新操作

更新操作有 "update with object" 和 "update with row" 两个接口。它们的原型分别

  1. func update<Object: TableEncodable>(
  2. table: String,
  3. on propertyConvertibleList: [PropertyConvertible],
  4. with object: Object,
  5. where condition: Condition? = nil,
  6. orderBy orderList: [OrderBy]? = nil,
  7. limit: Limit? = nil,
  8. offset: Offset? = nil) throws
  9.  
  10. func update(
  11. table: String,
  12. on propertyConvertibleList: [PropertyConvertible],
  13. with row: [ColumnEncodableBase],
  14. where condition: Condition? = nil,
  15. orderBy orderList: [OrderBy]? = nil,
  16. limit: Limit? = nil,
  17. offset: Offset? = nil) throws

其中 propertyConvertibleListconditionlimitoffset 都在前文介绍过了,这里不再赘述。两个接口除了 with 之后的参数,其他都一致。

"with object" 故名思义,通过 object 对象进行更新。以下是更新操作的示例代码:

  1. let object = Sample()
  2. object.description = "update"
  3.  
  4. // 将 sampleTable 中所有 identifier 大于 1 且 description 字段不为空 的行的 description 字段更新为 "update"
  5. try database.update(table: "sampleTable"
  6. on: Sample.Properties.description,
  7. with: object,
  8. where: Sample.Properites.identifier > 1 && Sample.Properties.description.isNotNull())
  9.  
  10. // 将 sampleTable 中前三行的 description 字段更新为 "update"
  11. try database.update(table: "sampleTable"
  12. on: Sample.Properties.description,
  13. with: object,
  14. limit: 3)

而 "with row" 接口则是通过 row 来对数据进行更新。row 是遵循 ColumnEncodable 协议的类型的数组。ColumnEncodable 协议会在后续自定义字段映射类型中进一步介绍。这里只需了解,能够进行字段映射的类型基本都遵循 ColumnEncodable 协议。

因此,与 "with object" 对应的示例代码为:

  1. let row: [ColumnCodableBase] = ["update"]
  2.  
  3. // 将 sampleTable 中所有 identifier 大于 1 且 description 字段不为空 的行的 description 字段更新为 "update"
  4. try database.update(table: "sampleTable"
  5. on: Sample.Properties.description,
  6. with: row,
  7. where: Sample.Properites.identifier > 1 && Sample.Properties.description.isNotNull())
  8.  
  9. // 将 sampleTable 中前三行的 description 字段更新为 "update"
  10. try database.update(table: "sampleTable"
  11. on: Sample.Properties.description,
  12. with: row,
  13. limit: 3)

查找操作

查找接口对应的操作有 8 个,分别为

  • getObjects
  • getObject
  • getRows
  • getRow
  • getColumn
  • getDistinctColumn
  • getValue
  • getDistinctValue
    虽然接口较多,但大部分都是为了简化操作而提供的便捷接口。实现上其实与 update 类似,只有 "object" 和 "row" 两种方式。

对象查找操作

"getObjects" 和 "getObject" 都是对象查找的接口,他们直接返回已进行模型绑定的对象。它们的函数原型为:

  1. func getObjects<Object: TableDecodable>(
  2. on propertyConvertibleList: [PropertyConvertible],
  3. fromTable table: String,
  4. where condition: Condition? = nil,
  5. orderBy orderList: [OrderBy]? = nil,
  6. limit: Limit? = nil,
  7. offset: Offset? = nil) throws -> [Object]
  8.  
  9. func getObject<Object: TableDecodable>(
  10. on propertyConvertibleList: [PropertyConvertible],
  11. fromTable table: String,
  12. where condition: Condition? = nil,
  13. orderBy orderList: [OrderBy]? = nil,
  14. offset: Offset? = nil) throws -> Object?

其中 propertyConvertibleListconditionlimitoffset 都在前文介绍过了,这里不再赘述。而 "getObject" 等价于 limit: 1 时的 "getObjects" 接口。不同的是,它直接返回 Object 对象,而不是一个数组,使用上更便捷。以下是对象查找操作的示例代码:

  1. // 返回 sampleTable 中的所有数据
  2. let allObjects: [Sample] = try database.getObjects(fromTable: "sampleTable")
  3.  
  4. // 返回 sampleTable 中 identifier 小于 5 或 大于 10 的行的数据
  5. let objects: [Sample] = try database.getObjects(fromTable: "sampleTable",
  6. where: Sample.Properties.identifier < 5 || Sample.Properties.identifier > 10)
  7.  
  8. // 返回 sampleTable 中 identifier 最大的行的数据
  9. let object: Sample? = try database.getObject(fromTable: "sampleTable",
  10. orderBy: Sample.Properties.identifier.asOrder(by: .descending))
由于对象查找操作使用了范型,因此需要显式声明返回值的类型以匹配范型。否则会报错let allObjects = try database.getObjects(fromTable: "sampleTable") // 没有显式声明 allObjects 类型,范型无法匹配,无法编译通过。

对象部分查询

与 "insert"、"update" 类似,对象查找操作也支持指定字段,例如:

  1. let objects: [Sample] = try database.getObjects(fromTable: "sampleTable",
  2. on: Sample.Properties.identifier)

这里只获取了 identifier 字段,而没有获取 description 的值。这就可能与 Swift 本身存在冲突。Swift 规定了对象创建时,必须初始化所有成员变量。而进行对象部分查询时,则可能出现某部分变量没有变查询,因此无法初始化的情况。因此,对于可能不被查询的成员变量,应将其类型定义为可选值。对于 Sample 类中,上述 "getObjects" 接口虽然没有获取 description 的值,但由于 descriptionString? 类型,因此不会出错。而以下则是会出错的例子:

  1. class PartialSample: TableCodable {
  2. var identifier: Int? = nil
  3. var description: String = ""
  4.  
  5. enum CodingKeys: String, CodingTableKey {
  6. typealias Root = PartialSample
  7. static let objectRelationalMapping = TableBinding(CodingKeys.self)
  8. case identifier
  9. case description
  10. }
  11. }
  12.  
  13. // 由于 description 是 String 类型,"getObject" 过程无法对其进行初始化,因此以下调用会出错。
  14. // 正确的方式应将 `var description: String` 改为 `var description: String?`
  15. let partialObjects: [PartialSample] = try database.getObjects(fromTable: "sampleTable", on: Sample.Properties.identifier)
倘若开发者不确定哪些字段可能会被进行对象部分查询,可以将所有字段都定义为可选。

值查询操作

其余的 6 个查询接口都是值查询操作,它们都属于 "getRows" 接口的简化接口。其接口声明如下:

  1. func getRows(on columnResultConvertibleList: [ColumnResultConvertible],
  2. fromTable table: String,
  3. where condition: Condition? = nil,
  4. orderBy orderList: [OrderBy]? = nil,
  5. limit: Limit? = nil,
  6. offset: Offset? = nil) throws -> FundamentalRowXColumn

其中 conditionorderListlimitoffset,前文已经介绍,这里不再赘述。columnResultConvertibleList 是遵循 ColumnResultConvertible 协议的对象数组,我们会在语言集成查询进一步介绍。

这里只需了解 Sample.Properties.identifier.max() 是遵循 ColumnResultConvertible 协议的对象,用于查找 identifier 列的最大值。

试考虑,表中的数据可以想象为一个矩阵的存在,假设其数据如下:

identifierdescription
1"sample1"
2"sample1"
3"sample2"
4"sample2"
5"sample2"

在不考虑 conditionorderListlimitoffset 参数的情况下:

  • "getRows" 接口获取整个矩阵的所有内容,即返回值为二维数组。
  • "getRow" 接口获取某一横行的数据,即返回值为一维数组。
  • "getColumn" 接口获取某一纵列的数据,即返回值为一维数组。
  • "getDistinctColumn" 与 "getColumn" 类似,但它会过滤掉重复的值。
  • "getValue" 接口获取矩阵中某一个格的内容。
  • "getDistinctValue" 与 "getValue" 类似,但它会过滤掉重复的值。
    以下是值查询操作的示例代码:
  1. // 获取所有内容
  2. let allRows = try database.getRows(fromTable: "sampleTable")
  3. print(allRows[row: 2, column: 0].int32Value) // 输出 3
  4.  
  5. // 获取第二行
  6. let secondRow = try database.getRow(fromTable: "sampleTable", offset: 1)
  7. print(secondRow[0].int32Value) // 输出 2
  8.  
  9. // 获取 description 列
  10. let descriptionColumn = try database.getColumn(on: Sample.Properties.description, fromTable: "sampleTable")
  11. print(descriptionColumn) // 输出 "sample1", "sample1", "sample1", "sample2", "sample2"
  12.  
  13. // 获取不重复的 description 列的值
  14. let distinctDescriptionColumn = try database.getDistinctColumn(on: Sample.Properties.description, fromTable: "sampleTable")
  15. print(distinctDescriptionColumn) // 输出 "sample1", "sample2"
  16.  
  17. // 获取第二行 description 列的值
  18. let value = try database.getValue(on: Sample.Properties.description, offset: 1)
  19. print(value.stringValue) // 输出 "sample1"
  20.  
  21. // 获取 identifier 的最大值
  22. let maxIdentifier = try database.getValue(on: Sample.Properties.identifier.max(), fromTable: "sampleTable")
  23.  
  24. // 获取不重复的 description 的值
  25. let distinctDescription = try database.getDistinctValue(on: Sample.Properties.description, fromTable: "sampleTable")
  26. print(distinctDescription.stringValue) // 输出 "sample1"