Skip to content

Instantly share code, notes, and snippets.

@cfilipov
Created July 2, 2016 03:05
Show Gist options
  • Save cfilipov/7e11f83035189e29686db4d71b86c544 to your computer and use it in GitHub Desktop.
Save cfilipov/7e11f83035189e29686db4d71b86c544 to your computer and use it in GitHub Desktop.
A Swift DSL for building SQLite table creation statements intended for GRDB
struct _CreateTableBuilder {}
struct _PrimaryKeyBuilder {}
struct _ForeignKeyBuilder {}
struct _SQLExpressible {}
struct _SQLSelectable {}
struct _CreateTableSelectBuilder {}
struct SQLColumn {
var name: String
init(name: String) {
self.name = name
}
}
enum SQL {
static func create(
table: String,
temp: Bool = false,
ifNotExists: Bool = false,
withoutRowID: Bool = false) -> _CreateTableBuilder {
return _CreateTableBuilder()
}
static func primaryKey(
autoIncrement: Bool? = nil,
order: _Order? = nil,
onConflict: _ConflictResolution? = nil) -> _PrimaryKeyBuilder {
return _PrimaryKeyBuilder()
}
static func foreignKey(
table: String,
columns: SQLColumn...,
onDelete: _DeleteUpdateAction? = nil,
onUpdate: _DeleteUpdateAction? = nil,
deferrable: Bool? = nil) -> _ForeignKeyBuilder {
return _ForeignKeyBuilder()
}
}
extension _CreateTableBuilder {
func column(
name: SQLColumn,
primaryKey: _PrimaryKeyBuilder = false,
affinity: _Affinity = .numeric,
notNull: _ConflictClause = false,
unique: _ConflictClause = false,
check: _SQLExpressible? = nil,
default: _DefaultOption? = nil,
collate: String? = nil,
references: _ForeignKeyBuilder? = nil) -> _CreateTableBuilder {
return _CreateTableBuilder()
}
func foreignKey(
columns: SQLColumn...,
onConflict: _ConflictResolution = .abort,
references: _ForeignKeyBuilder) -> _CreateTableBuilder {
return _CreateTableBuilder()
}
func primaryKey(
columns: SQLColumn...,
onConflict: _ConflictResolution = .abort) -> _CreateTableBuilder {
return _CreateTableBuilder()
}
func unique(
columns: SQLColumn...,
onConflict: _ConflictResolution = .abort) -> _CreateTableBuilder {
return _CreateTableBuilder()
}
func check(_ expr: _SQLExpressible) -> _CreateTableBuilder {
return _CreateTableBuilder()
}
func select(_ expr: _SQLSelectable) -> _CreateTableSelectBuilder {
return _CreateTableSelectBuilder()
}
}
enum _DefaultOption {
case number(Int)
case literal(String)
case expression(_SQLExpressible)
}
enum _DeleteUpdateAction {
case none
case restrict
case setNull
case setDefault
case cascade
}
enum _ConflictResolution {
case rollback
case abort
case fail
case ignore
case replace
}
enum _Order {
case asc
case desc
}
enum _ConflictClause {
case onConflict(_ConflictResolution)
case none
}
enum _Affinity {
case integer
case text
case double
case blob
case numeric
}
protocol _ConflictResolvable {
var resolution: _ConflictClause { get }
}
extension _ConflictClause: BooleanLiteralConvertible {
init(booleanLiteral value: Bool) {
self = value
? .onConflict(.abort)
: .none
}
}
extension _ConflictClause: _ConflictResolvable {
var resolution: _ConflictClause {
return self
}
}
extension _PrimaryKeyBuilder: BooleanLiteralConvertible {
init(booleanLiteral value: Bool) {
self = value
? _PrimaryKeyBuilder()
: _PrimaryKeyBuilder()
}
}
extension _DefaultOption: IntegerLiteralConvertible {
init(integerLiteral value: Int) {
self = .number(value)
}
}
extension SQLColumn: StringLiteralConvertible {
init(unicodeScalarLiteral value: String) {
self = SQLColumn(name: value)
}
init(extendedGraphemeClusterLiteral value: String) {
self = SQLColumn(name: value)
}
init(stringLiteral value: String) {
self = SQLColumn(name: value)
}
}
extension _DefaultOption: StringLiteralConvertible {
init(unicodeScalarLiteral value: String) {
self = .literal(value)
}
init(extendedGraphemeClusterLiteral value: String) {
self = .literal(value)
}
init(stringLiteral value: String) {
self = .literal(value)
}
}
SQL.create(table: "foo", ifNotExists: true)
.column(
name: "id",
primaryKey: true,
affinity: .integer,
notNull: true)
SQL.create(table: "foo", ifNotExists: true)
.column(
name: "id",
primaryKey: SQL.primaryKey(autoIncrement: true),
affinity: .integer,
notNull: true)
SQL.create(table: "foo", ifNotExists: true)
.column(
name: "id",
primaryKey: SQL.primaryKey(
autoIncrement: true,
order: .desc,
onConflict: .abort),
affinity: .integer,
notNull: true)
SQL.create(table: "foo", ifNotExists: true)
.column(
name: "id",
primaryKey: SQL.primaryKey(onConflict: .abort),
affinity: .integer,
notNull: true)
SQL.create(table: "foo", ifNotExists: true)
.column(
name: "bar_id",
affinity: .integer,
references: SQL.foreignKey(
table: "bar",
columns: "id"))
SQL.create(table: "foo", ifNotExists: true)
.column(
name: "bar_id",
affinity: .integer,
references: SQL.foreignKey(
table: "bar",
columns: "id",
onDelete: .cascade,
onUpdate: .cascade,
deferrable: true))
let fooId: SQLColumn = "foo_id"
let bazId: SQLColumn = "baz_id"
let a: SQLColumn = "a"
let b: SQLColumn = "b"
let c: SQLColumn = "c"
SQL.create(table: "bar", ifNotExists: true)
.column(name: fooId, affinity: .integer)
.column(name: bazId, affinity: .integer)
.column(name: a, affinity: .integer)
.column(name: b, affinity: .integer)
.column(name: c, affinity: .integer)
.unique(columns: fooId, bazId, onConflict: .rollback)
.primaryKey(columns: a, b, c, onConflict: .rollback) // runtime error if multiple `primaryKey()` used
.foreignKey(
columns: fooId,
onConflict: .rollback,
references: SQL.foreignKey(
table: "foo",
columns: "id"))
.foreignKey(
columns: bazId,
onConflict: .rollback,
references: SQL.foreignKey(
table: "baz",
columns: "id"))
SQL.create(table: "foo", ifNotExists: true)
.column(name: "id", affinity: .integer)
.column(name: "a", default: 1)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment