SQLite.swift源码浅析

898 阅读4分钟

一、简介

​ SQLite提供了C/C++的接口,直接在工程中使用的话需要维护一些指针的声明周期,有时候需要自己再去封装一些接口给其他人使用。SQLite.swift是一个使用swift语言设计的SQLite的封装库,提供了一些面向对象的接口,让使用者可以更方便的对SQLite数据库进行操作,在使用Swift进行开发的时候,不妨可以试一试这个库。详细信息可以查看SQLite.swift的Github地址

去年在小组的开源项目学习中选择了这个库,进行了一些学习,主要目的是为了学习一些经典的开源代码,同时为了公司的swift战略,提前学习下swift相关知识。本文将从源码角度讲解一下SQLite.swift的实现原理,并介绍一些开源项目中的一些语法技巧的应用,阅读这需要了解Swift基本语法,以及SQL相关语法。

二、关键代码解析

2.1 对SQL语句的抽象

创建表的代码如下

// 创建1个表对象,表名为users
let users = Table("users")

// 创建3个列,id、name、email
let id = Expression<Int64>("id")
let name = Expression<String?>("name")
let email = Expression<String>("email")

// 使用db执行语句,创建一个表users,包括3列,
// "id" INTEGER PRIMARY KEY NOT NULL,
// "name" TEXT,
//  "email" TEXT NOT NULL UNIQUE
try db.run(users.create { t in
    t.column(id, primaryKey: true)
    t.column(name)
    t.column(email, unique: true)
})

这是什么意思呢,其实很简单,所有代码的唯一目的,就是构建一个创建users表的语句,最终的SQL语句如下:

CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY NOT NULL, "name" TEXT NOT NULL, "email" TEXT UNIQUE)

看到这句SQL是不是会发现很熟悉,那么SQL语句有什么特点呢?可以先简单看一下下图中SQLite中是如何规定创建表的语法

有点复杂,简单来讲创建表的语句结构定义如下,基本上就是“CREATE TABLE”关键字后跟一个新的表名以及括号内的一堆定义和约束,对于前边的创建表的语句,如下形式格式化后,就可以和语法结构定义一致:

CREATE TABLE IF NOT EXISTS "users" (
	"id" INTEGER PRIMARY KEY NOT NULL, 
	"name" TEXT NOT NULL, 
	"email" TEXT UNIQUE
)

那么有结构,就可以进行抽象。这里介绍两个SQLite.swift中的协议的概念:

  1. Expressible:SQLite.swift将SQL语句中的每一个模块(如CREATE、TABLE、"users"等等),都抽象成了一个Expressible对象,
  2. ExpressionExpressionExpressible的实现类,是对SQL语句中一部分的抽象,以及与该部分关联的数据(如列数据id、name等)。

那么如何用Expressible表示一个创建表的语句呢,看一下前面user.create方法中的代码:

let clauses: [Expressible?] = [
    Expression<Void>(literal: "CREATE"),
    modifier.map { Expression<Void>(literal: $0.rawValue) },
    Expression<Void>(literal: identifier),
    ifNotExists ? Expression<Void>(literal: "IF NOT EXISTS") : nil,
    name
    "".wrap(builder.definitions) as Expression<Void>
]

解析如下,clauses是一个Expressible?的数组,包含了5个部分:

  1. Expression<Void>(literal: "CREATE"):代表CREATE这个字符串
  2. modifier.map { Expression<Void>(literal: $0.rawValue) }modifier是一个optional的枚举值,在创建表这里可能是"tempory"或者是nil,这里的操作则是将该枚举包装成Expression对象或者为nil
  3. Expression<Void>(literal: identifier)identifieruser类的中的静态变量,这里是字符串TABLE
  4. ifNotExists ? Expression<Void>(literal: "IF NOT EXISTS") : nil:根据ifNotExists字段判断是否要创建一个内容为IF NOT EXISTSExpression对象
  5. name:表的名称,在这里是"users"
  6. "".wrap(builder.definitions) as Expression<Void>:这里的builderdefinitions实际上也是一个Expressible数组,包含了需要创建的所有列(这里就包含id、name、email这三个列),这一步的操作将所有列转化为了一个Expression对象,内容是字符串(\"id\" INTEGER PRIMARY KEY NOT NULL, \"name\" TEXT NOT NULL, \"email\" TEXT UNIQUE)

所以最后clauses这个对象,就是一个包含了创建数据库表SQL语句每一个部分的数组。之后会调用以下代码,将这个对象转换成SQL语句:

" ".join(clauses.compactMap { $0 }).asSQL()

这句话的是将clauses数组中的空对象去除,用字符串空格拼接后,最后调用asSQL将语句中的?替换成对应关联的数据,最终得到了上面的SQL语句

CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY NOT NULL, "name" TEXT NOT NULL, "email" TEXT UNIQUE)

