• 插入操作
  • 删除操作
  • 更新操作
  • 查找操作
    • 对象查找操作
    • 对象部分查询
    • 值查询操作

    增删查改是数据库最常用的功能,因此 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"