Sqlable

Sqlable 是一个 Swift 库,可以像馅饼一样轻松地将数据存储在 SQLite 数据库中。创建一个结构体,使其成为 Sqlable,然后尽情地读取、写入、更新和删除数据吧!

我为什么要用它?

在制作 iOS 应用程序时,持久化通常很麻烦。如果您只想使用 Apple 的第一方框架,您的选择要么是 Core Data,要么是序列化为 JSON 或 plist 文件。也有一些不错的第三方选项,例如 Realm,但它主要是活动对象在后台进行大量动态操作。如果您只想操作简单的结构体,并且只想读取和写入数据库,Sqlable 是最简单的选择。

展示一下如何使用它!

假设您有以下结构体

struct Bicycle {
	let id : Int?
	var name : String
	var color : String
}

并且您想将其持久化到数据库中。为此,您只需通过实现 Sqlable 协议来使您的结构体成为 Sqlable,就像这样

extension Bicycle : Sqlable {
	static let id = Column("id", .integer, PrimaryKey(autoincrement: true))
	static let name = Column("name", .text)
	static let color = Column("color", .text)
	static let tableLayout = [id, name, color]
	
	func valueForColumn(column : Column) -> SqlValue? {
		switch column {
		case Bicycle.id: return id
		case Bicycle.name: return name
		case Bicycle.color: return color
		case _: return nil
		}
	}
	
	init(row : ReadRow) throws {
		id = try row.get(Bicycle.id)
		name = try row.get(Bicycle.name)
		color = try row.get(Bicycle.color)
	}
}

这些是您需要指定的仅有的三件事

• 表格布局(您要存储哪些列)

• 如何保存列

• 如何读取行

完成这些操作后,您就可以开始将您的结构体与 SQLite 一起使用了!

使用它的第一步是设置一个新的 SQLite 数据库并为 bicycle 创建表

db = try SqliteDatabase(filepath: documentsPath() + "/db.sqlite")
try db.createTable(Bicycle.self)

然后您就可以准备好从数据库中写入、读取、更新和删除 bicycle 了!

// Insert bicycle
var bike = Bicycle(id: 1, name: "My bike", color: "Black")
try bike.insert().run(db)

// Read all bicycles
let bicycles = try Bicycle.read().run(db)

// Read some bicycles
let redBikes = try Bicycle.read().filter(Bicycle.color == "red").limit(3).run(db)

// Count all bicycles
let bicycleCount = try Bicycle.count().run(db)

// Update a bicycle
bike.name = "Sportsbike"
try bike.update().run(db)

// Delete bike
try bike.delete().run(db)

1.2 版本的新功能

更新回调

现在,您可以通过插入、删除和更新的更新回调非常轻松地观察更改。

db.observe(.insert, on: Bicycle.self) { id in
	print("Inserted bicycle \(id)")
}

db.observe(.update, on: Bicycle.self, id: 2) { id in
	print("bicycle 2 has been updated")
}

并发

Sqlable 现在使用 SQLite 的预写式日志,这使得并发操作快速而简单。并且由于 Sqlable 使用的是简单的结构体进行操作,因此您可以在不同的线程之间自由传递信息。

let child = try db.createChild()

dispatch_async(background_queue) {
	let bicycles = try! Bicycle.read().run(child)
	
	dispatch_async(main_queue) {
		self.displayData(bicycles)
	}
}

您可以使用事务来锁定数据库,其他线程将等待直到事务完成。

完整文档

所有公共函数现在都有文档 😎

1.1 (+1.1.1) 版本的新功能

• 嵌套事务

• 用于过滤器的 SQL 函数调用(例如“like”、“upper”和“lower”)

它还有哪些其他酷炫的功能?

事务

事务非常容易实现

try db.transaction { db in
	if try Bicycle.count().run(db) == 0 {
		try bike.insert().run(db)
	}
}

您还可以进行回滚

try db.beginTransaction()
try bike.insert().run(db)
try db.rollbackTransaction()