简单总结一下:

SQL语句具有相对固定的结构,而SQLite.swift中将这种结构进行了封装,使用者只需要创建一些关键对象,调用相关的方法(比如create),即可生成对应的SQL语句。

2.2 扩展的应用

SQLite.swift中使用了非常多的extension,将不同的方法放入到不同的文件中,或着实现一些工具方法。这样做有好处,可以将不同功能的代码分散在不同的模块中,不好之处是在阅读代码上需要一些理解成本。对源码中的一些扩展举例如下:

  1. 对定义的类、结构体扩展

    Query.swift中定义了Table结构体,在Schema.swift中定义了Table的扩展,实现了createaddColumnrename等方法。

// Query.swift文件
public struct Table : SchemaType {
  public static let identifier = "TABLE"
  public var clauses: QueryClauses
  public init(...) {...}
}

// Schema.swift文件
extension Table {
 public func create(...) -> String {...}
 public func addColumn<V : Value>(...) -> String {...}
 public func rename(...) -> String {...}
 ...
}
  1. 对系统类扩展

    前面在拼接字符串时使用的join方法,功能是将expressible数组转换成一个Expression,内容为使用当前字符串进行拼接,一般的调用方法是"".join(xxx)

extension String {
  func join(_ expressions: [Expressible]) -> Expressible {
      var (template, bindings) = ([String](), [Binding?]())
      for expressible in expressions {
          let expression = expressible.expression
          template.append(expression.template)
          bindings.append(contentsOf: expression.bindings)
      }
      return Expression<Void>(template.joined(separator: self), bindings)
  }
}

其实在Objective-c中对扩展的应用已经非常普遍,在swift中又得到了进一步的加强。

2.3 泛型的应用

泛型也是在SQLite.swift中使用非常普遍的能力,前面所提到的Expression结构体,就使用了泛型:

let id = Expression<Int64>("id")
let name = Expression<String?>("name")
let email = Expression<String>("email")

可以看到,通过泛型创建了不同类型的列,其中Int64类型的idString类型可为NULLnameString类型不可为空的email

代码中Expression的定义如下:

public struct Expression<Datatype> : ExpressionType {
    // 当前的类型
    public typealias UnderlyingType = Datatype
    // Expression实际的内容
    public var template: String
    // 内容中待绑定的数据
    public var bindings: [Binding?]
}

下面看一个泛型在SQLite.swift中的应用,根据UnderlyingType对不同的泛型实例扩展方法

// Swift中Optional的定义,也就是swift语法中的?类型,其实是一个枚举
@frozen public enum Optional<Wrapped> : ExpressibleByNilLiteral {
  	case none
    case some(Wrapped)
}

// SQLite.swift对于Optional类型重新进行定义
public protocol _OptionalType {
    associatedtype WrappedType
}

extension Optional : _OptionalType {
    public typealias WrappedType = Wrapped
}

extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value {
  	associatedtype UnderlyingType = Void
}

这里主要的目的则是为了获取到Optional对象的类型,然后根据不同的条件来匹配Extension。

首先对系统的Optional扩展,实现了自定义的_OptionalType,将Optional中的Wrapped重新命名为WrappedType,这一步是而为了可以取到当前对象的类型。然后通过where来限定这个ExpressionType的扩展满足的条件则为:

  • 当前ExpressionType实例的泛型类型实现了_OptionalType,也就是是一个Optional类型

  • 这个泛型类型中的解包的类型,实现了Value协议,Value协议也是SQLite.swift定义的类型,系统的DoubleInt64String等类型都实现了扩展实现了该协议。

extension String : Binding, Value {}

所以对于前面所定义的name,就符合这个ExpressionType的扩展条件:

let name = Expression<String?>("name")

这个例子可以说是对泛型、扩展的应用,在Objective-c中是很难实现的。

2.4 运算符重载

在简介的例子中,有这样两个代码,第一次看到的可能不知道是干什么的

name <- "Alice"
email <- "alice@mac.com"

实际上,这里就是重载了运算符<-,看下具体代码:

public func <-<V : Value>(column: Expression<V>, value: V) -> Setter {
    return Setter(column: column, value: value)
}

public struct Setter {
    let column: Expressible
    let value: Expressible
}

可以看到,<-实际上就是创建了一个Setter对象,包含一个column对象和一个value对象,<-左边的就是column<-右边的就是value

在官方文档中,也对已经支持的运算符做了说明。

Infix Setters
OperatorTypes
<-Value -> Value
+=Number -> Number
-=Number -> Number
*=Number -> Number
/=Number -> Number
%=Int -> Int
<<=Int -> Int
>>=Int -> Int
&=Int -> Int
`=`Int -> Int
^=Int -> Int
+=String -> String
Postfix Setters
OperatorTypes
++Int -> Int
--Int -> Int

三、类图框架

通过前边的简介,应该已经大致了解SQLite.swift的原理,以及一些技巧的运用,接下来通过整体类图,介绍一些关键的协议以及结构体/类:

Expressible

Expressible这个单词是一个形容词,意思为可表达的。在SQLite.swift中的大部分类(结构体)直接或间接实现了该协议,其含义则表示这些结构体或类,都是一个可表达的实体,可以作为SQL语句中的一部分。其中主要包括了几个部分:

  • QueryType:是TableViewVirtualTable的基类;
  • ExpressionTypeExpression(列或一些固定的成分)、Insert等的基类;
  • Value:各种数据类型的基类,如DoubleString等。

Expressible中有一个Expression属性,代表所有可表达的对象,都可以转化为一个字符串表达式,而一个SQL则由不同的表达式组成,包含了固定的基本部分(如CREATE、TABLE)、需要操作的列(如列名)、以及对应的数据,分别由上面的QueryTypeExpressionTypeValue来组成。

这里就可以看出,SQLite.swift的设计精髓就是将SQL语句中的每一个部分,都抽象成了一个Expressible对象

Connection

在类图中,还有几个跟Expressible没有关系的类,ConnectionStatement,他们主要的职责是负责数据库的创建、连接、执行语句等功能。在程序开始时,我们需要创建一个Connection对象,用来创建或打开数据库,并在数据库的操作过程中持有该对象。使用Connection提供的run方法可以执行SQL语句。

执行过程中,可以通过返回值Statement来判断语句执行的结果,或者遍历Statement对象来获取查询结果。

四、关键流程

这里列举一些关键流程

4.1 创建表

初始化表的顺序如下:

  1. 使用路径创建Connection对象,打开数据库连接;
  2. 使用表名创建Table对象,并调用create方法获取创建表的SQL语句;
  3. 使用Connectionrun方法执行SQL语句;
  4. Connection内部则会创建Statement对象,并调用run方法真正执行SQL语句;
  5. Statement在初始化时,会调用SQLite的sqlite3_prepare_v2函数准备执行SQL,在run方法内部则会调用sqlite3_step函数执行SQL语句;
  6. 最后则会返回给调用者Statement对象,以供查询执行结果。

4.2 insert

插入语句的顺序如下:

  1. 创建Expression<String>对象,表示一个String类型的列,列名为name
  2. 通过<-运算符,将字符串Alice绑定到列name上,调用方式为name <- "Alice",该方法会生成一个Setter对象;
  3. 调用Tableinsert方法,得到一个Insert对象,该对象实际表达的就是将Alice插入到name列的SQL语句;
  4. 调用Connection对象的run方法,此时调用的顺序和创建表时类似,最终会执行SQL的API,并返回一个Statement对象。

这里Setter本身是一个实现了Expressible协议的对象,在insert方法中,就是创建了一个Expressible数组,代表了插入的SQL语句,最终生成了Insert对象,本身也是一个Expressible的实现类,生成的代码如下:

fileprivate func insert(_ or: OnConflict?, _ values: [Setter]) -> Insert {
    let insert = values.reduce((columns: [Expressible](), values: [Expressible]())) { insert, setter in
        (insert.columns + [setter.column], insert.values + [setter.value])
    }

    let clauses: [Expressible?] = [
        Expression<Void>(literal: "INSERT"),
        or.map { Expression<Void>(literal: "OR \($0.rawValue)") },
        Expression<Void>(literal: "INTO"),
        tableName(),
        "".wrap(insert.columns) as Expression<Void>,
        Expression<Void>(literal: "VALUES"),
        "".wrap(insert.values) as Expression<Void>,
        whereClause
    ]

    return Insert(" ".join(clauses.compactMap { $0 }).expression)
}

五、总结

SQLite.swift的学习和理解成本会比较高,和过往的SQL使用认知不同,需要在SQL的基础上,再学习SQLite.swift的使用方式后,才可以使用。个人认为这个库在实际工程中使用成本有待商榷,但我们可以学习其中的代码设计思想,以及swift语法技巧的应用。

六、参考文章

  1. SQLite的SQL语法

  2. SQLite.swift Documentation