Swift-Kuery
是一个可插拔的 SQL 数据库驱动/SDK 抽象层。它的主要思想是统一各种关系数据库的 API,提供一个 Swifty 风格但又类似 SQL 的 API。这使得在数据库之间轻松切换,并为对象关系映射 (ORM) 框架奠定基础。
Swift-Kuery-ORM 是一个构建于 Swift-Kuery 之上的 ORM,它允许您简化模型对象在服务器上的持久化。
Swift-Kuery
是一个易于学习、易于使用的框架,它带有一组 已实现的插件。
最新版本的 Swift-Kuery 需要 Swift 4.0 或更高版本。您可以通过点击此链接下载此版本的 Swift 二进制文件。不保证与其他 Swift 版本的兼容性。
此示例演示了如何使用 Swift-Kuery
和 Swift-Kuery-PostgreSQL 插件执行 SQL 查询。
此示例的起点是一个现有的 Swift 包。如果您还没有,请创建一个目录并进入该目录,例如 SwiftKueryExample
。现在运行 swift package 的 init 命令,创建一个可执行类型,通过运行 swift package init --type executable
。
brew install postgresql
sudo apt-get install postgresql postgresql-contrib
创建一个名为 school
的数据库
createdb school
psql school
创建一个名为 grades
的表
CREATE TABLE "Grades" (
id varchar(100) PRIMARY KEY,
course text NOT NULL,
grade integer
);
将 Swift-Kuery 和您的 Kuery 插件(在本例中为 Swift-Kuery-PostgreSQL)添加到应用程序 Package.swift
文件中的 dependencies 中。将 "x.x.x"
替换为最新的 Swift-Kuery
版本,将 "y.y.y"
替换为最新的插件 版本。
dependencies: [
...
// Add this line
.package(url: "https://github.com/Kitura/Swift-Kuery.git", from: "x.x.x"),
.package(url: "https://github.com/Kitura/Swift-Kuery-PostgreSQL.git", from: "y.y.y"),
],
targets: [
.target(
name: ...
// Add the module to your target(s)
dependencies: [..., "SwiftKuery", "SwiftKueryPostgreSQL"]),
]
在 main.swift
文件内部
import SwiftKuery
import SwiftKueryPostgreSQL
Table
类,它与您在数据库中创建的 grades
表匹配class Grades: Table {
let tableName = "Grades"
let id = Column("id", Int32.self, primaryKey: true)
let course = Column("course", String.self)
let grade = Column("grade", Int32.self)
}
let grades = Grades()
let pool = PostgreSQLConnection.createPool(host: "localhost", port: 5432, options: [.databaseName("school")], poolOptions: ConnectionPoolOptions(initialCapacity: 10, maxCapacity: 50))
let students: [[Any]] = [[0, "computing", 92], [1, "physics", 75], [2, "history", 83]]
pool.getConnection() { connection, error in
guard let connection = connection else {
guard let error = error else {
return print("Unknown error")
}
return print("Error when getting connection from pool: \(error.localizedDescription)")
}
let insertQuery = Insert(into: grades, rows: students)
connection.execute(query: insertQuery) { insertResult in
connection.execute(query: Select(from: grades)) { selectResult in
guard let resultSet = selectResult.asResultSet else {
return print("No result set returned from query")
}
resultSet.forEach() { row, error in
guard let row = row else {
guard let error = error else {
// Processed all results
return
}
// Handle error
return
}
guard row.count == 3 else {
// Expecting three elements per row
return print("Row has wrong number of elements. Expecting 3, returned: \(row.count)")
}
print("Student \(row[0] ?? ""), studying \(row[1] ?? ""), scored \(row[2] ?? "")")
}
}
}
}
// Add the following after the existing imports:
import Dispatch
let waitSemaphore = DispatchSemaphore(value: 0)
// Update the forEach callback to look like:
resultSet.forEach() { row, error in
guard let row = row else {
// Processed all results
waitSemaphore.signal()
return
}
print("Student \(row[0] ?? ""), studying \(row[1] ?? ""), scored \(row[2] ?? "")")
}
// Add the following line at the end of the main.swift file
waitSemaphore.wait()
main.swift
文件。运行 swift build
来构建可执行文件。.build/debug/<yourPackageName>.
这将打印每个学生的 id
、course
和 grade
,这些数据是从数据库中查询出来的
Student 0, studying computing, scored 92
Student 1, studying physics, scored 75
Student 2, studying history, scored 83
如果您使用 psql school
进入您的数据库并输入 TABLE grades;
,您可以看到该表已填充了学生数据。
在动态查询中使用未经消毒的数据是 SQL 注入漏洞最常见的起因之一。参数化查询可以帮助防止 SQL 注入攻击。
如果 supplied_key1
或 supplied_key2
包含不受信任的数据(即,未经验证的数据),则以下代码容易受到 SQL 注入攻击
let query = Select(from: confidential)
.where(confidential.key1 == supplied_key1 || confidential.key2 == supplied_key2)
connection.execute(query: query) { queryResult in
...
}
为了防止 SQL 注入攻击,请使用以下参数化版本的代码
let query = Select(from: confidential)
.where(confidential.key1 == Parameter() || confidential.key2 == Parameter())
connection.execute(query: query, parameters: supplied_key1, supplied_key2) { queryResult in
...
}
如果您的应用程序重复执行相同(或相似)的 SQL 语句,只是参数不同,您可以通过使用预处理语句来提高应用程序的性能。预处理语句可以减少解析时间,因为数据库仅解析和编译语句模板一次,然后存储结果但不执行它。稍后,应用程序为语句模板的参数提供值,数据库执行该语句。
例如,假设我们的应用程序需要检索平均成绩高于给定值的课程的平均成绩;我们希望改变这个值。让我们更改查询以使用参数而不是预定义的值
let query = Select(grades.course, round(avg(grades.grade), to: 1).as("average"), from: grades)
.group(by: grades.course)
.having(avg(grades.grade) > Parameter())
.order(by: .ASC(avg(grades.grade)))
现在,准备语句并根据需要使用不同的参数值执行多次。使用 release
函数释放预处理语句
connection.prepareStatement(query) { result in
guard let statement = result.asPreparedStatement else {
// Handle error
return
}
// Execute the statement
connection.execute(preparedStatement: preparedStatement, parameters: [70]) { result in
...
connection.execute(preparedStatement: preparedStatement, parameters: [25]) { result in
...
connection.release(preparedStatement: preparedStatement) { result in
...
}
}
}
}
注意:preparedStatement
是预处理语句的插件特定句柄。
Swift-Kuery 使您能够在数据库服务器上创建表。
让我们回顾一下我们在上面的示例中使用的 Grades 表
class Grades: Table {
let tableName = "Grades"
let id = Column("id", Int32.self, primaryKey: true)
let course = Column("course", String.self)
let grade = Column("grade", Int32.self)
}
我们将添加第二个表,名为 courses
class Courses: Table {
let tableName = "Courses"
let name = Column("name", String.self, primaryKey: true)
let credit = Column("credit", Int32.self)
let teacher = Column("teacher", String.self)
}
let courses = Courses()
我们可以向 Grades
添加一个外键,该外键引用另一个表中的列
let grades = Grades().foreignKey(grades.course, references: courses.name)
创建一个多列主键(如果未在列中设置,如 Grades.id
的情况)
grades.primaryKey(grades.id, grades.course)
在数据库中创建表
courses.create(connection: connection) { result in
guard result.success else {
print("Failed to create table: \(result.asError?)")
}
...
}
您可以使用 Swift-Kuery 通过以下方式管理索引
let index = Index("index", on: grades, columns: [grades.id, desc(grades.grade)])
index.create(connection: connection) { result in ... }
...
index.drop(connection: connection) { result in ... }
Swift-Kuery 有一个 Migration
类,用于帮助在表的两个版本之间进行迁移。
假设我们的应用程序中有一个表 MyTable
。建议的用法是将表类的版本保存在应用程序代码的某个位置
public class MyTable_v0: Table {
let a = Column("a", ...)
let b = Column("b", ...)
let tableName = "MyTable"
}
public class MyTable_v1: Table {
let b = Column("b", ...)
let c = Column("c", ...)
let tableName = "MyTable"
}
使用类型别名来引用应用程序中当前版本的表类
typealias MyTable = MyTable_v0
let t = MyTable()
let q = Select(from t)
...
从 v0 到 v1 的迁移代码应如下所示
let t0 = MyTable_v0()
let t1 = MyTable_v1()
let migration0 = Migration(from: t0, to: t1, using: connection)
migration0.alterTableAdd(column: t1.c) { result in ... }
如果需要,您还可以执行原始的 alter 操作
let dropColumnQuery = "ALTER TABLE " + t1.tableName + " DROP COLUMN " + t0.a.name
connection.execute(dropColumnQuery) { result in ... }
在以下部分中,我们将提供一个 SQL 查询示例,并向您展示如何使用 Swift-Kuery 在 Swift 中构建和执行相同的查询。
在这些示例中,我们将使用以下两个表
class T1 {
let tableName = "t1"
let a = Column("a")
let b = Column("b")
}
class T2 {
let tableName = "t2"
let c = Column("c")
let b = Column("b")
}
SELECT * FROM t1;
此查询将从表中选择所有结果。下面的示例展示了如何执行此查询,包括样板代码
let t1 = T1()
let query = Select(from: t1)
pool.getConnection() { connection, error in
guard let connection = connection else {
// Handle error
return
}
query.execute(connection) { queryResult in
guard let resultSet = queryResult.asResultSet else {
// Handle error
return
}
resultSet.getColumnTitles() { titles, error in
guard let titles = titles else {
// Handle error
return
}
//Process titles
resultSet.forEach() { row, error in
guard let row = row else {
// Processed all results
return
}
// Process row
}
}
}
}
以下示例展示了更复杂的查询,可以将其替换到上面的样板代码中。
SELECT a, b FROM t1
WHERE (a LIKE '%b' OR a = 'apple') AND b > 5
ORDER BY b ASC, a DESC
OFFSET 5;
let query = Select(t1.a, t1.b, from: t1)
.where((t1.a.like("b%") || t1.a == "apple") && t1.b > 5)
.order(by: .ASC(t1.b), .DESC(t1.a))
.offset(5)
SELECT UCASE(a) AS name FROM t1
WHERE b >= 0
GROUP BY a
HAVING SUM(b) > 3
ORDER BY a DESC;
let query = Select(ucase(t1.a).as("name"), from: t1)
.where(t1.b >= 0)
.group(by: t1.a)
.having(sum(t1.b) > 3)
.order(by: .DESC(t1.a))
INSERT INTO t1
VALUES ('apple', 10), ('apricot', 3), ('banana', 17);
let query = Insert(into: t1, rows: [["apple", 10], ["apricot", 3], ["banana", 17]])
INSERT INTO t1
VALUES ('apple', 10);
let query = Insert(into: t1, values: "apple", 10)
INSERT INTO t1 (a, b)
VALUES ('apricot', '3');
let query = Insert(into: t1, valueTuples: (t1.a, "apricot"), (t1.b, "3"))
INSERT INTO t1 (a, b)
VALUES ('apricot', '3');
let query = Insert(into: t1, columns: [t1.a, t1.b], values: ["apricot", 3])
UPDATE t1 SET a = 'peach', b = 2
WHERE a = 'banana';
let query = Update(t1, set: [(t1.a, "peach"), (t1.b, 2)])
.where(t1.a == "banana")
SELECT * FROM t1 AS left
LEFT JOIN t2 AS right
ON left.b = right.b;
let t1 = T1()
let t2 = T2()
let leftTable = t1.as("left")
let rightTable = t2.as("right")
let query = Select(from: leftTable)
.leftJoin(rightTable)
.on(leftTable.b == rightTable.b)
SELECT * FROM t1
JOIN t2
USING (b);
let query = Select(from: t1)
.join(t2)
.using(t1.b)
注意:命名参数受所有数据库支持,即使对于那些不支持命名参数的数据库(例如 PostgreSQL)。
INSERT INTO t1
VALUES (@0,@1);
let query = Insert(into: t1, values: Parameter(), Parameter())
connection.execute(query: query, parameters: "banana", 28) { queryResult in
// Process result
}
INSERT INTO t1
VALUES (@fruit,@number);
let query = Insert(into: t1, values: Parameter("fruit"), Parameter("number"))
connection.execute(query: query, parameters: ["number" : 28, "fruit" : "banana"]) { queryResult in
// Process result
}
可以使用参数插入 NULL 值:
connection.execute(query: query, parameters: ["number" : 28, "fruit" : nil]) { queryResult in
// Process result
}
原始查询
connection.execute("CREATE TABLE myTable (a varchar(40), b integer)") { queryResult in
// Process result
}
SELECT LEFT(a, 2) as raw FROM t1
WHERE b >= 0 GROUP BY a
HAVING sum(b) > 3
ORDER BY a DESC;
let query = Select(RawField("LEFT(a, 2) as raw"), from: t1)
.where("b >= 0")
.group(by: t1.a)
.having("sum(b) > 3")
.order(by: .DESC(t1.a))
SELECT * FROM t1
WHERE b >= ANY (SELECT b FROM t2);
let query = Select(from: t1)
.where(t1.b >= any(Select(t2.b, from: t2)))
SELECT * FROM t1
WHERE NOT EXISTS (SELECT * FROM t2 WHERE b < 8);
let query = Select(from: t1)
.where(notExists(Select(from: t2).where(t2.b < 8)))
SELECT c FROM t2 GROUP BY c
HAVING SUM(b) NOT IN (SELECT b FROM t1 WHERE a = 'apple');
let query = Select(t2.c, from: t2)
.group(by: t2.c)
.having(sum(t2.b).notIn(Select(t1.b, from: t1).where(t1.a == "apple")))
有关更多信息,请访问我们的 API 参考。
我们很乐意讨论服务器端 Swift 和 Kitura。加入我们的 Slack 与团队会面!
此库在 Apache 2.0 许可下获得许可。完整许可文本可在 LICENSE 中找到。