以及嵌套事务!(1.1.1 版本的新功能!

try db.transaction { db in
	try db.transaction { db in
		if try Bicycle.count().run(db) == 0 {
			try bike.insert().run(db)
		}
	}
}

外键约束

extension Bicycle : Sqlable {
	static let ownerId = Column("owner_id", .Integer, ForeignKey<Person>())
	...

您还可以指定其他列和删除/更新规则

extension Bicycle : Sqlable {
	static let ownerId = Column("owner_id", .Integer, ForeignKey<Person>(column: Person.regId, onDelete: .cascade))
	...

唯一性约束

1.1 版本的新功能!

如果您希望数据库中的每个 bicycle 都具有唯一的名称

extension Bicycle : Sqlable {
	static let name = Column("name", .text)
	static let tableConstraints : [TableConstraint] = [Unique(Bicycle.name)]
	...

或者,如果您希望名称和颜色的组合是唯一的

extension Bicycle : Sqlable {
	static let name = Column("name", .text)
	static let color = Column("color", .text)
	static let tableConstraints : [TableConstraint] = [Unique(Bicycle.name, Bicycle.color)]
	...

用于查询过滤器的 DSL

Bicycle.read().filter(Bicycle.color == "red" && !(Bicycle.id == 0 || Bicycle.id > 1000))

更新回调

1.2 版本的新功能!

在您的数据库处理程序上注册 didUpdate 回调,以便在任何内容发生更改时收到通知

db.didUpdate = { table, id, change in
	switch change {
	case .insert: print("Inserted \(id) into \(table)")
	case .update: print("Updated \(id) in \(table)")
	case .delete: print("Deleted \(id) from \(table)")
	}
}

但更好的是,您可以注册针对特定操作、表和 ID 的事件回调

db.observe(.insert, on: Bicycle.self) { id in
	print("Inserted bicycle \(id)")
}

db.observe(.update, on: Bicycle.self, id: 2) { id in
	print("bicycle 2 has been updated")
}

db.observe(.delete, on: Bicycle.self) { id in
	print("bicycle \(id) has been deleted")
}

db.observe(on: Bicycle.self) { id in
	print("Something was updated on bicycle \(id)")
}

Swift 风格的错误处理

每个可能失败的函数调用都标有 throws,因此您可以处理每个可能发生的错误。

API 的构造方式也使您可以在不接触数据库的情况下设置语句,因此您可以设置所有内容而无需任何错误处理

let idFilter = Bicycle.id > 2 && Bicyckle.id < 10
let read = Bicycle.read().filter(idFilter).limit(2)

do {
	let result = read.run(db)
} catch let error {
	// handle error
}

它还支持一个可选的方便回调,用于在发生任何错误时使用,如果它适合您的应用程序,您可以使用它。只需在您的数据库处理程序上调用 fail 方法,当您遇到如下错误时

do {
	try Bicycle.read().limit(-1).run(db)
} catch let error {
	db.fail(error)
}

它将被传递到您注册的错误处理程序

db.didFail = { error in
	print("Oh no! \(error)")
}

并发

1.2 版本的新功能!

曾经处理过 Core Data 中的并发问题吗?不用担心,Sqlable 与之完全不同。

每个 SqliteDatabase 实例对于一个线程/队列都是唯一的。如果您想在后台运行一些代码,您可以创建一个子实例

let child = try db.createChild()

dispatch_async(background_queue) {
	for bicycle in try! Bicycle.read().run(child) {
		...
	}
}

这是唯一的规则:保持每个实例在其线程/队列上是唯一的。Sqlable 和 Sqlite 将负责正确地锁定数据库。父实例甚至会收到来自子实例中所做更改的更新通知。如果您希望某些数据库操作按顺序发生,您可以将它们放在一个事务中,这将自动锁定所有线程上的数据库。

此外,由于模型实例只是值类型,因此您可以在线程上自由移动它们。如果您需要从数据库中加载 10,000,000 个 bicycle,只需在后台运行读取查询,并将结果分派到主队列。

如何安装它?

如果您正在使用 Carthage(您应该这样做!),只需将此添加到您的 Cartfile

github "ulrikdamm/Sqlable"

然后在您的源文件中

import Sqlable

您就可以开始了!

哪些功能即将推出?

• 迁移

• 连接

更多技术细节

语句

当您使一个结构体成为 Sqlable 时,它会获得实例方法和静态方法来返回语句。这些方法是 readcountinsertupdatedelete。所有这些都返回一个 Statement 结构体,您可以对其进行修改(使用 .filter、.limit、.onConflict、.orderBy)。语句只是一个不可变的结构体,没有任何魔法。您可以保存它、序列化它等等。当您想要运行语句时,只需调用 run 方法,该方法接受一个数据库处理程序来在其中运行它,并且可能会抛出错误,或者给您一个结果。结果的类型取决于创建语句的初始方法。

查询 DSL

查询 DSL 支持以下运算符

等于:column == value (例如 Bicycle.id == 1)

不等于:column != value

小于:column < value

小于或等于:column <= value

大于:column > value

大于或等于:column => value

与:expression && expression

或:expression || expression

反转:!expression

为空:!column

包含:column ∈ [value]contains(column, value)

字符串小写:column.lowercase()

字符串大写:column.uppercase()

字符串内搜索:column.like(value)(例如 Bicycle.name.like("%bike%"))

column 表示 Column 结构体的实例,例如 Bicycle.id

value 表示任何适用于 SQL 的值,例如 Int、String、Double 等。

expression 是这些运算符之一返回的任何内容

谁制作了这个?

我做的。我的名字是 Ulrik Flænø Damm,我是哥本哈根 Northplay 的 iOS 和 Unity 开发人员。您可以在 Twitter 上关注我,或访问 我的网站

如果您想贡献代码或想法,请打开一些 issues 或提交一些 pull requests!