diff --git a/.travis.yml b/.travis.yml index d8897e27..37785da9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,9 @@ language: objective-c env: - - BUILD_SDK=iphonesimulator ONLY_ACTIVE_ARCH=NO - - BUILD_SDK=macosx + - BUILD_SCHEME="SQLite iOS" + - BUILD_SCHEME="SQLite Mac" before_install: - gem install xcpretty --no-document script: - make test -osx_image: beta-xcode6.3 +osx_image: xcode7 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01793a0c..60c18370 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -54,29 +54,7 @@ first. While Swift error messaging is improving with each release, complex expressions still lend themselves to misleading errors. If you encounter an error on a complex line, breaking it down into smaller pieces generally - yields a more understandable error. _E.g._: - - ``` swift - users.insert(email <- "alice@mac.com" <- managerId <- db.lastInsertRowid) - // Cannot invoke 'insert' with an argument list of type '(Setter, Setter)' - ``` - - Not very helpful! If we break the expression down into smaller parts, - however, the real error materializes on the appropriate line. - - ``` swift - let emailSetter = email <- "alice@mac.com" - let managerIdSetter = managerId <- db.lastInsertRowId - // Binary operator '<-' cannot be applied to operands of type 'Expression' and 'Int64' - users.insert(emailSetter, managerIdSetter) - ``` - - The problem turns out to be a simple type mismatch. The fix is elsewhere: - - ``` diff - -let managerId = Expression("manager_id") - +let managerId = Expression("manager_id") - ``` + yields a more understandable error. - **Is it an _even more_ unhelpful build error?** diff --git a/Documentation/Index.md b/Documentation/Index.md index 3f815ca9..4b1b6c67 100644 --- a/Documentation/Index.md +++ b/Documentation/Index.md @@ -65,7 +65,7 @@ ## Installation -> _Note:_ SQLite.swift requires Swift 1.2 (and [Xcode 6.3](https://developer.apple.com/xcode/downloads/)) or greater. +> _Note:_ SQLite.swift requires Swift 2 (and [Xcode 7](https://developer.apple.com/xcode/downloads/)) or greater. ### CocoaPods @@ -78,8 +78,13 @@ ``` ruby use_frameworks! - pod 'SQLite.swift', git: 'https://github.com/stephencelis/SQLite.swift.git' - # pod 'SQLite.swift/Cipher', git: ... # instead, for SQLCipher support + + pod 'SQLite.swift', + git: 'https://github.com/stephencelis/SQLite.swift.git' + + # instead, for SQLCipher support + pod 'SQLiteCipher.swift', + git: 'https://github.com/stephencelis/SQLite.swift.git' ``` 3. Run `pod install`. @@ -153,7 +158,7 @@ import SQLite Database connections are established using the `Database` class. A database is initialized with a path. SQLite will attempt to create the database file if it does not already exist. ``` swift -let db = Database("path/to/db.sqlite3") +let db = try Connection("path/to/db.sqlite3") ``` @@ -164,9 +169,9 @@ On iOS, you can create a writable database in your app’s **Documents** directo ``` swift let path = NSSearchPathForDirectoriesInDomains( .DocumentDirectory, .UserDomainMask, true -).first as! String +).first! -let db = Database("\(path)/db.sqlite3") +let db = try Connection("\(path)/db.sqlite3") ``` On OS X, you can use your app’s **Application Support** directory: @@ -174,14 +179,14 @@ On OS X, you can use your app’s **Application Support** directory: ``` swift var path = NSSearchPathForDirectoriesInDomains( .ApplicationSupportDirectory, .UserDomainMask, true -).first as! String + NSBundle.mainBundle().bundleIdentifier! +).first! + NSBundle.mainBundle().bundleIdentifier! // create parent directory iff it doesn’t exist -NSFileManager.defaultManager().createDirectoryAtPath( - path, withIntermediateDirectories: true, attributes: nil, error: nil +try NSFileManager.defaultManager().createDirectoryAtPath( + path, withIntermediateDirectories: true, attributes: nil ) -let db = Database("\(path)/db.sqlite3") +let db = try Connection("\(path)/db.sqlite3") ``` @@ -192,7 +197,7 @@ If you bundle a database with your app (_i.e._, you’ve copied a database file ``` swift let path = NSBundle.mainBundle().pathForResource("db", ofType: "sqlite3")! -let db = Database(path, readonly: true) +let db = try Connection(path, readonly: true) ``` > _Note:_ Signed applications cannot modify their bundle resources. If you bundle a database file with your app for the purpose of bootstrapping, copy it to a writable location _before_ establishing a connection (see [Read-Write Databases](#read-write-databases), above, for typical, writable locations). @@ -203,13 +208,13 @@ let db = Database(path, readonly: true) If you omit the path, SQLite.swift will provision an [in-memory database](https://www.sqlite.org/inmemorydb.html). ``` swift -let db = Database() // equivalent to `Database(":memory:")` +let db = try Connection() // equivalent to `Connection(.InMemory)` ``` To create a temporary, disk-backed database, pass an empty file name. ``` swift -let db = Database("") +let db = try Connection(.Temporary) ``` In-memory databases are automatically deleted when the database connection is closed. @@ -217,7 +222,7 @@ In-memory databases are automatically deleted when the database connection is cl ### A Note on Thread-Safety -> _Note:_ Every database comes equipped with its own serial queue for statement execution and can be safely accessed across threads. Threads that open transactions and savepoints, however, do not block other threads from executing statements within the transaction. +> _Note:_ Every database comes equipped with its own serial queue for statement execution and can be safely accessed across threads. Threads that open transactions and savepoints will block other threads from executing statements while the transaction is open. ## Building Type-Safe SQL @@ -240,7 +245,7 @@ SQLite.swift comes with a typed expression layer that directly maps [Swift types > > See [Executing Arbitrary SQL](#executing-arbitrary-sql) to forego the typed layer and execute raw SQL, instead. -These expressions (in the form of the structure, [`Expression`](#expressions)) build on one another and, with a query ([`Query`](#queries)), can create and execute SQL statements. +These expressions (in the form of the structure, [`Expression`](#expressions)) build on one another and, with a query ([`QueryType`](#queries)), can create and execute SQL statements. ### Expressions @@ -260,7 +265,7 @@ Use optional generics for expressions that can evaluate to `NULL`. let name = Expression("name") ``` -> _Note:_ The default `Expression` initializer is for [quoted identifiers](https://www.sqlite.org/lang_keywords.html) (_i.e._, column names). To build a literal SQL expression, use `init(literal:)`. +> _Note:_ The default `Expression` initializer is for [quoted identifiers](https://www.sqlite.org/lang_keywords.html) (_i.e._, column names). To build a literal SQL expression, use `init(literal:)`. ### Compound Expressions @@ -270,10 +275,10 @@ Expressions can be combined with other expressions and types using [filter opera ### Queries -Queries are structures that reference a database and table name, and can be used to build a variety of statements using expressions. We can create a `Query` by subscripting a database connection with a table name. +Queries are structures that reference a database and table name, and can be used to build a variety of statements using expressions. We can create a query by initializing a `Table`, `View`, or `VirtualTable`. ``` swift -let users = db["users"] +let users = Table("users") ``` Assuming [the table exists](#creating-a-table), we can immediately [insert](#inserting-rows), [select](#selecting-rows), [update](#updating-rows), and [delete](#deleting-rows) rows. @@ -281,14 +286,14 @@ Assuming [the table exists](#creating-a-table), we can immediately [insert](#ins ## Creating a Table -We can run [`CREATE TABLE` statements](https://www.sqlite.org/lang_createtable.html) by calling the `create(table:)` function on a database connection. The following is a basic example of SQLite.swift code (using the [expressions](#expressions) and [query](#queries) above) and the corresponding SQL it generates. +We can build [`CREATE TABLE` statements](https://www.sqlite.org/lang_createtable.html) by calling the `create` function on a `Table`. The following is a basic example of SQLite.swift code (using the [expressions](#expressions) and [query](#queries) above) and the corresponding SQL it generates. ``` swift -db.create(table: users) { t in // CREATE TABLE "users" ( +try db.run(users.create { t in // CREATE TABLE "users" ( t.column(id, primaryKey: true) // "id" INTEGER PRIMARY KEY NOT NULL, t.column(email, unique: true) // "email" TEXT UNIQUE NOT NULL, t.column(name) // "name" TEXT -} // ) +}) // ) ``` > _Note:_ `Expression` structures (in this case, the `id` and `email` columns), generate `NOT NULL` constraints automatically, while `Expression` structures (`name`) do not. @@ -296,19 +301,19 @@ db.create(table: users) { t in // CREATE TABLE "users" ( ### Create Table Options -The `create(table:)` function has several default parameters we can override. +The `Table.create` function has several default parameters we can override. - `temporary` adds a `TEMPORARY` clause to the `CREATE TABLE` statement (to create a temporary table that will automatically drop when the database connection closes). Default: `false`. ``` swift - db.create(table: users, temporary: true) { t in /* ... */ } + try db.run(users.create(temporary: true) { t in /* ... */ }) // CREATE TEMPORARY TABLE "users" -- ... ``` - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. ``` swift - db.create(table: users, ifNotExists: true) { t in /* ... */ } + try db.run(users.create(ifNotExists: true) { t in /* ... */ }) // CREATE TABLE "users" IF NOT EXISTS -- ... ``` @@ -342,7 +347,7 @@ The `column` function is used for a single column definition. It takes an [expre - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). Boolean expressions can be easily built using [filter operators and functions](#filter-operators-and-functions). (See also the `check` function under [Table Constraints](#table-constraints).) ``` swift - t.column(email, check: like("%@%", email)) + t.column(email, check: email.like("%@%")) // "email" TEXT NOT NULL CHECK ("email" LIKE '%@%') ``` @@ -365,16 +370,18 @@ The `column` function is used for a single column definition. It takes an [expre // "name" TEXT COLLATE "RTRIM" ``` - - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`Query`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) + - `references` adds a `REFERENCES` clause to `Expression` (and `Expression`) column definitions and accepts a table (`SchemaType`) or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) ``` swift - t.column(user_id, references: users[id]) - // "user_id" INTEGER REFERENCES "users"("id") + t.column(user_id, references: users, id) + // "user_id" INTEGER REFERENCES "users" ("id") + > _Note:_ The `references` parameter cannot be used alongside `primaryKey` and `defaultValue`. If you need to create a column that has a default value and is also a primary and/or foreign key, use the `primaryKey` and `foreignKey` functions mentioned under [Table Constraints](#table-constraints). @@ -404,10 +411,10 @@ Additional constraints may be provided outside the scope of a single column usin // CHECK ("balance" >= 0.0) ``` - - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the `references` constraint, above](#column-constraints), it supports all SQLite types, and both [`ON UPDATE` and `ON DELETE` actions](https://www.sqlite.org/foreignkeys.html#fk_actions), and composite (multiple column) keys. + - `foreignKey` adds a `FOREIGN KEY` constraint to the table. Unlike [the `references` constraint, above](#column-constraints), it supports all SQLite types, both [`ON UPDATE` and `ON DELETE` actions](https://www.sqlite.org/foreignkeys.html#fk_actions), and composite (multiple column) keys. ``` swift - t.foreignKey(user_id, on: users[id], delete: .SetNull) + t.foreignKey(user_id, references: users, id, delete: .SetNull) // FOREIGN KEY("user_id") REFERENCES "users"("id") ON DELETE SET NULL ``` @@ -421,21 +428,21 @@ Additional constraints may be provided outside the scope of a single column usin We can insert rows into a table by calling a [query’s](#queries) `insert` function with a list of [setters](#setters)—typically [typed column expressions](#expressions) and values (which can also be expressions)—each joined by the `<-` operator. ``` swift -users.insert(email <- "alice@mac.com", name <- "Alice") +try db.run(users.insert(email <- "alice@mac.com", name <- "Alice")) // INSERT INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice') -users.insert(or: .Replace, email <- "alice@mac.com", name <- "Alice B.") +try db.run(users.insert(or: .Replace, email <- "alice@mac.com", name <- "Alice B.")) // INSERT OR REPLACE INTO "users" ("email", "name") VALUES ('alice@mac.com', 'Alice B.') ``` -The `insert` function returns a tuple with an `Int64?` representing the inserted row’s [`ROWID`][ROWID] (or `nil` on failure) and the associated `Statement`. +The `insert` function, when run successfully, returns an `Int64` representing the inserted row’s [`ROWID`][ROWID]. ``` swift -let insert = users.insert(email <- "alice@mac.com") -if let rowid = insert.rowid { - println("inserted id: \(rowid)") -} else if insert.statement.failed { - println("insertion failed: \(insert.statement.reason)") +do { + let rowid = try db.run(users.insert(email <- "alice@mac.com")) + print("inserted id: \(rowid)") +} catch { + print("insertion failed: \(error)") } ``` @@ -444,7 +451,7 @@ The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow s > _Note:_ If `insert` is called without any arguments, the statement will run with a `DEFAULT VALUES` clause. The table must not have any constraints that aren’t fulfilled by default values. > > ``` swift -> timestamps.insert() +> try db.run(timestamps.insert()) > // INSERT INTO "timestamps" DEFAULT VALUES > ``` @@ -454,8 +461,8 @@ The [`update`](#updating-rows) and [`delete`](#deleting-rows) functions follow s SQLite.swift typically uses the `<-` operator to set values during [inserts](#inserting-rows) and [updates](#updating-rows). ``` swift -views.update(count <- 0) -// UPDATE "views" SET "count" = 0 WHERE ("id" = 1) +try db.run(counter.update(count <- 0)) +// UPDATE "counters" SET "count" = 0 WHERE ("id" = 1) ``` There are also a number of convenience setters that take the existing value into account using native Swift operators. @@ -463,22 +470,22 @@ There are also a number of convenience setters that take the existing value into For example, to atomically increment a column, we can use `++`: ``` swift -views.update(count++) // equivalent to `views.update(count -> count + 1)` -// UPDATE "views" SET "count" = "count" + 1 WHERE ("id" = 1) +try db.run(counter.update(count++)) // equivalent to `counter.update(count -> count + 1)` +// UPDATE "counters" SET "count" = "count" + 1 WHERE ("id" = 1) ``` To take an amount and “move” it via transaction, we can use `-=` and `+=`: ``` swift let amount = 100.0 -db.transaction() - && alice.update(balance -= amount) - && betty.update(balance += amount) - && db.commit() || db.rollback() -// BEGIN DEFERRED TRANSACTION; -// UPDATE "users" SET "balance" = "balance" - 100.0 WHERE ("id" = 1); -// UPDATE "users" SET "balance" = "balance" + 100.0 WHERE ("id" = 2); -// COMMIT TRANSACTION; +try db.transaction { + try db.run(alice.update(balance -= amount)) + try db.run(betty.update(balance += amount)) +} +// BEGIN DEFERRED TRANSACTION +// UPDATE "users" SET "balance" = "balance" - 100.0 WHERE ("id" = 1) +// UPDATE "users" SET "balance" = "balance" + 100.0 WHERE ("id" = 2) +// COMMIT TRANSACTION ``` @@ -510,16 +517,16 @@ db.transaction() ## Selecting Rows -[`Query` structures](#queries) are `SELECT` statements waiting to happen. They execute via [iteration](#iterating-and-accessing-values) and [other means](#plucking-values) of sequence access. +[Query structures](#queries) are `SELECT` statements waiting to happen. They execute via [iteration](#iterating-and-accessing-values) and [other means](#plucking-values) of sequence access. ### Iterating and Accessing Values -[Queries](#queries) execute lazily upon iteration. Each row is returned as a `Row` object, which can be subscripted with a [column expression](#expressions) matching one of the columns returned. +Prepared [queries](#queries) execute lazily upon iteration. Each row is returned as a `Row` object, which can be subscripted with a [column expression](#expressions) matching one of the columns returned. ``` swift -for user in users { - println("id: \(user[id]), email: \(user[email]), name: \(user[name])") +for user in try db.prepare(users) { + print("id: \(user[id]), email: \(user[email]), name: \(user[name])") // id: 1, email: alice@mac.com, name: Optional("Alice") } // SELECT * FROM "users" @@ -530,24 +537,24 @@ for user in users { ### Plucking Rows -We can pluck the first row by calling the `first` computed property on [`Query`](#queries). +We can pluck the first row by passing a query to the `pluck` function on a database connection. ``` swift -if let user = users.first { /* ... */ } // Row +if let user = try db.pluck(users) { /* ... */ } // Row // SELECT * FROM "users" LIMIT 1 ``` To collect all rows into an array, we can simply wrap the sequence (though this is not always the most memory-efficient idea). ``` swift -let all = Array(users) +let all = Array(try db.prepare(users)) // SELECT * FROM "users" ``` ### Building Complex Queries -[`Query`](#queries) structures have a number of chainable functions that can be used (with [expressions](#expressions)) to add and modify [a number of clauses](https://www.sqlite.org/lang_select.html) to the underlying statement. +[Queries](#queries) have a number of chainable functions that can be used (with [expressions](#expressions)) to add and modify [a number of clauses](https://www.sqlite.org/lang_select.html) to the underlying statement. ``` swift let query = users.select(email) // SELECT "email" FROM "users" @@ -559,11 +566,11 @@ let query = users.select(email) // SELECT "email" FROM "users" #### Selecting Columns -By default, [`Query`](#queries) objects select every column of the result set (using `SELECT *`). We can use the `select` function with a list of [expressions](#expressions) to return specific columns instead. +By default, [queries](#queries) select every column of the result set (using `SELECT *`). We can use the `select` function with a list of [expressions](#expressions) to return specific columns instead. ``` swift -for user in users.select(id, email) { - println("id: \(user[id]), email: \(user[email])") +for user in try db.prepare(users.select(id, email)) { + print("id: \(user[id]), email: \(user[email])") // id: 1, email: alice@mac.com } // SELECT "id", "email" FROM "users" @@ -574,7 +581,7 @@ We can access the results of more complex expressions by holding onto a referenc ``` swift let sentence = name + " is " + cast(age) as Expression + " years old!" for user in users.select(sentence) { - println(user[sentence]) + print(user[sentence]) // Optional("Alice is 30 years old!") } // SELECT ((("name" || ' is ') || CAST ("age" AS TEXT)) || ' years old!') FROM "users" @@ -634,7 +641,7 @@ let query = users.join(managers, on: managers[id] == users[managerId]) If query results can have ambiguous column names, row values should be accessed with namespaced [column expressions](#expressions). In the above case, `SELECT *` immediately namespaces all columns of the result set. ``` swift -let user = query.first! +let user = try db.pluck(query) user[id] // fatal error: ambiguous column 'id' // (please disambiguate: ["users"."id", "managers"."id"]) @@ -651,13 +658,13 @@ SQLite.swift filters rows using a [query’s](#queries) `filter` function with a users.filter(id == 1) // SELECT * FROM "users" WHERE ("id" = 1) -users.filter(contains([1, 2, 3, 4, 5], id)) +users.filter([1, 2, 3, 4, 5].contains(id)) // SELECT * FROM "users" WHERE ("id" IN (1, 2, 3, 4, 5)) -users.filter(like("%@mac.com", email)) +users.filter(email.like("%@mac.com")) // SELECT * FROM "users" WHERE ("email" LIKE '%@mac.com') -users.filter(verified && lower(name) == "alice") +users.filter(verified && name.lowercaseString == "alice") // SELECT * FROM "users" WHERE ("verified" AND (lower("name") == 'alice')) users.filter(verified || balance >= 10_000) @@ -749,68 +756,68 @@ users.limit(5, offset: 5) #### Aggregation -[`Query`](#queries) structures come with a number of functions that quickly return aggregate values from the table. These mirror the [core aggregate functions](#aggregate-sqlite-functions) and are executed immediately against the query. +[Queries](#queries) come with a number of functions that quickly return aggregate scalar values from the table. These mirror the [core aggregate functions](#aggregate-sqlite-functions) and are executed immediately against the query. ``` swift -users.count +let count = try db.scalar(users.count) // SELECT count(*) FROM "users" ``` Filtered queries will appropriately filter aggregate values. ``` swift -users.filter(name != nil).count +let count = try db.scalar(users.filter(name != nil).count) // SELECT count(*) FROM "users" WHERE "name" IS NOT NULL ``` - - `count` as a computed property (see examples above) returns the total number of rows matching the query. + - `count` as a computed property on a query (see examples above) returns the total number of rows matching the query. - `count` as a function takes a [column name](#expressions) and returns the total number of rows where that column is not `NULL`. + `count` as a computed property on a column expression returns the total number of rows where that column is not `NULL`. ``` swift - users.count(name) // -> Int + let count = try db.scalar(users.select(name.count)) // -> Int // SELECT count("name") FROM "users" ``` - `max` takes a comparable column expression and returns the largest value if any exists. ``` swift - users.max(id) // -> Int64? + let max = try db.scalar(users.select(id.max)) // -> Int64? // SELECT max("id") FROM "users" ``` - `min` takes a comparable column expression and returns the smallest value if any exists. ``` swift - users.min(id) // -> Int64? + let min = try db.scalar(users.select(id.min)) // -> Int64? // SELECT min("id") FROM "users" ``` - `average` takes a numeric column expression and returns the average row value (as a `Double`) if any exists. ``` swift - users.average(balance) // -> Double? + let average = try db.scalar(users.select(balance.average)) // -> Double? // SELECT avg("balance") FROM "users" ``` - `sum` takes a numeric column expression and returns the sum total of all rows if any exist. ``` swift - users.sum(balance) // -> Double? + let sum = try db.scalar(users.select(balance.sum)) // -> Double? // SELECT sum("balance") FROM "users" ``` - `total`, like `sum`, takes a numeric column expression and returns the sum total of all rows, but in this case always returns a `Double`, and returns `0.0` for an empty query. ``` swift - users.total(balance) // -> Double + let total = try db.scalar(users.select(balance.total)) // -> Double // SELECT total("balance") FROM "users" ``` -> _Note:_ Most of the above aggregate functions (except `max` and `min`) can be called with a `distinct` parameter to aggregate `DISTINCT` values only. +> _Note:_ Expressions can be prefixed with a `DISTINCT` clause by calling the `distinct` computed property. > > ``` swift -> users.count(distinct: name) +> let count = try db.scalar(users.select(name.distinct.count) // -> Int > // SELECT count(DISTINCT "name") FROM "users" > ``` @@ -822,7 +829,7 @@ We can update a table’s rows by calling a [query’s](#queries) `update` funct When an unscoped query calls `update`, it will update _every_ row in the table. ``` swift -users.update(email <- "alice@me.com") +try db.run(users.update(email <- "alice@me.com")) // UPDATE "users" SET "email" = 'alice@me.com' ``` @@ -830,18 +837,21 @@ Be sure to scope `UPDATE` statements beforehand using [the `filter` function](#f ``` swift let alice = users.filter(id == 1) -alice.update(email <- "alice@me.com") +try db.run(alice.update(email <- "alice@me.com")) // UPDATE "users" SET "email" = 'alice@me.com' WHERE ("id" = 1) ``` -The `update` function returns a tuple with an `Int?` representing the number of updates (or `nil` on failure) and the associated `Statement`. +The `update` function returns an `Int` representing the number of updated rows. ``` swift -let update = alice.update(email <- "alice@me.com") -if let changes = update.changes where changes > 0 { - println("updated alice") -} else if update.statement.failed { - println("update failed: \(update.statement.reason)") +do { + if try db.run(alice.update(email <- "alice@me.com")) > 0 { + print("updated alice") + } else { + print("alice not found") + } +} catch { + print("update failed: \(error)") } ``` @@ -853,7 +863,7 @@ We can delete rows from a table by calling a [query’s](#queries) `delete` func When an unscoped query calls `delete`, it will delete _every_ row in the table. ``` swift -users.delete() +try db.run(users.delete()) // DELETE FROM "users" ``` @@ -861,91 +871,83 @@ Be sure to scope `DELETE` statements beforehand using [the `filter` function](#f ``` swift let alice = users.filter(id == 1) -alice.delete() +try db.run(alice.delete()) // DELETE FROM "users" WHERE ("id" = 1) ``` -The `delete` function returns a tuple with an `Int?` representing the number of deletes (or `nil` on failure) and the associated `Statement`. +The `delete` function returns an `Int` representing the number of deleted rows. ``` swift -let delete = alice.delete() -if let changes = delete.changes where changes > 0 { - println("deleted alice") -} else if delete.statement.failed { - println("delete failed: \(delete.statement.reason)") +do { + if try db.run(alice.delete()) > 0 { + print("deleted alice") + } else { + print("alice not found") + } +} catch { + print("delete failed: \(error)") } ``` ## Transactions and Savepoints -Using the `transaction` and `savepoint` functions, we can run a series of statements chained together (using `&&`). If a single statement fails, we can short-circuit the series (using `||`) and roll back the changes. +Using the `transaction` and `savepoint` functions, we can run a series of statements in a transaction. If a single statement fails or the block throws an error, the changes will be rolled back. ``` swift -db.transaction() - && users.insert(email <- "betty@icloud.com") - && users.insert(email <- "cathy@icloud.com", managerId <- db.lastInsertRowid) - && db.commit() || db.rollback() -``` - -> _Note:_ Each statement is captured in an auto-closure and won’t execute till the preceding statement succeeds. This is why we can use the `lastInsertRowid` property on `Database` to reference the previous statement’s insert [`ROWID`][ROWID]. - -For more complex transactions and savepoints, block helpers exist. Using a block helper, the former statement can be written (more verbosely) as follows: - -``` swift -db.transaction { txn in - if let rowid = users.insert(email <- "betty@icloud.com").rowid { - if users.insert(email <- "cathy@icloud.com", managerId <- db.lastInsertRowid).rowid != nil { - return .Commit - } - } - return .Rollback +try db.transaction { + let rowid = try db.run(users.insert(email <- "betty@icloud.com")) + try db.run(users.insert(email <- "cathy@icloud.com", managerId <- rowid)) } +// BEGIN DEFERRED TRANSACTION +// INSERT INTO "users" ("email") VALUES ('betty@icloud.com') +// INSERT INTO "users" ("email", "manager_id") VALUES ('cathy@icloud.com', 2) +// COMMIT TRANSACTION ``` +> _Note:_ Transactions run in a serial queue. + ## Altering the Schema -SQLite.swift comes with several functions (in addition to `create(table:)`) for altering a database schema in a type-safe manner. +SQLite.swift comes with several functions (in addition to `Table.create`) for altering a database schema in a type-safe manner. ### Renaming Tables -We can rename a table by calling the `rename(table:to:)` function on a database connection. +We can build an `ALTER TABLE … RENAME TO` statement by calling the `rename` function on a `Table` or `VirtualTable`. ``` swift -db.rename(users, to: "users_old") +try db.run(users.rename(Table("users_old")) // ALTER TABLE "users" RENAME TO "users_old" ``` ### Adding Columns -We can add columns to a table by calling `alter` function on a database connection. SQLite.swift enforces [the same limited subset](https://www.sqlite.org/lang_altertable.html) of `ALTER TABLE` that SQLite supports. +We can add columns to a table by calling `addColumn` function on a `Table`. SQLite.swift enforces [the same limited subset](https://www.sqlite.org/lang_altertable.html) of `ALTER TABLE` that SQLite supports. ``` swift -db.alter(table: users, add: suffix) +try db.run(users.addColumn(suffix)) // ALTER TABLE "users" ADD COLUMN "suffix" TEXT ``` #### Added Column Constraints -The `alter` function shares several of the same [`column` function parameters](#column-constraints) used when [creating tables](#creating-a-table). +The `addColumn` function shares several of the same [`column` function parameters](#column-constraints) used when [creating tables](#creating-a-table). - `check` attaches a `CHECK` constraint to a column definition in the form of a boolean expression (`Expression`). (See also the `check` function under [Table Constraints](#table-constraints).) ``` swift - let check = contains(["JR", "SR"], suffix) - db.alter(table: users, add: suffix, check: check) - // ALTER TABLE "users" - // ADD COLUMN "suffix" TEXT CHECK ("suffix" IN ('JR', 'SR')) + try db.run(users.addColumn(suffix, check: ["JR", "SR"].contains(suffix))) + // ALTER TABLE "users" ADD COLUMN "suffix" TEXT CHECK ("suffix" IN ('JR', 'SR')) ``` - `defaultValue` adds a `DEFAULT` clause to a column definition and _only_ accepts a value matching the column’s type. This value is used if none is explicitly provided during [an `INSERT`](#inserting-rows). ``` swift - db.alter(table: users, add: suffix, defaultValue: "SR") + try db.run(users.addColumn(suffix, defaultValue: "SR")) // ALTER TABLE "users" ADD COLUMN "suffix" TEXT DEFAULT 'SR' ``` @@ -954,24 +956,18 @@ The `alter` function shares several of the same [`column` function parameters](# - `collate` adds a `COLLATE` clause to `Expression` (and `Expression`) column definitions with [a collating sequence](https://www.sqlite.org/datatype3.html#collation) defined in the `Collation` enumeration. ``` swift - t.alter(table: users, add: email, collate: .Nocase) - // ALTER TABLE "users" - // ADD COLUMN "email" TEXT NOT NULL COLLATE "NOCASE" + try db.run(users.addColumn(email, collate: .Nocase)) + // ALTER TABLE "users" ADD COLUMN "email" TEXT NOT NULL COLLATE "NOCASE" - t.alter(table: users, add: name, collate: .Rtrim) - // ALTER TABLE "users" - // ADD COLUMN "name" TEXT COLLATE "RTRIM" + try db.run(users.addColumn(name, collate: .Rtrim)) + // ALTER TABLE "users" ADD COLUMN "name" TEXT COLLATE "RTRIM" ``` - `references` adds a `REFERENCES` clause to `Int64` (and `Int64?`) column definitions and accepts a table or namespaced column expression. (See the `foreignKey` function under [Table Constraints](#table-constraints) for non-integer foreign key support.) ``` swift - db.alter(table: posts, add: user_id, references: users[id]) - // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users"("id") - - db.alter(table: posts, add: user_id, references: users) - // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users" - // -- assumes "users" has a PRIMARY KEY + try db.run(posts.addColumn(userId, references: users, id) + // ALTER TABLE "posts" ADD COLUMN "user_id" INTEGER REFERENCES "users" ("id") ``` @@ -980,67 +976,67 @@ The `alter` function shares several of the same [`column` function parameters](# #### Creating Indexes -We can run [`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.html) by calling the `create(index:)` function on a database connection. +We can build [`CREATE INDEX` statements](https://www.sqlite.org/lang_createindex.html) by calling the `createIndex` function on a `SchemaType`. ``` swift -db.create(index: users, on: email) +try db.run(users.createIndex(email)) // CREATE INDEX "index_users_on_email" ON "users" ("email") ``` The index name is generated automatically based on the table and column names. -The `create(index:)` function has a couple default parameters we can override. +The `createIndex` function has a couple default parameters we can override. - `unique` adds a `UNIQUE` constraint to the index. Default: `false`. ``` swift - db.create(index: users, on: email, unique: true) + try db.run(users.createIndex(email, unique: true)) // CREATE UNIQUE INDEX "index_users_on_email" ON "users" ("email") ``` - `ifNotExists` adds an `IF NOT EXISTS` clause to the `CREATE TABLE` statement (which will bail out gracefully if the table already exists). Default: `false`. ``` swift - db.create(index: users, on: email, ifNotExists: true) + try db.run(users.createIndex(email, ifNotExists: true)) // CREATE INDEX IF NOT EXISTS "index_users_on_email" ON "users" ("email") ``` #### Dropping Indexes -We can run [`DROP INDEX` statements](https://www.sqlite.org/lang_dropindex.html) by calling the `drop(index:)` function on a database connection. +We can build [`DROP INDEX` statements](https://www.sqlite.org/lang_dropindex.html) by calling the `dropIndex` function on a `SchemaType`. ``` swift -db.drop(index: users, on: email) +try db.run(users.dropIndex(email)) // DROP INDEX "index_users_on_email" ``` -The `drop(index:)` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. +The `dropIndex` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. ``` swift -db.drop(index: users, on: email, ifExists: true) +try db.run(users.dropIndex(email, ifExists: true)) // DROP INDEX IF EXISTS "index_users_on_email" ``` ### Dropping Tables -We can run [`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) by calling the `drop(table:)` function on a database connection. +We can build [`DROP TABLE` statements](https://www.sqlite.org/lang_droptable.html) by calling the `dropTable` function on a `SchemaType`. ``` swift -db.drop(table: users) +try db.run(users.drop()) // DROP TABLE "users" ``` -The `drop(table:)` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. +The `drop` function has one additional parameter, `ifExists`, which (when `true`) adds an `IF EXISTS` clause to the statement. ``` swift -db.drop(table: users, ifExists: true) +try db.run(users.drop(ifExists: true)) // DROP TABLE IF EXISTS "users" ``` -### Migrations and Schema Versioning + ## Custom Types @@ -1267,16 +1263,16 @@ Most of SQLite’s [aggregate functions](https://www.sqlite.org/lang_aggfunc.htm ## Custom SQL Functions -We can create custom SQL functions by calling `create(function:)` on a database connection. +We can create custom SQL functions by calling `createFunction` on a database connection. For example, to give queries access to [`MobileCoreServices.UTTypeConformsTo`](https://developer.apple.com/library/ios/documentation/MobileCoreServices/Reference/UTTypeRef/index.html#//apple_ref/c/func/UTTypeConformsTo), we can write the following: ``` swift import MobileCoreServices -let typeConformsTo: (String, Expression) -> Expression = ( - db.create(function: "typeConformsTo", deterministic: true) { UTI, conformsToUTI in - return UTTypeConformsTo(UTI, conformsToUTI) != 0 +let typeConformsTo: (Expression, String) -> Expression = ( + try db.createFunction("typeConformsTo", deterministic: true) { UTI, conformsToUTI in + return UTTypeConformsTo(UTI, conformsToUTI) } ) ``` @@ -1289,7 +1285,7 @@ Note `typeConformsTo`’s signature: (Expression, String) -> Expression ``` -Because of this, `create(function:)` expects a block with the following signature: +Because of this, `createFunction` expects a block with the following signature: ``` swift (String, String) -> Bool @@ -1298,10 +1294,10 @@ Because of this, `create(function:)` expects a block with the following signatur Once assigned, the closure can be called wherever boolean expressions are accepted. ``` swift -let attachments = db["attachments"] +let attachments = Table("attachments") let UTI = Expression("UTI") -attachments.filter(typeConformsTo(UTI, kUTTypeImage)) +let images = attachments.filter(typeConformsTo(UTI, kUTTypeImage)) // SELECT * FROM "attachments" WHERE "typeConformsTo"("UTI", 'public.image') ``` @@ -1310,28 +1306,26 @@ attachments.filter(typeConformsTo(UTI, kUTTypeImage)) We can create loosely-typed functions by handling an array of raw arguments, instead. ``` swift -db.create(function: "typeConformsTo", deterministic: true) { args in - if let UTI = args[0] as? String, conformsToUTI = args[1] as? String { - return Int(UTTypeConformsTo(UTI, conformsToUTI)) - } - return nil +db.createFunction("typeConformsTo", deterministic: true) { args in + guard let UTI = args[0] as? String, conformsToUTI = args[1] as? String else { return nil } + return UTTypeConformsTo(UTI, conformsToUTI) } ``` Creating a loosely-typed function cannot return a closure and instead must be wrapped manually or executed [using raw SQL](#executing-arbitrary-sql). ``` swift -let stmt = db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)") +let stmt = try db.prepare("SELECT * FROM attachments WHERE typeConformsTo(UTI, ?)") for row in stmt.bind(kUTTypeImage) { /* ... */ } ``` ## Custom Collations -We can create custom collating sequences by calling `create(collation:)` on a database connection. +We can create custom collating sequences by calling `createCollation` on a database connection. ``` swift -db.create(collation: "NODIACRITIC") { lhs, rhs in +try db.createCollation("NODIACRITIC") { lhs, rhs in return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) } ``` @@ -1346,36 +1340,36 @@ restaurants.order(collate(.Custom("NODIACRITIC"), name)) ## Full-text Search -We can create a virtual table using the [FTS4 module](http://www.sqlite.org/fts3.html) by calling `create(vtable:)` on a database connection. +We can create a virtual table using the [FTS4 module](http://www.sqlite.org/fts3.html) by calling `create` on a `VirtualTable`. ``` swift -let emails = db["emails"] +let emails = VirtualTable("emails") let subject = Expression("subject") let body = Expression("body") -db.create(vtable: emails, using: fts4(subject, body)) +try db.run(emails.create(.FTS4(subject, body))) // CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body") ``` We can specify a [tokenizer](http://www.sqlite.org/fts3.html#tokenizer) using the `tokenize` parameter. ``` swift -db.create(vtable: emails, using: fts4([subject, body], tokenize: .Porter)) +try db.run(emails.create(.FTS4([subject, body], tokenize: .Porter))) // CREATE VIRTUAL TABLE "emails" USING fts4("subject", "body", tokenize=porter) ``` Once we insert a few rows, we can search using the `match` function, which takes a table or column as its first argument and a query string as its second. ``` swift -emails.insert( +try db.run(emails.insert( subject <- "Just Checking In", body <- "Hey, I was just wondering...did you get my last email?" -)! +)) -emails.filter(match(emails, "wonder*")) +let wonderfulEmails = emails.match("wonder*") // SELECT * FROM "emails" WHERE "emails" MATCH 'wonder*' -emails.filter(match(subject, "Re:*")) +let replies = emails.filter(subject.match("Re:*")) // SELECT * FROM "emails" WHERE "subject" MATCH 'Re:*' ``` @@ -1387,7 +1381,7 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building - `execute` runs an arbitrary number of SQL statements as a convenience. ``` swift - db.execute( + try db.execute( "BEGIN TRANSACTION;" + "CREATE TABLE users (" + "id INTEGER PRIMARY KEY NOT NULL," + @@ -1408,22 +1402,22 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building - `prepare` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), and returns the statement for deferred execution. ``` swift - let stmt = db.prepare("INSERT INTO users (email) VALUES (?)") + let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") ``` Once prepared, statements may be executed using `run`, binding any unbound parameters. ``` swift - stmt.run("alice@mac.com") + try stmt.run("alice@mac.com") db.changes // -> {Some 1} ``` Statements with results may be iterated over. ``` swift - let stmt = db.prepare("SELECT id, email FROM users") + let stmt = try db.prepare("SELECT id, email FROM users") for row in stmt { - println("id: \(row[0]), email: \(row[1])") + print("id: \(row[0]), email: \(row[1])") // id: Optional(1), email: Optional("alice@mac.com") } ``` @@ -1431,20 +1425,20 @@ Though we recommend you stick with SQLite.swift’s [type-safe system](#building - `run` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the statement. ``` swift - db.run("INSERT INTO users (email) VALUES (?)", "alice@mac.com") + try db.run("INSERT INTO users (email) VALUES (?)", "alice@mac.com") ``` - `scalar` prepares a single `Statement` object from a SQL string, optionally binds values to it (using the statement’s `bind` function), executes, and returns the first value of the first row. ``` swift - db.scalar("SELECT count(*) FROM users") as! Int64 + let count = try db.scalar("SELECT count(*) FROM users") as! Int64 ``` Statements also have a `scalar` function, which can optionally re-bind values at execution. ``` swift - let stmt = db.prepare("SELECT count (*) FROM users") - stmt.scalar() as! Int64 + let stmt = try db.prepare("SELECT count (*) FROM users") + let count = try stmt.scalar() as! Int64 ``` @@ -1454,7 +1448,7 @@ We can log SQL using the database’s `trace` function. ``` swift #if DEBUG - db.trace(println) + db.trace(print) #endif ``` diff --git a/Makefile b/Makefile index 7fe0d757..ff0a2054 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BUILD_TOOL = xcodebuild -BUILD_SDK = macosx -BUILD_ARGUMENTS = -scheme SQLite -sdk $(BUILD_SDK) +BUILD_SCHEME = SQLite Mac +BUILD_ARGUMENTS = -scheme "$(BUILD_SCHEME)" XCPRETTY := $(shell command -v xcpretty) SWIFTCOV := $(shell command -v swiftcov) diff --git a/README.md b/README.md index d80951f3..a155e0cd 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ A type-safe, [Swift][]-language layer over [SQLite3][]. [SQLite.swift][] provides compile-time confidence in SQL statement syntax _and_ intent. -[Badge]: https://img.shields.io/travis/stephencelis/SQLite.swift/master.svg?style=flat +[Badge]: https://img.shields.io/travis/stephencelis/SQLite.swift/swift-2.svg?style=flat [Travis]: https://travis-ci.org/stephencelis/SQLite.swift [Swift]: https://developer.apple.com/swift/ [SQLite3]: http://www.sqlite.org @@ -34,46 +34,44 @@ syntax _and_ intent. ``` swift import SQLite -let db = Database("path/to/db.sqlite3") +let db = try Connection("path/to/db.sqlite3") -let users = db["users"] +let users = Table("users") let id = Expression("id") let name = Expression("name") let email = Expression("email") -db.create(table: users) { t in +try db.run(users.create { t in t.column(id, primaryKey: true) t.column(name) t.column(email, unique: true) -} +}) // CREATE TABLE "users" ( // "id" INTEGER PRIMARY KEY NOT NULL, // "name" TEXT, // "email" TEXT NOT NULL UNIQUE // ) -var alice: Query? -if let rowid = users.insert(name <- "Alice", email <- "alice@mac.com").rowid { - println("inserted id: \(rowid)") - // inserted id: 1 - alice = users.filter(id == rowid) -} +let insert = users.insert(name <- "Alice", email <- "alice@mac.com") +let rowid = try db.run(insert) // INSERT INTO "users" ("name", "email") VALUES ('Alice', 'alice@mac.com') -for user in users { +for user in db.prepare(users) { println("id: \(user[id]), name: \(user[name]), email: \(user[email])") // id: 1, name: Optional("Alice"), email: alice@mac.com } // SELECT * FROM "users" -alice?.update(email <- replace(email, "mac.com", "me.com")) +let alice = users.filter(id == rowid) + +try db.run(alice.update(email <- email.replace("mac.com", "me.com"))) // UPDATE "users" SET "email" = replace("email", 'mac.com', 'me.com') // WHERE ("id" = 1) -alice?.delete() +try db.run(alice.delete()) // DELETE FROM "users" WHERE ("id" = 1) -users.count +db.scalar(users.count) // 0 // SELECT count(*) FROM "users" ``` @@ -107,8 +105,7 @@ interactively, from the Xcode project’s playground. ## Installation -> _Note:_ SQLite.swift requires Swift 1.2 (and [Xcode][] 6.3) or -> greater. +> _Note:_ SQLite.swift requires Swift 2 (and [Xcode][] 7) or greater. > > The following instructions apply to targets that support embedded > Swift frameworks. To use SQLite.swift in iOS 7 or an OS X command line @@ -116,20 +113,46 @@ interactively, from the Xcode project’s playground. > documentation. +### Carthage + +[Carthage][] is a simple, decentralized dependency manager for Cocoa. To +install SQLite.swift with Carthage: + + 1. Make sure Carthage is [installed][Carthage Installation]. + + 2. Update your Cartfile to include the following: + + ``` + github "stephencelis/SQLite.swift" "swift-2" + ``` + + 3. Run `carthage update` and [add the appropriate framework][Carthage Usage]. + + +[Carthage]: https://github.com/Carthage/Carthage +[Carthage Installation]: https://github.com/Carthage/Carthage#installing-carthage +[Carthage Usage]: https://github.com/Carthage/Carthage#adding-frameworks-to-an-application + + ### CocoaPods [CocoaPods][] is a dependency manager for Cocoa projects. To install SQLite.swift with CocoaPods: - 1. Make sure CocoaPods is [installed][CocoaPods Installation] (SQLite.swift - requires version 0.37 or greater). + 1. Make sure CocoaPods is [installed][CocoaPods Installation]. (SQLite.swift + requires version 0.37 or greater.) 2. Update your Podfile to include the following: ``` ruby use_frameworks! - pod 'SQLite.swift', git: 'https://github.com/stephencelis/SQLite.swift.git' - # pod 'SQLite.swift/Cipher', git: ... # instead, for SQLCipher support + + pod 'SQLite.swift', + git: 'https://github.com/stephencelis/SQLite.swift.git' + + # instead, for SQLCipher support + pod 'SQLiteCipher.swift', + git: 'https://github.com/stephencelis/SQLite.swift.git' ``` 3. Run `pod install`. diff --git a/SQLite Tests/DatabaseTests.swift b/SQLite Tests/DatabaseTests.swift deleted file mode 100644 index b535bc76..00000000 --- a/SQLite Tests/DatabaseTests.swift +++ /dev/null @@ -1,422 +0,0 @@ -import XCTest -import SQLite - -class DatabaseTests: SQLiteTestCase { - - override func setUp() { - super.setUp() - - createUsersTable() - } - - func test_init_withInMemory_returnsInMemoryConnection() { - let db = Database(.InMemory) - XCTAssertEqual("", db.description) - XCTAssertEqual(":memory:", Database.Location.InMemory.description) - } - - func test_init_returnsInMemoryByDefault() { - let db = Database() - XCTAssertEqual("", db.description) - } - - func test_init_withTemporary_returnsTemporaryConnection() { - let db = Database(.Temporary) - XCTAssertEqual("", db.description) - } - - func test_init_withURI_returnsURIConnection() { - let db = Database(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) - XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) - } - - func test_init_withString_returnsURIConnection() { - let db = Database("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") - XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) - } - - func test_readonly_returnsFalseOnReadWriteConnections() { - XCTAssert(!db.readonly) - } - - func test_readonly_returnsTrueOnReadOnlyConnections() { - let db = Database(readonly: true) - XCTAssert(db.readonly) - } - - func test_lastInsertRowid_returnsNilOnNewConnections() { - XCTAssert(db.lastInsertRowid == nil) - } - - func test_lastInsertRowid_returnsLastIdAfterInserts() { - insertUser("alice") - XCTAssert(db.lastInsertRowid! == 1) - } - - func test_changes_returnsZeroOnNewConnections() { - XCTAssertEqual(0, db.changes) - } - - func test_changes_returnsNumberOfChanges() { - insertUser("alice") - XCTAssertEqual(1, db.changes) - insertUser("betsy") - XCTAssertEqual(1, db.changes) - } - - func test_totalChanges_returnsTotalNumberOfChanges() { - XCTAssertEqual(0, db.totalChanges) - insertUser("alice") - XCTAssertEqual(1, db.totalChanges) - insertUser("betsy") - XCTAssertEqual(2, db.totalChanges) - } - - func test_prepare_preparesAndReturnsStatements() { - db.prepare("SELECT * FROM users WHERE admin = 0") - db.prepare("SELECT * FROM users WHERE admin = ?", 0) - db.prepare("SELECT * FROM users WHERE admin = ?", [0]) - db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) - // no-op assert-nothing-asserted - } - - func test_run_preparesRunsAndReturnsStatements() { - db.run("SELECT * FROM users WHERE admin = 0") - db.run("SELECT * FROM users WHERE admin = ?", 0) - db.run("SELECT * FROM users WHERE admin = ?", [0]) - db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) - AssertSQL("SELECT * FROM users WHERE admin = 0", 4) - } - - func test_scalar_preparesRunsAndReturnsScalarValues() { - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as! Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as! Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as! Int64) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as! Int64) - AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) - } - - func test_transaction_executesBeginDeferred() { - db.transaction(.Deferred) { _ in .Commit } - - AssertSQL("BEGIN DEFERRED TRANSACTION") - } - - func test_transaction_executesBeginImmediate() { - db.transaction(.Immediate) { _ in .Commit } - - AssertSQL("BEGIN IMMEDIATE TRANSACTION") - } - - func test_transaction_executesBeginExclusive() { - db.transaction(.Exclusive) { _ in .Commit } - - AssertSQL("BEGIN EXCLUSIVE TRANSACTION") - } - - func test_commit_commitsTransaction() { - db.transaction() - - db.commit() - - AssertSQL("COMMIT TRANSACTION") - } - - func test_rollback_rollsTransactionBack() { - db.transaction() - - db.rollback() - - AssertSQL("ROLLBACK TRANSACTION") - } - - func test_transaction_beginsAndCommitsTransactions() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)", "alice@example.com", 1) - - db.transaction { _ in stmt.run().failed ? .Rollback : .Commit } - - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)") - AssertSQL("COMMIT TRANSACTION") - AssertSQL("ROLLBACK TRANSACTION", 0) - } - - func test_transaction_beginsAndRollsTransactionsBack() { - let stmt = db.run("INSERT INTO users (email, admin) VALUES (?, ?)", "alice@example.com", 1) - - db.transaction { _ in stmt.run().failed ? .Rollback : .Commit } - - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) - AssertSQL("ROLLBACK TRANSACTION") - AssertSQL("COMMIT TRANSACTION", 0) - } - - func test_transaction_withOperators_allowsForFlowControl() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - let txn = db.transaction() && - stmt.bind("alice@example.com", 1) && - stmt.bind("alice@example.com", 1) && - stmt.bind("alice@example.com", 1) && - db.commit() - txn || db.rollback() - - XCTAssertTrue(txn.failed) - XCTAssert(txn.reason!.lowercaseString.rangeOfString("unique") != nil) - - AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) - AssertSQL("ROLLBACK TRANSACTION") - AssertSQL("COMMIT TRANSACTION", 0) - } - - func test_savepoint_quotesSavepointNames() { - db.savepoint("That's all, Folks!") - - AssertSQL("SAVEPOINT 'That''s all, Folks!'") - } - - func test_release_quotesSavepointNames() { - let savepointName = "That's all, Folks!" - db.savepoint(savepointName) - - db.release(savepointName) - - AssertSQL("RELEASE SAVEPOINT 'That''s all, Folks!'") - } - - func test_release_defaultsToCurrentSavepointName() { - db.savepoint("Hello, World!") - - db.release() - - AssertSQL("RELEASE SAVEPOINT 'Hello, World!'") - } - - func test_release_maintainsTheSavepointNameStack() { - db.savepoint("1") - db.savepoint("2") - db.savepoint("3") - - db.release("2") - db.release() - - AssertSQL("RELEASE SAVEPOINT '2'") - AssertSQL("RELEASE SAVEPOINT '1'") - AssertSQL("RELEASE SAVEPOINT '3'", 0) - } - - func test_rollback_quotesSavepointNames() { - let savepointName = "That's all, Folks!" - db.savepoint(savepointName) - - db.rollback(savepointName) - - AssertSQL("ROLLBACK TO SAVEPOINT 'That''s all, Folks!'") - } - - func test_rollback_defaultsToCurrentSavepointName() { - db.savepoint("Hello, World!") - - db.rollback() - - AssertSQL("ROLLBACK TO SAVEPOINT 'Hello, World!'") - } - - func test_rollback_maintainsTheSavepointNameStack() { - db.savepoint("1") - db.savepoint("2") - db.savepoint("3") - - db.rollback("2") - db.rollback() - - AssertSQL("ROLLBACK TO SAVEPOINT '2'") - AssertSQL("ROLLBACK TO SAVEPOINT '1'") - AssertSQL("ROLLBACK TO SAVEPOINT '3'", 0) - } - - func test_savepoint_beginsAndReleasesTransactions() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)", "alice@example.com", 1) - - db.savepoint("1") { _ in stmt.run().failed ? .Rollback : .Release } - - AssertSQL("SAVEPOINT '1'") - AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)") - AssertSQL("RELEASE SAVEPOINT '1'") - AssertSQL("ROLLBACK TO SAVEPOINT '1'", 0) - } - - func test_savepoint_beginsAndRollsTransactionsBack() { - let stmt = db.run("INSERT INTO users (email, admin) VALUES (?, ?)", "alice@example.com", 1) - - db.savepoint("1") { _ in stmt.run().failed ? .Rollback : .Release } - - AssertSQL("SAVEPOINT '1'") - AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) - AssertSQL("ROLLBACK TO SAVEPOINT '1'", 1) - AssertSQL("RELEASE SAVEPOINT '1'", 0) - } - - func test_savepoint_withOperators_allowsForFlowControl() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - - var txn = db.savepoint("1") - - txn = txn && ( - db.savepoint("2") && - stmt.run("alice@example.com", 1) && - stmt.run("alice@example.com", 1) && - stmt.run("alice@example.com", 1) && - db.release() - ) - txn || db.rollback() - - txn = txn && ( - db.savepoint("2") && - stmt.run("alice@example.com", 1) && - stmt.run("alice@example.com", 1) && - stmt.run("alice@example.com", 1) && - db.release() - ) - txn || db.rollback() - - txn && db.release() || db.rollback() - - AssertSQL("SAVEPOINT '1'") - AssertSQL("SAVEPOINT '2'") - AssertSQL("RELEASE SAVEPOINT '2'", 0) - AssertSQL("RELEASE SAVEPOINT '1'", 0) - AssertSQL("ROLLBACK TO SAVEPOINT '1'") - AssertSQL("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)", 2) - } - - func test_interrupt_interruptsLongRunningQuery() { - insertUsers(map("abcdefghijklmnopqrstuvwxyz") { String($0) }) - db.create(function: "sleep") { args in - usleep(UInt32(Double(args[0] as? Double ?? Double(args[0] as? Int64 ?? 1)) * 1_000_000)) - return nil - } - - let stmt = db.prepare("SELECT *, sleep(?) FROM users", 0.1) - stmt.run() - XCTAssert(!stmt.failed) - - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), db.interrupt) - stmt.run() - XCTAssert(stmt.failed) - } - - func test_userVersion_getsAndSetsUserVersion() { - XCTAssertEqual(0, db.userVersion) - db.userVersion = 1 - XCTAssertEqual(1, db.userVersion) - } - - func test_foreignKeys_getsAndSetsForeignKeys() { - XCTAssertEqual(false, db.foreignKeys) - db.foreignKeys = true - XCTAssertEqual(true, db.foreignKeys) - } - - func test_updateHook_setsUpdateHook() { - async { done in - db.updateHook { operation, db, table, rowid in - XCTAssertEqual(.Insert, operation) - XCTAssertEqual("main", db) - XCTAssertEqual("users", table) - XCTAssertEqual(1, rowid) - done() - } - insertUser("alice") - } - } - - func test_commitHook_setsCommitHook() { - async { done in - db.commitHook { - done() - return .Commit - } - db.transaction { _ in - insertUser("alice") - return .Commit - } - XCTAssertEqual(1, db.scalar("SELECT count(*) FROM users") as! Int64) - } - } - - func test_rollbackHook_setsRollbackHook() { - async { done in - db.rollbackHook(done) - db.transaction { _ in - insertUser("alice") - return .Rollback - } - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as! Int64) - } - } - - func test_commitHook_withRollback_rollsBack() { - async { done in - db.commitHook { .Rollback } - db.rollbackHook(done) - db.transaction { _ in - insertUser("alice") - return .Commit - } - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as! Int64) - } - } - - func test_createFunction_withArrayArguments() { - db.create(function: "hello") { $0[0].map { "Hello, \($0)!" } } - - XCTAssertEqual("Hello, world!", db.scalar("SELECT hello('world')") as! String) - XCTAssert(db.scalar("SELECT hello(NULL)") == nil) - } - - func test_createFunction_createsQuotableFunction() { - db.create(function: "hello world") { $0[0].map { "Hello, \($0)!" } } - - XCTAssertEqual("Hello, world!", db.scalar("SELECT \"hello world\"('world')") as! String) - XCTAssert(db.scalar("SELECT \"hello world\"(NULL)") == nil) - } - - func test_createCollation_createsCollation() { - db.create(collation: "NODIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) - } - XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as! Int64) - } - - func test_createCollation_createsQuotableCollation() { - db.create(collation: "NO DIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) - } - XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as! Int64) - } - - func test_lastError_withOK_returnsNil() { - XCTAssertNil(db.lastError) - } - - func test_lastError_withRow_returnsNil() { - insertUsers("alice", "betty") - db.prepare("SELECT * FROM users").step() - - XCTAssertNil(db.lastError) - } - - func test_lastError_withDone_returnsNil() { - db.prepare("SELECT * FROM users").step() - - XCTAssertNil(db.lastError) - } - - func test_lastError_withError_returnsError() { - insertUsers("alice", "alice") - - XCTAssertNotNil(db.lastError) - } - -} diff --git a/SQLite Tests/ExpressionTests.swift b/SQLite Tests/ExpressionTests.swift deleted file mode 100644 index 27d329b5..00000000 --- a/SQLite Tests/ExpressionTests.swift +++ /dev/null @@ -1,757 +0,0 @@ -import XCTest -import SQLite - -let stringA = Expression(value: "A") -let stringB = Expression(value: "B") - -let int1 = Expression(value: 1) -let int2 = Expression(value: 2) - -let double1 = Expression(value: 1.5) -let double2 = Expression(value: 2.5) - -let bool0 = Expression(value: false) -let bool1 = Expression(value: true) - -class ExpressionTests: SQLiteTestCase { - - func AssertSQLContains(SQL: String, _ expression: Expression, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { - AssertSQL("SELECT \(SQL) FROM \"users\"", users.select(expression), file: file, line: line) - } - - override func setUp() { - createUsersTable() - - super.setUp() - } - - func test_alias_aliasesExpression() { - let aliased = stringA.alias("string_a") - AssertSQLContains("('A') AS \"string_a\"", aliased) - } - - func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() { - AssertSQLContains("('A' || 'A')", stringA + stringA) - AssertSQLContains("('A' || 'B')", stringA + stringB) - AssertSQLContains("('B' || 'A')", stringB + stringA) - AssertSQLContains("('B' || 'B')", stringB + stringB) - AssertSQLContains("('A' || 'B')", stringA + "B") - AssertSQLContains("('B' || 'A')", stringB + "A") - AssertSQLContains("('B' || 'A')", "B" + stringA) - AssertSQLContains("('A' || 'B')", "A" + stringB) - } - - func test_integerExpression_plusIntegerExpression_buildsAdditiveIntegerExpression() { - AssertSQLContains("(1 + 1)", int1 + int1) - AssertSQLContains("(1 + 2)", int1 + int2) - AssertSQLContains("(2 + 1)", int2 + int1) - AssertSQLContains("(2 + 2)", int2 + int2) - AssertSQLContains("(1 + 2)", int1 + 2) - AssertSQLContains("(2 + 1)", int2 + 1) - AssertSQLContains("(2 + 1)", 2 + int1) - AssertSQLContains("(1 + 2)", 1 + int2) - } - - func test_doubleExpression_plusDoubleExpression_buildsAdditiveDoubleExpression() { - AssertSQLContains("(1.5 + 1.5)", double1 + double1) - AssertSQLContains("(1.5 + 2.5)", double1 + double2) - AssertSQLContains("(2.5 + 1.5)", double2 + double1) - AssertSQLContains("(2.5 + 2.5)", double2 + double2) - AssertSQLContains("(1.5 + 2.5)", double1 + 2.5) - AssertSQLContains("(2.5 + 1.5)", double2 + 1.5) - AssertSQLContains("(2.5 + 1.5)", 2.5 + double1) - AssertSQLContains("(1.5 + 2.5)", 1.5 + double2) - } - - func test_integerExpression_minusIntegerExpression_buildsSubtractiveIntegerExpression() { - AssertSQLContains("(1 - 1)", int1 - int1) - AssertSQLContains("(1 - 2)", int1 - int2) - AssertSQLContains("(2 - 1)", int2 - int1) - AssertSQLContains("(2 - 2)", int2 - int2) - AssertSQLContains("(1 - 2)", int1 - 2) - AssertSQLContains("(2 - 1)", int2 - 1) - AssertSQLContains("(2 - 1)", 2 - int1) - AssertSQLContains("(1 - 2)", 1 - int2) - } - - func test_doubleExpression_minusDoubleExpression_buildsSubtractiveDoubleExpression() { - AssertSQLContains("(1.5 - 1.5)", double1 - double1) - AssertSQLContains("(1.5 - 2.5)", double1 - double2) - AssertSQLContains("(2.5 - 1.5)", double2 - double1) - AssertSQLContains("(2.5 - 2.5)", double2 - double2) - AssertSQLContains("(1.5 - 2.5)", double1 - 2.5) - AssertSQLContains("(2.5 - 1.5)", double2 - 1.5) - AssertSQLContains("(2.5 - 1.5)", 2.5 - double1) - AssertSQLContains("(1.5 - 2.5)", 1.5 - double2) - } - - func test_integerExpression_timesIntegerExpression_buildsMultiplicativeIntegerExpression() { - AssertSQLContains("(1 * 1)", int1 * int1) - AssertSQLContains("(1 * 2)", int1 * int2) - AssertSQLContains("(2 * 1)", int2 * int1) - AssertSQLContains("(2 * 2)", int2 * int2) - AssertSQLContains("(1 * 2)", int1 * 2) - AssertSQLContains("(2 * 1)", int2 * 1) - AssertSQLContains("(2 * 1)", 2 * int1) - AssertSQLContains("(1 * 2)", 1 * int2) - } - - func test_doubleExpression_timesDoubleExpression_buildsMultiplicativeDoubleExpression() { - AssertSQLContains("(1.5 * 1.5)", double1 * double1) - AssertSQLContains("(1.5 * 2.5)", double1 * double2) - AssertSQLContains("(2.5 * 1.5)", double2 * double1) - AssertSQLContains("(2.5 * 2.5)", double2 * double2) - AssertSQLContains("(1.5 * 2.5)", double1 * 2.5) - AssertSQLContains("(2.5 * 1.5)", double2 * 1.5) - AssertSQLContains("(2.5 * 1.5)", 2.5 * double1) - AssertSQLContains("(1.5 * 2.5)", 1.5 * double2) - } - - func test_integerExpression_dividedByIntegerExpression_buildsDivisiveIntegerExpression() { - AssertSQLContains("(1 / 1)", int1 / int1) - AssertSQLContains("(1 / 2)", int1 / int2) - AssertSQLContains("(2 / 1)", int2 / int1) - AssertSQLContains("(2 / 2)", int2 / int2) - AssertSQLContains("(1 / 2)", int1 / 2) - AssertSQLContains("(2 / 1)", int2 / 1) - AssertSQLContains("(2 / 1)", 2 / int1) - AssertSQLContains("(1 / 2)", 1 / int2) - } - - func test_doubleExpression_dividedByDoubleExpression_buildsDivisiveDoubleExpression() { - AssertSQLContains("(1.5 / 1.5)", double1 / double1) - AssertSQLContains("(1.5 / 2.5)", double1 / double2) - AssertSQLContains("(2.5 / 1.5)", double2 / double1) - AssertSQLContains("(2.5 / 2.5)", double2 / double2) - AssertSQLContains("(1.5 / 2.5)", double1 / 2.5) - AssertSQLContains("(2.5 / 1.5)", double2 / 1.5) - AssertSQLContains("(2.5 / 1.5)", 2.5 / double1) - AssertSQLContains("(1.5 / 2.5)", 1.5 / double2) - } - - func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() { - AssertSQLContains("(1 % 1)", int1 % int1) - AssertSQLContains("(1 % 2)", int1 % int2) - AssertSQLContains("(2 % 1)", int2 % int1) - AssertSQLContains("(2 % 2)", int2 % int2) - AssertSQLContains("(1 % 2)", int1 % 2) - AssertSQLContains("(2 % 1)", int2 % 1) - AssertSQLContains("(2 % 1)", 2 % int1) - AssertSQLContains("(1 % 2)", 1 % int2) - } - - func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() { - AssertSQLContains("(1 << 1)", int1 << int1) - AssertSQLContains("(1 << 2)", int1 << int2) - AssertSQLContains("(2 << 1)", int2 << int1) - AssertSQLContains("(2 << 2)", int2 << int2) - AssertSQLContains("(1 << 2)", int1 << 2) - AssertSQLContains("(2 << 1)", int2 << 1) - AssertSQLContains("(2 << 1)", 2 << int1) - AssertSQLContains("(1 << 2)", 1 << int2) - } - - func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() { - AssertSQLContains("(1 >> 1)", int1 >> int1) - AssertSQLContains("(1 >> 2)", int1 >> int2) - AssertSQLContains("(2 >> 1)", int2 >> int1) - AssertSQLContains("(2 >> 2)", int2 >> int2) - AssertSQLContains("(1 >> 2)", int1 >> 2) - AssertSQLContains("(2 >> 1)", int2 >> 1) - AssertSQLContains("(2 >> 1)", 2 >> int1) - AssertSQLContains("(1 >> 2)", 1 >> int2) - } - - func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() { - AssertSQLContains("(1 & 1)", int1 & int1) - AssertSQLContains("(1 & 2)", int1 & int2) - AssertSQLContains("(2 & 1)", int2 & int1) - AssertSQLContains("(2 & 2)", int2 & int2) - AssertSQLContains("(1 & 2)", int1 & 2) - AssertSQLContains("(2 & 1)", int2 & 1) - AssertSQLContains("(2 & 1)", 2 & int1) - AssertSQLContains("(1 & 2)", 1 & int2) - } - - func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() { - AssertSQLContains("(1 | 1)", int1 | int1) - AssertSQLContains("(1 | 2)", int1 | int2) - AssertSQLContains("(2 | 1)", int2 | int1) - AssertSQLContains("(2 | 2)", int2 | int2) - AssertSQLContains("(1 | 2)", int1 | 2) - AssertSQLContains("(2 | 1)", int2 | 1) - AssertSQLContains("(2 | 1)", 2 | int1) - AssertSQLContains("(1 | 2)", 1 | int2) - } - - func test_integerExpression_bitwiseExclusiveOrIntegerExpression_buildsOredIntegerExpression() { - AssertSQLContains("(~((1 & 1)) & (1 | 1))", int1 ^ int1) - AssertSQLContains("(~((1 & 2)) & (1 | 2))", int1 ^ int2) - AssertSQLContains("(~((2 & 1)) & (2 | 1))", int2 ^ int1) - AssertSQLContains("(~((2 & 2)) & (2 | 2))", int2 ^ int2) - AssertSQLContains("(~((1 & 2)) & (1 | 2))", int1 ^ 2) - AssertSQLContains("(~((2 & 1)) & (2 | 1))", int2 ^ 1) - AssertSQLContains("(~((2 & 1)) & (2 | 1))", 2 ^ int1) - AssertSQLContains("(~((1 & 2)) & (1 | 2))", 1 ^ int2) - } - - func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() { - AssertSQLContains("~(1)", ~int1) - AssertSQLContains("~(2)", ~int2) - } - - func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() { - AssertSQLContains("(0 = 0)", bool0 == bool0) - AssertSQLContains("(0 = 1)", bool0 == bool1) - AssertSQLContains("(1 = 0)", bool1 == bool0) - AssertSQLContains("(1 = 1)", bool1 == bool1) - AssertSQLContains("(0 = 1)", bool0 == true) - AssertSQLContains("(1 = 0)", bool1 == false) - AssertSQLContains("(1 = 0)", true == bool0) - AssertSQLContains("(0 = 1)", false == bool1) - } - - func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { - AssertSQLContains("(0 != 0)", bool0 != bool0) - AssertSQLContains("(0 != 1)", bool0 != bool1) - AssertSQLContains("(1 != 0)", bool1 != bool0) - AssertSQLContains("(1 != 1)", bool1 != bool1) - AssertSQLContains("(0 != 1)", bool0 != true) - AssertSQLContains("(1 != 0)", bool1 != false) - AssertSQLContains("(1 != 0)", true != bool0) - AssertSQLContains("(0 != 1)", false != bool1) - } - - func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQLContains("(1 > 1)", int1 > int1) - AssertSQLContains("(1 > 2)", int1 > int2) - AssertSQLContains("(2 > 1)", int2 > int1) - AssertSQLContains("(2 > 2)", int2 > int2) - AssertSQLContains("(1 > 2)", int1 > 2) - AssertSQLContains("(2 > 1)", int2 > 1) - AssertSQLContains("(2 > 1)", 2 > int1) - AssertSQLContains("(1 > 2)", 1 > int2) - } - - func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQLContains("(1 >= 1)", int1 >= int1) - AssertSQLContains("(1 >= 2)", int1 >= int2) - AssertSQLContains("(2 >= 1)", int2 >= int1) - AssertSQLContains("(2 >= 2)", int2 >= int2) - AssertSQLContains("(1 >= 2)", int1 >= 2) - AssertSQLContains("(2 >= 1)", int2 >= 1) - AssertSQLContains("(2 >= 1)", 2 >= int1) - AssertSQLContains("(1 >= 2)", 1 >= int2) - } - - func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQLContains("(1 < 1)", int1 < int1) - AssertSQLContains("(1 < 2)", int1 < int2) - AssertSQLContains("(2 < 1)", int2 < int1) - AssertSQLContains("(2 < 2)", int2 < int2) - AssertSQLContains("(1 < 2)", int1 < 2) - AssertSQLContains("(2 < 1)", int2 < 1) - AssertSQLContains("(2 < 1)", 2 < int1) - AssertSQLContains("(1 < 2)", 1 < int2) - } - - func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { - AssertSQLContains("(1 <= 1)", int1 <= int1) - AssertSQLContains("(1 <= 2)", int1 <= int2) - AssertSQLContains("(2 <= 1)", int2 <= int1) - AssertSQLContains("(2 <= 2)", int2 <= int2) - AssertSQLContains("(1 <= 2)", int1 <= 2) - AssertSQLContains("(2 <= 1)", int2 <= 1) - AssertSQLContains("(2 <= 1)", 2 <= int1) - AssertSQLContains("(1 <= 2)", 1 <= int2) - } - - func test_unaryMinusOperator_withIntegerExpression_buildsNegativeIntegerExpression() { - AssertSQLContains("-(1)", -int1) - AssertSQLContains("-(2)", -int2) - } - - func test_unaryMinusOperator_withDoubleExpression_buildsNegativeDoubleExpression() { - AssertSQLContains("-(1.5)", -double1) - AssertSQLContains("-(2.5)", -double2) - } - - func test_betweenOperator_withComparableExpression_buildsBetweenBooleanExpression() { - AssertSQLContains("1 BETWEEN 0 AND 5", 0...5 ~= int1) - AssertSQLContains("2 BETWEEN 0 AND 5", 0...5 ~= int2) - } - - func test_likeOperator_withStringExpression_buildsLikeExpression() { - AssertSQLContains("('A' LIKE 'B%')", like("B%", stringA)) - AssertSQLContains("('B' LIKE 'A%')", like("A%", stringB)) - } - - func test_globOperator_withStringExpression_buildsGlobExpression() { - AssertSQLContains("('A' GLOB 'B*')", glob("B*", stringA)) - AssertSQLContains("('B' GLOB 'A*')", glob("A*", stringB)) - } - - func test_matchOperator_withStringExpression_buildsMatchExpression() { - AssertSQLContains("('A' MATCH 'B')", match("B", stringA)) - AssertSQLContains("('B' MATCH 'A')", match("A", stringB)) - } - - func test_collateOperator_withStringExpression_buildsCollationExpression() { - AssertSQLContains("('A' COLLATE \"BINARY\")", collate(.Binary, stringA)) - AssertSQLContains("('B' COLLATE \"NOCASE\")", collate(.Nocase, stringB)) - AssertSQLContains("('A' COLLATE \"RTRIM\")", collate(.Rtrim, stringA)) - - db.create(collation: "NODIACRITIC") { lhs, rhs in - return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) - } - AssertSQLContains("('A' COLLATE \"NODIACRITIC\")", collate(.Custom("NODIACRITIC"), stringA)) - } - - func test_cast_buildsCastingExpressions() { - let string1 = Expression(value: "10") - let string2 = Expression(value: "10") - AssertSQLContains("CAST ('10' AS REAL)", cast(string1) as Expression) - AssertSQLContains("CAST ('10' AS INTEGER)", cast(string1) as Expression) - AssertSQLContains("CAST ('10' AS TEXT)", cast(string1) as Expression) - AssertSQLContains("CAST ('10' AS REAL)", cast(string2) as Expression) - AssertSQLContains("CAST ('10' AS INTEGER)", cast(string2) as Expression) - AssertSQLContains("CAST ('10' AS TEXT)", cast(string2) as Expression) - } - - func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { - AssertSQLContains("(0 AND 0)", bool0 && bool0) - AssertSQLContains("(0 AND 1)", bool0 && bool1) - AssertSQLContains("(1 AND 0)", bool1 && bool0) - AssertSQLContains("(1 AND 1)", bool1 && bool1) - AssertSQLContains("(0 AND 1)", bool0 && true) - AssertSQLContains("(1 AND 0)", bool1 && false) - AssertSQLContains("(1 AND 0)", true && bool0) - AssertSQLContains("(0 AND 1)", false && bool1) - } - - func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { - AssertSQLContains("(0 OR 0)", bool0 || bool0) - AssertSQLContains("(0 OR 1)", bool0 || bool1) - AssertSQLContains("(1 OR 0)", bool1 || bool0) - AssertSQLContains("(1 OR 1)", bool1 || bool1) - AssertSQLContains("(0 OR 1)", bool0 || true) - AssertSQLContains("(1 OR 0)", bool1 || false) - AssertSQLContains("(1 OR 0)", true || bool0) - AssertSQLContains("(0 OR 1)", false || bool1) - } - - func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { - AssertSQLContains("NOT (0)", !bool0) - AssertSQLContains("NOT (1)", !bool1) - } - - func test_absFunction_withNumberExpressions_buildsAbsExpression() { - let int1 = Expression(value: -1) - let int2 = Expression(value: -2) - - AssertSQLContains("abs(-1)", abs(int1)) - AssertSQLContains("abs(-2)", abs(int2)) - } - - func test_coalesceFunction_withValueExpressions_buildsCoalesceExpression() { - let int1 = Expression(value: nil as Int?) - let int2 = Expression(value: nil as Int?) - let int3 = Expression(value: 3) - - AssertSQLContains("coalesce(NULL, NULL, 3)", coalesce(int1, int2, int3)) - } - - func test_ifNullFunction_withValueExpressionAndValue_buildsIfNullExpression() { - let int1 = Expression(value: nil as Int?) - let int2 = Expression(value: 2) - let int3 = Expression(value: 3) - - AssertSQLContains("ifnull(NULL, 1)", ifnull(int1, 1)) - AssertSQLContains("ifnull(NULL, 1)", int1 ?? 1) - AssertSQLContains("ifnull(NULL, 2)", ifnull(int1, int2)) - AssertSQLContains("ifnull(NULL, 2)", int1 ?? int2) - AssertSQLContains("ifnull(NULL, 3)", ifnull(int1, int3)) - AssertSQLContains("ifnull(NULL, 3)", int1 ?? int3) - } - - func test_lengthFunction_withValueExpression_buildsLengthIntExpression() { - AssertSQLContains("length('A')", length(stringA)) - AssertSQLContains("length('B')", length(stringB)) - } - - func test_lowerFunction_withStringExpression_buildsLowerStringExpression() { - AssertSQLContains("lower('A')", lower(stringA)) - AssertSQLContains("lower('B')", lower(stringB)) - } - - func test_ltrimFunction_withStringExpression_buildsTrimmedStringExpression() { - AssertSQLContains("ltrim('A')", ltrim(stringA)) - AssertSQLContains("ltrim('B')", ltrim(stringB)) - } - - func test_ltrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { - AssertSQLContains("ltrim('A', 'A?')", ltrim(stringA, "A?")) - AssertSQLContains("ltrim('B', 'B?')", ltrim(stringB, "B?")) - } - - func test_randomFunction_buildsRandomIntExpression() { - AssertSQLContains("random()", random()) - } - - func test_replaceFunction_withStringExpressionAndFindReplaceStrings_buildsReplacedStringExpression() { - AssertSQLContains("replace('A', 'A', 'B')", replace(stringA, "A", "B")) - AssertSQLContains("replace('B', 'B', 'A')", replace(stringB, "B", "A")) - } - - func test_roundFunction_withDoubleExpression_buildsRoundedDoubleExpression() { - AssertSQLContains("round(1.5)", round(double1)) - AssertSQLContains("round(2.5)", round(double2)) - } - - func test_roundFunction_withDoubleExpressionAndPrecision_buildsRoundedDoubleExpression() { - AssertSQLContains("round(1.5, 1)", round(double1, 1)) - AssertSQLContains("round(2.5, 1)", round(double2, 1)) - } - - func test_rtrimFunction_withStringExpression_buildsTrimmedStringExpression() { - AssertSQLContains("rtrim('A')", rtrim(stringA)) - AssertSQLContains("rtrim('B')", rtrim(stringB)) - } - - func test_rtrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { - AssertSQLContains("rtrim('A', 'A?')", rtrim(stringA, "A?")) - AssertSQLContains("rtrim('B', 'B?')", rtrim(stringB, "B?")) - } - - func test_substrFunction_withStringExpressionAndStartIndex_buildsSubstringExpression() { - AssertSQLContains("substr('A', 1)", substr(stringA, 1)) - AssertSQLContains("substr('B', 1)", substr(stringB, 1)) - } - - func test_substrFunction_withStringExpressionPositionAndLength_buildsSubstringExpression() { - AssertSQLContains("substr('A', 1, 2)", substr(stringA, 1, 2)) - AssertSQLContains("substr('B', 1, 2)", substr(stringB, 1, 2)) - } - - func test_substrFunction_withStringExpressionAndRange_buildsSubstringExpression() { - AssertSQLContains("substr('A', 1, 2)", substr(stringA, 1..<3)) - AssertSQLContains("substr('B', 1, 2)", substr(stringB, 1..<3)) - } - - func test_trimFunction_withStringExpression_buildsTrimmedStringExpression() { - AssertSQLContains("trim('A')", trim(stringA)) - AssertSQLContains("trim('B')", trim(stringB)) - } - - func test_trimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() { - AssertSQLContains("trim('A', 'A?')", trim(stringA, "A?")) - AssertSQLContains("trim('B', 'B?')", trim(stringB, "B?")) - } - - func test_upperFunction_withStringExpression_buildsLowerStringExpression() { - AssertSQLContains("upper('A')", upper(stringA)) - AssertSQLContains("upper('B')", upper(stringB)) - } - - let email2 = Expression("email") - let age2 = Expression("age") - let salary2 = Expression("salary") - let admin2 = Expression("admin") - - func test_countFunction_withExpression_buildsCountExpression() { - AssertSQLContains("count(\"age\")", count(age)) - AssertSQLContains("count(\"email\")", count(email2)) - AssertSQLContains("count(\"salary\")", count(salary2)) - AssertSQLContains("count(\"admin\")", count(admin2)) - AssertSQLContains("count(DISTINCT \"id\")", count(distinct: id)) - AssertSQLContains("count(DISTINCT \"age\")", count(distinct: age)) - AssertSQLContains("count(DISTINCT \"email\")", count(distinct: email)) - AssertSQLContains("count(DISTINCT \"email\")", count(distinct: email2)) - AssertSQLContains("count(DISTINCT \"salary\")", count(distinct: salary)) - AssertSQLContains("count(DISTINCT \"salary\")", count(distinct: salary2)) - AssertSQLContains("count(DISTINCT \"admin\")", count(distinct: admin)) - AssertSQLContains("count(DISTINCT \"admin\")", count(distinct: admin2)) - } - - func test_countFunction_withStar_buildsCountExpression() { - AssertSQLContains("count(*)", count(*)) - } - - func test_maxFunction_withExpression_buildsMaxExpression() { - AssertSQLContains("max(\"id\")", max(id)) - AssertSQLContains("max(\"age\")", max(age)) - AssertSQLContains("max(\"email\")", max(email)) - AssertSQLContains("max(\"email\")", max(email2)) - AssertSQLContains("max(\"salary\")", max(salary)) - AssertSQLContains("max(\"salary\")", max(salary2)) - } - - func test_minFunction_withExpression_buildsMinExpression() { - AssertSQLContains("min(\"id\")", min(id)) - AssertSQLContains("min(\"age\")", min(age)) - AssertSQLContains("min(\"email\")", min(email)) - AssertSQLContains("min(\"email\")", min(email2)) - AssertSQLContains("min(\"salary\")", min(salary)) - AssertSQLContains("min(\"salary\")", min(salary2)) - } - - func test_averageFunction_withExpression_buildsAverageExpression() { - AssertSQLContains("avg(\"id\")", average(id)) - AssertSQLContains("avg(\"age\")", average(age)) - AssertSQLContains("avg(\"salary\")", average(salary)) - AssertSQLContains("avg(\"salary\")", average(salary2)) - AssertSQLContains("avg(DISTINCT \"id\")", average(distinct: id)) - AssertSQLContains("avg(DISTINCT \"age\")", average(distinct: age)) - AssertSQLContains("avg(DISTINCT \"salary\")", average(distinct: salary)) - AssertSQLContains("avg(DISTINCT \"salary\")", average(distinct: salary2)) - } - - func test_sumFunction_withExpression_buildsSumExpression() { - AssertSQLContains("sum(\"id\")", sum(id)) - AssertSQLContains("sum(\"age\")", sum(age)) - AssertSQLContains("sum(\"salary\")", sum(salary)) - AssertSQLContains("sum(\"salary\")", sum(salary2)) - AssertSQLContains("sum(DISTINCT \"id\")", sum(distinct: id)) - AssertSQLContains("sum(DISTINCT \"age\")", sum(distinct: age)) - AssertSQLContains("sum(DISTINCT \"salary\")", sum(distinct: salary)) - AssertSQLContains("sum(DISTINCT \"salary\")", sum(distinct: salary2)) - } - - func test_totalFunction_withExpression_buildsTotalExpression() { - AssertSQLContains("total(\"id\")", total(id)) - AssertSQLContains("total(\"age\")", total(age)) - AssertSQLContains("total(\"salary\")", total(salary)) - AssertSQLContains("total(\"salary\")", total(salary2)) - AssertSQLContains("total(DISTINCT \"id\")", total(distinct: id)) - AssertSQLContains("total(DISTINCT \"age\")", total(distinct: age)) - AssertSQLContains("total(DISTINCT \"salary\")", total(distinct: salary)) - AssertSQLContains("total(DISTINCT \"salary\")", total(distinct: salary2)) - } - - func test_containsFunction_withValueExpressionAndValueArray_buildsInExpression() { - AssertSQLContains("(\"id\" IN (1, 2, 3))", contains([1, 2, 3], id)) - AssertSQLContains("(\"age\" IN (20, 30, 40))", contains([20, 30, 40], age)) - - AssertSQLContains("(\"id\" IN (1))", contains(Set([1]), id)) - AssertSQLContains("(\"age\" IN (20))", contains(Set([20]), age)) - } - - func test_containsFunction_withValueExpressionAndQuery_buildsInExpression() { - let query = users.select(max(age)).group(id) - AssertSQLContains("(\"id\" IN (SELECT max(\"age\") FROM \"users\" GROUP BY \"id\"))", contains(query, id)) - } - - func test_plusEquals_withStringExpression_buildsSetter() { - users.update(email += email) - users.update(email += email2) - users.update(email2 += email) - users.update(email2 += email2) - AssertSQL("UPDATE \"users\" SET \"email\" = (\"email\" || \"email\")", 4) - } - - func test_plusEquals_withStringValue_buildsSetter() { - users.update(email += ".com") - users.update(email2 += ".com") - - AssertSQL("UPDATE \"users\" SET \"email\" = (\"email\" || '.com')", 2) - } - - func test_plusEquals_withNumberExpression_buildsSetter() { - users.update(age += age) - users.update(age += age2) - users.update(age2 += age) - users.update(age2 += age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + \"age\")", 4) - - users.update(salary += salary) - users.update(salary += salary2) - users.update(salary2 += salary) - users.update(salary2 += salary2) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" + \"salary\")", 4) - } - - func test_plusEquals_withNumberValue_buildsSetter() { - users.update(age += 1) - users.update(age2 += 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + 1)", 2) - - users.update(salary += 100) - users.update(salary2 += 100) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" + 100.0)", 2) - } - - func test_minusEquals_withNumberExpression_buildsSetter() { - users.update(age -= age) - users.update(age -= age2) - users.update(age2 -= age) - users.update(age2 -= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - \"age\")", 4) - - users.update(salary -= salary) - users.update(salary -= salary2) - users.update(salary2 -= salary) - users.update(salary2 -= salary2) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" - \"salary\")", 4) - } - - func test_minusEquals_withNumberValue_buildsSetter() { - users.update(age -= 1) - users.update(age2 -= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - 1)", 2) - - users.update(salary -= 100) - users.update(salary2 -= 100) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" - 100.0)", 2) - } - - func test_timesEquals_withNumberExpression_buildsSetter() { - users.update(age *= age) - users.update(age *= age2) - users.update(age2 *= age) - users.update(age2 *= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" * \"age\")", 4) - - users.update(salary *= salary) - users.update(salary *= salary2) - users.update(salary2 *= salary) - users.update(salary2 *= salary2) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" * \"salary\")", 4) - } - - func test_timesEquals_withNumberValue_buildsSetter() { - users.update(age *= 1) - users.update(age2 *= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" * 1)", 2) - - users.update(salary *= 100) - users.update(salary2 *= 100) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" * 100.0)", 2) - } - - func test_divideEquals_withNumberExpression_buildsSetter() { - users.update(age /= age) - users.update(age /= age2) - users.update(age2 /= age) - users.update(age2 /= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" / \"age\")", 4) - - users.update(salary /= salary) - users.update(salary /= salary2) - users.update(salary2 /= salary) - users.update(salary2 /= salary2) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" / \"salary\")", 4) - } - - func test_divideEquals_withNumberValue_buildsSetter() { - users.update(age /= 1) - users.update(age2 /= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" / 1)", 2) - - users.update(salary /= 100) - users.update(salary2 /= 100) - AssertSQL("UPDATE \"users\" SET \"salary\" = (\"salary\" / 100.0)", 2) - } - - func test_moduloEquals_withIntegerExpression_buildsSetter() { - users.update(age %= age) - users.update(age %= age2) - users.update(age2 %= age) - users.update(age2 %= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" % \"age\")", 4) - } - - func test_moduloEquals_withIntegerValue_buildsSetter() { - users.update(age %= 10) - users.update(age2 %= 10) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" % 10)", 2) - } - - func test_rightShiftEquals_withIntegerExpression_buildsSetter() { - users.update(age >>= age) - users.update(age >>= age2) - users.update(age2 >>= age) - users.update(age2 >>= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" >> \"age\")", 4) - } - - func test_rightShiftEquals_withIntegerValue_buildsSetter() { - users.update(age >>= 1) - users.update(age2 >>= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" >> 1)", 2) - } - - func test_leftShiftEquals_withIntegerExpression_buildsSetter() { - users.update(age <<= age) - users.update(age <<= age2) - users.update(age2 <<= age) - users.update(age2 <<= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" << \"age\")", 4) - } - - func test_leftShiftEquals_withIntegerValue_buildsSetter() { - users.update(age <<= 1) - users.update(age2 <<= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" << 1)", 2) - } - - func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { - users.update(age &= age) - users.update(age &= age2) - users.update(age2 &= age) - users.update(age2 &= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" & \"age\")", 4) - } - - func test_bitwiseAndEquals_withIntegerValue_buildsSetter() { - users.update(age &= 1) - users.update(age2 &= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" & 1)", 2) - } - - func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { - users.update(age |= age) - users.update(age |= age2) - users.update(age2 |= age) - users.update(age2 |= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" | \"age\")", 4) - } - - func test_bitwiseOrEquals_withIntegerValue_buildsSetter() { - users.update(age |= 1) - users.update(age2 |= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" | 1)", 2) - } - - func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { - users.update(age ^= age) - users.update(age ^= age2) - users.update(age2 ^= age) - users.update(age2 ^= age2) - AssertSQL("UPDATE \"users\" SET \"age\" = (~((\"age\" & \"age\")) & (\"age\" | \"age\"))", 4) - } - - func test_bitwiseExclusiveOrEquals_withIntegerValue_buildsSetter() { - users.update(age ^= 1) - users.update(age2 ^= 1) - AssertSQL("UPDATE \"users\" SET \"age\" = (~((\"age\" & 1)) & (\"age\" | 1))", 2) - } - - func test_postfixPlus_withIntegerValue_buildsSetter() { - users.update(age++) - users.update(age2++) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" + 1)", 2) - } - - func test_postfixMinus_withIntegerValue_buildsSetter() { - users.update(age--) - users.update(age2--) - AssertSQL("UPDATE \"users\" SET \"age\" = (\"age\" - 1)", 2) - } - - func test_precedencePreserved() { - let n = Expression(value: 1) - AssertSQLContains("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n) - AssertSQLContains("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) - } - -} diff --git a/SQLite Tests/FTSTests.swift b/SQLite Tests/FTSTests.swift deleted file mode 100644 index 6093f859..00000000 --- a/SQLite Tests/FTSTests.swift +++ /dev/null @@ -1,67 +0,0 @@ -import XCTest -import SQLite - -let subject = Expression("subject") -let body = Expression("body") - -class FTSTests: SQLiteTestCase { - - var emails: Query { return db["emails"] } - - func test_createVtable_usingFts4_createsVirtualTable() { - db.create(vtable: emails, using: fts4(subject, body)) - - AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\")") - } - - func test_createVtable_usingFts4_withSimpleTokenizer_createsVirtualTableWithTokenizer() { - db.create(vtable: emails, using: fts4([subject, body], tokenize: .Simple)) - - AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=simple)") - } - - func test_createVtable_usingFts4_withPorterTokenizer_createsVirtualTableWithTokenizer() { - db.create(vtable: emails, using: fts4([subject, body], tokenize: .Porter)) - - AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=porter)") - } - - func test_match_withColumnExpression_buildsMatchExpressionWithColumnIdentifier() { - db.create(vtable: emails, using: fts4(subject, body)) - - AssertSQL("SELECT * FROM \"emails\" WHERE (\"subject\" MATCH 'hello')", emails.filter(match("hello", subject))) - } - - func test_match_withQuery_buildsMatchExpressionWithTableIdentifier() { - db.create(vtable: emails, using: fts4(subject, body)) - - AssertSQL("SELECT * FROM \"emails\" WHERE (\"emails\" MATCH 'hello')", emails.filter(match("hello", emails))) - } - - func test_registerTokenizer_registersTokenizer() { - let locale = CFLocaleCopyCurrent() - let tokenizer = CFStringTokenizerCreate(nil, "", CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) - - db.register(tokenizer: "tokenizer") { string in - CFStringTokenizerSetString(tokenizer, string, CFRangeMake(0, CFStringGetLength(string))) - if CFStringTokenizerAdvanceToNextToken(tokenizer) == .None { - return nil - } - let range = CFStringTokenizerGetCurrentTokenRange(tokenizer) - let input = CFStringCreateWithSubstring(kCFAllocatorDefault, string, range) - var token = CFStringCreateMutableCopy(nil, range.length, input) - CFStringLowercase(token, locale) - CFStringTransform(token, nil, kCFStringTransformStripDiacritics, 0) - return (token as String, string.rangeOfString(input as String)!) - } - - db.create(vtable: emails, using: fts4([subject, body], tokenize: .Custom("tokenizer"))) - - AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" 'tokenizer')") - - emails.insert(subject <- "Aún más cáfe!") - - XCTAssertEqual(1, emails.filter(match("aun", emails)).count) - } - -} diff --git a/SQLite Tests/FunctionsTests.swift b/SQLite Tests/FunctionsTests.swift deleted file mode 100644 index d49d572e..00000000 --- a/SQLite Tests/FunctionsTests.swift +++ /dev/null @@ -1,164 +0,0 @@ -import XCTest -import SQLite - -class FunctionsTests: SQLiteTestCase { - - func test_createFunction_withZeroArguments() { - let f1: () -> Expression = db.create(function: "f1") { true } - let f2: () -> Expression = db.create(function: "f2") { nil } - - let table = db["table"] - db.create(table: table) { $0.column(Expression("id"), primaryKey: true) } - table.insert().rowid! - - XCTAssert(table.select(f1()).first![f1()]) - AssertSQL("SELECT \"f1\"() FROM \"table\" LIMIT 1") - - XCTAssertNil(table.select(f2()).first![f2()]) - AssertSQL("SELECT \"f2\"() FROM \"table\" LIMIT 1") - } - - func test_createFunction_withOneArgument() { - let f1: Expression -> Expression = db.create(function: "f1") { _ in return true } - let f2: Expression -> Expression = db.create(function: "f2") { _ in return false } - let f3: Expression -> Expression = db.create(function: "f3") { _ in return true } - let f4: Expression -> Expression = db.create(function: "f4") { _ in return nil } - - let table = db["table"] - let s1 = Expression("s1") - let s2 = Expression("s2") - db.create(table: table) { t in - t.column(s1) - t.column(s2) - } - table.insert(s1 <- "s1").rowid! - - let null = Expression(value: nil as String?) - - XCTAssert(table.select(f1(s1)).first![f1(s1)]) - AssertSQL("SELECT \"f1\"(\"s1\") FROM \"table\" LIMIT 1") - - XCTAssert(!table.select(f2(s2)).first![f2(s2)]) - AssertSQL("SELECT \"f2\"(\"s2\") FROM \"table\" LIMIT 1") - - XCTAssert(table.select(f3(s1)).first![f3(s1)]!) - AssertSQL("SELECT \"f3\"(\"s1\") FROM \"table\" LIMIT 1") - - XCTAssertNil(table.select(f4(null)).first![f4(null)]) - AssertSQL("SELECT \"f4\"(NULL) FROM \"table\" LIMIT 1") - } - - func test_createFunction_withValueArgument() { - let f1: Expression -> Expression = ( - db.create(function: "f1") { (a: Bool) -> Bool in - return a - } - ) - - let table = db["table"] - let b = Expression("b") - db.create(table: table) { t in - t.column(b) - } - table.insert(b <- true).rowid! - - XCTAssert(table.select(f1(b)).first![f1(b)]) - AssertSQL("SELECT \"f1\"(\"b\") FROM \"table\" LIMIT 1") - } - - func test_createFunction_withTwoArguments() { - let table = db["table"] - let b1 = Expression("b1") - let b2 = Expression("b2") - db.create(table: table) { t in - t.column(b1) - t.column(b2) - } - table.insert(b1 <- true).rowid! - - let f1: (Bool, Expression) -> Expression = db.create(function: "f1") { $0 && $1 } - let f2: (Bool?, Expression) -> Expression = db.create(function: "f2") { $0 ?? $1 } - let f3: (Bool, Expression) -> Expression = db.create(function: "f3") { $0 && $1 != nil } - let f4: (Bool?, Expression) -> Expression = db.create(function: "f4") { $0 ?? $1 != nil } - - XCTAssert(table.select(f1(true, b1)).first![f1(true, b1)]) - AssertSQL("SELECT \"f1\"(1, \"b1\") FROM \"table\" LIMIT 1") - XCTAssert(table.select(f2(nil, b1)).first![f2(nil, b1)]) - AssertSQL("SELECT \"f2\"(NULL, \"b1\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f3(false, b2)).first![f3(false, b2)]) - AssertSQL("SELECT \"f3\"(0, \"b2\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f4(nil, b2)).first![f4(nil, b2)]) - AssertSQL("SELECT \"f4\"(NULL, \"b2\") FROM \"table\" LIMIT 1") - - let f5: (Bool, Expression) -> Expression = db.create(function: "f5") { $0 && $1 } - let f6: (Bool?, Expression) -> Expression = db.create(function: "f6") { $0 ?? $1 } - let f7: (Bool, Expression) -> Expression = db.create(function: "f7") { $0 && $1 != nil } - let f8: (Bool?, Expression) -> Expression = db.create(function: "f8") { $0 ?? $1 != nil } - - XCTAssert(table.select(f5(true, b1)).first![f5(true, b1)]!) - AssertSQL("SELECT \"f5\"(1, \"b1\") FROM \"table\" LIMIT 1") - XCTAssert(table.select(f6(nil, b1)).first![f6(nil, b1)]!) - AssertSQL("SELECT \"f6\"(NULL, \"b1\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f7(false, b2)).first![f7(false, b2)]!) - AssertSQL("SELECT \"f7\"(0, \"b2\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f8(nil, b2)).first![f8(nil, b2)]!) - AssertSQL("SELECT \"f8\"(NULL, \"b2\") FROM \"table\" LIMIT 1") - - let f9: (Expression, Expression) -> Expression = db.create(function: "f9") { $0 && $1 } - let f10: (Expression, Expression) -> Expression = db.create(function: "f10") { $0 ?? $1 } - let f11: (Expression, Expression) -> Expression = db.create(function: "f11") { $0 && $1 != nil } - let f12: (Expression, Expression) -> Expression = db.create(function: "f12") { $0 ?? $1 != nil } - - XCTAssert(table.select(f9(b1, b1)).first![f9(b1, b1)]) - AssertSQL("SELECT \"f9\"(\"b1\", \"b1\") FROM \"table\" LIMIT 1") - XCTAssert(table.select(f10(b2, b1)).first![f10(b2, b1)]) - AssertSQL("SELECT \"f10\"(\"b2\", \"b1\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f11(b1, b2)).first![f11(b1, b2)]) - AssertSQL("SELECT \"f11\"(\"b1\", \"b2\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f12(b2, b2)).first![f12(b2, b2)]) - AssertSQL("SELECT \"f12\"(\"b2\", \"b2\") FROM \"table\" LIMIT 1") - - let f13: (Expression, Expression) -> Expression = db.create(function: "f13") { $0 && $1 } - let f14: (Expression, Expression) -> Expression = db.create(function: "f14") { $0 ?? $1 } - let f15: (Expression, Expression) -> Expression = db.create(function: "f15") { $0 && $1 != nil } - let f16: (Expression, Expression) -> Expression = db.create(function: "f16") { $0 ?? $1 != nil } - - XCTAssert(table.select(f13(b1, b1)).first![f13(b1, b1)]!) - AssertSQL("SELECT \"f13\"(\"b1\", \"b1\") FROM \"table\" LIMIT 1") - XCTAssert(table.select(f14(b2, b1)).first![f14(b2, b1)]!) - AssertSQL("SELECT \"f14\"(\"b2\", \"b1\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f15(b1, b2)).first![f15(b1, b2)]!) - AssertSQL("SELECT \"f15\"(\"b1\", \"b2\") FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f16(b2, b2)).first![f16(b2, b2)]!) - AssertSQL("SELECT \"f16\"(\"b2\", \"b2\") FROM \"table\" LIMIT 1") - - let f17: (Expression, Bool) -> Expression = db.create(function: "f17") { $0 && $1 } - let f18: (Expression, Bool) -> Expression = db.create(function: "f18") { $0 ?? $1 } - let f19: (Expression, Bool?) -> Expression = db.create(function: "f19") { $0 && $1 != nil } - let f20: (Expression, Bool?) -> Expression = db.create(function: "f20") { $0 ?? $1 != nil } - - XCTAssert(table.select(f17(b1, true)).first![f17(b1, true)]) - AssertSQL("SELECT \"f17\"(\"b1\", 1) FROM \"table\" LIMIT 1") - XCTAssert(table.select(f18(b2, true)).first![f18(b2, true)]) - AssertSQL("SELECT \"f18\"(\"b2\", 1) FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f19(b1, nil)).first![f19(b1, nil)]) - AssertSQL("SELECT \"f19\"(\"b1\", NULL) FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f20(b2, nil)).first![f20(b2, nil)]) - AssertSQL("SELECT \"f20\"(\"b2\", NULL) FROM \"table\" LIMIT 1") - - let f21: (Expression, Bool) -> Expression = db.create(function: "f21") { $0 && $1 } - let f22: (Expression, Bool) -> Expression = db.create(function: "f22") { $0 ?? $1 } - let f23: (Expression, Bool?) -> Expression = db.create(function: "f23") { $0 && $1 != nil } - let f24: (Expression, Bool?) -> Expression = db.create(function: "f24") { $0 ?? $1 != nil } - - XCTAssert(table.select(f21(b1, true)).first![f21(b1, true)]!) - AssertSQL("SELECT \"f21\"(\"b1\", 1) FROM \"table\" LIMIT 1") - XCTAssert(table.select(f22(b2, true)).first![f22(b2, true)]!) - AssertSQL("SELECT \"f22\"(\"b2\", 1) FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f23(b1, nil)).first![f23(b1, nil)]!) - AssertSQL("SELECT \"f23\"(\"b1\", NULL) FROM \"table\" LIMIT 1") - XCTAssertFalse(table.select(f24(b2, nil)).first![f24(b2, nil)]!) - AssertSQL("SELECT \"f24\"(\"b2\", NULL) FROM \"table\" LIMIT 1") - } - -} diff --git a/SQLite Tests/QueryTests.swift b/SQLite Tests/QueryTests.swift deleted file mode 100644 index fcb2e898..00000000 --- a/SQLite Tests/QueryTests.swift +++ /dev/null @@ -1,492 +0,0 @@ -import XCTest -import SQLite - -class QueryTests: SQLiteTestCase { - - override func setUp() { - createUsersTable() - - super.setUp() - } - - func test_select_withExpression_compilesSelectClause() { - AssertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) - } - - func test_select_withVariadicExpressions_compilesSelectClause() { - AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select(email, count(*))) - } - - func test_select_withStar_resetsSelectClause() { - let query = users.select(email) - - AssertSQL("SELECT * FROM \"users\"", query.select(*)) - } - - func test_selectDistinct_withExpression_compilesSelectClause() { - AssertSQL("SELECT DISTINCT \"age\" FROM \"users\"", users.select(distinct: age)) - } - - func test_selectDistinct_withStar_compilesSelectClause() { - AssertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) - } - - func test_select_withSubquery() { - let subquery = users.select(id) - - AssertSQL("SELECT (SELECT \"id\" FROM \"users\") FROM \"users\"", users.select(subquery)) - AssertSQL("SELECT (SELECT \"id\" FROM (\"users\") AS \"u\") AS \"u\" FROM \"users\"", - users.select(subquery.alias("u"))) - } - - func test_join_compilesJoinClause() { - let managers = db["users"].alias("managers") - - let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN (\"users\") AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")" - AssertSQL(SQL, users.join(managers, on: managers[id] == users[manager_id])) - } - - func test_join_withExplicitType_compilesJoinClauseWithType() { - let managers = db["users"].alias("managers") - - let SQL = "SELECT * FROM \"users\" " + - "LEFT OUTER JOIN (\"users\") AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\")" - AssertSQL(SQL, users.join(.LeftOuter, managers, on: managers[id] == users[manager_id])) - } - - func test_join_withTableCondition_compilesJoinClauseWithTableCondition() { - var managers = db["users"].alias("managers") - managers = managers.filter(managers[admin]) - - let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN (\"users\") AS \"managers\" " + - "ON ((\"managers\".\"id\" = \"users\".\"manager_id\") " + - "AND \"managers\".\"admin\")" - AssertSQL(SQL, users.join(managers, on: managers[id] == users[manager_id])) - } - - func test_join_whenChained_compilesAggregateJoinClause() { - let managers = users.alias("managers") - let managed = users.alias("managed") - - let middleManagers = users - .join(managers, on: managers[id] == users[manager_id]) - .join(managed, on: managed[manager_id] == users[id]) - - let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN (\"users\") AS \"managers\" ON (\"managers\".\"id\" = \"users\".\"manager_id\") " + - "INNER JOIN (\"users\") AS \"managed\" ON (\"managed\".\"manager_id\" = \"users\".\"id\")" - AssertSQL(SQL, middleManagers) - } - - func test_join_withNamespacedStar_expandsColumnNames() { - let managers = db["users"].alias("managers") - - let aliceId = users.insert(email <- "alice@example.com").rowid! - users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId)).rowid! - - let query = users - .select(users[*], managers[*]) - .join(managers, on: managers[id] == users[manager_id]) - - let SQL = "SELECT \"users\".*, \"managers\".* FROM \"users\" " + - "INNER JOIN (\"users\") AS \"managers\" " + - "ON (\"managers\".\"id\" = \"users\".\"manager_id\")" - AssertSQL(SQL, query) - } - - func test_join_withSubquery_joinsSubquery() { - insertUser("alice", age: 20) - - let maxId = max(id).alias("max_id") - let subquery = users.select(maxId).group(age) - let query = users.join(subquery, on: maxId == id) - - XCTAssertEqual(Int64(1), query.first![maxId]!) - - let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN (SELECT (max(\"id\")) AS \"max_id\" FROM \"users\" GROUP BY \"age\") " + - "ON (\"max_id\" = \"id\") LIMIT 1" - AssertSQL(SQL, query) - } - - func test_join_withAliasedSubquery_joinsSubquery() { - insertUser("alice", age: 20) - - let maxId = max(id).alias("max_id") - let subquery = users.select(maxId).group(age).alias("u") - let query = users.join(subquery, on: subquery[maxId] == id) - - XCTAssertEqual(Int64(1), query.first![subquery[maxId]]!) - - let SQL = "SELECT * FROM \"users\" " + - "INNER JOIN (SELECT (max(\"id\")) AS \"max_id\" FROM (\"users\") AS \"u\" GROUP BY \"age\") AS \"u\" " + - "ON (\"u\".\"max_id\" = \"id\") LIMIT 1" - AssertSQL(SQL, query) - } - - func test_namespacedColumnRowValueAccess() { - let aliceId = users.insert(email <- "alice@example.com").rowid! - let bettyId = users.insert(email <- "betty@example.com", manager_id <- Int64(aliceId)).rowid! - - let alice = users.first! - XCTAssertEqual(Int64(aliceId), alice[id]) - - let managers = db["users"].alias("managers") - let query = users.join(managers, on: managers[id] == users[manager_id]) - - let betty = query.first! - XCTAssertEqual(alice[email], betty[managers[email]]) - } - - func test_aliasedColumnRowValueAccess() { - users.insert(email <- "alice@example.com").rowid! - - let alias = email.alias("user_email") - let query = users.select(alias) - let alice = query.first! - - XCTAssertEqual("alice@example.com", alice[alias]) - } - - func test_filter_compilesWhereClause() { - AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(admin == true)) - } - - func test_filter_whenChained_compilesAggregateWhereClause() { - let query = users - .filter(email == "alice@example.com") - .filter(age >= 21) - - let SQL = "SELECT * FROM \"users\" " + - "WHERE ((\"email\" = 'alice@example.com') " + - "AND (\"age\" >= 21))" - AssertSQL(SQL, query) - } - - func test_group_withSingleExpressionName_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\"", - users.group(age)) - } - - func test_group_withVariadicExpressionNames_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\"", users.group(age, admin)) - } - - func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)", users.group(age, having: age >= 30)) - } - - func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() { - AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING (\"age\" >= 30)", - users.group([age, admin], having: age >= 30)) - } - - func test_order_withSingleExpressionName_compilesOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(age)) - } - - func test_order_withVariadicExpressionNames_compilesOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order(age, email)) - } - - func test_order_withExpressionAndSortDirection_compilesOrderClause() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age.desc, email.asc)) - } - - func test_order_whenChained_overridesOrder() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(email).order(age)) - } - - func test_reverse_withoutOrder_ordersByRowIdDescending() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"ROWID\" DESC", users.reverse()) - } - - func test_reverse_withOrder_reversesOrder() { - AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age, email.desc).reverse()) - } - - func test_limit_compilesLimitClause() { - AssertSQL("SELECT * FROM \"users\" LIMIT 5", users.limit(5)) - } - - func test_limit_withOffset_compilesOffsetClause() { - AssertSQL("SELECT * FROM \"users\" LIMIT 5 OFFSET 5", users.limit(5, offset: 5)) - } - - func test_limit_whenChained_overridesLimit() { - let query = users.limit(5) - - AssertSQL("SELECT * FROM \"users\" LIMIT 10", query.limit(10)) - AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) - } - - func test_limit_whenChained_withOffset_overridesOffset() { - let query = users.limit(5, offset: 5) - - AssertSQL("SELECT * FROM \"users\" LIMIT 10 OFFSET 10", query.limit(10, offset: 10)) - AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) - } - - func test_alias_compilesAliasInSelectClause() { - AssertSQL("SELECT * FROM (\"users\") AS \"managers\"", users.alias("managers")) - } - - func test_subscript_withExpression_returnsNamespacedExpression() { - AssertSQL("SELECT \"users\".\"admin\" FROM \"users\"", users.select(users[admin])) - AssertSQL("SELECT \"users\".\"salary\" FROM \"users\"", users.select(users[salary])) - AssertSQL("SELECT \"users\".\"age\" FROM \"users\"", users.select(users[age])) - AssertSQL("SELECT \"users\".\"email\" FROM \"users\"", users.select(users[email])) - AssertSQL("SELECT \"users\".* FROM \"users\"", users.select(users[*])) - } - - func test_subscript_withAliasAndExpression_returnsAliasedExpression() { - let managers = users.alias("managers") - AssertSQL("SELECT \"managers\".\"admin\" FROM (\"users\") AS \"managers\"", managers.select(managers[admin])) - AssertSQL("SELECT \"managers\".\"salary\" FROM (\"users\") AS \"managers\"", managers.select(managers[salary])) - AssertSQL("SELECT \"managers\".\"age\" FROM (\"users\") AS \"managers\"", managers.select(managers[age])) - AssertSQL("SELECT \"managers\".\"email\" FROM (\"users\") AS \"managers\"", managers.select(managers[email])) - AssertSQL("SELECT \"managers\".* FROM (\"users\") AS \"managers\"", managers.select(managers[*])) - } - - func test_SQL_compilesProperly() { - var managers = users.alias("managers") - // TODO: automatically namespace in the future? - managers = managers.filter(managers[admin] == true) - - let query = users - .select(users[email], count(users[age])) - .join(.LeftOuter, managers, on: managers[id] == users[manager_id]) - .filter(21..<32 ~= users[age]) - .group(users[age], having: count(users[age]) > 1) - .order(users[email].desc) - .limit(1, offset: 2) - - let SQL = "SELECT \"users\".\"email\", count(\"users\".\"age\") FROM \"users\" " + - "LEFT OUTER JOIN (\"users\") AS \"managers\" " + - "ON ((\"managers\".\"id\" = \"users\".\"manager_id\") AND (\"managers\".\"admin\" = 1)) " + - "WHERE \"users\".\"age\" BETWEEN 21 AND 32 " + - "GROUP BY \"users\".\"age\" HAVING (count(\"users\".\"age\") > 1) " + - "ORDER BY \"users\".\"email\" DESC " + - "LIMIT 1 " + - "OFFSET 2" - AssertSQL(SQL, query) - } - - func test_first_withAnEmptyQuery_returnsNil() { - XCTAssert(users.first == nil) - } - - func test_first_returnsTheFirstRow() { - insertUsers("alice", "betsy") - - XCTAssertEqual(1, users.first![id]) - AssertSQL("SELECT * FROM \"users\" LIMIT 1") - } - - func test_first_withOffset_retainsOffset() { - insertUsers("alice", "betsy") - - XCTAssertEqual(2, users.limit(2, offset: 1).first![id]) - AssertSQL("SELECT * FROM \"users\" LIMIT 1 OFFSET 1") - } - - func test_isEmpty_returnsWhetherOrNotTheQueryIsEmpty() { - XCTAssertTrue(users.isEmpty) - - insertUser("alice") - - XCTAssertFalse(users.isEmpty) - - AssertSQL("SELECT * FROM \"users\" LIMIT 1", 2) - } - - func test_insert_insertsRows() { - XCTAssertEqual(1, users.insert(email <- "alice@example.com", age <- 30).rowid!) - - AssertSQL("INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)") - - XCTAssert(users.insert(email <- "alice@example.com", age <- 30).rowid == nil) - } - - func test_insert_withQuery_insertsRows() { - db.execute("CREATE TABLE \"emails\" (\"email\" TEXT)") - let emails = db["emails"] - let admins = users.select(email).filter(admin == true) - - emails.insert(admins) - AssertSQL("INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE (\"admin\" = 1)") - } - - func test_insert_insertsDefaultRow() { - db.execute("CREATE TABLE \"timestamps\" (\"id\" INTEGER PRIMARY KEY, \"timestamp\" TEXT DEFAULT CURRENT_DATETIME)") - let table = db["timestamps"] - - XCTAssertEqual(1, table.insert().rowid!) - AssertSQL("INSERT INTO \"timestamps\" DEFAULT VALUES") - } - - func test_replace_replaceRows() { - XCTAssertEqual(1, users.insert(or: .Replace, email <- "alice@example.com", age <- 30).rowid!) - AssertSQL("INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)") - - XCTAssertEqual(1, users.insert(or: .Replace, id <- 1, email <- "betty@example.com", age <- 30).rowid!) - AssertSQL("INSERT OR REPLACE INTO \"users\" (\"id\", \"email\", \"age\") VALUES (1, 'betty@example.com', 30)") - } - - func test_update_updatesRows() { - insertUsers("alice", "betsy") - insertUser("dolly", admin: true) - - XCTAssertEqual(2, users.filter(!admin).update(age <- 30, admin <- true).changes!) - XCTAssertEqual(0, users.filter(!admin).update(age <- 30, admin <- true).changes!) - } - - func test_delete_deletesRows() { - insertUser("alice", age: 20) - XCTAssertEqual(0, users.filter(email == "betsy@example.com").delete().changes!) - - insertUser("betsy", age: 30) - XCTAssertEqual(2, users.delete().changes!) - XCTAssertEqual(0, users.delete().changes!) - } - - func test_count_returnsCount() { - XCTAssertEqual(0, users.count) - - insertUser("alice") - XCTAssertEqual(1, users.count) - XCTAssertEqual(0, users.filter(age != nil).count) - } - - func test_count_withExpression_returnsCount() { - insertUser("alice", age: 20) - insertUser("betsy", age: 20) - insertUser("cindy") - - XCTAssertEqual(2, users.count(age)) - XCTAssertEqual(1, users.count(distinct: age)) - } - - func test_max_withInt_returnsMaximumInt() { - XCTAssert(users.max(age) == nil) - - insertUser("alice", age: 20) - insertUser("betsy", age: 30) - XCTAssertEqual(30, users.max(age)!) - } - - func test_min_withInt_returnsMinimumInt() { - XCTAssert(users.min(age) == nil) - - insertUser("alice", age: 20) - insertUser("betsy", age: 30) - XCTAssertEqual(20, users.min(age)!) - } - - func test_averageWithInt_returnsDouble() { - XCTAssert(users.average(age) == nil) - - insertUser("alice", age: 20) - insertUser("betsy", age: 50) - insertUser("cindy", age: 50) - XCTAssertEqual(40.0, users.average(age)!) - XCTAssertEqual(35.0, users.average(distinct: age)!) - } - - func test_sum_returnsSum() { - XCTAssert(users.sum(age) == nil) - - insertUser("alice", age: 20) - insertUser("betsy", age: 30) - insertUser("cindy", age: 30) - XCTAssertEqual(80, users.sum(age)!) - XCTAssertEqual(50, users.sum(distinct: age)!) - } - - func test_total_returnsTotal() { - XCTAssertEqual(0.0, users.total(age)) - - insertUser("alice", age: 20) - insertUser("betsy", age: 30) - insertUser("cindy", age: 30) - XCTAssertEqual(80.0, users.total(age)) - XCTAssertEqual(50.0, users.total(distinct: age)) - } - - func test_row_withBoundColumn_returnsValue() { - insertUser("alice", age: 20) - XCTAssertEqual(21, users.select(age + 1).first![age + 1]!) - } - - func test_valueExtension_serializesAndDeserializes() { - let id = Expression("id") - let timestamp = Expression("timestamp") - let touches = db["touches"] - db.create(table: touches) { t in - t.column(id, primaryKey: true) - t.column(timestamp) - } - - let date = NSDate(timeIntervalSince1970: 0) - touches.insert(timestamp <- date) - XCTAssertEqual(touches.first!.get(timestamp)!, date) - - XCTAssertNil(touches.filter(id == Int64(touches.insert().rowid!)).first!.get(timestamp)) - - XCTAssert(touches.filter(timestamp < NSDate()).first != nil) - } - - func test_shortCircuitingInserts() { - db.transaction() - && users.insert(email <- "alice@example.com") - && users.insert(email <- "alice@example.com") - && users.insert(email <- "alice@example.com") - && db.commit() - || db.rollback() - - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("INSERT INTO \"users\" (\"email\") VALUES ('alice@example.com')", 2) - AssertSQL("ROLLBACK TRANSACTION") - AssertSQL("COMMIT TRANSACTION", 0) - } - - func test_shortCircuitingChanges() { - db.transaction() - && users.insert(email <- "foo@example.com") - && users.insert(email <- "bar@example.com") - && users.filter(email == "bar@example.com").update(email <- "foo@example.com") - && users.filter(email == "bar@example.com").update(email <- "foo@example.com") - && db.commit() - || db.rollback() - - AssertSQL("BEGIN DEFERRED TRANSACTION") - AssertSQL("UPDATE \"users\" SET \"email\" = 'foo@example.com' WHERE (\"email\" = 'bar@example.com')") - AssertSQL("ROLLBACK TRANSACTION") - AssertSQL("COMMIT TRANSACTION", 0) - } - -} - -private let formatter: NSDateFormatter = { - let formatter = NSDateFormatter() - formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" - formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") - formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) - return formatter -}() - -extension NSDate: Value { - - public class var declaredDatatype: String { return String.declaredDatatype } - - public class func fromDatatypeValue(datatypeValue: String) -> NSDate { - return formatter.dateFromString(datatypeValue)! - } - - public var datatypeValue: String { - return formatter.stringFromDate(self) - } - -} diff --git a/SQLite Tests/RTreeTests.swift b/SQLite Tests/RTreeTests.swift deleted file mode 100644 index fe5feb57..00000000 --- a/SQLite Tests/RTreeTests.swift +++ /dev/null @@ -1,19 +0,0 @@ -import XCTest -import SQLite - -let minX = Expression("minX") -let maxX = Expression("maxX") -let minY = Expression("minY") -let maxY = Expression("maxY") - -class RTreeTests: SQLiteTestCase { - - var index: Query { return db["index"] } - - func test_createVtable_usingRtree_createsVirtualTable() { - db.create(vtable: index, using: rtree(id, minX, maxX, minY, maxY)) - - AssertSQL("CREATE VIRTUAL TABLE \"index\" USING rtree(\"id\", \"minX\", \"maxX\", \"minY\", \"maxY\")") - } - -} diff --git a/SQLite Tests/SchemaTests.swift b/SQLite Tests/SchemaTests.swift deleted file mode 100644 index b65aed0a..00000000 --- a/SQLite Tests/SchemaTests.swift +++ /dev/null @@ -1,372 +0,0 @@ -import XCTest -import SQLite - -class SchemaTests: SQLiteTestCase { - - override func setUp() { - db.foreignKeys = true - - super.setUp() - } - - func test_createTable_createsTable() { - db.create(table: users) { $0.column(age) } - - AssertSQL("CREATE TABLE \"users\" (\"age\" INTEGER)") - } - - func test_createTable_temporary_createsTemporaryTable() { - db.create(table: users, temporary: true) { $0.column(age) } - - AssertSQL("CREATE TEMPORARY TABLE \"users\" (\"age\" INTEGER)") - } - - func test_createTable_ifNotExists_createsTableIfNotExists() { - db.create(table: users, ifNotExists: true) { $0.column(age) } - - AssertSQL("CREATE TABLE IF NOT EXISTS \"users\" (\"age\" INTEGER)") - } - - func test_createTable_column_buildsColumnDefinition() { - db.create(table: users) { $0.column(email) } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL)") - } - - func test_createTable_column_nonIntegerPrimaryKey() { - db.create(table: users) { $0.column(email, primaryKey: true) } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT PRIMARY KEY NOT NULL)") - } - - func test_createTable_column_nonIntegerPrimaryKey_withDefaultValue() { - let uuid = Expression("uuid") - let uuidgen: () -> Expression = db.create(function: "uuidgen") { - return NSUUID().UUIDString - } - db.create(table: users) { $0.column(uuid, primaryKey: true, defaultValue: uuidgen()) } - - AssertSQL("CREATE TABLE \"users\" (\"uuid\" TEXT PRIMARY KEY NOT NULL DEFAULT (\"uuidgen\"()))") - } - - func test_createTable_column_withPrimaryKey_buildsPrimaryKeyClause() { - db.create(table: users) { $0.column(id, primaryKey: true) } - - AssertSQL("CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY NOT NULL)") - } - - func test_createTable_column_withPrimaryKey_buildsPrimaryKeyAutoincrementClause() { - db.create(table: users) { $0.column(id, primaryKey: .Autoincrement) } - - AssertSQL("CREATE TABLE \"users\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)") - } - - func test_createTable_column_withNullFalse_buildsNotNullClause() { - db.create(table: users) { $0 .column(email) } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL)") - } - - func test_createTable_column_withUnique_buildsUniqueClause() { - db.create(table: users) { $0.column(email, unique: true) } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL UNIQUE)") - } - - func test_createTable_column_withCheck_buildsCheckClause() { - db.create(table: users) { $0.column(admin, check: contains([false, true], admin)) } - - AssertSQL("CREATE TABLE \"users\" (\"admin\" INTEGER NOT NULL CHECK ((\"admin\" IN (0, 1))))") - } - - func test_createTable_column_withDefaultValue_buildsDefaultClause() { - db.create(table: users) { $0.column(salary, defaultValue: 0) } - - AssertSQL("CREATE TABLE \"users\" (\"salary\" REAL NOT NULL DEFAULT 0.0)") - } - - func test_createTable_stringColumn_collation_buildsCollateClause() { - db.create(table: users) { $0.column(email, collate: .Nocase) } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL COLLATE NOCASE)") - } - - func test_createTable_intColumn_referencingNamespacedColumn_buildsReferencesClause() { - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id, references: users[id]) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER REFERENCES \"users\"(\"id\"))") - } - - func test_createTable_intColumn_referencingQuery_buildsReferencesClause() { - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id, references: users) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER REFERENCES \"users\")") - } - - func test_createTable_primaryKey_buildsPrimaryKeyTableConstraint() { - db.create(table: users) { t in - t.column(email) - t.primaryKey(email) - } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL, PRIMARY KEY(\"email\"))") - } - - func test_createTable_primaryKey_buildsCompositePrimaryKeyTableConstraint() { - db.create(table: users) { t in - t.column(id) - t.column(email) - t.primaryKey(id, email) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, PRIMARY KEY(\"id\", \"email\"))") - } - - func test_createTable_unique_buildsUniqueTableConstraint() { - db.create(table: users) { t in - t.column(email) - t.unique(email) - } - - AssertSQL("CREATE TABLE \"users\" (\"email\" TEXT NOT NULL, UNIQUE(\"email\"))") - } - - func test_createTable_unique_buildsCompositeUniqueTableConstraint() { - db.create(table: users) { t in - t.column(id) - t.column(email) - t.unique(id, email) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER NOT NULL, \"email\" TEXT NOT NULL, UNIQUE(\"id\", \"email\"))") - } - - func test_createTable_check_buildsCheckTableConstraint() { - db.create(table: users) { t in - t.column(admin) - t.check(contains([false, true], admin)) - } - - AssertSQL("CREATE TABLE \"users\" (\"admin\" INTEGER NOT NULL, CHECK ((\"admin\" IN (0, 1))))") - } - - func test_createTable_foreignKey_referencingNamespacedColumn_buildsForeignKeyTableConstraint() { - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id) - t.foreignKey(manager_id, references: users[id]) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER, " + - "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\"))") - } - - func test_createTable_foreignKey_withUpdateDependency_buildsUpdateDependency() { - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id) - t.foreignKey(manager_id, references: users[id], update: .Cascade) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER, " + - "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\") ON UPDATE CASCADE)") - } - - func test_create_foreignKey_withDeleteDependency_buildsDeleteDependency() { - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id) - t.foreignKey(manager_id, references: users[id], delete: .Cascade) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER, " + - "FOREIGN KEY(\"manager_id\") REFERENCES \"users\"(\"id\") ON DELETE CASCADE)") - } - - func test_createTable_foreignKey_withCompositeKey_buildsForeignKeyTableConstraint() { - let manager_id = Expression("manager_id") // required - - db.create(table: users) { t in - t.column(id, primaryKey: true) - t.column(manager_id) - t.column(email) - t.foreignKey((manager_id, email), references: (users[id], email)) - } - - AssertSQL("CREATE TABLE \"users\" (" + - "\"id\" INTEGER PRIMARY KEY NOT NULL, " + - "\"manager_id\" INTEGER NOT NULL, " + - "\"email\" TEXT NOT NULL, " + - "FOREIGN KEY(\"manager_id\", \"email\") REFERENCES \"users\"(\"id\", \"email\"))") - } - - func test_createTable_withQuery_createsTableWithQuery() { - createUsersTable() - - db.create(table: db["emails"], from: users.select(email)) - AssertSQL("CREATE TABLE \"emails\" AS SELECT \"email\" FROM \"users\"") - - db.create(table: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) - AssertSQL("CREATE TEMPORARY TABLE IF NOT EXISTS \"emails\" AS SELECT \"email\" FROM \"users\"") - } - - func test_alterTable_renamesTable() { - createUsersTable() - let people = db["people"] - - db.rename(table: "users", to: people) - AssertSQL("ALTER TABLE \"users\" RENAME TO \"people\"") - } - - func test_alterTable_addsNotNullColumn() { - createUsersTable() - let column = Expression("bonus") - - db.alter(table: users, add: column, defaultValue: 0) - AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL NOT NULL DEFAULT 0.0") - } - - func test_alterTable_addsRegularColumn() { - createUsersTable() - let column = Expression("bonus") - - db.alter(table: users, add: column) - AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL") - } - - func test_alterTable_withDefaultValue_addsRegularColumn() { - createUsersTable() - let column = Expression("bonus") - - db.alter(table: users, add: column, defaultValue: 0) - AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"bonus\" REAL DEFAULT 0.0") - } - - func test_alterTable_withForeignKey_addsRegularColumn() { - createUsersTable() - let column = Expression("parent_id") - - db.alter(table: users, add: column, references: users[id]) - AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"parent_id\" INTEGER REFERENCES \"users\"(\"id\")") - } - - func test_alterTable_stringColumn_collation_buildsCollateClause() { - createUsersTable() - let columnA = Expression("column_a") - let columnB = Expression("column_b") - - db.alter(table: users, add: columnA, defaultValue: "", collate: .Nocase) - AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"column_a\" TEXT NOT NULL DEFAULT '' COLLATE \"NOCASE\"") - - db.alter(table: users, add: columnB, collate: .Nocase) - AssertSQL("ALTER TABLE \"users\" ADD COLUMN \"column_b\" TEXT COLLATE \"NOCASE\"") - } - - func test_dropTable_dropsTable() { - createUsersTable() - - db.drop(table: users) - AssertSQL("DROP TABLE \"users\"") - - db.drop(table: users, ifExists: true) - AssertSQL("DROP TABLE IF EXISTS \"users\"") - } - - func test_index_executesIndexStatement() { - createUsersTable() - - db.create(index: users, on: email) - AssertSQL("CREATE INDEX \"index_users_on_email\" ON \"users\" (\"email\")") - } - - func test_index_withUniqueness_executesUniqueIndexStatement() { - createUsersTable() - - db.create(index: users, unique: true, on: email) - AssertSQL("CREATE UNIQUE INDEX \"index_users_on_email\" ON \"users\" (\"email\")") - } - - func test_index_ifNotExists_executesIndexStatement() { - createUsersTable() - - db.create(index: users, ifNotExists: true, on: email) - AssertSQL("CREATE INDEX IF NOT EXISTS \"index_users_on_email\" ON \"users\" (\"email\")") - } - - func test_index_withMultipleColumns_executesCompoundIndexStatement() { - createUsersTable() - - db.create(index: users, on: age.desc, email) - AssertSQL("CREATE INDEX \"index_users_on_age_email\" ON \"users\" (\"age\" DESC, \"email\")") - } - -// func test_index_withFilter_executesPartialIndexStatementWithWhereClause() { -// if SQLITE_VERSION >= "3.8" { -// CreateUsersTable(db) -// ExpectExecution(db, -// "CREATE INDEX index_users_on_age ON \"users\" (age) WHERE admin", -// db.create(index: users.filter(admin), on: age) -// ) -// } -// } - - func test_dropIndex_dropsIndex() { - createUsersTable() - db.create(index: users, on: email) - - db.drop(index: users, on: email) - AssertSQL("DROP INDEX \"index_users_on_email\"") - - db.drop(index: users, ifExists: true, on: email) - AssertSQL("DROP INDEX IF EXISTS \"index_users_on_email\"") - } - - func test_createView_withQuery_createsViewWithQuery() { - createUsersTable() - - db.create(view: db["emails"], from: users.select(email)) - AssertSQL("CREATE VIEW \"emails\" AS SELECT \"email\" FROM \"users\"") - - db.create(view: db["emails"], temporary: true, ifNotExists: true, from: users.select(email)) - AssertSQL("CREATE TEMPORARY VIEW IF NOT EXISTS \"emails\" AS SELECT \"email\" FROM \"users\"") - } - - func test_dropView_dropsView() { - createUsersTable() - let emails = db["emails"] - db.create(view: emails, from: users.select(email)) - - db.drop(view: emails) - AssertSQL("DROP VIEW \"emails\"") - - db.drop(view: emails, ifExists: true) - AssertSQL("DROP VIEW IF EXISTS \"emails\"") - } - - func test_quotedIdentifiers() { - let table = db["table"] - let column = Expression("My lil' primary key, \"Kiwi\"") - - db.create(table: table) { $0.column(column) } - AssertSQL("CREATE TABLE \"table\" (\"My lil' primary key, \"\"Kiwi\"\"\" INTEGER NOT NULL)") - } - -} diff --git a/SQLite Tests/StatementTests.swift b/SQLite Tests/StatementTests.swift deleted file mode 100644 index 1015282a..00000000 --- a/SQLite Tests/StatementTests.swift +++ /dev/null @@ -1,160 +0,0 @@ -import XCTest -import SQLite - -class StatementTests: SQLiteTestCase { - - override func setUp() { - createUsersTable() - - super.setUp() - } - - func test_bind_withVariadicParameters_bindsParameters() { - let stmt = db.prepare("SELECT ?, ?, ?, ?") - withBlob { stmt.bind(1, 2.0, "3", $0) } - AssertSQL("SELECT 1, 2.0, '3', x'34'", stmt) - } - - func test_bind_withArrayOfParameters_bindsParameters() { - let stmt = db.prepare("SELECT ?, ?, ?, ?") - withBlob { stmt.bind([1, 2.0, "3", $0]) } - AssertSQL("SELECT 1, 2.0, '3', x'34'", stmt) - } - - func test_bind_withNamedParameters_bindsParameters() { - let stmt = db.prepare("SELECT ?1, ?2, ?3, ?4") - withBlob { stmt.bind(["?1": 1, "?2": 2.0, "?3": "3", "?4": $0]) } - AssertSQL("SELECT 1, 2.0, '3', x'34'", stmt) - } - - func test_bind_withBlob_bindsBlob() { - let stmt = db.prepare("SELECT ?") - withBlob { stmt.bind($0) } - AssertSQL("SELECT x'34'", stmt) - } - - func test_bind_withDouble_bindsDouble() { - AssertSQL("SELECT 2.0", db.prepare("SELECT ?").bind(2.0)) - } - - func test_bind_withInt_bindsInt() { - AssertSQL("SELECT 3", db.prepare("SELECT ?").bind(3)) - } - - func test_bind_withString() { - AssertSQL("SELECT '4'", db.prepare("SELECT ?").bind("4")) - } - - func test_run_withNoParameters() { - db.prepare("INSERT INTO users (email, admin) VALUES ('alice@example.com', 1)").run() - XCTAssertEqual(1, db.totalChanges) - } - - func test_run_withVariadicParameters() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - stmt.run("alice@example.com", 1) - XCTAssertEqual(1, db.totalChanges) - } - - func test_run_withArrayOfParameters() { - let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - stmt.run(["alice@example.com", 1]) - XCTAssertEqual(1, db.totalChanges) - } - - func test_run_withNamedParameters() { - let stmt = db.prepare( - "INSERT INTO users (email, admin) VALUES ($email, $admin)" - ) - stmt.run(["$email": "alice@example.com", "$admin": 1]) - XCTAssertEqual(1, db.totalChanges) - } - - func test_scalar_withNoParameters() { - let zero = db.prepare("SELECT 0") - XCTAssertEqual(0, zero.scalar() as! Int64) - } - - func test_scalar_withNoParameters_retainsBindings() { - let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?", 21) - XCTAssertEqual(0, count.scalar() as! Int64) - - insertUser("alice", age: 21) - XCTAssertEqual(1, count.scalar() as! Int64) - } - - func test_scalar_withVariadicParameters() { - insertUser( "alice", age: 21) - let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?") - XCTAssertEqual(1, count.scalar(21) as! Int64) - } - - func test_scalar_withArrayOfParameters() { - insertUser( "alice", age: 21) - let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?") - XCTAssertEqual(1, count.scalar([21]) as! Int64) - } - - func test_scalar_withNamedParameters() { - insertUser("alice", age: 21) - let count = db.prepare("SELECT count(*) FROM users WHERE age >= $age") - XCTAssertEqual(1, count.scalar(["$age": 21]) as! Int64) - } - - func test_scalar_withParameters_updatesBindings() { - insertUser("alice", age: 21) - let count = db.prepare("SELECT count(*) FROM users WHERE age >= ?") - XCTAssertEqual(1, count.scalar(21) as! Int64) - XCTAssertEqual(0, count.scalar(22) as! Int64) - } - - func test_scalar_doubleReturnValue() { - XCTAssertEqual(2.0, db.scalar("SELECT 2.0") as! Double) - } - - func test_scalar_intReturnValue() { - XCTAssertEqual(3, db.scalar("SELECT 3") as! Int64) - } - - func test_scalar_stringReturnValue() { - XCTAssertEqual("4", db.scalar("SELECT '4'") as! String) - } - - func test_generate_allowsIteration() { - insertUsers("alice", "betsy", "cindy") - var count = 0 - for row in db.prepare("SELECT id FROM users") { - XCTAssertEqual(1, row.count) - count++ - } - XCTAssertEqual(3, count) - } - - func test_generate_allowsReuse() { - insertUsers("alice", "betsy", "cindy") - var count = 0 - let stmt = db.prepare("SELECT id FROM users") - for row in stmt { count++ } - for row in stmt { count++ } - XCTAssertEqual(6, count) - } - - func test_row_returnsValues() { - insertUser("alice") - let stmt = db.prepare("SELECT id, email FROM users") - stmt.step() - - XCTAssertEqual(1, stmt.row[0] as Int64) - XCTAssertEqual("alice@example.com", stmt.row[1] as String) - } - -} - -func withBlob(block: Blob -> Void) { - let length = 1 - let buflen = Int(length) + 1 - let buffer = UnsafeMutablePointer.alloc(buflen) - memcpy(buffer, "4", length) - block(Blob(bytes: buffer, length: length)) - buffer.dealloc(buflen) -} diff --git a/SQLite Tests/TestHelper.swift b/SQLite Tests/TestHelper.swift deleted file mode 100644 index 41600001..00000000 --- a/SQLite Tests/TestHelper.swift +++ /dev/null @@ -1,83 +0,0 @@ -import SQLite -import XCTest - -let id = Expression("id") -let email = Expression("email") -let age = Expression("age") -let salary = Expression("salary") -let admin = Expression("admin") -let manager_id = Expression("manager_id") - -class SQLiteTestCase: XCTestCase { - - var trace = [String: Int]() - - let db = Database() - - var users: Query { return db["users"] } - - override func setUp() { - super.setUp() - - db.trace { SQL in - println(SQL) - self.trace[SQL] = (self.trace[SQL] ?? 0) + 1 - } - } - - func createUsersTable() { - db.execute( - "CREATE TABLE \"users\" (" + - "id INTEGER PRIMARY KEY, " + - "email TEXT NOT NULL UNIQUE, " + - "age INTEGER, " + - "salary REAL, " + - "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users(id)" + - ")" - ) - } - - func insertUsers(names: String...) { - insertUsers(names) - } - - func insertUsers(names: [String]) { - for name in names { insertUser(name) } - } - - func insertUser(name: String, age: Int? = nil, admin: Bool = false) -> Statement { - return db.run( - "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", - ["\(name)@example.com", age, admin.datatypeValue] - ) - } - - func AssertSQL(SQL: String, _ executions: Int = 1, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { - XCTAssertEqual( - executions, trace[SQL] ?? 0, - message ?? SQL, - file: file, line: line - ) - } - - func AssertSQL(SQL: String, _ statement: Statement, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { - statement.run() - AssertSQL(SQL, 1, message, file: file, line: line) - if let count = trace[SQL] { trace[SQL] = count - 1 } - } - - func AssertSQL(SQL: String, _ query: Query, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { - for _ in query {} - AssertSQL(SQL, 1, message, file: file, line: line) - if let count = trace[SQL] { trace[SQL] = count - 1 } - } - - func async(expect description: String = "async", timeout: Double = 5, @noescape block: (() -> Void) -> Void) { - let expectation = expectationWithDescription(description) - block(expectation.fulfill) - waitForExpectationsWithTimeout(timeout, handler: nil) - } - -} diff --git a/SQLite Tests/ValueTests.swift b/SQLite Tests/ValueTests.swift deleted file mode 100644 index edd16ab1..00000000 --- a/SQLite Tests/ValueTests.swift +++ /dev/null @@ -1,13 +0,0 @@ -import XCTest -import SQLite - -class ValueTests: SQLiteTestCase { - - func test_blob_toHex() { - let blob = Blob( - data:[0,10,20,30,40,50,60,70,80,90,100,150,250,255] as [UInt8] - ) - XCTAssertEqual(blob.toHex(), "000a141e28323c46505a6496faff") - } - -} diff --git a/SQLite.playground/Contents.swift b/SQLite.playground/Contents.swift index bf7f843b..3a685679 100644 --- a/SQLite.playground/Contents.swift +++ b/SQLite.playground/Contents.swift @@ -1,218 +1,43 @@ -/*: -> _Note_: This playground must be running inside the Xcode project to run. Build the OS X framework prior to use. (You may have to close and reopen the project after building it.) - -# SQLite.swift - -This playground contains sample code to explore [SQLite.swift](https://github.com/stephencelis/SQLite.swift), a [Swift](https://developer.apple.com/swift) wrapper for [SQLite3](https://sqlite.org). - -Let’s get started by importing the framework and opening a new in-memory database connection using the `Database` class. -*/ import SQLite -let db = Database() -/*: -This implicitly opens a database in memory (using `.InMemory`). To open a database at a specific location, pass the path as a parameter during instantiation, *e.g.*, - - Database("path/to/database.sqlite3") - -Pass `.Temporary` to open a temporary, disk-backed database, instead. - -Once we initialize a database connection, we can execute SQL statements directly against it. Let’s create a table. -*/ -db.execute( - "CREATE TABLE users (" + - "id INTEGER PRIMARY KEY, " + - "email TEXT NOT NULL UNIQUE, " + - "age INTEGER, " + - "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + - "manager_id INTEGER, " + - "FOREIGN KEY(manager_id) REFERENCES users(id)" + - ")" -) -/*: -The `execute` function can run multiple SQL statements at once as a convenience and will throw an assertion failure if an error occurs during execution. This is useful for seeding and migrating databases with well-tested statements that are guaranteed to succeed (or where failure can be graceful and silent). - -It’s generally safer to prepare SQL statements individually. Let’s build a `Statement` object and insert a couple rows. - -*/ -let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") -for (email, admin) in ["alice@acme.com": 1, "betsy@acme.com": 0] { - stmt.run(email, admin) -} -/*: -Prepared statements can bind and escape input values safely. In this case, `email` and `admin` columns are bound with different values over two executions. - -The `Database` class exposes information about recently run queries via several properties: `totalChanges` returns the total number of changes (inserts, updates, and deletes) since the connection was opened; `changes` returns the number of changes from the last statement that modified the database; `lastInsertRowid` returns the rowid of the last insert. -*/ -db.totalChanges -db.changes -db.lastInsertRowid -/*: -## Querying - -`Statement` objects act as both sequences _and_ generators. We can iterate over a select statement’s rows directly using a `for`–`in` loop. -*/ -for row in db.prepare("SELECT id, email FROM users") { - println("id: \(row[0]), email: \(row[1])") -} -/*: -Single, scalar values can be plucked directly from a statement. -*/ -let count = db.prepare("SELECT count(*) FROM users") -count.scalar() +let db = try! Connection() -db.scalar("SELECT email FROM users WHERE id = ?", 1) -/*: -> ### Experiment -> -> Try plucking a single row by taking advantage of the fact that `Statement` conforms to the `GeneratorType` protocol. -> -> Also try using the `Array` initializer to return an array of all rows at once. +db.trace(print) -## Transactions & Savepoints +let users = Table("users") -Using the `transaction` and `savepoint` functions, we can run a series of statements, commiting the changes to the database if they all succeed. If a single statement fails, we bail out early and roll back. In the following example we prepare two statements: one to insert a manager into the database, and one—given a manager’s rowid—to insert a managed user into the database. -*/ -let sr = db.prepare("INSERT INTO users (email, admin) VALUES (?, 1)") -let jr = db.prepare("INSERT INTO users (email, admin, manager_id) VALUES (?, 0, ?)") -/*: -Statements can be chained with other statements using the `&&` and `||` operators. The right-hand side is an auto-closure and therefore has access to database information at the time of execution. In this case, we insert Dolly, a supervisor, and immediately reference her rowid when we insert her assistant, Emery. -*/ -db.transaction() - && sr.run("dolly@acme.com") - && jr.run("emery@acme.com", db.lastInsertRowid) - && db.commit() - || db.rollback() -/*: -Our database has a uniqueness constraint on email address, so let’s see what happens when we insert Fiona, who also claims to be managing Emery. -*/ -let txn = db.transaction() - && sr.run("fiona@acme.com") - && jr.run("emery@acme.com", db.lastInsertRowid) - && db.commit() -txn || db.rollback() - -count.scalar() - -txn.failed -txn.reason -/*: -This time, our transaction fails because Emery has already been added to the database. The addition of Fiona has been rolled back, and we’ll need to get to the bottom of this discrepancy (or make some schematic changes to our database to allow for multiple managers per user). - -> ### Experiment -> -> Transactions can’t be nested, but savepoints can! Try calling the `savepoint` function instead, which shares semantics with `transaction`, but can successfully run in layers. - -## Query Building - -SQLite.swift provides a powerful, type-safe query builder. With only a small amount of boilerplate to map our columns to types, we can ensure the queries we build are valid upon compilation. -*/ let id = Expression("id") let email = Expression("email") -let age = Expression("age") -let admin = Expression("admin") -let managerId = Expression("manager_id") -/*: -The query-building interface is provided via the `Query` struct. We can access this interface by subscripting our database connection with a table name. -*/ -let users = db["users"] -/*: -From here, we can build a variety of queries. For example, we can build and run an `INSERT` statement by calling the query’s `insert` function. Let’s add a few new rows this way. -*/ -users.insert(email <- "giles@acme.com", age <- 42, admin <- true).rowid -users.insert(email <- "haley@acme.com", age <- 30, admin <- true).rowid -users.insert(email <- "inigo@acme.com", age <- 24).rowid -/*: -No room for syntax errors! Try changing an input to the wrong type and see what happens. +let name = Expression("name") -The `insert` function can return a `rowid` (which will be `nil` in the case of failure) and the just-run `statement`. It can also return a `Statement` object directly, making it easy to run in a transaction. -*/ -db.transaction() - && users.insert(email <- "julie@acme.com") - && users.insert(email <- "kelly@acme.com", managerId <- db.lastInsertRowid) - && db.commit() - || db.rollback() -/*: -`Query` objects can also build `SELECT` statements. A freshly-subscripted query will select every row (and every column) from a table. Iteration lazily executes the statement. -*/ -// SELECT * FROM users -for user in users { - println(user[email]) -} -/*: -You may notice that iteration works a little differently here. Rather than arrays of raw values, we are given `Row` objects, which can be subscripted with the same expressions we prepared above. This gives us a little more powerful of a mapping to work with and pass around. +try! db.run(users.create { t in + t.column(id, primaryKey: true) + t.column(email, unique: true, check: email.like("%@%")) + t.column(name) +}) -Queries can be used and reused, and can quickly return rows, counts and other aggregate values. -*/ -// SELECT * FROM users LIMIT 1 -users.first +let rowid = try! db.run(users.insert(email <- "alice@mac.com")) +let alice = users.filter(id == rowid) -// SELECT count(*) FROM users -users.count - -users.min(age) -users.max(age) -users.average(age) -/*: -> ### Experiment -> -> In addition to `first`, you can also try plucking the `last` row from the result set in an optimized fashion. -> -> The example above uses the computed variable `count`, but `Query` has a `count` function, as well. (The computed variable is actually a convenience wrapper around `count(*)`.) Try counting the distinct ages in our group of users. -> -> Try calling the `sum` and `total` functions. Note the differences! - -Queries can be refined using a collection of chainable helper functions. Let’s filter our query to the administrator subset. -*/ -let admins = users.filter(admin) -/*: -Filtered queries will in turn filter their aggregate functions. -*/ -// SELECT count(*) FROM users WHERE admin -admins.count -/*: -Alongside `filter`, we can use the `select`, `join`, `group`, `order`, and `limit` functions to compose rich queries with safety and ease. Let’s say we want to order our results by email, then age, and return no more than three rows. -*/ -let ordered = admins.order(email.asc, age.asc).limit(3) - -// SELECT * FROM users WHERE admin ORDER BY email ASC, age ASC LIMIT 3 -for admin in ordered { - println(admin[id]) - println(admin[age]) +for user in db.prepare(users) { + print("id: \(user[id]), email: \(user[email])") } -/*: -> ### Experiment -> -> Try using the `select` function to specify which columns are returned. -> -> Try using the `group` function to group users by a column. -> -> Try to return results by a column in descending order. -> -> Try using an alternative `limit` function to add an `OFFSET` clause to the query. -We can further filter by chaining additional conditions onto the query. Let’s find administrators that haven’t (yet) provided their ages. -*/ -let agelessAdmins = admins.filter(age == nil) +let emails = VirtualTable("emails") -// SELECT count(*) FROM users WHERE (admin AND age IS NULL) -agelessAdmins.count -/*: -Unfortunately, the HR department has ruled that age disclosure is required for administrator responsibilities. We can use our query’s `update` interface to (temporarily) revoke their privileges while we wait for them to update their profiles. -*/ -// UPDATE users SET admin = 0 WHERE (admin AND age IS NULL) -agelessAdmins.update(admin <- false).changes -/*: -If we ever need to remove rows from our database, we can use the `delete` function, which will be scoped to a query’s filters. **Be careful!** You may just want to archive the records, instead. +let subject = Expression("subject") +let body = Expression("body") -We don’t archive user data at Acme Inc. (we respect privacy, after all), and unfortunately, Alice has decided to move on. We can carefully, _carefully_ scope a query to match her and delete her record. -*/ -// DELETE FROM users WHERE (email = 'alice@acme.com') -users.filter(email == "alice@acme.com").delete().changes -/*: -And that’s that. +try! db.run(emails.create(.FTS4(subject, body))) -## & More… +try! db.run(emails.insert( + subject <- "Hello, world!", + body <- "This is a hello world message." +)) -We’ve only explored the surface to SQLite.swift. Dive into the code to discover more! -*/ +let row = db.pluck(emails.match("hello")) + +let query = db.prepare(emails.match("hello")) +for row in query { + print(row[subject]) +} diff --git a/SQLite.playground/contents.xcplayground b/SQLite.playground/contents.xcplayground index 3de2b51b..59b4af3e 100644 --- a/SQLite.playground/contents.xcplayground +++ b/SQLite.playground/contents.xcplayground @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/SQLite.swift.podspec b/SQLite.swift.podspec index 5aad0d24..f2f43fbe 100644 --- a/SQLite.swift.podspec +++ b/SQLite.swift.podspec @@ -1,48 +1,16 @@ -Pod::Spec.new do |s| - s.name = 'SQLite.swift' - s.module_name = 'SQLite' - s.version = '0.1.0.pre' - s.summary = 'A type-safe, Swift-language layer over SQLite3.' +require_relative 'Supporting Files/podspec.rb' - s.description = <<-DESC +Pod::Spec.new do |spec| + spec.name = 'SQLite.swift' + spec.summary = 'A type-safe, Swift-language layer over SQLite3.' + + spec.description = <<-DESC SQLite.swift provides compile-time confidence in SQL statement syntax and intent. DESC - s.homepage = 'https://github.com/stephencelis/SQLite.swift' - s.license = { type: 'MIT', file: 'LICENSE.txt' } - - s.author = { 'Stephen Celis' => 'stephen@stephencelis.com' } - s.social_media_url = 'https://twitter.com/stephencelis' - - s.source = { - git: 'https://github.com/stephencelis/SQLite.swift.git', - tag: s.version - } - - s.module_map = 'SQLite/module.modulemap' - - s.default_subspec = 'Library' - - s.subspec 'Core' do |ss| - ss.source_files = 'SQLite/**/*.{swift,c,h,m}' - ss.private_header_files = 'SQLite/fts3_tokenizer.h' - end - - s.subspec 'Library' do |ss| - ss.dependency 'SQLite.swift/Core' - - ss.library = 'sqlite3' - end - - s.subspec 'Cipher' do |ss| - ss.dependency 'SQLCipher' - ss.dependency 'SQLite.swift/Core' - - ss.source_files = 'SQLiteCipher/**/*.{swift,c,h,m}' + apply_shared_config spec, 'SQLite' - ss.xcconfig = { - 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' - } - end + spec.exclude_files = 'Source/Cipher/Cipher.swift' + spec.library = 'sqlite3' end diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj index dabac2e8..cc0edaaf 100644 --- a/SQLite.xcodeproj/project.pbxproj +++ b/SQLite.xcodeproj/project.pbxproj @@ -3,474 +3,794 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 47; objects = { /* Begin PBXBuildFile section */ - 8E7D32B61B8FB67C003C1892 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E7D32B51B8FB67C003C1892 /* ValueTests.swift */; }; - DC109CE11A0C4D970070988E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE01A0C4D970070988E /* Schema.swift */; }; - DC109CE41A0C4F5D0070988E /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE31A0C4F5D0070988E /* SchemaTests.swift */; }; - DC2393C81ABE35F8003FF113 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC2393C91ABE35F8003FF113 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC2393CA1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */; }; - DC2393CB1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */; }; - DC2DD6B01ABE437500C2C71A /* libsqlcipher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */; }; - DC2DD6B31ABE439A00C2C71A /* libsqlcipher.a in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */; }; - DC2F675E1AEA7CD600151C15 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2F675D1AEA7CD600151C15 /* fts3_tokenizer.h */; }; - DC2F675F1AEA7CD600151C15 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC2F675D1AEA7CD600151C15 /* fts3_tokenizer.h */; }; - DC3773F919C8CBB3004FCF85 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DC3773FF19C8CBB3004FCF85 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC3773F319C8CBB3004FCF85 /* SQLite.framework */; }; - DC37743519C8D626004FCF85 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743419C8D626004FCF85 /* Database.swift */; }; - DC37743819C8D693004FCF85 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743719C8D693004FCF85 /* Value.swift */; }; - DC37743B19C8D6C0004FCF85 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743A19C8D6C0004FCF85 /* Statement.swift */; }; - DC475EA219F219AF00788FBD /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC475E9E19F2199900788FBD /* ExpressionTests.swift */; }; - DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC650B9519F0CDC3002FBE91 /* Expression.swift */; }; - DC70AC991AC2331000371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; - DC70AC9A1AC2331100371524 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; }; - DCAD429719E2E0F1004A51DF /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; }; - DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429919E2EE50004A51DF /* QueryTests.swift */; }; - DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; - DCAFEAD41AABC818000C21A1 /* FTS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD21AABC818000C21A1 /* FTS.swift */; }; - DCAFEAD71AABEFA7000C21A1 /* FTSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */; }; - DCBE28411ABDF18F0042A3FC /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBE28401ABDF18F0042A3FC /* RTree.swift */; }; - DCBE28421ABDF18F0042A3FC /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBE28401ABDF18F0042A3FC /* RTree.swift */; }; - DCBE28451ABDF2A80042A3FC /* RTreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */; }; - DCC6B36F1A9191C300734B78 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; }; - DCC6B3701A9191C300734B78 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743A19C8D6C0004FCF85 /* Statement.swift */; }; - DCC6B3711A9191C300734B78 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC650B9519F0CDC3002FBE91 /* Expression.swift */; }; - DCC6B3721A9191C300734B78 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743419C8D626004FCF85 /* Database.swift */; }; - DCC6B3731A9191C300734B78 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743719C8D693004FCF85 /* Value.swift */; }; - DCC6B3741A9191C300734B78 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC109CE01A0C4D970070988E /* Schema.swift */; }; - DCC6B3791A9191C300734B78 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DC3773F819C8CBB3004FCF85 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; - DCC6B3A41A9194A800734B78 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC6B3A31A9194A800734B78 /* Cipher.swift */; }; - DCC6B3A61A9194FB00734B78 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCC6B3A51A9194FB00734B78 /* CipherTests.swift */; }; - DCC6B3A71A91974B00734B78 /* FunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3F17121A814F7000C83A2F /* FunctionsTests.swift */; }; - DCC6B3A81A91975700734B78 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3F170F1A8127A300C83A2F /* Functions.swift */; }; - DCC6B3A91A91975C00734B78 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC3F170F1A8127A300C83A2F /* Functions.swift */; }; - DCC6B3AC1A91979F00734B78 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCC6B3801A9191C300734B78 /* SQLite.framework */; }; - DCC6B3AD1A9197B500734B78 /* TestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF37F8719DDAF79001534AA /* TestHelper.swift */; }; - DCF37F8219DDAC2D001534AA /* DatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF37F8119DDAC2D001534AA /* DatabaseTests.swift */; }; - DCF37F8519DDAF3F001534AA /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF37F8419DDAF3F001534AA /* StatementTests.swift */; }; - DCF37F8819DDAF79001534AA /* TestHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF37F8719DDAF79001534AA /* TestHelper.swift */; }; + DC0B8BAB1B485B620084D886 /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */; }; + DC0B8BAD1B497A400084D886 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAC1B497A400084D886 /* Query.swift */; }; + DC2609C01B47504200C3E6B5 /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */; }; + DC2609C21B475C9900C3E6B5 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C11B475C9900C3E6B5 /* Operators.swift */; }; + DC2609C41B475CA500C3E6B5 /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */; }; + DC2B29ED1B26F718001C60EA /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC2B29E21B26F718001C60EA /* SQLite.framework */; }; + DC2B2A161B26FE7F001C60EA /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A151B26FE7F001C60EA /* Connection.swift */; }; + DC2D38BE1B2C7A73003EE2F2 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */; }; + DC303D6E1B2CFA1F00469AEA /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6D1B2CFA1F00469AEA /* Statement.swift */; }; + DC303D701B2CFA4700469AEA /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6F1B2CFA4700469AEA /* Value.swift */; }; + DC303D731B2E2ECD00469AEA /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D711B2E2E8600469AEA /* ConnectionTests.swift */; }; + DC303D761B2E306000469AEA /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D751B2E306000469AEA /* TestHelpers.swift */; }; + DC32A7861B575FD7002FFBE2 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7851B575FD7002FFBE2 /* FTS4.swift */; }; + DC32A7871B575FD7002FFBE2 /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7851B575FD7002FFBE2 /* FTS4.swift */; }; + DC32A7961B576715002FFBE2 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA7B9E71B4D4F30006A9F04 /* Schema.swift */; }; + DC32A7971B57672E002FFBE2 /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A151B26FE7F001C60EA /* Connection.swift */; }; + DC32A7981B57672E002FFBE2 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6D1B2CFA1F00469AEA /* Statement.swift */; }; + DC32A7991B57672E002FFBE2 /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6F1B2CFA4700469AEA /* Value.swift */; }; + DC32A79A1B57672E002FFBE2 /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC32A79B1B57672E002FFBE2 /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */; }; + DC32A79C1B57672E002FFBE2 /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */; }; + DC32A79D1B57672E002FFBE2 /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */; }; + DC32A79E1B57672E002FFBE2 /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAC1B497A400084D886 /* Query.swift */; }; + DC32A79F1B57672E002FFBE2 /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDF55B21B49ACBC00E1A168 /* Collation.swift */; }; + DC32A7A01B57672E002FFBE2 /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C11B475C9900C3E6B5 /* Operators.swift */; }; + DC32A7A11B57672E002FFBE2 /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */; }; + DC32A7A21B57672E002FFBE2 /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */; }; + DC32A7A31B57672E002FFBE2 /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */; }; + DC32A7A41B57672E002FFBE2 /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72349D1B49D3AB007F513E /* Setter.swift */; }; + DC32A7A51B57672E002FFBE2 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA22851B4A9CB2005E00A2 /* Helpers.swift */; }; + DC32A7A91B5AAE75002FFBE2 /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */; }; + DC32A7AA1B5AAE76002FFBE2 /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */; }; + DC32A7AE1B5AC2C5002FFBE2 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A78D1B576209002FFBE2 /* StatementTests.swift */; }; + DC32A7AF1B5AC2C5002FFBE2 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7901B576219002FFBE2 /* ValueTests.swift */; }; + DC32A7B01B5AC2C5002FFBE2 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7931B57622C002FFBE2 /* BlobTests.swift */; }; + DC32A7B11B5AC2C5002FFBE2 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A021B26FD44001C60EA /* ExpressionTests.swift */; }; + DC32A7B21B5AC2C5002FFBE2 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2763B81B4A012A0010A7E9 /* QueryTests.swift */; }; + DC32A7B31B5AC2C5002FFBE2 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A91B53216000B12E66 /* SchemaTests.swift */; }; + DC32A7B41B5AC2C5002FFBE2 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BB11B4987FC0084D886 /* OperatorsTests.swift */; }; + DC32A7B51B5AC2C5002FFBE2 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAE1B4982730084D886 /* AggregateFunctionsTests.swift */; }; + DC32A7B61B5AC2C5002FFBE2 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0F75551B49918900E459AF /* CoreFunctionsTests.swift */; }; + DC32A7B71B5AC2C5002FFBE2 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2763B61B49DCE10010A7E9 /* SetterTests.swift */; }; + DC32A7B81B5AC2C5002FFBE2 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7881B575FE9002FFBE2 /* FTS4Tests.swift */; }; + DC32A7B91B5AC2C5002FFBE2 /* R*TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7AB1B5AB18C002FFBE2 /* R*TreeTests.swift */; }; + DC32A7BA1B5C1769002FFBE2 /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A78D1B576209002FFBE2 /* StatementTests.swift */; }; + DC32A7BB1B5C1769002FFBE2 /* ValueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7901B576219002FFBE2 /* ValueTests.swift */; }; + DC32A7BC1B5C1769002FFBE2 /* BlobTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7931B57622C002FFBE2 /* BlobTests.swift */; }; + DC32A7BD1B5C1769002FFBE2 /* ExpressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A021B26FD44001C60EA /* ExpressionTests.swift */; }; + DC32A7BE1B5C1769002FFBE2 /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2763B81B4A012A0010A7E9 /* QueryTests.swift */; }; + DC32A7BF1B5C1769002FFBE2 /* SchemaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A91B53216000B12E66 /* SchemaTests.swift */; }; + DC32A7C01B5C1769002FFBE2 /* OperatorsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BB11B4987FC0084D886 /* OperatorsTests.swift */; }; + DC32A7C11B5C1769002FFBE2 /* AggregateFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAE1B4982730084D886 /* AggregateFunctionsTests.swift */; }; + DC32A7C21B5C1769002FFBE2 /* CoreFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0F75551B49918900E459AF /* CoreFunctionsTests.swift */; }; + DC32A7C31B5C1769002FFBE2 /* SetterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2763B61B49DCE10010A7E9 /* SetterTests.swift */; }; + DC32A7C41B5C1769002FFBE2 /* FTS4Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7881B575FE9002FFBE2 /* FTS4Tests.swift */; }; + DC32A7C51B5C1769002FFBE2 /* R*TreeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7AB1B5AB18C002FFBE2 /* R*TreeTests.swift */; }; + DC32A7C91B5C9814002FFBE2 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7C81B5C9814002FFBE2 /* CustomFunctionsTests.swift */; }; + DC32A7CA1B5C9814002FFBE2 /* CustomFunctionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7C81B5C9814002FFBE2 /* CustomFunctionsTests.swift */; }; + DC461BB81B2E687900E9DABD /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC461BB91B2E687900E9DABD /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */; }; + DC461BBB1B2E68BC00E9DABD /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */; }; + DC47D0661B52C9D200B12E66 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0651B52C9D200B12E66 /* Blob.swift */; }; + DC47D0671B52C9D200B12E66 /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0651B52C9D200B12E66 /* Blob.swift */; }; + DC47D0A61B52F5B900B12E66 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A51B52F5B900B12E66 /* Foundation.swift */; }; + DC47D0A71B52F5B900B12E66 /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A51B52F5B900B12E66 /* Foundation.swift */; }; + DC6C03541B74451F00EFC04E /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7BB721B7434B70040D364 /* Cipher.swift */; }; + DC6C03551B74453300EFC04E /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A151B26FE7F001C60EA /* Connection.swift */; }; + DC6C03561B74453300EFC04E /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6D1B2CFA1F00469AEA /* Statement.swift */; }; + DC6C03571B74453300EFC04E /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6F1B2CFA4700469AEA /* Value.swift */; }; + DC6C03581B74453300EFC04E /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0651B52C9D200B12E66 /* Blob.swift */; }; + DC6C03591B74454100EFC04E /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */; }; + DC6C035A1B74454100EFC04E /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAC1B497A400084D886 /* Query.swift */; }; + DC6C035B1B74454100EFC04E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA7B9E71B4D4F30006A9F04 /* Schema.swift */; }; + DC6C035C1B74454100EFC04E /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDF55B21B49ACBC00E1A168 /* Collation.swift */; }; + DC6C035D1B74454100EFC04E /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C11B475C9900C3E6B5 /* Operators.swift */; }; + DC6C035E1B74454100EFC04E /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */; }; + DC6C035F1B74454100EFC04E /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */; }; + DC6C03601B74454100EFC04E /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */; }; + DC6C03611B74454100EFC04E /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72349D1B49D3AB007F513E /* Setter.swift */; }; + DC6C03621B74454100EFC04E /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7851B575FD7002FFBE2 /* FTS4.swift */; }; + DC6C03631B74454100EFC04E /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */; }; + DC6C03641B74454100EFC04E /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A51B52F5B900B12E66 /* Foundation.swift */; }; + DC6C03651B74454100EFC04E /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA22851B4A9CB2005E00A2 /* Helpers.swift */; }; + DC6C03661B74454D00EFC04E /* Connection.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2B2A151B26FE7F001C60EA /* Connection.swift */; }; + DC6C03671B74454D00EFC04E /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6D1B2CFA1F00469AEA /* Statement.swift */; }; + DC6C03681B74454D00EFC04E /* Value.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D6F1B2CFA4700469AEA /* Value.swift */; }; + DC6C03691B74454D00EFC04E /* Blob.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0651B52C9D200B12E66 /* Blob.swift */; }; + DC6C036A1B74454D00EFC04E /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */; }; + DC6C036B1B74454D00EFC04E /* Expression.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */; }; + DC6C036C1B74454D00EFC04E /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAC1B497A400084D886 /* Query.swift */; }; + DC6C036D1B74454D00EFC04E /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA7B9E71B4D4F30006A9F04 /* Schema.swift */; }; + DC6C036E1B74454D00EFC04E /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDF55B21B49ACBC00E1A168 /* Collation.swift */; }; + DC6C036F1B74454D00EFC04E /* Operators.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C11B475C9900C3E6B5 /* Operators.swift */; }; + DC6C03701B74454D00EFC04E /* AggregateFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */; }; + DC6C03711B74454D00EFC04E /* CoreFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */; }; + DC6C03721B74454D00EFC04E /* CustomFunctions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */; }; + DC6C03731B74454D00EFC04E /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72349D1B49D3AB007F513E /* Setter.swift */; }; + DC6C03741B74454D00EFC04E /* FTS4.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7851B575FD7002FFBE2 /* FTS4.swift */; }; + DC6C03751B74454D00EFC04E /* R*Tree.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */; }; + DC6C03761B74454D00EFC04E /* Foundation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC47D0A51B52F5B900B12E66 /* Foundation.swift */; }; + DC6C03771B74454D00EFC04E /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA22851B4A9CB2005E00A2 /* Helpers.swift */; }; + DC6C03791B74459C00EFC04E /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC6C037A1B74459F00EFC04E /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7489A31B69A66C0032CF28 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC6C037B1B7445A100EFC04E /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */; }; + DC6C037C1B7445AC00EFC04E /* SQLite-Bridging.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC6C037D1B7445B200EFC04E /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7489A31B69A66C0032CF28 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC6C037E1B7445B400EFC04E /* fts3_tokenizer.h in Headers */ = {isa = PBXBuildFile; fileRef = DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */; }; + DC6C037F1B74461300EFC04E /* SQLite-Bridging.m in Sources */ = {isa = PBXBuildFile; fileRef = DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */; }; + DC72349E1B49D3AB007F513E /* Setter.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC72349D1B49D3AB007F513E /* Setter.swift */; }; + DC7253B61B52ADEC009B38B1 /* SQLite.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC7253AC1B52ADEB009B38B1 /* SQLite.framework */; }; + DC7489A41B69A66C0032CF28 /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7489A31B69A66C0032CF28 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC7489A51B69A66C0032CF28 /* sqlite3.h in Headers */ = {isa = PBXBuildFile; fileRef = DC7489A31B69A66C0032CF28 /* sqlite3.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DC795C561B55AC8100DA0EF9 /* TestHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D751B2E306000469AEA /* TestHelpers.swift */; }; + DC795C571B55AC8100DA0EF9 /* ConnectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC303D711B2E2E8600469AEA /* ConnectionTests.swift */; }; + DCA7B9E81B4D4F30006A9F04 /* Schema.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCA7B9E71B4D4F30006A9F04 /* Schema.swift */; }; + DCAA22861B4A9CB2005E00A2 /* Helpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAA22851B4A9CB2005E00A2 /* Helpers.swift */; }; + DCDF55B31B49ACBC00E1A168 /* Collation.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCDF55B21B49ACBC00E1A168 /* Collation.swift */; }; + DCE7BB841B7434F90040D364 /* SQLiteCipher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE7BB7A1B7434F90040D364 /* SQLiteCipher.framework */; }; + DCE7BB911B74350E0040D364 /* Cipher.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7BB721B7434B70040D364 /* Cipher.swift */; }; + DCE7BB9C1B7435E90040D364 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE7BB971B7435E90040D364 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE7BB9D1B7435E90040D364 /* SQLite.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE7BB971B7435E90040D364 /* SQLite.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE7BBA21B7436140040D364 /* SQLiteCipher.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE7BBA01B7436140040D364 /* SQLiteCipher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE7BBBA1B7438620040D364 /* SQLiteCipher.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE7BBB01B7438610040D364 /* SQLiteCipher.framework */; }; + DCE7BBC71B7438740040D364 /* SQLiteCipher.h in Headers */ = {isa = PBXBuildFile; fileRef = DCE7BBA01B7436140040D364 /* SQLiteCipher.h */; settings = {ATTRIBUTES = (Public, ); }; }; + DCE7BBC91B743A320040D364 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7BBC81B743A320040D364 /* CipherTests.swift */; }; + DCE7BBCA1B743A320040D364 /* CipherTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCE7BBC81B743A320040D364 /* CipherTests.swift */; }; + DCE7BBD41B743C970040D364 /* libsqlcipher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE7BB711B7434310040D364 /* libsqlcipher.a */; }; + DCE7BBD51B743C9C0040D364 /* libsqlcipher.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DCE7BB711B7434310040D364 /* libsqlcipher.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - DC2DD6AB1ABE428E00C2C71A /* PBXContainerItemProxy */ = { + DC2B29EE1B26F718001C60EA /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */; + containerPortal = DC2B29D91B26F718001C60EA /* Project object */; + proxyType = 1; + remoteGlobalIDString = DC2B29E11B26F718001C60EA; + remoteInfo = SQLite; + }; + DC7253B71B52ADEC009B38B1 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DC2B29D91B26F718001C60EA /* Project object */; + proxyType = 1; + remoteGlobalIDString = DC7253AB1B52ADEB009B38B1; + remoteInfo = "SQLite Mac"; + }; + DCE7BB701B7434310040D364 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */; proxyType = 2; remoteGlobalIDString = D2AAC046055464E500DB518D; remoteInfo = sqlcipher; }; - DC2DD6B11ABE438900C2C71A /* PBXContainerItemProxy */ = { + DCE7BB851B7434F90040D364 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */; + containerPortal = DC2B29D91B26F718001C60EA /* Project object */; + proxyType = 1; + remoteGlobalIDString = DCE7BB791B7434F90040D364; + remoteInfo = SQLiteCipher; + }; + DCE7BBA51B7436D10040D364 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */; proxyType = 1; remoteGlobalIDString = D2AAC045055464E500DB518D; remoteInfo = sqlcipher; }; - DC37740019C8CBB3004FCF85 /* PBXContainerItemProxy */ = { + DCE7BBBB1B7438620040D364 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = DC3773EA19C8CBB3004FCF85 /* Project object */; + containerPortal = DC2B29D91B26F718001C60EA /* Project object */; proxyType = 1; - remoteGlobalIDString = DC3773F219C8CBB3004FCF85; - remoteInfo = SQLite; + remoteGlobalIDString = DCE7BBAF1B7438610040D364; + remoteInfo = "SQLiteCipher Mac"; }; - DCC6B3AA1A91979100734B78 /* PBXContainerItemProxy */ = { + DCE7BBD01B743C220040D364 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; - containerPortal = DC3773EA19C8CBB3004FCF85 /* Project object */; + containerPortal = DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */; proxyType = 1; - remoteGlobalIDString = DCC6B36D1A9191C300734B78; - remoteInfo = SQLiteCipher; + remoteGlobalIDString = D2AAC045055464E500DB518D; + remoteInfo = sqlcipher; }; /* End PBXContainerItemProxy section */ -/* Begin PBXCopyFilesBuildPhase section */ - DC2393D21ABE37C4003FF113 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; +/* Begin PBXFileReference section */ + DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctions.swift; sourceTree = ""; }; + DC0B8BAC1B497A400084D886 /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; }; + DC0B8BAE1B4982730084D886 /* AggregateFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunctionsTests.swift; sourceTree = ""; }; + DC0B8BB11B4987FC0084D886 /* OperatorsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperatorsTests.swift; sourceTree = ""; }; + DC0F75551B49918900E459AF /* CoreFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctionsTests.swift; sourceTree = ""; }; + DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctions.swift; sourceTree = ""; }; + DC2609C11B475C9900C3E6B5 /* Operators.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Operators.swift; sourceTree = ""; }; + DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreFunctions.swift; sourceTree = ""; }; + DC2763B61B49DCE10010A7E9 /* SetterTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetterTests.swift; sourceTree = ""; }; + DC2763B81B4A012A0010A7E9 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; + DC2B29E21B26F718001C60EA /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DC2B29EC1B26F718001C60EA /* SQLite iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLite iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DC2B2A011B26FD44001C60EA /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DC2B2A021B26FD44001C60EA /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; + DC2B2A151B26FE7F001C60EA /* Connection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Connection.swift; sourceTree = ""; }; + DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Expression.swift; sourceTree = ""; }; + DC303D6D1B2CFA1F00469AEA /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Statement.swift; sourceTree = ""; }; + DC303D6F1B2CFA4700469AEA /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Value.swift; sourceTree = ""; }; + DC303D711B2E2E8600469AEA /* ConnectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectionTests.swift; sourceTree = ""; }; + DC303D751B2E306000469AEA /* TestHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelpers.swift; sourceTree = ""; }; + DC32A7851B575FD7002FFBE2 /* FTS4.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4.swift; sourceTree = ""; }; + DC32A7881B575FE9002FFBE2 /* FTS4Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS4Tests.swift; sourceTree = ""; }; + DC32A78D1B576209002FFBE2 /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatementTests.swift; sourceTree = ""; }; + DC32A7901B576219002FFBE2 /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; + DC32A7931B57622C002FFBE2 /* BlobTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlobTests.swift; sourceTree = ""; }; + DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*Tree.swift"; sourceTree = ""; }; + DC32A7AB1B5AB18C002FFBE2 /* R*TreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "R*TreeTests.swift"; sourceTree = ""; }; + DC32A7C61B5C26A6002FFBE2 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + DC32A7C81B5C9814002FFBE2 /* CustomFunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomFunctionsTests.swift; sourceTree = ""; }; + DC40A4C61B7D2B3C005BA12D /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; + DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; + DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; + DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; + DC47D0651B52C9D200B12E66 /* Blob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Blob.swift; sourceTree = ""; }; + DC47D0A51B52F5B900B12E66 /* Foundation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Foundation.swift; sourceTree = ""; }; + DC47D0A91B53216000B12E66 /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; + DC72349D1B49D3AB007F513E /* Setter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Setter.swift; sourceTree = ""; }; + DC7253AC1B52ADEB009B38B1 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DC7253B51B52ADEC009B38B1 /* SQLite Mac Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLite Mac Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DC7489A31B69A66C0032CF28 /* sqlite3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sqlite3.h; path = usr/include/sqlite3.h; sourceTree = SDKROOT; }; + DCA7B9E71B4D4F30006A9F04 /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Schema.swift; sourceTree = ""; }; + DCAA22851B4A9CB2005E00A2 /* Helpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Helpers.swift; sourceTree = ""; }; + DCDF55B21B49ACBC00E1A168 /* Collation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Collation.swift; sourceTree = ""; }; + DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sqlcipher.xcodeproj; path = Vendor/sqlcipher/sqlcipher.xcodeproj; sourceTree = ""; }; + DCE7BB721B7434B70040D364 /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; + DCE7BB7A1B7434F90040D364 /* SQLiteCipher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLiteCipher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DCE7BB831B7434F90040D364 /* SQLiteCipher iOS Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteCipher iOS Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DCE7BB951B7435E90040D364 /* SQLite.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SQLite.xcconfig; sourceTree = ""; }; + DCE7BB961B7435E90040D364 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DCE7BB971B7435E90040D364 /* SQLite.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQLite.h; sourceTree = ""; }; + DCE7BB9F1B7436140040D364 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + DCE7BBA01B7436140040D364 /* SQLiteCipher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQLiteCipher.h; sourceTree = ""; }; + DCE7BBA91B7437780040D364 /* Base.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Base.xcconfig; sourceTree = ""; }; + DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = SQLiteCipher.xcconfig; sourceTree = ""; }; + DCE7BBB01B7438610040D364 /* SQLiteCipher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLiteCipher.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + DCE7BBB91B7438620040D364 /* SQLiteCipher Mac Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteCipher Mac Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + DCE7BBC81B743A320040D364 /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; + DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Test.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + DC2B29DE1B26F718001C60EA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; files = ( - DC2DD6B31ABE439A00C2C71A /* libsqlcipher.a in Embed Frameworks */, ); - name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; }; -/* End PBXCopyFilesBuildPhase section */ - -/* Begin PBXFileReference section */ - 8E7D32B51B8FB67C003C1892 /* ValueTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ValueTests.swift; sourceTree = ""; }; - DC109CE01A0C4D970070988E /* Schema.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Schema.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DC109CE31A0C4F5D0070988E /* SchemaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SchemaTests.swift; sourceTree = ""; }; - DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "SQLite-Bridging.h"; sourceTree = ""; }; - DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "SQLite-Bridging.m"; sourceTree = ""; }; - DC2F675D1AEA7CD600151C15 /* fts3_tokenizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fts3_tokenizer.h; sourceTree = ""; }; - DC3773F319C8CBB3004FCF85 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - DC3773F719C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../SQLite/Info.plist; sourceTree = ""; }; - DC3773F819C8CBB3004FCF85 /* SQLite.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SQLite.h; path = ../SQLite/SQLite.h; sourceTree = ""; }; - DC3773FE19C8CBB3004FCF85 /* SQLite Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLite Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - DC37740419C8CBB3004FCF85 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = SQLite.xcconfig; sourceTree = ""; }; - DC37743419C8D626004FCF85 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Database.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DC37743719C8D693004FCF85 /* Value.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Value.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DC37743A19C8D6C0004FCF85 /* Statement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Statement.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DC37744219C8DC91004FCF85 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = usr/lib/libsqlite3.dylib; sourceTree = SDKROOT; }; - DC37744719C8F50B004FCF85 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; - DC3F170F1A8127A300C83A2F /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = ""; }; - DC3F17121A814F7000C83A2F /* FunctionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FunctionsTests.swift; sourceTree = ""; }; - DC475E9E19F2199900788FBD /* ExpressionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExpressionTests.swift; sourceTree = ""; }; - DC650B9519F0CDC3002FBE91 /* Expression.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Expression.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DC74B0421B095575007E1138 /* module.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = module.modulemap; sourceTree = ""; }; - DC97EC9F1ADE955E00F550A6 /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; }; - DCAD429619E2E0F1004A51DF /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Query.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; - DCAFEAD21AABC818000C21A1 /* FTS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTS.swift; sourceTree = ""; }; - DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FTSTests.swift; sourceTree = ""; }; - DCBE28401ABDF18F0042A3FC /* RTree.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = ""; }; - DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RTreeTests.swift; sourceTree = ""; }; - DCC6B3801A9191C300734B78 /* SQLite.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SQLite.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - DCC6B3921A9191D100734B78 /* SQLiteCipher Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SQLiteCipher Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; - DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = sqlcipher.xcodeproj; path = Vendor/sqlcipher/sqlcipher.xcodeproj; sourceTree = ""; }; - DCC6B3A31A9194A800734B78 /* Cipher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Cipher.swift; sourceTree = ""; }; - DCC6B3A51A9194FB00734B78 /* CipherTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CipherTests.swift; sourceTree = ""; }; - DCF37F8119DDAC2D001534AA /* DatabaseTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DatabaseTests.swift; sourceTree = ""; }; - DCF37F8419DDAF3F001534AA /* StatementTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = StatementTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; - DCF37F8719DDAF79001534AA /* TestHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelper.swift; sourceTree = ""; }; -/* End PBXFileReference section */ - -/* Begin PBXFrameworksBuildPhase section */ - DC3773EF19C8CBB3004FCF85 /* Frameworks */ = { + DC2B29E91B26F718001C60EA /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DC2B29ED1B26F718001C60EA /* SQLite.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC7253A81B52ADEB009B38B1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC7253B21B52ADEC009B38B1 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC70AC991AC2331000371524 /* libsqlite3.dylib in Frameworks */, + DC7253B61B52ADEC009B38B1 /* SQLite.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - DC3773FB19C8CBB3004FCF85 /* Frameworks */ = { + DCE7BB761B7434F90040D364 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC3773FF19C8CBB3004FCF85 /* SQLite.framework in Frameworks */, + DCE7BBD41B743C970040D364 /* libsqlcipher.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B3761A9191C300734B78 /* Frameworks */ = { + DCE7BB801B7434F90040D364 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DC2DD6B01ABE437500C2C71A /* libsqlcipher.a in Frameworks */, - DC70AC9A1AC2331100371524 /* libsqlite3.dylib in Frameworks */, + DCE7BB841B7434F90040D364 /* SQLiteCipher.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B38C1A9191D100734B78 /* Frameworks */ = { + DCE7BBAC1B7438610040D364 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - DCC6B3AC1A91979F00734B78 /* SQLite.framework in Frameworks */, + DCE7BBD51B743C9C0040D364 /* libsqlcipher.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BBB61B7438620040D364 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + DCE7BBBA1B7438620040D364 /* SQLiteCipher.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - DC037C0919F0240100959746 /* Query Building */ = { + DC2B29D81B26F718001C60EA = { isa = PBXGroup; children = ( - DC650B9519F0CDC3002FBE91 /* Expression.swift */, - DC3F170F1A8127A300C83A2F /* Functions.swift */, - DCAD429619E2E0F1004A51DF /* Query.swift */, - DC109CE01A0C4D970070988E /* Schema.swift */, - DCAFEAD21AABC818000C21A1 /* FTS.swift */, - DCBE28401ABDF18F0042A3FC /* RTree.swift */, - ); - name = "Query Building"; + DC32A7C61B5C26A6002FFBE2 /* README.md */, + DC40A4C61B7D2B3C005BA12D /* SQLite.playground */, + DC2B29FF1B26FD44001C60EA /* Source */, + DC2B2A001B26FD44001C60EA /* Tests */, + DC2B2A0C1B26FDEF001C60EA /* Supporting Files */, + DC2B29E31B26F718001C60EA /* Products */, + DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */, + ); sourceTree = ""; }; - DC10500F19C904DD00D8CA30 /* SQLite Tests */ = { + DC2B29E31B26F718001C60EA /* Products */ = { isa = PBXGroup; children = ( - DCF37F8719DDAF79001534AA /* TestHelper.swift */, - DCF37F8119DDAC2D001534AA /* DatabaseTests.swift */, - DCF37F8419DDAF3F001534AA /* StatementTests.swift */, - DC475E9E19F2199900788FBD /* ExpressionTests.swift */, - DC3F17121A814F7000C83A2F /* FunctionsTests.swift */, - DCAD429919E2EE50004A51DF /* QueryTests.swift */, - DC109CE31A0C4F5D0070988E /* SchemaTests.swift */, - DCAFEAD61AABEFA7000C21A1 /* FTSTests.swift */, - DCBE28441ABDF2A80042A3FC /* RTreeTests.swift */, - 8E7D32B51B8FB67C003C1892 /* ValueTests.swift */, - DC37740319C8CBB3004FCF85 /* Supporting Files */, - ); - path = "SQLite Tests"; + DC2B29E21B26F718001C60EA /* SQLite.framework */, + DC2B29EC1B26F718001C60EA /* SQLite iOS Tests.xctest */, + DC7253AC1B52ADEB009B38B1 /* SQLite.framework */, + DC7253B51B52ADEC009B38B1 /* SQLite Mac Tests.xctest */, + DCE7BB7A1B7434F90040D364 /* SQLiteCipher.framework */, + DCE7BB831B7434F90040D364 /* SQLiteCipher iOS Tests.xctest */, + DCE7BBB01B7438610040D364 /* SQLiteCipher.framework */, + DCE7BBB91B7438620040D364 /* SQLiteCipher Mac Tests.xctest */, + ); + name = Products; sourceTree = ""; }; - DC2DD6A71ABE428E00C2C71A /* Products */ = { + DC2B29FF1B26FD44001C60EA /* Source */ = { isa = PBXGroup; children = ( - DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */, + DC2B2A141B26FE5C001C60EA /* Core */, + DC2D38BC1B2C7A39003EE2F2 /* Typed */, + DC2D38BA1B2C7A39003EE2F2 /* Extensions */, + DC2D38B91B2C7A39003EE2F2 /* Cipher */, + DC47D0A51B52F5B900B12E66 /* Foundation.swift */, + DCAA22851B4A9CB2005E00A2 /* Helpers.swift */, ); - name = Products; + path = Source; sourceTree = ""; }; - DC3773E919C8CBB3004FCF85 = { + DC2B2A001B26FD44001C60EA /* Tests */ = { isa = PBXGroup; children = ( - DC37744719C8F50B004FCF85 /* README.md */, - DC97EC9F1ADE955E00F550A6 /* SQLite.playground */, - DC37742D19C8CC90004FCF85 /* SQLite */, - DC10500F19C904DD00D8CA30 /* SQLite Tests */, - DCC6B3A11A91949C00734B78 /* SQLiteCipher */, - DCC6B3A21A91949C00734B78 /* SQLiteCipher Tests */, - DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */, - DC3773F419C8CBB3004FCF85 /* Products */, - ); - indentWidth = 4; + DC2B2A011B26FD44001C60EA /* Info.plist */, + DC303D751B2E306000469AEA /* TestHelpers.swift */, + DC303D711B2E2E8600469AEA /* ConnectionTests.swift */, + DC32A78D1B576209002FFBE2 /* StatementTests.swift */, + DC32A7901B576219002FFBE2 /* ValueTests.swift */, + DC32A7931B57622C002FFBE2 /* BlobTests.swift */, + DC2B2A021B26FD44001C60EA /* ExpressionTests.swift */, + DC2763B81B4A012A0010A7E9 /* QueryTests.swift */, + DC47D0A91B53216000B12E66 /* SchemaTests.swift */, + DC0B8BB11B4987FC0084D886 /* OperatorsTests.swift */, + DC0B8BAE1B4982730084D886 /* AggregateFunctionsTests.swift */, + DC0F75551B49918900E459AF /* CoreFunctionsTests.swift */, + DC32A7C81B5C9814002FFBE2 /* CustomFunctionsTests.swift */, + DC2763B61B49DCE10010A7E9 /* SetterTests.swift */, + DC32A7881B575FE9002FFBE2 /* FTS4Tests.swift */, + DC32A7AB1B5AB18C002FFBE2 /* R*TreeTests.swift */, + DCE7BBC81B743A320040D364 /* CipherTests.swift */, + ); + path = Tests; sourceTree = ""; - tabWidth = 4; - usesTabs = 0; }; - DC3773F419C8CBB3004FCF85 /* Products */ = { + DC2B2A0C1B26FDEF001C60EA /* Supporting Files */ = { isa = PBXGroup; children = ( - DC3773F319C8CBB3004FCF85 /* SQLite.framework */, - DC3773FE19C8CBB3004FCF85 /* SQLite Tests.xctest */, - DCC6B3801A9191C300734B78 /* SQLite.framework */, - DCC6B3921A9191D100734B78 /* SQLiteCipher Tests.xctest */, + DCE7BBA91B7437780040D364 /* Base.xcconfig */, + DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */, + DCE7BB941B7435E90040D364 /* SQLite */, + DCE7BB9E1B7436140040D364 /* SQLiteCipher */, ); - name = Products; + path = "Supporting Files"; sourceTree = ""; }; - DC37740319C8CBB3004FCF85 /* Supporting Files */ = { + DC2B2A141B26FE5C001C60EA /* Core */ = { isa = PBXGroup; children = ( - DC37740419C8CBB3004FCF85 /* Info.plist */, + DC2B2A151B26FE7F001C60EA /* Connection.swift */, + DC303D6D1B2CFA1F00469AEA /* Statement.swift */, + DC303D6F1B2CFA4700469AEA /* Value.swift */, + DC47D0651B52C9D200B12E66 /* Blob.swift */, + DC461BB61B2E687900E9DABD /* SQLite-Bridging.h */, + DC461BB71B2E687900E9DABD /* SQLite-Bridging.m */, + DC7489A31B69A66C0032CF28 /* sqlite3.h */, + DC461BBA1B2E68BC00E9DABD /* fts3_tokenizer.h */, ); - name = "Supporting Files"; - path = "../SQLite Tests"; + path = Core; sourceTree = ""; }; - DC37742D19C8CC90004FCF85 /* SQLite */ = { + DC2D38B91B2C7A39003EE2F2 /* Cipher */ = { isa = PBXGroup; children = ( - DC37743419C8D626004FCF85 /* Database.swift */, - DC37743A19C8D6C0004FCF85 /* Statement.swift */, - DC37743719C8D693004FCF85 /* Value.swift */, - DC037C0919F0240100959746 /* Query Building */, - DC37743319C8CFCE004FCF85 /* Supporting Files */, + DCE7BB721B7434B70040D364 /* Cipher.swift */, ); - path = SQLite; + path = Cipher; sourceTree = ""; }; - DC37743319C8CFCE004FCF85 /* Supporting Files */ = { + DC2D38BA1B2C7A39003EE2F2 /* Extensions */ = { isa = PBXGroup; children = ( - DC3773F819C8CBB3004FCF85 /* SQLite.h */, - DC2393C61ABE35F8003FF113 /* SQLite-Bridging.h */, - DC2393C71ABE35F8003FF113 /* SQLite-Bridging.m */, - DC2F675D1AEA7CD600151C15 /* fts3_tokenizer.h */, - DC3773F719C8CBB3004FCF85 /* Info.plist */, - DC37744219C8DC91004FCF85 /* libsqlite3.dylib */, - DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */, - DC74B0421B095575007E1138 /* module.modulemap */, - ); - name = "Supporting Files"; + DC32A7851B575FD7002FFBE2 /* FTS4.swift */, + DC32A7A61B5AAE70002FFBE2 /* R*Tree.swift */, + ); + path = Extensions; sourceTree = ""; }; - DCC6B3A11A91949C00734B78 /* SQLiteCipher */ = { + DC2D38BC1B2C7A39003EE2F2 /* Typed */ = { isa = PBXGroup; children = ( - DCC6B3A31A9194A800734B78 /* Cipher.swift */, + DC2D38BD1B2C7A73003EE2F2 /* Expression.swift */, + DC0B8BAC1B497A400084D886 /* Query.swift */, + DCA7B9E71B4D4F30006A9F04 /* Schema.swift */, + DCDF55B21B49ACBC00E1A168 /* Collation.swift */, + DC2609C11B475C9900C3E6B5 /* Operators.swift */, + DC0B8BAA1B485B620084D886 /* AggregateFunctions.swift */, + DC2609C31B475CA500C3E6B5 /* CoreFunctions.swift */, + DC2609BF1B47504200C3E6B5 /* CustomFunctions.swift */, + DC72349D1B49D3AB007F513E /* Setter.swift */, ); - path = SQLiteCipher; + path = Typed; sourceTree = ""; }; - DCC6B3A21A91949C00734B78 /* SQLiteCipher Tests */ = { + DCE7BB6C1B7434310040D364 /* Products */ = { isa = PBXGroup; children = ( - DCC6B3A51A9194FB00734B78 /* CipherTests.swift */, + DCE7BB711B7434310040D364 /* libsqlcipher.a */, ); - path = "SQLiteCipher Tests"; + name = Products; + sourceTree = ""; + }; + DCE7BB941B7435E90040D364 /* SQLite */ = { + isa = PBXGroup; + children = ( + DCE7BB971B7435E90040D364 /* SQLite.h */, + DCE7BB961B7435E90040D364 /* Info.plist */, + DCE7BB951B7435E90040D364 /* SQLite.xcconfig */, + ); + path = SQLite; + sourceTree = ""; + }; + DCE7BB9E1B7436140040D364 /* SQLiteCipher */ = { + isa = PBXGroup; + children = ( + DCE7BBA01B7436140040D364 /* SQLiteCipher.h */, + DCE7BB9F1B7436140040D364 /* Info.plist */, + DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */, + ); + path = SQLiteCipher; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ - DC3773F019C8CBB3004FCF85 /* Headers */ = { + DC2B29DF1B26F718001C60EA /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - DC2F675E1AEA7CD600151C15 /* fts3_tokenizer.h in Headers */, - DC3773F919C8CBB3004FCF85 /* SQLite.h in Headers */, - DC2393C81ABE35F8003FF113 /* SQLite-Bridging.h in Headers */, + DCE7BB9C1B7435E90040D364 /* SQLite.h in Headers */, + DC461BB81B2E687900E9DABD /* SQLite-Bridging.h in Headers */, + DC7489A41B69A66C0032CF28 /* sqlite3.h in Headers */, + DC461BBB1B2E68BC00E9DABD /* fts3_tokenizer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B3781A9191C300734B78 /* Headers */ = { + DC7253A91B52ADEB009B38B1 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( - DC2F675F1AEA7CD600151C15 /* fts3_tokenizer.h in Headers */, - DCC6B3791A9191C300734B78 /* SQLite.h in Headers */, - DC2393C91ABE35F8003FF113 /* SQLite-Bridging.h in Headers */, + DCE7BB9D1B7435E90040D364 /* SQLite.h in Headers */, + DC32A79A1B57672E002FFBE2 /* SQLite-Bridging.h in Headers */, + DC32A79C1B57672E002FFBE2 /* fts3_tokenizer.h in Headers */, + DC7489A51B69A66C0032CF28 /* sqlite3.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BB771B7434F90040D364 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + DCE7BBA21B7436140040D364 /* SQLiteCipher.h in Headers */, + DC6C03791B74459C00EFC04E /* SQLite-Bridging.h in Headers */, + DC6C037A1B74459F00EFC04E /* sqlite3.h in Headers */, + DC6C037B1B7445A100EFC04E /* fts3_tokenizer.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BBAD1B7438610040D364 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + DCE7BBC71B7438740040D364 /* SQLiteCipher.h in Headers */, + DC6C037C1B7445AC00EFC04E /* SQLite-Bridging.h in Headers */, + DC6C037D1B7445B200EFC04E /* sqlite3.h in Headers */, + DC6C037E1B7445B400EFC04E /* fts3_tokenizer.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ - DC3773F219C8CBB3004FCF85 /* SQLite */ = { + DC2B29E11B26F718001C60EA /* SQLite iOS */ = { isa = PBXNativeTarget; - buildConfigurationList = DC37740919C8CBB3004FCF85 /* Build configuration list for PBXNativeTarget "SQLite" */; + buildConfigurationList = DC2B29F61B26F718001C60EA /* Build configuration list for PBXNativeTarget "SQLite iOS" */; buildPhases = ( - DC3773EE19C8CBB3004FCF85 /* Sources */, - DC3773EF19C8CBB3004FCF85 /* Frameworks */, - DC3773F019C8CBB3004FCF85 /* Headers */, - DC3773F119C8CBB3004FCF85 /* Resources */, + DC2B29DD1B26F718001C60EA /* Sources */, + DC2B29DE1B26F718001C60EA /* Frameworks */, + DC2B29DF1B26F718001C60EA /* Headers */, + DC2B29E01B26F718001C60EA /* Resources */, ); buildRules = ( ); dependencies = ( ); - name = SQLite; + name = "SQLite iOS"; productName = SQLite; - productReference = DC3773F319C8CBB3004FCF85 /* SQLite.framework */; + productReference = DC2B29E21B26F718001C60EA /* SQLite.framework */; productType = "com.apple.product-type.framework"; }; - DC3773FD19C8CBB3004FCF85 /* SQLite Tests */ = { + DC2B29EB1B26F718001C60EA /* SQLite iOS Tests */ = { isa = PBXNativeTarget; - buildConfigurationList = DC37740C19C8CBB3004FCF85 /* Build configuration list for PBXNativeTarget "SQLite Tests" */; + buildConfigurationList = DC2B29F91B26F718001C60EA /* Build configuration list for PBXNativeTarget "SQLite iOS Tests" */; buildPhases = ( - DC3773FA19C8CBB3004FCF85 /* Sources */, - DC3773FB19C8CBB3004FCF85 /* Frameworks */, - DC3773FC19C8CBB3004FCF85 /* Resources */, + DC2B29E81B26F718001C60EA /* Sources */, + DC2B29E91B26F718001C60EA /* Frameworks */, + DC2B29EA1B26F718001C60EA /* Resources */, ); buildRules = ( ); dependencies = ( - DC37740119C8CBB3004FCF85 /* PBXTargetDependency */, + DC2B29EF1B26F718001C60EA /* PBXTargetDependency */, ); - name = "SQLite Tests"; - productName = "SQLite Tests"; - productReference = DC3773FE19C8CBB3004FCF85 /* SQLite Tests.xctest */; + name = "SQLite iOS Tests"; + productName = SQLiteTests; + productReference = DC2B29EC1B26F718001C60EA /* SQLite iOS Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; - DCC6B36D1A9191C300734B78 /* SQLiteCipher */ = { + DC7253AB1B52ADEB009B38B1 /* SQLite Mac */ = { isa = PBXNativeTarget; - buildConfigurationList = DCC6B37D1A9191C300734B78 /* Build configuration list for PBXNativeTarget "SQLiteCipher" */; + buildConfigurationList = DC7253C11B52ADEC009B38B1 /* Build configuration list for PBXNativeTarget "SQLite Mac" */; buildPhases = ( - DCC6B36E1A9191C300734B78 /* Sources */, - DCC6B3761A9191C300734B78 /* Frameworks */, - DCC6B3781A9191C300734B78 /* Headers */, - DCC6B37C1A9191C300734B78 /* Resources */, - DC2393D21ABE37C4003FF113 /* Embed Frameworks */, + DC7253A71B52ADEB009B38B1 /* Sources */, + DC7253A81B52ADEB009B38B1 /* Frameworks */, + DC7253A91B52ADEB009B38B1 /* Headers */, + DC7253AA1B52ADEB009B38B1 /* Resources */, ); buildRules = ( ); dependencies = ( - DC2DD6B21ABE438900C2C71A /* PBXTargetDependency */, ); - name = SQLiteCipher; - productName = SQLite; - productReference = DCC6B3801A9191C300734B78 /* SQLite.framework */; + name = "SQLite Mac"; + productName = "SQLite Mac"; + productReference = DC7253AC1B52ADEB009B38B1 /* SQLite.framework */; productType = "com.apple.product-type.framework"; }; - DCC6B3821A9191D100734B78 /* SQLiteCipher Tests */ = { + DC7253B41B52ADEC009B38B1 /* SQLite Mac Tests */ = { isa = PBXNativeTarget; - buildConfigurationList = DCC6B38F1A9191D100734B78 /* Build configuration list for PBXNativeTarget "SQLiteCipher Tests" */; + buildConfigurationList = DC7253C21B52ADEC009B38B1 /* Build configuration list for PBXNativeTarget "SQLite Mac Tests" */; buildPhases = ( - DCC6B3851A9191D100734B78 /* Sources */, - DCC6B38C1A9191D100734B78 /* Frameworks */, - DCC6B38E1A9191D100734B78 /* Resources */, + DC7253B11B52ADEC009B38B1 /* Sources */, + DC7253B21B52ADEC009B38B1 /* Frameworks */, + DC7253B31B52ADEC009B38B1 /* Resources */, ); buildRules = ( ); dependencies = ( - DCC6B3AB1A91979100734B78 /* PBXTargetDependency */, + DC7253B81B52ADEC009B38B1 /* PBXTargetDependency */, ); - name = "SQLiteCipher Tests"; - productName = "SQLite Tests"; - productReference = DCC6B3921A9191D100734B78 /* SQLiteCipher Tests.xctest */; + name = "SQLite Mac Tests"; + productName = "SQLite MacTests"; + productReference = DC7253B51B52ADEC009B38B1 /* SQLite Mac Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + DCE7BB791B7434F90040D364 /* SQLiteCipher iOS */ = { + isa = PBXNativeTarget; + buildConfigurationList = DCE7BB8B1B7434F90040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher iOS" */; + buildPhases = ( + DCE7BB751B7434F90040D364 /* Sources */, + DCE7BB761B7434F90040D364 /* Frameworks */, + DCE7BB771B7434F90040D364 /* Headers */, + DCE7BB781B7434F90040D364 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DCE7BBA61B7436D10040D364 /* PBXTargetDependency */, + ); + name = "SQLiteCipher iOS"; + productName = SQLiteCipher; + productReference = DCE7BB7A1B7434F90040D364 /* SQLiteCipher.framework */; + productType = "com.apple.product-type.framework"; + }; + DCE7BB821B7434F90040D364 /* SQLiteCipher iOS Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = DCE7BB8E1B7434F90040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher iOS Tests" */; + buildPhases = ( + DCE7BB7F1B7434F90040D364 /* Sources */, + DCE7BB801B7434F90040D364 /* Frameworks */, + DCE7BB811B7434F90040D364 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DCE7BB861B7434F90040D364 /* PBXTargetDependency */, + ); + name = "SQLiteCipher iOS Tests"; + productName = SQLiteCipherTests; + productReference = DCE7BB831B7434F90040D364 /* SQLiteCipher iOS Tests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + DCE7BBAF1B7438610040D364 /* SQLiteCipher Mac */ = { + isa = PBXNativeTarget; + buildConfigurationList = DCE7BBC11B7438620040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher Mac" */; + buildPhases = ( + DCE7BBAB1B7438610040D364 /* Sources */, + DCE7BBAC1B7438610040D364 /* Frameworks */, + DCE7BBAD1B7438610040D364 /* Headers */, + DCE7BBAE1B7438610040D364 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DCE7BBD11B743C220040D364 /* PBXTargetDependency */, + ); + name = "SQLiteCipher Mac"; + productName = "SQLiteCipher Mac"; + productReference = DCE7BBB01B7438610040D364 /* SQLiteCipher.framework */; + productType = "com.apple.product-type.framework"; + }; + DCE7BBB81B7438620040D364 /* SQLiteCipher Mac Tests */ = { + isa = PBXNativeTarget; + buildConfigurationList = DCE7BBC41B7438620040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher Mac Tests" */; + buildPhases = ( + DCE7BBB51B7438620040D364 /* Sources */, + DCE7BBB61B7438620040D364 /* Frameworks */, + DCE7BBB71B7438620040D364 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + DCE7BBBC1B7438620040D364 /* PBXTargetDependency */, + ); + name = "SQLiteCipher Mac Tests"; + productName = "SQLiteCipher MacTests"; + productReference = DCE7BBB91B7438620040D364 /* SQLiteCipher Mac Tests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ - DC3773EA19C8CBB3004FCF85 /* Project object */ = { + DC2B29D91B26F718001C60EA /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0640; - ORGANIZATIONNAME = "Stephen Celis"; + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0700; + ORGANIZATIONNAME = stephencelis; TargetAttributes = { - DC3773F219C8CBB3004FCF85 = { - CreatedOnToolsVersion = 6.1; + DC2B29E11B26F718001C60EA = { + CreatedOnToolsVersion = 7.0; + }; + DC2B29EB1B26F718001C60EA = { + CreatedOnToolsVersion = 7.0; + }; + DC7253AB1B52ADEB009B38B1 = { + CreatedOnToolsVersion = 7.0; + }; + DC7253B41B52ADEC009B38B1 = { + CreatedOnToolsVersion = 7.0; + }; + DCE7BB791B7434F90040D364 = { + CreatedOnToolsVersion = 7.0; + }; + DCE7BB821B7434F90040D364 = { + CreatedOnToolsVersion = 7.0; + }; + DCE7BBAF1B7438610040D364 = { + CreatedOnToolsVersion = 7.0; }; - DC3773FD19C8CBB3004FCF85 = { - CreatedOnToolsVersion = 6.1; + DCE7BBB81B7438620040D364 = { + CreatedOnToolsVersion = 7.0; }; }; }; - buildConfigurationList = DC3773ED19C8CBB3004FCF85 /* Build configuration list for PBXProject "SQLite" */; - compatibilityVersion = "Xcode 3.2"; + buildConfigurationList = DC2B29DC1B26F718001C60EA /* Build configuration list for PBXProject "SQLite" */; + compatibilityVersion = "Xcode 6.3"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, ); - mainGroup = DC3773E919C8CBB3004FCF85; - productRefGroup = DC3773F419C8CBB3004FCF85 /* Products */; + mainGroup = DC2B29D81B26F718001C60EA; + productRefGroup = DC2B29E31B26F718001C60EA /* Products */; projectDirPath = ""; projectReferences = ( { - ProductGroup = DC2DD6A71ABE428E00C2C71A /* Products */; - ProjectRef = DCC6B3961A91938F00734B78 /* sqlcipher.xcodeproj */; + ProductGroup = DCE7BB6C1B7434310040D364 /* Products */; + ProjectRef = DCE7BB6B1B7434310040D364 /* sqlcipher.xcodeproj */; }, ); projectRoot = ""; targets = ( - DC3773F219C8CBB3004FCF85 /* SQLite */, - DC3773FD19C8CBB3004FCF85 /* SQLite Tests */, - DCC6B36D1A9191C300734B78 /* SQLiteCipher */, - DCC6B3821A9191D100734B78 /* SQLiteCipher Tests */, + DC2B29E11B26F718001C60EA /* SQLite iOS */, + DC2B29EB1B26F718001C60EA /* SQLite iOS Tests */, + DC7253AB1B52ADEB009B38B1 /* SQLite Mac */, + DC7253B41B52ADEC009B38B1 /* SQLite Mac Tests */, + DCE7BB791B7434F90040D364 /* SQLiteCipher iOS */, + DCE7BB821B7434F90040D364 /* SQLiteCipher iOS Tests */, + DCE7BBAF1B7438610040D364 /* SQLiteCipher Mac */, + DCE7BBB81B7438620040D364 /* SQLiteCipher Mac Tests */, ); }; /* End PBXProject section */ /* Begin PBXReferenceProxy section */ - DC2DD6AC1ABE428E00C2C71A /* libsqlcipher.a */ = { + DCE7BB711B7434310040D364 /* libsqlcipher.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; path = libsqlcipher.a; - remoteRef = DC2DD6AB1ABE428E00C2C71A /* PBXContainerItemProxy */; + remoteRef = DCE7BB701B7434310040D364 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXReferenceProxy section */ /* Begin PBXResourcesBuildPhase section */ - DC3773F119C8CBB3004FCF85 /* Resources */ = { + DC2B29E01B26F718001C60EA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC2B29EA1B26F718001C60EA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC7253AA1B52ADEB009B38B1 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DC7253B31B52ADEC009B38B1 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - DC3773FC19C8CBB3004FCF85 /* Resources */ = { + DCE7BB781B7434F90040D364 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B37C1A9191C300734B78 /* Resources */ = { + DCE7BB811B7434F90040D364 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B38E1A9191D100734B78 /* Resources */ = { + DCE7BBAE1B7438610040D364 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BBB71B7438620040D364 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -480,89 +800,205 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - DC3773EE19C8CBB3004FCF85 /* Sources */ = { + DC2B29DD1B26F718001C60EA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DC37743519C8D626004FCF85 /* Database.swift in Sources */, - DC37743B19C8D6C0004FCF85 /* Statement.swift in Sources */, - DC37743819C8D693004FCF85 /* Value.swift in Sources */, - DC650B9619F0CDC3002FBE91 /* Expression.swift in Sources */, - DCC6B3A81A91975700734B78 /* Functions.swift in Sources */, - DCAD429719E2E0F1004A51DF /* Query.swift in Sources */, - DCAFEAD31AABC818000C21A1 /* FTS.swift in Sources */, - DCBE28411ABDF18F0042A3FC /* RTree.swift in Sources */, - DC109CE11A0C4D970070988E /* Schema.swift in Sources */, - DC2393CA1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */, + DC2B2A161B26FE7F001C60EA /* Connection.swift in Sources */, + DC303D6E1B2CFA1F00469AEA /* Statement.swift in Sources */, + DC303D701B2CFA4700469AEA /* Value.swift in Sources */, + DC47D0661B52C9D200B12E66 /* Blob.swift in Sources */, + DC461BB91B2E687900E9DABD /* SQLite-Bridging.m in Sources */, + DC2D38BE1B2C7A73003EE2F2 /* Expression.swift in Sources */, + DC0B8BAD1B497A400084D886 /* Query.swift in Sources */, + DCA7B9E81B4D4F30006A9F04 /* Schema.swift in Sources */, + DCDF55B31B49ACBC00E1A168 /* Collation.swift in Sources */, + DC2609C21B475C9900C3E6B5 /* Operators.swift in Sources */, + DC0B8BAB1B485B620084D886 /* AggregateFunctions.swift in Sources */, + DC2609C41B475CA500C3E6B5 /* CoreFunctions.swift in Sources */, + DC2609C01B47504200C3E6B5 /* CustomFunctions.swift in Sources */, + DC72349E1B49D3AB007F513E /* Setter.swift in Sources */, + DC32A7861B575FD7002FFBE2 /* FTS4.swift in Sources */, + DC32A7AA1B5AAE76002FFBE2 /* R*Tree.swift in Sources */, + DC47D0A61B52F5B900B12E66 /* Foundation.swift in Sources */, + DCAA22861B4A9CB2005E00A2 /* Helpers.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - DC3773FA19C8CBB3004FCF85 /* Sources */ = { + DC2B29E81B26F718001C60EA /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DCF37F8819DDAF79001534AA /* TestHelper.swift in Sources */, - DCAFEAD71AABEFA7000C21A1 /* FTSTests.swift in Sources */, - DCF37F8219DDAC2D001534AA /* DatabaseTests.swift in Sources */, - DCF37F8519DDAF3F001534AA /* StatementTests.swift in Sources */, - DC475EA219F219AF00788FBD /* ExpressionTests.swift in Sources */, - DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */, - DC109CE41A0C4F5D0070988E /* SchemaTests.swift in Sources */, - 8E7D32B61B8FB67C003C1892 /* ValueTests.swift in Sources */, - DCC6B3A71A91974B00734B78 /* FunctionsTests.swift in Sources */, - DCBE28451ABDF2A80042A3FC /* RTreeTests.swift in Sources */, + DC32A7BF1B5C1769002FFBE2 /* SchemaTests.swift in Sources */, + DC32A7C51B5C1769002FFBE2 /* R*TreeTests.swift in Sources */, + DC303D761B2E306000469AEA /* TestHelpers.swift in Sources */, + DC32A7BE1B5C1769002FFBE2 /* QueryTests.swift in Sources */, + DC303D731B2E2ECD00469AEA /* ConnectionTests.swift in Sources */, + DC32A7C21B5C1769002FFBE2 /* CoreFunctionsTests.swift in Sources */, + DC32A7BA1B5C1769002FFBE2 /* StatementTests.swift in Sources */, + DC32A7BB1B5C1769002FFBE2 /* ValueTests.swift in Sources */, + DC32A7C91B5C9814002FFBE2 /* CustomFunctionsTests.swift in Sources */, + DC32A7C41B5C1769002FFBE2 /* FTS4Tests.swift in Sources */, + DC32A7BC1B5C1769002FFBE2 /* BlobTests.swift in Sources */, + DC32A7C11B5C1769002FFBE2 /* AggregateFunctionsTests.swift in Sources */, + DC32A7BD1B5C1769002FFBE2 /* ExpressionTests.swift in Sources */, + DC32A7C31B5C1769002FFBE2 /* SetterTests.swift in Sources */, + DC32A7C01B5C1769002FFBE2 /* OperatorsTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B36E1A9191C300734B78 /* Sources */ = { + DC7253A71B52ADEB009B38B1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DCC6B3721A9191C300734B78 /* Database.swift in Sources */, - DCC6B3701A9191C300734B78 /* Statement.swift in Sources */, - DCC6B3731A9191C300734B78 /* Value.swift in Sources */, - DCC6B3711A9191C300734B78 /* Expression.swift in Sources */, - DCC6B36F1A9191C300734B78 /* Query.swift in Sources */, - DCC6B3741A9191C300734B78 /* Schema.swift in Sources */, - DCC6B3A91A91975C00734B78 /* Functions.swift in Sources */, - DCAFEAD41AABC818000C21A1 /* FTS.swift in Sources */, - DCBE28421ABDF18F0042A3FC /* RTree.swift in Sources */, - DC2393CB1ABE35F8003FF113 /* SQLite-Bridging.m in Sources */, - DCC6B3A41A9194A800734B78 /* Cipher.swift in Sources */, + DC32A7971B57672E002FFBE2 /* Connection.swift in Sources */, + DC32A7981B57672E002FFBE2 /* Statement.swift in Sources */, + DC32A7991B57672E002FFBE2 /* Value.swift in Sources */, + DC47D0671B52C9D200B12E66 /* Blob.swift in Sources */, + DC32A79B1B57672E002FFBE2 /* SQLite-Bridging.m in Sources */, + DC32A79D1B57672E002FFBE2 /* Expression.swift in Sources */, + DC32A79E1B57672E002FFBE2 /* Query.swift in Sources */, + DC32A7961B576715002FFBE2 /* Schema.swift in Sources */, + DC32A79F1B57672E002FFBE2 /* Collation.swift in Sources */, + DC32A7A01B57672E002FFBE2 /* Operators.swift in Sources */, + DC32A7A11B57672E002FFBE2 /* AggregateFunctions.swift in Sources */, + DC32A7A21B57672E002FFBE2 /* CoreFunctions.swift in Sources */, + DC32A7A31B57672E002FFBE2 /* CustomFunctions.swift in Sources */, + DC32A7A41B57672E002FFBE2 /* Setter.swift in Sources */, + DC32A7871B575FD7002FFBE2 /* FTS4.swift in Sources */, + DC32A7A91B5AAE75002FFBE2 /* R*Tree.swift in Sources */, + DC47D0A71B52F5B900B12E66 /* Foundation.swift in Sources */, + DC32A7A51B57672E002FFBE2 /* Helpers.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; - DCC6B3851A9191D100734B78 /* Sources */ = { + DC7253B11B52ADEC009B38B1 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - DCC6B3AD1A9197B500734B78 /* TestHelper.swift in Sources */, - DCC6B3A61A9194FB00734B78 /* CipherTests.swift in Sources */, + DC32A7B31B5AC2C5002FFBE2 /* SchemaTests.swift in Sources */, + DC32A7B91B5AC2C5002FFBE2 /* R*TreeTests.swift in Sources */, + DC795C561B55AC8100DA0EF9 /* TestHelpers.swift in Sources */, + DC32A7B21B5AC2C5002FFBE2 /* QueryTests.swift in Sources */, + DC795C571B55AC8100DA0EF9 /* ConnectionTests.swift in Sources */, + DC32A7B61B5AC2C5002FFBE2 /* CoreFunctionsTests.swift in Sources */, + DC32A7AE1B5AC2C5002FFBE2 /* StatementTests.swift in Sources */, + DC32A7AF1B5AC2C5002FFBE2 /* ValueTests.swift in Sources */, + DC32A7CA1B5C9814002FFBE2 /* CustomFunctionsTests.swift in Sources */, + DC32A7B81B5AC2C5002FFBE2 /* FTS4Tests.swift in Sources */, + DC32A7B01B5AC2C5002FFBE2 /* BlobTests.swift in Sources */, + DC32A7B51B5AC2C5002FFBE2 /* AggregateFunctionsTests.swift in Sources */, + DC32A7B11B5AC2C5002FFBE2 /* ExpressionTests.swift in Sources */, + DC32A7B71B5AC2C5002FFBE2 /* SetterTests.swift in Sources */, + DC32A7B41B5AC2C5002FFBE2 /* OperatorsTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BB751B7434F90040D364 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DC6C03551B74453300EFC04E /* Connection.swift in Sources */, + DC6C03561B74453300EFC04E /* Statement.swift in Sources */, + DC6C03571B74453300EFC04E /* Value.swift in Sources */, + DC6C037F1B74461300EFC04E /* SQLite-Bridging.m in Sources */, + DC6C03581B74453300EFC04E /* Blob.swift in Sources */, + DC6C03591B74454100EFC04E /* Expression.swift in Sources */, + DC6C035A1B74454100EFC04E /* Query.swift in Sources */, + DC6C035B1B74454100EFC04E /* Schema.swift in Sources */, + DC6C035C1B74454100EFC04E /* Collation.swift in Sources */, + DC6C035D1B74454100EFC04E /* Operators.swift in Sources */, + DC6C035E1B74454100EFC04E /* AggregateFunctions.swift in Sources */, + DC6C035F1B74454100EFC04E /* CoreFunctions.swift in Sources */, + DC6C03601B74454100EFC04E /* CustomFunctions.swift in Sources */, + DC6C03611B74454100EFC04E /* Setter.swift in Sources */, + DC6C03621B74454100EFC04E /* FTS4.swift in Sources */, + DC6C03631B74454100EFC04E /* R*Tree.swift in Sources */, + DC6C03641B74454100EFC04E /* Foundation.swift in Sources */, + DC6C03651B74454100EFC04E /* Helpers.swift in Sources */, + DCE7BB911B74350E0040D364 /* Cipher.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BB7F1B7434F90040D364 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DCE7BBC91B743A320040D364 /* CipherTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BBAB1B7438610040D364 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DC6C03661B74454D00EFC04E /* Connection.swift in Sources */, + DC6C03671B74454D00EFC04E /* Statement.swift in Sources */, + DC6C03681B74454D00EFC04E /* Value.swift in Sources */, + DC6C03691B74454D00EFC04E /* Blob.swift in Sources */, + DC6C036A1B74454D00EFC04E /* SQLite-Bridging.m in Sources */, + DC6C036B1B74454D00EFC04E /* Expression.swift in Sources */, + DC6C036C1B74454D00EFC04E /* Query.swift in Sources */, + DC6C036D1B74454D00EFC04E /* Schema.swift in Sources */, + DC6C036E1B74454D00EFC04E /* Collation.swift in Sources */, + DC6C036F1B74454D00EFC04E /* Operators.swift in Sources */, + DC6C03701B74454D00EFC04E /* AggregateFunctions.swift in Sources */, + DC6C03711B74454D00EFC04E /* CoreFunctions.swift in Sources */, + DC6C03721B74454D00EFC04E /* CustomFunctions.swift in Sources */, + DC6C03731B74454D00EFC04E /* Setter.swift in Sources */, + DC6C03741B74454D00EFC04E /* FTS4.swift in Sources */, + DC6C03751B74454D00EFC04E /* R*Tree.swift in Sources */, + DC6C03761B74454D00EFC04E /* Foundation.swift in Sources */, + DC6C03771B74454D00EFC04E /* Helpers.swift in Sources */, + DC6C03541B74451F00EFC04E /* Cipher.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + DCE7BBB51B7438620040D364 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + DCE7BBCA1B743A320040D364 /* CipherTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - DC2DD6B21ABE438900C2C71A /* PBXTargetDependency */ = { + DC2B29EF1B26F718001C60EA /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DC2B29E11B26F718001C60EA /* SQLite iOS */; + targetProxy = DC2B29EE1B26F718001C60EA /* PBXContainerItemProxy */; + }; + DC7253B81B52ADEC009B38B1 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DC7253AB1B52ADEB009B38B1 /* SQLite Mac */; + targetProxy = DC7253B71B52ADEC009B38B1 /* PBXContainerItemProxy */; + }; + DCE7BB861B7434F90040D364 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = DCE7BB791B7434F90040D364 /* SQLiteCipher iOS */; + targetProxy = DCE7BB851B7434F90040D364 /* PBXContainerItemProxy */; + }; + DCE7BBA61B7436D10040D364 /* PBXTargetDependency */ = { isa = PBXTargetDependency; name = sqlcipher; - targetProxy = DC2DD6B11ABE438900C2C71A /* PBXContainerItemProxy */; + targetProxy = DCE7BBA51B7436D10040D364 /* PBXContainerItemProxy */; }; - DC37740119C8CBB3004FCF85 /* PBXTargetDependency */ = { + DCE7BBBC1B7438620040D364 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = DC3773F219C8CBB3004FCF85 /* SQLite */; - targetProxy = DC37740019C8CBB3004FCF85 /* PBXContainerItemProxy */; + target = DCE7BBAF1B7438610040D364 /* SQLiteCipher Mac */; + targetProxy = DCE7BBBB1B7438620040D364 /* PBXContainerItemProxy */; }; - DCC6B3AB1A91979100734B78 /* PBXTargetDependency */ = { + DCE7BBD11B743C220040D364 /* PBXTargetDependency */ = { isa = PBXTargetDependency; - target = DCC6B36D1A9191C300734B78 /* SQLiteCipher */; - targetProxy = DCC6B3AA1A91979100734B78 /* PBXContainerItemProxy */; + name = sqlcipher; + targetProxy = DCE7BBD01B743C220040D364 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - DC37740719C8CBB3004FCF85 /* Debug */ = { + DC2B29F41B26F718001C60EA /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -579,32 +1015,38 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); - GCC_SYMBOLS_PRIVATE_EXTERN = NO; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Debug; }; - DC37740819C8CBB3004FCF85 /* Release */ = { + DC2B29F51B26F718001C60EA /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -621,197 +1063,352 @@ CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - COPY_PHASE_STRIP = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; name = Release; }; - DC37740A19C8CBB3004FCF85 /* Debug */ = { + DC2B29F71B26F718001C60EA /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BB951B7435E90040D364 /* SQLite.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - DC37740B19C8CBB3004FCF85 /* Release */ = { + DC2B29F81B26F718001C60EA /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BB951B7435E90040D364 /* SQLite.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; }; name = Release; }; - DC37740D19C8CBB3004FCF85 /* Debug */ = { + DC2B29FA1B26F718001C60EA /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = NO; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; - INFOPLIST_FILE = "SQLite Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; }; name = Debug; }; - DC37740E19C8CBB3004FCF85 /* Release */ = { + DC2B29FB1B26F718001C60EA /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = NO; - INFOPLIST_FILE = "SQLite Tests/Info.plist"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; }; name = Release; }; - DCC6B37E1A9191C300734B78 /* Debug */ = { + DC7253BD1B52ADEC009B38B1 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BB951B7435E90040D364 /* SQLite.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "SQLITE_HAS_CODEC=1", - ); - INFOPLIST_FILE = SQLite/Info.plist; + FRAMEWORK_VERSION = A; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = SQLite; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - DCC6B37F1A9191C300734B78 /* Release */ = { + DC7253BE1B52ADEC009B38B1 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BB951B7435E90040D364 /* SQLite.xcconfig */; buildSettings = { CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.10; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Release; + }; + DC7253BF1B52ADEC009B38B1 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + DC7253C01B52ADEC009B38B1 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + }; + name = Release; + }; + DCE7BB8C1B7434F90040D364 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */; + buildSettings = { + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + DCE7BB8D1B7434F90040D364 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */; + buildSettings = { DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - GCC_PREPROCESSOR_DEFINITIONS = ( - "$(inherited)", - "SQLITE_HAS_CODEC=1", - ); - INFOPLIST_FILE = SQLite/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - PRODUCT_NAME = SQLite; SKIP_INSTALL = YES; }; name = Release; }; - DCC6B3901A9191D100734B78 /* Debug */ = { + DCE7BB8F1B7434F90040D364 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = NO; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; - INFOPLIST_FILE = "SQLite Tests/Info.plist"; - PRODUCT_NAME = "SQLiteCipher Tests"; - SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; + CLANG_ENABLE_MODULES = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteCipherTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; - DCC6B3911A9191D100734B78 /* Release */ = { + DCE7BB901B7434F90040D364 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DC37742E19C8CE67004FCF85 /* SQLite.xcconfig */; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; buildSettings = { - APPLICATION_EXTENSION_API_ONLY = NO; - INFOPLIST_FILE = "SQLite Tests/Info.plist"; - PRODUCT_NAME = "SQLiteCipher Tests"; - SWIFT_WHOLE_MODULE_OPTIMIZATION = NO; + CLANG_ENABLE_MODULES = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.SQLiteCipherTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; + DCE7BBC21B7438620040D364 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + DCE7BBC31B7438620040D364 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBAA1B7437DB0040D364 /* SQLiteCipher.xcconfig */; + buildSettings = { + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + SDKROOT = macosx; + SKIP_INSTALL = YES; + }; + name = Release; + }; + DCE7BBC51B7438620040D364 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = "com.stephencelis.SQLiteCipher-MacTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + DCE7BBC61B7438620040D364 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DCE7BBCD1B743A9F0040D364 /* Test.xcconfig */; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + COMBINE_HIDPI_IMAGES = YES; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.11; + PRODUCT_BUNDLE_IDENTIFIER = "com.stephencelis.SQLiteCipher-MacTests"; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - DC3773ED19C8CBB3004FCF85 /* Build configuration list for PBXProject "SQLite" */ = { + DC2B29DC1B26F718001C60EA /* Build configuration list for PBXProject "SQLite" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC2B29F41B26F718001C60EA /* Debug */, + DC2B29F51B26F718001C60EA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC2B29F61B26F718001C60EA /* Build configuration list for PBXNativeTarget "SQLite iOS" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC2B29F71B26F718001C60EA /* Debug */, + DC2B29F81B26F718001C60EA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC2B29F91B26F718001C60EA /* Build configuration list for PBXNativeTarget "SQLite iOS Tests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC2B29FA1B26F718001C60EA /* Debug */, + DC2B29FB1B26F718001C60EA /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC7253C11B52ADEC009B38B1 /* Build configuration list for PBXNativeTarget "SQLite Mac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DC7253BD1B52ADEC009B38B1 /* Debug */, + DC7253BE1B52ADEC009B38B1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + DC7253C21B52ADEC009B38B1 /* Build configuration list for PBXNativeTarget "SQLite Mac Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( - DC37740719C8CBB3004FCF85 /* Debug */, - DC37740819C8CBB3004FCF85 /* Release */, + DC7253BF1B52ADEC009B38B1 /* Debug */, + DC7253C01B52ADEC009B38B1 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DC37740919C8CBB3004FCF85 /* Build configuration list for PBXNativeTarget "SQLite" */ = { + DCE7BB8B1B7434F90040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( - DC37740A19C8CBB3004FCF85 /* Debug */, - DC37740B19C8CBB3004FCF85 /* Release */, + DCE7BB8C1B7434F90040D364 /* Debug */, + DCE7BB8D1B7434F90040D364 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DC37740C19C8CBB3004FCF85 /* Build configuration list for PBXNativeTarget "SQLite Tests" */ = { + DCE7BB8E1B7434F90040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher iOS Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( - DC37740D19C8CBB3004FCF85 /* Debug */, - DC37740E19C8CBB3004FCF85 /* Release */, + DCE7BB8F1B7434F90040D364 /* Debug */, + DCE7BB901B7434F90040D364 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DCC6B37D1A9191C300734B78 /* Build configuration list for PBXNativeTarget "SQLiteCipher" */ = { + DCE7BBC11B7438620040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher Mac" */ = { isa = XCConfigurationList; buildConfigurations = ( - DCC6B37E1A9191C300734B78 /* Debug */, - DCC6B37F1A9191C300734B78 /* Release */, + DCE7BBC21B7438620040D364 /* Debug */, + DCE7BBC31B7438620040D364 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - DCC6B38F1A9191D100734B78 /* Build configuration list for PBXNativeTarget "SQLiteCipher Tests" */ = { + DCE7BBC41B7438620040D364 /* Build configuration list for PBXNativeTarget "SQLiteCipher Mac Tests" */ = { isa = XCConfigurationList; buildConfigurations = ( - DCC6B3901A9191D100734B78 /* Debug */, - DCC6B3911A9191D100734B78 /* Release */, + DCE7BBC51B7438620040D364 /* Debug */, + DCE7BBC61B7438620040D364 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; - rootObject = DC3773EA19C8CBB3004FCF85 /* Project object */; + rootObject = DC2B29D91B26F718001C60EA /* Project object */; } diff --git a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xccheckout b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xccheckout deleted file mode 100644 index 60ff51e5..00000000 --- a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xccheckout +++ /dev/null @@ -1,53 +0,0 @@ - - - - - IDESourceControlProjectFavoriteDictionaryKey - - IDESourceControlProjectIdentifier - E427014D-8915-4E3B-859D-5EB72D455321 - IDESourceControlProjectName - SQLite - IDESourceControlProjectOriginsDictionary - - 3AE8ED2E684071AF4FB151FA51BF266B82FF45BD - github.com:stephencelis/SQLite.swift.git - 55011AEBD444630C0E9DF47BD2E18FDBBDCC285D - https://github.com/sqlcipher/sqlcipher.git - - IDESourceControlProjectPath - SQLite.xcodeproj - IDESourceControlProjectRelativeInstallPathDictionary - - 3AE8ED2E684071AF4FB151FA51BF266B82FF45BD - ../.. - 55011AEBD444630C0E9DF47BD2E18FDBBDCC285D - ../../Vendor/sqlcipher - - IDESourceControlProjectURL - github.com:stephencelis/SQLite.swift.git - IDESourceControlProjectVersion - 111 - IDESourceControlProjectWCCIdentifier - 3AE8ED2E684071AF4FB151FA51BF266B82FF45BD - IDESourceControlProjectWCConfigurations - - - IDESourceControlRepositoryExtensionIdentifierKey - public.vcs.git - IDESourceControlWCCIdentifierKey - 55011AEBD444630C0E9DF47BD2E18FDBBDCC285D - IDESourceControlWCCName - sqlcipher - - - IDESourceControlRepositoryExtensionIdentifierKey - public.vcs.git - IDESourceControlWCCIdentifierKey - 3AE8ED2E684071AF4FB151FA51BF266B82FF45BD - IDESourceControlWCCName - SQLite - - - - diff --git a/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xcscmblueprint b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xcscmblueprint new file mode 100644 index 00000000..9029cbd1 --- /dev/null +++ b/SQLite.xcodeproj/project.xcworkspace/xcshareddata/SQLite.xcscmblueprint @@ -0,0 +1,30 @@ +{ + "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "3AE8ED2E684071AF4FB151FA51BF266B82FF45BD", + "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { + + }, + "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { + "55011AEBD444630C0E9DF47BD2E18FDBBDCC285D" : 0, + "3AE8ED2E684071AF4FB151FA51BF266B82FF45BD" : 0 + }, + "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "7DE16DEF-6846-4DBA-8E4B-370D97AACD60", + "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { + "55011AEBD444630C0E9DF47BD2E18FDBBDCC285D" : "SQLite.swift\/Vendor\/sqlcipher\/", + "3AE8ED2E684071AF4FB151FA51BF266B82FF45BD" : "SQLite.swift\/" + }, + "DVTSourceControlWorkspaceBlueprintNameKey" : "SQLite", + "DVTSourceControlWorkspaceBlueprintVersion" : 204, + "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "SQLite.xcodeproj", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:stephencelis\/SQLite.swift.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "3AE8ED2E684071AF4FB151FA51BF266B82FF45BD" + }, + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/sqlcipher\/sqlcipher.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "55011AEBD444630C0E9DF47BD2E18FDBBDCC285D" + } + ] +} \ No newline at end of file diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme similarity index 70% rename from SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme rename to SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme index b41d3602..d4751729 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite Mac.xcscheme @@ -1,6 +1,6 @@ - - - - + codeCoverageEnabled = "YES"> @@ -56,28 +43,31 @@ + + @@ -85,17 +75,17 @@ diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme similarity index 79% rename from SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher.xcscheme rename to SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme index d464a46e..4c110773 100644 --- a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher.xcscheme +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLite iOS.xcscheme @@ -1,6 +1,6 @@ @@ -26,15 +26,16 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES" buildConfiguration = "Debug"> @@ -42,12 +43,14 @@ + + @@ -79,9 +83,9 @@ diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher Mac.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher Mac.xcscheme new file mode 100644 index 00000000..8ff655e1 --- /dev/null +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher Mac.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher iOS.xcscheme b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher iOS.xcscheme new file mode 100644 index 00000000..3e5b96b9 --- /dev/null +++ b/SQLite.xcodeproj/xcshareddata/xcschemes/SQLiteCipher iOS.xcscheme @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/SQLite/Database.swift b/SQLite/Database.swift deleted file mode 100644 index e59d3166..00000000 --- a/SQLite/Database.swift +++ /dev/null @@ -1,663 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// A connection (handle) to SQLite. -public final class Database { - - /// The location of a SQLite database. - public enum Location { - - /// An in-memory database (equivalent to `.URI(":memory:")`). - /// - /// See: - case InMemory - - /// A temporary, file-backed database (equivalent to `.URI("")`). - /// - /// See: - case Temporary - - /// A database located at the given URI filename (or path). - /// - /// See: - /// - /// :param: filename A URI filename - case URI(String) - - } - - internal var handle: COpaquePointer = nil - - /// Whether or not the database was opened in a read-only state. - public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 } - - /// Initializes a new connection to a database. - /// - /// :param: location The location of the database. Creates a new database if - /// it doesn’t already exist (unless in read-only mode). - /// - /// Default: `.InMemory`. - /// - /// :param: readonly Whether or not to open the database in a read-only - /// state. - /// - /// Default: `false`. - /// - /// :returns: A new database connection. - public init(_ location: Location = .InMemory, readonly: Bool = false) { - let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE - try { sqlite3_open_v2(location.description, &self.handle, flags | SQLITE_OPEN_FULLMUTEX, nil) } - } - - /// Initializes a new connection to a database. - /// - /// :param: filename The location of the database. Creates a new database if - /// it doesn’t already exist (unless in read-only mode). - /// - /// :param: readonly Whether or not to open the database in a read-only - /// state. - /// - /// Default: `false`. - /// - /// :returns: A new database connection. - public convenience init(_ filename: String, readonly: Bool = false) { - self.init(.URI(filename), readonly: readonly) - } - - deinit { try { sqlite3_close(self.handle) } } // sqlite3_close_v2 in Yosemite/iOS 8? - - // MARK: - - - /// The last rowid inserted into the database via this connection. - public var lastInsertRowid: Int64? { - let rowid = sqlite3_last_insert_rowid(handle) - return rowid == 0 ? nil : rowid - } - - /// The last number of changes (inserts, updates, or deletes) made to the - /// database via this connection. - public var changes: Int { - return Int(sqlite3_changes(handle)) - } - - /// The total number of changes (inserts, updates, or deletes) made to the - /// database via this connection. - public var totalChanges: Int { return Int(sqlite3_total_changes(handle)) } - - // MARK: - Execute - - /// Executes a batch of SQL statements. - /// - /// :param: SQL A batch of zero or more semicolon-separated SQL statements. - public func execute(SQL: String) { - try { sqlite3_exec(self.handle, SQL, nil, nil, nil) } - } - - // MARK: - Prepare - - /// Prepares a single SQL statement (with optional parameter bindings). - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: A prepared statement. - public func prepare(statement: String, _ bindings: Binding?...) -> Statement { - if !bindings.isEmpty { return prepare(statement, bindings) } - return Statement(self, statement) - } - - /// Prepares a single SQL statement and binds parameters to it. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: A prepared statement. - public func prepare(statement: String, _ bindings: [Binding?]) -> Statement { - return prepare(statement).bind(bindings) - } - - /// Prepares a single SQL statement and binds parameters to it. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A dictionary of named parameters to bind to the - /// statement. - /// - /// :returns: A prepared statement. - public func prepare(statement: String, _ bindings: [String: Binding?]) -> Statement { - return prepare(statement).bind(bindings) - } - - // MARK: - Run - - /// Runs a single SQL statement (with optional parameter bindings). - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The statement. - public func run(statement: String, _ bindings: Binding?...) -> Statement { - return run(statement, bindings) - } - - /// Prepares, binds, and runs a single SQL statement. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The statement. - public func run(statement: String, _ bindings: [Binding?]) -> Statement { - return prepare(statement).run(bindings) - } - - /// Prepares, binds, and runs a single SQL statement. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A dictionary of named parameters to bind to the - /// statement. - /// - /// :returns: The statement. - public func run(statement: String, _ bindings: [String: Binding?]) -> Statement { - return prepare(statement).run(bindings) - } - - // MARK: - Scalar - - /// Runs a single SQL statement (with optional parameter bindings), - /// returning the first value of the first row. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The first value of the first row returned. - public func scalar(statement: String, _ bindings: Binding?...) -> Binding? { - return scalar(statement, bindings) - } - - /// Prepares, binds, and runs a single SQL statement, returning the first - /// value of the first row. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The first value of the first row returned. - public func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { - return prepare(statement).scalar(bindings) - } - - /// Prepares, binds, and runs a single SQL statement, returning the first - /// value of the first row. - /// - /// :param: statement A single SQL statement. - /// - /// :param: bindings A dictionary of named parameters to bind to the - /// statement. - /// - /// :returns: The first value of the first row returned. - public func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { - return prepare(statement).scalar(bindings) - } - - // MARK: - Transactions - - /// The mode in which a transaction acquires a lock. - public enum TransactionMode: String { - - /// Defers locking the database till the first read/write executes. - case Deferred = "DEFERRED" - - /// Immediately acquires a reserved lock on the database. - case Immediate = "IMMEDIATE" - - /// Immediately acquires an exclusive lock on all databases. - case Exclusive = "EXCLUSIVE" - - } - - /// The result of a transaction. - public enum TransactionResult: String { - - /// Commits a transaction. - case Commit = "COMMIT TRANSACTION" - - /// Rolls a transaction back. - case Rollback = "ROLLBACK TRANSACTION" - - } - - /// Starts a new transaction with the given mode. - /// - /// :param: mode The mode in which a transaction acquires a lock. - /// - /// Default: `.Deferred` - /// - /// :returns: The BEGIN TRANSACTION statement. - public func transaction(_ mode: TransactionMode = .Deferred) -> Statement { - return run("BEGIN \(mode.rawValue) TRANSACTION") - } - - /// Runs a transaction with the given savepoint name (if omitted, it will - /// generate a UUID). - /// - /// :param: mode The mode in which a transaction acquires a lock. - /// - /// Default: `.Deferred` - /// - /// :param: block A closure to run SQL statements within the transaction. - /// Should return a TransactionResult depending on success or - /// failure. - /// - /// :returns: The COMMIT or ROLLBACK statement. - public func transaction(_ mode: TransactionMode = .Deferred, @noescape _ block: (txn: Statement) -> TransactionResult) -> Statement { - return run(block(txn: transaction(mode)).rawValue) - } - - /// Commits the current transaction (or, if a savepoint is open, releases - /// the current savepoint). - /// - /// :param: all Only applicable if a savepoint is open. If true, commits all - /// open savepoints, otherwise releases the current savepoint. - /// - /// Default: `false` - /// - /// :returns: The COMMIT (or RELEASE) statement. - public func commit(all: Bool = false) -> Statement { - if !savepointStack.isEmpty && !all { - return release() - } - savepointStack.removeAll() - return run(TransactionResult.Commit.rawValue) - } - - /// Rolls back the current transaction (or, if a savepoint is open, the - /// current savepoint). - /// - /// :param: all Only applicable if a savepoint is open. If true, rolls back - /// all open savepoints, otherwise rolls back the current - /// savepoint. - /// - /// Default: `false` - /// - /// :returns: The ROLLBACK statement. - public func rollback(all: Bool = false) -> Statement { - if !savepointStack.isEmpty && !all { - return rollback(savepointStack.removeLast()) - } - savepointStack.removeAll() - return run(TransactionResult.Rollback.rawValue) - } - - // MARK: - Savepoints - - /// The result of a savepoint. - public enum SavepointResult { - - /// Releases a savepoint. - case Release - - /// Rolls a savepoint back. - case Rollback - - } - - private var savepointStack = [String]() - - /// Starts a new transaction with the given savepoint name. - /// - /// :param: savepointName A unique identifier for the savepoint. - /// - /// :returns: The SAVEPOINT statement. - public func savepoint(_ savepointName: String? = nil) -> Statement { - let name = savepointName ?? NSUUID().UUIDString - savepointStack.append(name) - return run("SAVEPOINT \(quote(literal: name))") - } - - /// Runs a transaction with the given savepoint name (if omitted, it will - /// generate a UUID). - /// - /// :param: savepointName A unique identifier for the savepoint (optional). - /// - /// :param: block A closure to run SQL statements within the - /// transaction. Should return a SavepointResult - /// depending on success or failure. - /// - /// :returns: The RELEASE or ROLLBACK statement. - public func savepoint(_ savepointName: String? = nil, @noescape _ block: (txn: Statement) -> SavepointResult) -> Statement { - switch block(txn: savepoint(savepointName)) { - case .Release: - return release() - case .Rollback: - return rollback() - } - } - - /// Releases a savepoint with the given savepoint name (or the most - /// recently-opened savepoint). - /// - /// :param: savepointName A unique identifier for the savepoint (optional). - /// - /// :returns: The RELEASE SAVEPOINT statement. - public func release(_ savepointName: String? = nil) -> Statement { - let name = savepointName ?? savepointStack.removeLast() - if let idx = find(savepointStack, name) { savepointStack.removeRange(idx.. Statement { - if let idx = find(savepointStack, savepointName) { savepointStack.removeRange(idx.. Bool)?) { - try { - if let callback = callback { - self.busyHandler = { callback(tries: Int($0)) ? 1 : 0 } - } else { - self.busyHandler = nil - } - return _SQLiteBusyHandler(self.handle, self.busyHandler) - } - } - private var busyHandler: _SQLiteBusyHandlerCallback? - - /// Sets a handler to call when a statement is executed with the compiled - /// SQL. - /// - /// :param: callback This block is invoked when a statement is executed with - /// the compiled SQL as its argument. E.g., pass `println` - /// to act as a logger. - public func trace(callback: ((SQL: String) -> Void)?) { - if let callback = callback { - trace = { callback(SQL: String.fromCString($0)!) } - } else { - trace = nil - } - _SQLiteTrace(handle, trace) - } - private var trace: _SQLiteTraceCallback? - - /// An SQL operation passed to update callbacks. - public enum Operation { - - /// An INSERT operation. - case Insert - - /// An UPDATE operation. - case Update - - /// A DELETE operation. - case Delete - - private static func fromRawValue(rawValue: Int32) -> Operation { - switch rawValue { - case SQLITE_INSERT: - return .Insert - case SQLITE_UPDATE: - return .Update - case SQLITE_DELETE: - return .Delete - default: - fatalError("unhandled operation code: \(rawValue)") - } - } - - } - - /// Registers a callback to be invoked whenever a row is inserted, updated, - /// or deleted in a rowid table. - /// - /// :param: callback A callback invoked with the `Operation` (one - /// of `.Insert`, `.Update`, or `.Delete`), database name, - /// table name, and rowid. - public func updateHook(callback: ((operation: Operation, db: String, table: String, rowid: Int64) -> Void)?) { - if let callback = callback { - updateHook = { operation, db, table, rowid in - callback( - operation: .fromRawValue(operation), - db: String.fromCString(db)!, - table: String.fromCString(table)!, - rowid: rowid - ) - } - } else { - updateHook = nil - } - _SQLiteUpdateHook(handle, updateHook) - } - private var updateHook: _SQLiteUpdateHookCallback? - - /// Registers a callback to be invoked whenever a transaction is committed. - /// - /// :param: callback A callback that must return `.Commit` or `.Rollback` to - /// determine whether a transaction should be committed or - /// not. - public func commitHook(callback: (() -> TransactionResult)?) { - if let callback = callback { - commitHook = { callback() == .Commit ? 0 : 1 } - } else { - commitHook = nil - } - _SQLiteCommitHook(handle, commitHook) - } - private var commitHook: _SQLiteCommitHookCallback? - - /// Registers a callback to be invoked whenever a transaction rolls back. - /// - /// :param: callback A callback invoked when a transaction is rolled back. - public func rollbackHook(callback: (() -> Void)?) { - rollbackHook = callback.map { $0 } - _SQLiteRollbackHook(handle, rollbackHook) - } - private var rollbackHook: _SQLiteRollbackHookCallback? - - /// Creates or redefines a custom SQL function. - /// - /// :param: function The name of the function to create or redefine. - /// - /// :param: argc The number of arguments that the function takes. - /// If this parameter is `-1`, then the SQL function - /// may take any number of arguments. - /// - /// Default: `-1` - /// - /// :param: deterministic Whether or not the function is deterministic. If - /// the function always returns the same result for a - /// given input, SQLite can make optimizations. - /// - /// Default: `false` - /// - /// :param: block A block of code to run when the function is - /// called. The block is called with an array of raw - /// SQL values mapped to the function’s parameters and - /// should return a raw SQL value (or nil). - public func create(#function: String, argc: Int = -1, deterministic: Bool = false, _ block: (args: [Binding?]) -> Binding?) { - try { - if self.functions[function] == nil { self.functions[function] = [:] } - self.functions[function]?[argc] = { context, argc, argv in - let arguments: [Binding?] = map(0.. ComparisonResult) { - try { - self.collations[collation] = { lhs, rhs in - return Int32(block(lhs: String.fromCString(lhs)!, rhs: String.fromCString(rhs)!).rawValue) - } - return _SQLiteCreateCollation(self.handle, collation, self.collations[collation]) - } - } - private var collations = [String: _SQLiteCreateCollationCallback]() - - // MARK: - Error Handling - - /// Returns the last error produced on this connection. - public var lastError: String? { - if successCodes.contains(sqlite3_errcode(handle)) { - return nil - } - return String.fromCString(sqlite3_errmsg(handle))! - } - - internal func try(block: () -> Int32) { - perform { if block() != SQLITE_OK { assertionFailure("\(self.lastError!)") } } - } - - // MARK: - Threading - - private let queue = dispatch_queue_create("SQLite.Database", DISPATCH_QUEUE_SERIAL) - - internal func perform(block: () -> Void) { dispatch_sync(queue, block) } - -} - -// MARK: - Printable -extension Database: Printable { - - public var description: String { - return String.fromCString(sqlite3_db_filename(handle, nil))! - } - -} - -extension Database.Location: Printable { - - public var description: String { - switch self { - case .InMemory: - return ":memory:" - case .Temporary: - return "" - case .URI(let URI): - return URI - } - } - -} - -internal let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] - -internal func quote(#literal: String) -> String { - return quote(literal, "'") -} - -internal func quote(#identifier: String) -> String { - return quote(identifier, "\"") -} - -private func quote(string: String, mark: Character) -> String { - let escaped = reduce(string, "") { string, character in - string + (character == mark ? "\(mark)\(mark)" : "\(character)") - } - return "\(mark)\(escaped)\(mark)" -} diff --git a/SQLite/Expression.swift b/SQLite/Expression.swift deleted file mode 100644 index 1262f76b..00000000 --- a/SQLite/Expression.swift +++ /dev/null @@ -1,931 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// A typed SQL expression that wraps a raw string and bindings. -public struct Expression { - - public let SQL: String - - public let bindings: [Binding?] - - private var ascending: Bool? - - private var original: Expressible? - - /// Builds an SQL expression with a literal string and an optional list of - /// bindings. - /// - /// :param: SQL An SQL string. - /// - /// :param: bindings An optional list of values to be bound to the SQL. - public init(literal SQL: String, _ bindings: [Binding?] = []) { - (self.SQL, self.bindings) = (SQL, bindings) - } - - /// Builds an SQL expression with a quoted identifier. - /// - /// :param: identifier An SQL identifier (*e.g.*, a column name). - public init(_ identifier: String) { - self.init(literal: quote(identifier: identifier)) - } - - /// Builds an SQL expression with the given value. - /// - /// :param: value An encodable SQL value. - public init(value: V?) { - self.init(binding: value?.datatypeValue) - } - - /// Builds an SQL expression with the given value. - /// - /// :param: binding A raw SQL value. - internal init(binding: Binding?) { - self.init(literal: "?", [binding]) - } - - /// Returns an ascending sort version of the expression. - public var asc: Expression { - var expression = self - expression.ascending = true - return expression - } - - /// Returns an descending sort version of the expression. - public var desc: Expression { - var expression = self - expression.ascending = false - return expression - } - - /// Returns an aliased version of the expression using AS. The expression is - /// expanded contextually (e.g., in SELECT clauses). - public func alias(alias: String) -> Expression { - return self.alias(Expression(alias)) - } - - private func alias(alias: Expression) -> Expression { - var expression = Expression(alias) - expression.original = self - return expression - } - - internal static func join(separator: String, _ expressions: [Expressible]) -> Expression { - var (SQL, bindings) = ([String](), [Binding?]()) - for expressible in expressions { - let expression = expressible.expression - SQL.append(expression.SQL) - bindings.extend(expression.bindings) - } - return Expression(literal: Swift.join(separator, SQL), bindings) - } - - internal init(_ expression: Expression) { - self.init(literal: expression.SQL, expression.bindings) - (ascending, original) = (expression.ascending, expression.original) - } - - internal var ordered: Expression { - if let ascending = ascending { - return Expression.join(" ", [self, Expression(literal: ascending ? "ASC" : "DESC")]) - } - return Expression(self) - } - - internal var aliased: Expression { - if let aliased = original?.expression { - return Expression(literal: "(\(aliased.SQL)) AS \(SQL)", aliased.bindings + bindings) - } - return self - } - - internal var unaliased: Expression { - return original.map { Expression($0.expression) } ?? self - } - - internal func reverse() -> Expression { - var expression = self - expression.ascending = expression.ascending.map(!) ?? false - return expression - } - - // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE - internal func compile() -> String { - var idx = 0 - return reduce(SQL, "") { SQL, character in - let string = String(character) - return SQL + (string == "?" ? transcode(self.bindings[idx++]) : string) - } - } - -} - -public protocol Expressible { - - var expression: Expression { get } - -} - -extension Blob: Expressible { - - public var expression: Expression { - return Expression(binding: self) - } - -} - -extension Bool: Expressible { - - public var expression: Expression { - return Expression(value: self) - } - -} - -extension Double: Expressible { - - public var expression: Expression { - return Expression(binding: self) - } - -} - -extension Int: Expressible { - - public var expression: Expression { - // FIXME: rdar://TODO segfaults during archive // return Expression(value: self) - return Expression(binding: datatypeValue) - } - -} - -extension Int64: Expressible { - - public var expression: Expression { - return Expression(binding: self) - } - -} - -extension String: Expressible { - - public var expression: Expression { - return Expression(binding: self) - } - -} - -extension Expression: Expressible { - - public var expression: Expression { - return Expression(self) - } - -} - -extension Query: Expressible { - - public var expression: Expression { - if tableName.original != nil { - return selectExpression.alias(tableName) - } - return wrap("", selectExpression) - } - -} - -// MARK: - Expressions - -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix("||", lhs, rhs) } -public func + (lhs: Expression, rhs: String) -> Expression { return lhs + Expression(binding: rhs) } -public func + (lhs: Expression, rhs: String) -> Expression { return lhs + Expression(binding: rhs) } -public func + (lhs: String, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs } -public func + (lhs: String, rhs: Expression) -> Expression { return Expression(binding: lhs) + rhs } - -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func + (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func + (lhs: Expression, rhs: V) -> Expression { return lhs + Expression(value: rhs) } -public func + (lhs: Expression, rhs: V) -> Expression { return lhs + Expression(value: rhs) } -public func + (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } -public func + (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) + rhs } - -public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func - (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func - (lhs: Expression, rhs: V) -> Expression { return lhs - Expression(value: rhs) } -public func - (lhs: Expression, rhs: V) -> Expression { return lhs - Expression(value: rhs) } -public func - (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } -public func - (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) - rhs } - -public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func * (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func * (lhs: Expression, rhs: V) -> Expression { return lhs * Expression(value: rhs) } -public func * (lhs: Expression, rhs: V) -> Expression { return lhs * Expression(value: rhs) } -public func * (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } -public func * (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) * rhs } - -public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func / (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func / (lhs: Expression, rhs: V) -> Expression { return lhs / Expression(value: rhs) } -public func / (lhs: Expression, rhs: V) -> Expression { return lhs / Expression(value: rhs) } -public func / (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } -public func / (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) / rhs } - -public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func % (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func % (lhs: Expression, rhs: V) -> Expression { return lhs % Expression(value: rhs) } -public func % (lhs: Expression, rhs: V) -> Expression { return lhs % Expression(value: rhs) } -public func % (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) % rhs } -public func % (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) % rhs } - -public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func << (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func << (lhs: Expression, rhs: V) -> Expression { return lhs << Expression(value: rhs) } -public func << (lhs: Expression, rhs: V) -> Expression { return lhs << Expression(value: rhs) } -public func << (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs } -public func << (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs } - -public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >> (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func >> (lhs: Expression, rhs: V) -> Expression { return lhs >> Expression(value: rhs) } -public func >> (lhs: Expression, rhs: V) -> Expression { return lhs >> Expression(value: rhs) } -public func >> (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) >> rhs } -public func >> (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) >> rhs } - -public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func & (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func & (lhs: Expression, rhs: V) -> Expression { return lhs & Expression(value: rhs) } -public func & (lhs: Expression, rhs: V) -> Expression { return lhs & Expression(value: rhs) } -public func & (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) & rhs } -public func & (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) & rhs } - -public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func | (lhs: Expression, rhs: Expression) -> Expression { return infix(__FUNCTION__, lhs, rhs) } -public func | (lhs: Expression, rhs: V) -> Expression { return lhs | Expression(value: rhs) } -public func | (lhs: Expression, rhs: V) -> Expression { return lhs | Expression(value: rhs) } -public func | (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs } -public func | (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs } - -public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^ (lhs: Expression, rhs: Expression) -> Expression { return (~(lhs & rhs)) & (lhs | rhs) } -public func ^ (lhs: Expression, rhs: V) -> Expression { return lhs ^ Expression(value: rhs) } -public func ^ (lhs: Expression, rhs: V) -> Expression { return lhs ^ Expression(value: rhs) } -public func ^ (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) ^ rhs } -public func ^ (lhs: V, rhs: Expression) -> Expression { return Expression(value: lhs) ^ rhs } - -public prefix func ~ (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } -public prefix func ~ (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } - -/// A collating function used to compare to strings. -/// -/// See: -public enum Collation { - - /// Compares string by raw data. - case Binary - - /// Like binary, but folds uppercase ASCII letters into their lowercase - /// equivalents. - case Nocase - - /// Like binary, but strips trailing space. - case Rtrim - - /// A custom collating sequence identified by the given string, registered - /// using `Database.create(collation:…)` - case Custom(String) - -} - -extension Collation: Printable { - - public var description: String { - switch self { - case Binary: - return "BINARY" - case Nocase: - return "NOCASE" - case Rtrim: - return "RTRIM" - case Custom(let collation): - return collation - } - } - -} - -public func collate(collation: Collation, expression: Expression) -> Expression { - return infix("COLLATE", expression, Expression(collation.description)) -} -public func collate(collation: Collation, expression: Expression) -> Expression { - return infix("COLLATE", expression, Expression(collation.description)) -} - -public func cast(expression: Expression) -> Expression { - return Expression(literal: "CAST (\(expression.SQL) AS \(U.declaredDatatype))", expression.bindings) -} -public func cast(expression: Expression) -> Expression { - return Expression(literal: "CAST (\(expression.SQL) AS \(U.declaredDatatype))", expression.bindings) -} - -// MARK: - Predicates - -public func == (lhs: Expression, rhs: Expression) -> Expression { - return infix("=", lhs, rhs) -} -public func == (lhs: Expression, rhs: Expression) -> Expression { - return infix("=", lhs, rhs) -} -public func == (lhs: Expression, rhs: Expression) -> Expression { - return infix("=", lhs, rhs) -} -public func == (lhs: Expression, rhs: Expression) -> Expression { - return infix("=", lhs, rhs) -} -public func == (lhs: Expression, rhs: V) -> Expression { - return lhs == Expression(value: rhs) -} -public func == (lhs: Expression, rhs: V?) -> Expression { - if let rhs = rhs { return lhs == Expression(value: rhs) } - return Expression(literal: "\(lhs.SQL) IS ?", lhs.bindings + [nil]) -} -public func == (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) == rhs -} -public func == (lhs: V?, rhs: Expression) -> Expression { - if let lhs = lhs { return Expression(value: lhs) == rhs } - return Expression(literal: "? IS \(rhs.SQL)", [nil] + rhs.bindings) -} - -public func != (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func != (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func != (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func != (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func != (lhs: Expression, rhs: V) -> Expression { - return lhs != Expression(value: rhs) -} -public func != (lhs: Expression, rhs: V?) -> Expression { - if let rhs = rhs { return lhs != Expression(value: rhs) } - return Expression(literal: "\(lhs.SQL) IS NOT ?", lhs.bindings + [nil]) -} -public func != (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) != rhs -} -public func != (lhs: V?, rhs: Expression) -> Expression { - if let lhs = lhs { return Expression(value: lhs) != rhs } - return Expression(literal: "? IS NOT \(rhs.SQL)", [nil] + rhs.bindings) -} - -public func > (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func > (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func > (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func > (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func > (lhs: Expression, rhs: V) -> Expression { - return lhs > Expression(value: rhs) -} -public func > (lhs: Expression, rhs: V) -> Expression { - return lhs > Expression(value: rhs) -} -public func > (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) > rhs -} -public func > (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) > rhs -} - -public func >= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func >= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func >= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func >= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func >= (lhs: Expression, rhs: V) -> Expression { - return lhs >= Expression(value: rhs) -} -public func >= (lhs: Expression, rhs: V) -> Expression { - return lhs >= Expression(value: rhs) -} -public func >= (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) >= rhs -} -public func >= (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) >= rhs -} - -public func < (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func < (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func < (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func < (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func < (lhs: Expression, rhs: V) -> Expression { - return lhs < Expression(value: rhs) -} -public func < (lhs: Expression, rhs: V) -> Expression { - return lhs < Expression(value: rhs) -} -public func < (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) < rhs -} -public func < (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) < rhs -} - -public func <= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func <= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func <= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func <= (lhs: Expression, rhs: Expression) -> Expression { - return infix(__FUNCTION__, lhs, rhs) -} -public func <= (lhs: Expression, rhs: V) -> Expression { - return lhs <= Expression(value: rhs) -} -public func <= (lhs: Expression, rhs: V) -> Expression { - return lhs <= Expression(value: rhs) -} -public func <= (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) <= rhs -} -public func <= (lhs: V, rhs: Expression) -> Expression { - return Expression(value: lhs) <= rhs -} - -public prefix func - (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } -public prefix func - (rhs: Expression) -> Expression { return wrap(__FUNCTION__, rhs) } - -public func ~= , V.Datatype == I.Bound>(lhs: I, rhs: Expression) -> Expression { - return Expression(literal: "\(rhs.SQL) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) -} -public func ~= , V.Datatype == I.Bound>(lhs: I, rhs: Expression) -> Expression { - return Expression(lhs ~= Expression(rhs)) -} - -// MARK: Operators - -public func like(pattern: String, expression: Expression) -> Expression { - return infix("LIKE", expression, Expression(binding: pattern)) -} -public func like(pattern: String, expression: Expression) -> Expression { - return infix("LIKE", expression, Expression(binding: pattern)) -} - -public func glob(pattern: String, expression: Expression) -> Expression { - return infix("GLOB", expression, Expression(binding: pattern)) -} -public func glob(pattern: String, expression: Expression) -> Expression { - return infix("GLOB", expression, Expression(binding: pattern)) -} - -public func match(string: String, expression: Expression) -> Expression { - return infix("MATCH", expression, Expression(binding: string)) -} -public func match(string: String, expression: Expression) -> Expression { - return infix("MATCH", expression, Expression(binding: string)) -} - -public func regexp(pattern: String, expression: Expression) -> Expression { - return infix("REGEXP", expression, Expression(binding: pattern)) -} -public func regexp(pattern: String, expression: Expression) -> Expression { - return infix("REGEXP", expression, Expression(binding: pattern)) -} - -// MARK: Compound - -public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } -public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } -public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } -public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } -public func && (lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } -public func && (lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(value: rhs) } -public func && (lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } -public func && (lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) && rhs } - -public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } -public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } -public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } -public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } -public func || (lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } -public func || (lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(value: rhs) } -public func || (lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } -public func || (lhs: Bool, rhs: Expression) -> Expression { return Expression(value: lhs) || rhs } - -public prefix func ! (rhs: Expression) -> Expression { return wrap("NOT ", rhs) } -public prefix func ! (rhs: Expression) -> Expression { return wrap("NOT ", rhs) } - -// MARK: - Core Functions - -public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func abs(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -// FIXME: support Expression..., Expression when Swift supports inner variadic signatures -public func coalesce(expressions: Expression...) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", expressions.map { $0 } as [Expressible])) -} - -public func ifnull(expression: Expression, defaultValue: V) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, defaultValue])) -} -public func ifnull(expression: Expression, defaultValue: Expression) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, defaultValue])) -} -public func ifnull(expression: Expression, defaultValue: Expression) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, defaultValue])) -} -public func ?? (expression: Expression, defaultValue: V) -> Expression { - return ifnull(expression, defaultValue) -} -public func ?? (expression: Expression, defaultValue: Expression) -> Expression { - return ifnull(expression, defaultValue) -} -public func ?? (expression: Expression, defaultValue: Expression) -> Expression { - return ifnull(expression, defaultValue) -} - -public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func length(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -public func lower(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func lower(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -public func ltrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func ltrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -public func ltrim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) -} -public func ltrim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) -} - -public func random() -> Expression { return Expression(literal: __FUNCTION__) } - -public func replace(expression: Expression, match: String, subtitute: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, match, subtitute])) -} -public func replace(expression: Expression, match: String, subtitute: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, match, subtitute])) -} - -public func round(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func round(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func round(expression: Expression, precision: Int) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, precision])) -} -public func round(expression: Expression, precision: Int) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, precision])) -} - -public func rtrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func rtrim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func rtrim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) -} -public func rtrim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) -} - -public func substr(expression: Expression, startIndex: Int) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, startIndex])) -} -public func substr(expression: Expression, startIndex: Int) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, startIndex])) -} - -public func substr(expression: Expression, position: Int, length: Int) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, position, length])) -} -public func substr(expression: Expression, position: Int, length: Int) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, position, length])) -} - -public func substr(expression: Expression, subRange: Range) -> Expression { - return substr(expression, subRange.startIndex, subRange.endIndex - subRange.startIndex) -} -public func substr(expression: Expression, subRange: Range) -> Expression { - return substr(expression, subRange.startIndex, subRange.endIndex - subRange.startIndex) -} - -public func trim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func trim(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func trim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) -} -public func trim(expression: Expression, characters: String) -> Expression { - return wrap(__FUNCTION__, Expression.join(", ", [expression, characters])) -} - -public func upper(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func upper(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -// MARK: - Aggregate Functions - -public func count(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -public func count(#distinct: Expression) -> Expression { return wrapDistinct("count", distinct) } -public func count(#distinct: Expression) -> Expression { return wrapDistinct("count", distinct) } - -public func count(star: Star) -> Expression { return wrap(__FUNCTION__, star(nil, nil)) } - -public func max(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} -public func max(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} - -public func min(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} -public func min(expression: Expression) -> Expression { - return wrap(__FUNCTION__, expression) -} - -public func average(expression: Expression) -> Expression { return wrap("avg", expression) } -public func average(expression: Expression) -> Expression { return wrap("avg", expression) } - -public func average(#distinct: Expression) -> Expression { return wrapDistinct("avg", distinct) } -public func average(#distinct: Expression) -> Expression { return wrapDistinct("avg", distinct) } - -public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func sum(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -public func sum(#distinct: Expression) -> Expression { return wrapDistinct("sum", distinct) } -public func sum(#distinct: Expression) -> Expression { return wrapDistinct("sum", distinct) } - -public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } -public func total(expression: Expression) -> Expression { return wrap(__FUNCTION__, expression) } - -public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } -public func total(#distinct: Expression) -> Expression { return wrapDistinct("total", distinct) } - -private func wrapDistinct(function: String, expression: Expression) -> Expression { - return wrap(function, Expression.join(" ", [Expression(literal: "DISTINCT"), expression])) -} - -// MARK: - Helper - -public let rowid = Expression("ROWID") - -public typealias Star = (Expression?, Expression?) -> Expression - -public func * (Expression?, Expression?) -> Expression { - return Expression(literal: "*") -} -public func contains(values: C, column: Expression) -> Expression { - let templates = join(", ", [String](count: count(values), repeatedValue: "?")) - return infix("IN", column, Expression(literal: "(\(templates))", map(values) { $0.datatypeValue })) -} -public func contains(values: C, column: Expression) -> Expression { - return contains(values, Expression(column)) -} -public func contains(values: Query, column: Expression) -> Expression { - return infix("IN", column, wrap("", values.selectExpression) as Expression) -} - -// MARK: - Modifying - -/// A pair of expressions used to set values in INSERT and UPDATE statements. -public typealias Setter = (Expressible, Expressible) - -/// Returns a setter to be used with INSERT and UPDATE statements. -/// -/// :param: column The column being set. -/// -/// :param: value The value the column is being set to. -/// -/// :returns: A setter that can be used in a Query’s insert and update -/// functions. -public func set(column: Expression, value: V) -> Setter { - return (column, Expression(value: value)) -} -public func set(column: Expression, value: V?) -> Setter { - return (column, Expression(value: value)) -} -public func set(column: Expression, value: Expression) -> Setter { return (column, value) } -public func set(column: Expression, value: Expression) -> Setter { return (column, value) } -public func set(column: Expression, value: Expression) -> Setter { return (column, value) } -public func set(column: Expression, value: Expression) -> Setter { return (column, value) } - -infix operator <- { associativity left precedence 140 } -public func <- (column: Expression, value: Expression) -> Setter { return set(column, value) } -public func <- (column: Expression, value: Expression) -> Setter { return set(column, value) } -public func <- (column: Expression, value: Expression) -> Setter { return set(column, value) } -public func <- (column: Expression, value: Expression) -> Setter { return set(column, value) } -public func <- (column: Expression, value: V) -> Setter { return set(column, value) } -public func <- (column: Expression, value: V?) -> Setter { return set(column, value) } - -public func += (column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func += (column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func += (column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func += (column: Expression, value: Expression) -> Setter { return set(column, column + value) } -public func += (column: Expression, value: String) -> Setter { return set(column, column + value) } -public func += (column: Expression, value: String) -> Setter { return set(column, column + value) } - -public func += (column: Expression, value: Expression) -> Setter { - return set(column, column + value) -} -public func += (column: Expression, value: Expression) -> Setter { - return set(column, column + value) -} -public func += (column: Expression, value: Expression) -> Setter { - return set(column, column + value) -} -public func += (column: Expression, value: Expression) -> Setter { - return set(column, column + value) -} -public func += (column: Expression, value: V) -> Setter { return set(column, column + value) } -public func += (column: Expression, value: V) -> Setter { return set(column, column + value) } - -public func -= (column: Expression, value: Expression) -> Setter { - return set(column, column - value) -} -public func -= (column: Expression, value: Expression) -> Setter { - return set(column, column - value) -} -public func -= (column: Expression, value: Expression) -> Setter { - return set(column, column - value) -} -public func -= (column: Expression, value: Expression) -> Setter { - return set(column, column - value) -} -public func -= (column: Expression, value: V) -> Setter { return set(column, column - value) } -public func -= (column: Expression, value: V) -> Setter { return set(column, column - value) } - -public func *= (column: Expression, value: Expression) -> Setter { - return set(column, column * value) -} -public func *= (column: Expression, value: Expression) -> Setter { - return set(column, column * value) -} -public func *= (column: Expression, value: Expression) -> Setter { - return set(column, column * value) -} -public func *= (column: Expression, value: Expression) -> Setter { - return set(column, column * value) -} -public func *= (column: Expression, value: V) -> Setter { return set(column, column * value) } -public func *= (column: Expression, value: V) -> Setter { return set(column, column * value) } - -public func /= (column: Expression, value: Expression) -> Setter { - return set(column, column / value) -} -public func /= (column: Expression, value: Expression) -> Setter { - return set(column, column / value) -} -public func /= (column: Expression, value: Expression) -> Setter { - return set(column, column / value) -} -public func /= (column: Expression, value: Expression) -> Setter { - return set(column, column / value) -} -public func /= (column: Expression, value: V) -> Setter { - return set(column, column / value) -} -public func /= (column: Expression, value: V) -> Setter { - return set(column, column / value) -} - -public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: Expression) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: V) -> Setter { return set(column, column % value) } -public func %= (column: Expression, value: V) -> Setter { return set(column, column % value) } - -public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: Expression) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: V) -> Setter { return set(column, column << value) } -public func <<= (column: Expression, value: V) -> Setter { return set(column, column << value) } - -public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: Expression) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: V) -> Setter { return set(column, column >> value) } -public func >>= (column: Expression, value: V) -> Setter { return set(column, column >> value) } - -public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: Expression) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: V) -> Setter { return set(column, column & value) } -public func &= (column: Expression, value: V) -> Setter { return set(column, column & value) } - -public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: Expression) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: V) -> Setter { return set(column, column | value) } -public func |= (column: Expression, value: V) -> Setter { return set(column, column | value) } - -public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: Expression) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: V) -> Setter { return set(column, column ^ value) } -public func ^= (column: Expression, value: V) -> Setter { return set(column, column ^ value) } - -public postfix func ++ (column: Expression) -> Setter { return Expression(column) += 1 } -public postfix func ++ (column: Expression) -> Setter { return Expression(column) += 1 } -public postfix func -- (column: Expression) -> Setter { return Expression(column) -= 1 } -public postfix func -- (column: Expression) -> Setter { return Expression(column) -= 1 } - -// MARK: - Internal - -internal func transcode(literal: Binding?) -> String { - if let literal = literal { - if let literal = literal as? Blob { return literal.description } - if let literal = literal as? String { return quote(literal: literal) } - return "\(literal)" - } - return "NULL" -} - -internal func wrap(function: String, expression: Expression) -> Expression { - return Expression(literal: "\(function)\(surround(expression.SQL))", expression.bindings) -} - -internal func infix(function: String, lhs: Expression, rhs: Expression) -> Expression { - return Expression(literal: surround("\(lhs.SQL) \(function) \(rhs.SQL)"), lhs.bindings + rhs.bindings) -} - -private func surround(expression: String) -> String { return "(\(expression))" } diff --git a/SQLite/FTS.swift b/SQLite/FTS.swift deleted file mode 100644 index 30dfccbe..00000000 --- a/SQLite/FTS.swift +++ /dev/null @@ -1,92 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public func fts4(columns: Expression...) -> Expression { - return fts4(columns) -} - -// TODO: matchinfo, compress, uncompress -public func fts4(columns: [Expression], tokenize tokenizer: Tokenizer? = nil) -> Expression { - var options = [String: String]() - options["tokenize"] = tokenizer?.description - return fts("fts4", columns, options) -} - -private func fts(function: String, columns: [Expression], options: [String: String]) -> Expression { - var definitions: [Expressible] = columns.map { $0.expression } - for (key, value) in options { - definitions.append(Expression(literal: "\(key)=\(value)")) - } - return wrap(function, Expression.join(", ", definitions)) -} - -public enum Tokenizer { - - internal static var moduleName = "SQLite.swift" - - case Simple - - case Porter - - case Custom(String) - -} - -extension Tokenizer: Printable { - - public var description: String { - switch self { - case .Simple: - return "simple" - case .Porter: - return "porter" - case .Custom(let tokenizer): - return "\(quote(identifier: Tokenizer.moduleName)) \(quote(literal: tokenizer))" - } - } - -} - -public func match(string: String, expression: Query) -> Expression { - return infix("MATCH", Expression(expression.tableName), Expression(binding: string)) -} - -extension Database { - - public func register(tokenizer submoduleName: String, next: String -> (String, Range)?) { - try { - _SQLiteRegisterTokenizer(self.handle, Tokenizer.moduleName, submoduleName) { input, offset, length in - let string = String.fromCString(input)! - if var (token, range) = next(string) { - let view = string.utf8 - offset.memory += count(string.substringToIndex(range.startIndex).utf8) - length.memory = Int32(distance(range.startIndex.samePositionIn(view), range.endIndex.samePositionIn(view))) - return token - } - return nil - } - } - } - -} diff --git a/SQLite/Functions.swift b/SQLite/Functions.swift deleted file mode 100644 index ef83b57e..00000000 --- a/SQLite/Functions.swift +++ /dev/null @@ -1,188 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public extension Database { - - // MARK: - Type-Safe Function Creation Shims - - // MARK: 0 Arguments - - /// Creates or redefines a custom SQL function. - /// - /// :param: function The name of the function to create or redefine. - /// - /// :param: deterministic Whether or not the function is deterministic. If - /// the function always returns the same result for a - /// given input, SQLite can make optimizations. - /// - /// Default: `false` - /// - /// :param: block A block of code to run when the function is - /// called. The assigned types must be explicit. - /// - /// :returns: A closure returning an SQL expression to call the function. - public func create(#function: String, deterministic: Bool = false, _ block: () -> Z) -> (() -> Expression) { - return { self.create(function, 0, deterministic) { _ in return block() }([]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: () -> Z?) -> (() -> Expression) { - return { self.create(function, 0, deterministic) { _ in return block() }([]) } - } - - // MARK: 1 Argument - - public func create(#function: String, deterministic: Bool = false, _ block: A -> Z) -> (Expression -> Expression) { - return { self.create(function, 1, deterministic) { block(asValue($0[0])) }([$0]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: A? -> Z) -> (Expression -> Expression) { - return { self.create(function, 1, deterministic) { block($0[0].map(asValue)) }([$0]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: A -> Z?) -> (Expression -> Expression) { - return { self.create(function, 1, deterministic) { block(asValue($0[0])) }([$0]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: A? -> Z?) -> (Expression -> Expression) { - return { self.create(function, 1, deterministic) { block($0[0].map(asValue)) }([$0]) } - } - - // MARK: 2 Arguments - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z) -> ((A, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) -> ((A?, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([Expression(value: $0), $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) -> ((A, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) -> ((A?, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([Expression(value: $0), $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) -> ((A, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) -> ((A?, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([Expression(value: $0), $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) -> ((A, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } - } - - public func create, B: Value>(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) -> ((A?, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([Expression(value: $0), $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, $1]) } - } - - public func create(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) -> ((Expression, Expression) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, $1]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z) -> ((Expression, B) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) -> ((Expression, B) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) -> ((Expression, B?) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) -> ((Expression, B?) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) -> ((Expression, B) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), asValue($0[1])) }([$0, $1]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) -> ((Expression, B) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), asValue($0[1])) }([$0, $1]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) -> ((Expression, B?) -> Expression) { - return { self.create(function, 2, deterministic) { block(asValue($0[0]), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } - } - - public func create>(#function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) -> ((Expression, B?) -> Expression) { - return { self.create(function, 2, deterministic) { block($0[0].map(asValue), $0[1].map(asValue)) }([$0, Expression(value: $1)]) } - } - - // MARK: - - - private func create(function: String, _ argc: Int, _ deterministic: Bool, _ block: [Binding?] -> Z) -> ([Expressible] -> Expression) { - return { Expression(self.create(function, argc, deterministic) { (arguments: [Binding?]) -> Z? in block(arguments) }($0)) } - } - - private func create(function: String, _ argc: Int, _ deterministic: Bool, _ block: [Binding?] -> Z?) -> ([Expressible] -> Expression) { - create(function: function, argc: argc, deterministic: deterministic) { block($0)?.datatypeValue } - return { arguments in wrap(quote(identifier: function), Expression.join(", ", arguments)) } - } - -} - -private func asValue(value: Binding) -> A { - return A.fromDatatypeValue(value as! A.Datatype) as! A -} - -private func asValue(value: Binding?) -> A { - return asValue(value!) -} diff --git a/SQLite/Query.swift b/SQLite/Query.swift deleted file mode 100644 index 8e2f37a2..00000000 --- a/SQLite/Query.swift +++ /dev/null @@ -1,880 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -/// A query object. Used to build SQL statements with a collection of chainable -/// helper functions. -public struct Query { - - internal var database: Database - - internal init(_ database: Database, _ tableName: String) { - (self.database, self.tableName) = (database, Expression(tableName)) - } - - private init(_ database: Database, _ tableName: Expression) { - (self.database, self.tableName) = (database, tableName) - } - - // MARK: - Keywords - - /// Determines the join operator for a query’s JOIN clause. - public enum JoinType: String { - - /// A CROSS JOIN. - case Cross = "CROSS" - - /// An INNER JOIN. - case Inner = "INNER" - - /// A LEFT OUTER JOIN. - case LeftOuter = "LEFT OUTER" - - } - - private var columns: [Expressible]? - private var distinct: Bool = false - internal var tableName: Expression - private var joins: [(type: JoinType, table: Query, condition: Expression)] = [] - private var filter: Expression? - private var group: Expressible? - private var order = [Expressible]() - private var limit: (to: Int, offset: Int?)? = nil - - public func alias(alias: String) -> Query { - var query = self - query.tableName = query.tableName.alias(alias) - return query - } - - /// Sets the SELECT clause on the query. - /// - /// :param: all A list of expressions to select. - /// - /// :returns: A query with the given SELECT clause applied. - public func select(all: Expressible...) -> Query { - return select(all) - } - - /// Sets the SELECT clause on the query. - /// - /// :param: all A list of expressions to select. - /// - /// :returns: A query with the given SELECT clause applied. - public func select(all: [Expressible]) -> Query { - var query = self - (query.distinct, query.columns) = (false, all) - return query - } - - /// Sets the SELECT DISTINCT clause on the query. - /// - /// :param: columns A list of expressions to select. - /// - /// :returns: A query with the given SELECT DISTINCT clause applied. - public func select(distinct columns: Expressible...) -> Query { - return select(distinct: columns) - } - - /// Sets the SELECT DISTINCT clause on the query. - /// - /// :param: columns A list of expressions to select. - /// - /// :returns: A query with the given SELECT DISTINCT clause applied. - public func select(distinct columns: [Expressible]) -> Query { - var query = self - (query.distinct, query.columns) = (true, columns) - return query - } - - /// Sets the SELECT clause on the query. - /// - /// :param: star A literal *. - /// - /// :returns: A query with SELECT * applied. - public func select(star: Star) -> Query { - var query = self - (query.distinct, query.columns) = (false, nil) - return query - } - - /// Sets the SELECT DISTINCT * clause on the query. - /// - /// :param: star A literal *. - /// - /// :returns: A query with SELECT * applied. - public func select(distinct star: Star) -> Query { - return select(distinct: star(nil, nil)) - } - - /// Adds an INNER JOIN clause to the query. - /// - /// :param: table A query representing the other table. - /// - /// :param: on A boolean expression describing the join condition. - /// - /// :returns: A query with the given INNER JOIN clause applied. - public func join(table: Query, on: Expression) -> Query { - return join(.Inner, table, on: on) - } - - /// Adds an INNER JOIN clause to the query. - /// - /// :param: table A query representing the other table. - /// - /// :param: on A boolean expression describing the join condition. - /// - /// :returns: A query with the given INNER JOIN clause applied. - public func join(table: Query, on: Expression) -> Query { - return join(.Inner, table, on: on) - } - - /// Adds a JOIN clause to the query. - /// - /// :param: type The JOIN operator. - /// - /// :param: table A query representing the other table. - /// - /// :param: on A boolean expression describing the join condition. - /// - /// :returns: A query with the given JOIN clause applied. - public func join(type: JoinType, _ table: Query, on: Expression) -> Query { - var query = self - let join = (type: type, table: table, condition: table.filter.map { on && $0 } ?? on) - query.joins.append(join) - return query - } - - /// Adds a JOIN clause to the query. - /// - /// :param: type The JOIN operator. - /// - /// :param: table A query representing the other table. - /// - /// :param: on A boolean expression describing the join condition. - /// - /// :returns: A query with the given JOIN clause applied. - public func join(type: JoinType, _ table: Query, on: Expression) -> Query { - return join(type, table, on: Expression(on)) - } - - /// Adds a condition to the query’s WHERE clause. - /// - /// :param: condition A boolean expression to filter on. - /// - /// :returns: A query with the given WHERE clause applied. - public func filter(condition: Expression) -> Query { - var query = self - query.filter = filter.map { $0 && condition } ?? condition - return query - } - - /// Adds a condition to the query’s WHERE clause. - /// - /// :param: condition A boolean expression to filter on. - /// - /// :returns: A query with the given WHERE clause applied. - public func filter(condition: Expression) -> Query { - return filter(Expression(condition)) - } - - /// Sets a GROUP BY clause on the query. - /// - /// :param: by A list of columns to group by. - /// - /// :returns: A query with the given GROUP BY clause applied. - public func group(by: Expressible...) -> Query { - return group(by) - } - - /// Sets a GROUP BY clause (with optional HAVING) on the query. - /// - /// :param: by A column to group by. - /// - /// :param: having A condition determining which groups are returned. - /// - /// :returns: A query with the given GROUP BY clause applied. - public func group(by: Expressible, having: Expression) -> Query { - return group([by], having: having) - } - - /// Sets a GROUP BY clause (with optional HAVING) on the query. - /// - /// :param: by A column to group by. - /// - /// :param: having A condition determining which groups are returned. - /// - /// :returns: A query with the given GROUP BY clause applied. - public func group(by: Expressible, having: Expression) -> Query { - return group([by], having: having) - } - - /// Sets a GROUP BY-HAVING clause on the query. - /// - /// :param: by A list of columns to group by. - /// - /// :param: having A condition determining which groups are returned. - /// - /// :returns: A query with the given GROUP BY clause applied. - public func group(by: [Expressible], having: Expression) -> Query { - return group(by, having: Expression(having)) - } - - private func group(by: [Expressible], having: Expression? = nil) -> Query { - var query = self - var group = Expression.join(" ", [Expression(literal: "GROUP BY"), Expression.join(", ", by)]) - if let having = having { group = Expression.join(" ", [group, Expression(literal: "HAVING"), having]) } - query.group = group - return query - } - - /// Sets an ORDER BY clause on the query. - /// - /// :param: by An ordered list of columns and directions to sort by. - /// - /// :returns: A query with the given ORDER BY clause applied. - public func order(by: Expressible...) -> Query { - return order(by) - } - - /// Sets an ORDER BY clause on the query. - /// - /// :param: by An ordered list of columns and directions to sort by. - /// - /// :returns: A query with the given ORDER BY clause applied. - public func order(by: [Expressible]) -> Query { - var query = self - query.order = by - return query - } - - /// Sets the LIMIT clause (and resets any OFFSET clause) on the query. - /// - /// :param: to The maximum number of rows to return. - /// - /// :returns: A query with the given LIMIT clause applied. - public func limit(to: Int?) -> Query { - return limit(to: to, offset: nil) - } - - /// Sets LIMIT and OFFSET clauses on the query. - /// - /// :param: to The maximum number of rows to return. - /// - /// :param: offset The number of rows to skip. - /// - /// :returns: A query with the given LIMIT and OFFSET clauses applied. - public func limit(to: Int, offset: Int) -> Query { - return limit(to: to, offset: offset) - } - - // prevents limit(nil, offset: 5) - private func limit(#to: Int?, offset: Int? = nil) -> Query { - var query = self - query.limit = to.map { ($0, offset) } - return query - } - - // MARK: - Namespacing - - /// Prefixes a column expression with the query’s table name or alias. - /// - /// :param: column A column expression. - /// - /// :returns: A column expression namespaced with the query’s table name or - /// alias. - public func namespace(column: Expression) -> Expression { - return Expression(.join(".", [tableName, column])) - } - - // FIXME: rdar://18673897 // ... subscript(expression: Expression) -> Expression - - public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { return namespace(column) } - - public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { return namespace(column) } - - public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { return namespace(column) } - - public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { return namespace(column) } - - public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { return namespace(column) } - - public subscript(column: Expression) -> Expression { return namespace(column) } - public subscript(column: Expression) -> Expression { return namespace(column) } - - /// Prefixes a star with the query’s table name or alias. - /// - /// :param: star A literal `*`. - /// - /// :returns: A `*` expression namespaced with the query’s table name or - /// alias. - public subscript(star: Star) -> Expression { - return namespace(star(nil, nil)) - } - - // MARK: - Compiling Statements - - internal var selectStatement: Statement { - let expression = selectExpression - return database.prepare(expression.SQL, expression.bindings) - } - - internal var selectExpression: Expression { - var expressions = [selectClause] - joinClause.map(expressions.append) - whereClause.map(expressions.append) - group.map(expressions.append) - orderClause.map(expressions.append) - limitClause.map(expressions.append) - return Expression.join(" ", expressions) - } - - /// ON CONFLICT resolutions. - public enum OnConflict: String { - - case Replace = "REPLACE" - - case Rollback = "ROLLBACK" - - case Abort = "ABORT" - - case Fail = "FAIL" - - case Ignore = "IGNORE" - - } - - private func insertStatement(or: OnConflict? = nil, _ values: [Setter]) -> Statement { - var insertClause = "INSERT" - if let or = or { insertClause = "\(insertClause) OR \(or.rawValue)" } - var expressions: [Expressible] = [Expression(literal: "\(insertClause) INTO \(tableName.unaliased.SQL)")] - let (c, v) = (Expression.join(", ", values.map { $0.0 }), Expression.join(", ", values.map { $0.1 })) - expressions.append(Expression(literal: "(\(c.SQL)) VALUES (\(v.SQL))", c.bindings + v.bindings)) - whereClause.map(expressions.append) - let expression = Expression.join(" ", expressions) - return database.prepare(expression.SQL, expression.bindings) - } - - private func updateStatement(values: [Setter]) -> Statement { - var expressions: [Expressible] = [Expression(literal: "UPDATE \(tableName.unaliased.SQL) SET")] - expressions.append(Expression.join(", ", values.map { Expression.join(" = ", [$0, $1]) })) - whereClause.map(expressions.append) - let expression = Expression.join(" ", expressions) - return database.prepare(expression.SQL, expression.bindings) - } - - private var deleteStatement: Statement { - var expressions: [Expressible] = [Expression(literal: "DELETE FROM \(tableName.unaliased.SQL)")] - whereClause.map(expressions.append) - let expression = Expression.join(" ", expressions) - return database.prepare(expression.SQL, expression.bindings) - } - - // MARK: - - - private var selectClause: Expressible { - var expressions: [Expressible] = [Expression(literal: "SELECT")] - if distinct { expressions.append(Expression(literal: "DISTINCT")) } - expressions.append(Expression.join(", ", (columns ?? [Expression(literal: "*")]).map { $0.expression.aliased })) - expressions.append(Expression(literal: "FROM \(tableName.aliased.SQL)")) - return Expression.join(" ", expressions) - } - - private var joinClause: Expressible? { - if joins.count == 0 { return nil } - return Expression.join(" ", joins.map { type, table, condition in - let join = (table.columns == nil ? table.tableName : table.expression).aliased - return Expression(literal: "\(type.rawValue) JOIN \(join.SQL) ON \(condition.SQL)", join.bindings + condition.bindings) - }) - } - - internal var whereClause: Expressible? { - if let filter = filter { - return Expression(literal: "WHERE \(filter.SQL)", filter.bindings) - } - return nil - } - - private var orderClause: Expressible? { - if order.count == 0 { return nil } - let clause = Expression.join(", ", order.map { $0.expression.ordered }) - return Expression(literal: "ORDER BY \(clause.SQL)", clause.bindings) - } - - private var limitClause: Expressible? { - if let limit = limit { - var clause = Expression(literal: "LIMIT \(limit.to)") - if let offset = limit.offset { - clause = Expression.join(" ", [clause, Expression(literal: "OFFSET \(offset)")]) - } - return clause - } - return nil - } - - // MARK: - Modifying - - /// Runs an INSERT statement against the query. - /// - /// :param: action An optional action to run in case of a conflict. - /// :param: value A value to set. - /// :param: more A list of additional values to set. - /// - /// :returns: The rowid and statement. - public func insert(or action: OnConflict? = nil, _ value: Setter, _ more: Setter...) -> Insert { - return insert(or: action, [value] + more) - } - - /// Runs an INSERT statement against the query. - /// - /// :param: action An optional action to run in case of a conflict. - /// :param: values An array of values to set. - /// - /// :returns: The rowid and statement. - public func insert(or action: OnConflict? = nil, _ values: [Setter]) -> Insert { - let statement = insertStatement(or: action, values).run() - return (statement.failed ? nil : database.lastInsertRowid, statement) - } - - /// Runs an INSERT statement against the query with DEFAULT VALUES. - /// - /// :returns: The rowid and statement. - public func insert() -> Insert { - let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) DEFAULT VALUES") - return (statement.failed ? nil : database.lastInsertRowid, statement) - } - - /// Runs an INSERT statement against the query with the results of another - /// query. - /// - /// :param: query A query to SELECT results from. - /// - /// :returns: The number of updated rows and statement. - public func insert(query: Query) -> Change { - let expression = query.selectExpression - let statement = database.run("INSERT INTO \(tableName.unaliased.SQL) \(expression.SQL)", expression.bindings) - return (statement.failed ? nil : database.changes, statement) - } - - /// Runs an UPDATE statement against the query. - /// - /// :param: values A list of values to set. - /// - /// :returns: The number of updated rows and statement. - public func update(values: Setter...) -> Change { - return update(values) - } - - /// Runs an UPDATE statement against the query. - /// - /// :param: values An array of of values to set. - /// - /// :returns: The number of updated rows and statement. - public func update(values: [Setter]) -> Change { - let statement = updateStatement(values).run() - return (statement.failed ? nil : database.changes, statement) - } - - /// Runs a DELETE statement against the query. - /// - /// :returns: The number of deleted rows and statement. - public func delete() -> Change { - let statement = deleteStatement.run() - return (statement.failed ? nil : database.changes, statement) - } - - // MARK: - Aggregate Functions - - /// Runs `count()` against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The number of rows matching the given column. - public func count(column: Expression) -> Int { - return calculate(_count(column))! - } - - /// Runs `count()` with DISTINCT against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The number of rows matching the given column. - public func count(distinct column: Expression) -> Int { - return calculate(_count(distinct: column))! - } - - /// Runs `count()` with DISTINCT against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The number of rows matching the given column. - public func count(distinct column: Expression) -> Int { - return calculate(_count(distinct: column))! - } - - /// Runs `max()` against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The largest value of the given column. - public func max(column: Expression) -> V? { - return calculate(_max(column)) - } - public func max(column: Expression) -> V? { - return calculate(_max(column)) - } - - /// Runs `min()` against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The smallest value of the given column. - public func min(column: Expression) -> V? { - return calculate(_min(column)) - } - public func min(column: Expression) -> V? { - return calculate(_min(column)) - } - - /// Runs `avg()` against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The average value of the given column. - public func average(column: Expression) -> Double? { - return calculate(_average(column)) - } - public func average(column: Expression) -> Double? { - return calculate(_average(column)) - } - - /// Runs `avg()` with DISTINCT against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The average value of the given column. - public func average(distinct column: Expression) -> Double? { - return calculate(_average(distinct: column)) - } - public func average(distinct column: Expression) -> Double? { - return calculate(_average(distinct: column)) - } - - /// Runs `sum()` against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The sum of the given column’s values. - public func sum(column: Expression) -> V? { - return calculate(_sum(column)) - } - public func sum(column: Expression) -> V? { - return calculate(_sum(column)) - } - - /// Runs `sum()` with DISTINCT against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The sum of the given column’s values. - public func sum(distinct column: Expression) -> V? { - return calculate(_sum(distinct: column)) - } - public func sum(distinct column: Expression) -> V? { - return calculate(_sum(distinct: column)) - } - - /// Runs `total()` against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The total of the given column’s values. - public func total(column: Expression) -> Double { - return calculate(_total(column))! - } - public func total(column: Expression) -> Double { - return calculate(_total(column))! - } - - /// Runs `total()` with DISTINCT against the query. - /// - /// :param: column The column used for the calculation. - /// - /// :returns: The total of the given column’s values. - public func total(distinct column: Expression) -> Double { - return calculate(_total(distinct: column))! - } - public func total(distinct column: Expression) -> Double { - return calculate(_total(distinct: column))! - } - - private func calculate(expression: Expression) -> V? { - return (select(expression).selectStatement.scalar() as? V.Datatype).map(V.fromDatatypeValue) as? V - } - private func calculate(expression: Expression) -> V? { - return (select(expression).selectStatement.scalar() as? V.Datatype).map(V.fromDatatypeValue) as? V - } - - // MARK: - Array - - /// Runs `count(*)` against the query and returns the number of rows. - public var count: Int { return calculate(_count(*))! } - - /// Returns `true` iff the query has no rows. - public var isEmpty: Bool { return first == nil } - - /// The first row (or `nil` if the query returns no rows). - public var first: Row? { - var generator = limit(to: 1, offset: limit?.offset).generate() - return generator.next() - } - - /// The last row (or `nil` if the query returns no rows). - public var last: Row? { - return reverse().first - } - - /// A query in reverse order. - public func reverse() -> Query { - return order(order.isEmpty ? [rowid.desc] : order.map { $0.expression.reverse() }) - } - -} - -/// A row object. Returned by iterating over a Query. Provides typed subscript -/// access to a row’s values. -public struct Row { - - private let columnNames: [String: Int] - private let values: [Binding?] - - private init(_ columnNames: [String: Int], _ values: [Binding?]) { - (self.columnNames, self.values) = (columnNames, values) - } - - /// Returns a row’s value for the given column. - /// - /// :param: column An expression representing a column selected in a Query. - /// - /// returns The value for the given column. - public func get(column: Expression) -> V { - return get(Expression(column))! - } - public func get(column: Expression) -> V? { - func valueAtIndex(idx: Int) -> V? { - if let value = values[idx] as? V.Datatype { return (V.fromDatatypeValue(value) as! V) } - return nil - } - - if let idx = columnNames[column.SQL] { return valueAtIndex(idx) } - - let similar = filter(columnNames.keys) { $0.hasSuffix(".\(column.SQL)") } - if similar.count == 1 { return valueAtIndex(columnNames[similar[0]]!) } - - if similar.count > 1 { - fatalError("ambiguous column \(quote(literal: column.SQL)) (please disambiguate: \(similar))") - } - fatalError("no such column \(quote(literal: column.SQL)) in columns: \(sorted(columnNames.keys))") - } - - // FIXME: rdar://18673897 // ... subscript(expression: Expression) -> Expression - - public subscript(column: Expression) -> Blob { return get(column) } - public subscript(column: Expression) -> Blob? { return get(column) } - - public subscript(column: Expression) -> Bool { return get(column) } - public subscript(column: Expression) -> Bool? { return get(column) } - - public subscript(column: Expression) -> Double { return get(column) } - public subscript(column: Expression) -> Double? { return get(column) } - - public subscript(column: Expression) -> Int { return get(column) } - public subscript(column: Expression) -> Int? { return get(column) } - - public subscript(column: Expression) -> Int64 { return get(column) } - public subscript(column: Expression) -> Int64? { return get(column) } - - public subscript(column: Expression) -> String { return get(column) } - public subscript(column: Expression) -> String? { return get(column) } - -} - -// MARK: - SequenceType -extension Query: SequenceType { - - public func generate() -> QueryGenerator { return QueryGenerator(self) } - -} - -// MARK: - GeneratorType -public struct QueryGenerator: GeneratorType { - - private let query: Query - private let statement: Statement - - private lazy var columnNames: [String: Int] = { - var (columnNames, idx) = ([String: Int](), 0) - column: for each in self.query.columns ?? [Expression(literal: "*")] { - let pair = split(each.expression.SQL) { $0 == "." } - let (tableName, column) = (pair.count > 1 ? pair.first : nil, pair.last!) - - func expandGlob(namespace: Bool) -> Query -> Void { - return { table in - var query = Query(table.database, table.tableName.unaliased) - if let columns = table.columns { query.columns = columns } - var names = query.selectStatement.columnNames.map { quote(identifier: $0) } - if namespace { names = names.map { "\(table.tableName.SQL).\($0)" } } - for name in names { columnNames[name] = idx++ } - } - } - - if column == "*" { - let tables = [self.query.select(*)] + self.query.joins.map { $0.table } - if let tableName = tableName { - for table in tables { - if table.tableName.SQL == tableName { - expandGlob(true)(table) - continue column - } - } - assertionFailure("no such table: \(tableName)") - } - tables.map(expandGlob(self.query.joins.count > 0)) - continue - } - - columnNames[each.expression.SQL] = idx++ - } - return columnNames - }() - - private init(_ query: Query) { - (self.query, self.statement) = (query, query.selectStatement) - } - - public mutating func next() -> Row? { - return statement.next().map { Row(self.columnNames, $0) } - } - -} - -// MARK: - Printable -extension Query: Printable { - - public var description: String { - return tableName.SQL - } - -} - -/// The result of an INSERT executed by a query. -/// -/// :param: rowid The insert rowid of the result (or `nil` on failure). -/// -/// :param: statement The associated statement. -public typealias Insert = (rowid: Int64?, statement: Statement) - -/// The result of an bulk INSERT, UPDATE or DELETE executed by a query. -/// -/// :param: changes The number of rows affected (or `nil` on failure). -/// -/// :param: statement The associated statement. -public typealias Change = (changes: Int?, statement: Statement) - -/// If `lhs` fails, return it. Otherwise, execute `rhs` and return its -/// associated statement. -public func && (lhs: Statement, @autoclosure rhs: () -> Insert) -> Statement { - return lhs && rhs().statement -} - -/// If `lhs` succeeds, return it. Otherwise, execute `rhs` and return its -/// associated statement. -public func || (lhs: Statement, @autoclosure rhs: () -> Insert) -> Statement { - return lhs || rhs().statement -} - -/// If `lhs` fails, return it. Otherwise, execute `rhs` and return its -/// associated statement. -public func && (lhs: Statement, @autoclosure rhs: () -> Change) -> Statement { - return lhs && rhs().statement -} - -/// If `lhs` succeeds, return it. Otherwise, execute `rhs` and return its -/// associated statement. -public func || (lhs: Statement, @autoclosure rhs: () -> Change) -> Statement { - return lhs || rhs().statement -} - -extension Database { - - public subscript(tableName: String) -> Query { - return Query(self, tableName) - } - -} - -// Private shims provide frameworkless support by avoiding the SQLite module namespace. - -private func _count(expression: Expression) -> Expression { return count(expression) } - -private func _count(#distinct: Expression) -> Expression { return count(distinct: distinct) } -private func _count(#distinct: Expression) -> Expression { return count(distinct: distinct) } - -private func _count(star: Star) -> Expression { return count(star) } - -private func _max(expression: Expression) -> Expression { - return max(expression) -} -private func _max(expression: Expression) -> Expression { - return max(expression) -} - -private func _min(expression: Expression) -> Expression { - return min(expression) -} -private func _min(expression: Expression) -> Expression { - return min(expression) -} - -private func _average(expression: Expression) -> Expression { return average(expression) } -private func _average(expression: Expression) -> Expression { return average(expression) } - -private func _average(#distinct: Expression) -> Expression { return average(distinct: distinct) } -private func _average(#distinct: Expression) -> Expression { return average(distinct: distinct) } - -private func _sum(expression: Expression) -> Expression { return sum(expression) } -private func _sum(expression: Expression) -> Expression { return sum(expression) } - -private func _sum(#distinct: Expression) -> Expression { return sum(distinct: distinct) } -private func _sum(#distinct: Expression) -> Expression { return sum(distinct: distinct) } - -private func _total(expression: Expression) -> Expression { return total(expression) } -private func _total(expression: Expression) -> Expression { return total(expression) } - -private func _total(#distinct: Expression) -> Expression { return total(distinct: distinct) } -private func _total(#distinct: Expression) -> Expression { return total(distinct: distinct) } diff --git a/SQLite/SQLite-Bridging.h b/SQLite/SQLite-Bridging.h deleted file mode 100644 index 6bb9f2de..00000000 --- a/SQLite/SQLite-Bridging.h +++ /dev/null @@ -1,56 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -@import Foundation; - -typedef struct SQLiteHandle SQLiteHandle; -typedef struct SQLiteContext SQLiteContext; -typedef struct SQLiteValue SQLiteValue; - -NS_ASSUME_NONNULL_BEGIN -typedef int (^_SQLiteBusyHandlerCallback)(int times); -int _SQLiteBusyHandler(SQLiteHandle * handle, _SQLiteBusyHandlerCallback __nullable callback); - -typedef void (^_SQLiteTraceCallback)(const char * SQL); -void _SQLiteTrace(SQLiteHandle * handle, _SQLiteTraceCallback __nullable callback); - -typedef void (^_SQLiteUpdateHookCallback)(int operation, const char * db, const char * table, long long rowid); -void _SQLiteUpdateHook(SQLiteHandle * handle, _SQLiteUpdateHookCallback __nullable callback); - -typedef int (^_SQLiteCommitHookCallback)(); -void _SQLiteCommitHook(SQLiteHandle * handle, _SQLiteCommitHookCallback __nullable callback); - -typedef void (^_SQLiteRollbackHookCallback)(); -void _SQLiteRollbackHook(SQLiteHandle * handle, _SQLiteRollbackHookCallback __nullable callback); - -typedef void (^_SQLiteCreateFunctionCallback)(SQLiteContext * context, int argc, SQLiteValue * __nonnull * __nonnull argv); -int _SQLiteCreateFunction(SQLiteHandle * handle, const char * name, int argc, int deterministic, _SQLiteCreateFunctionCallback __nullable callback); - -typedef int (^_SQLiteCreateCollationCallback)(const char * lhs, const char * rhs); -int _SQLiteCreateCollation(SQLiteHandle * handle, const char * name, _SQLiteCreateCollationCallback __nullable callback); - -typedef NSString * __nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); -int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, __nullable _SQLiteTokenizerNextCallback callback); -NS_ASSUME_NONNULL_END - diff --git a/SQLite/SQLite.xcconfig b/SQLite/SQLite.xcconfig deleted file mode 100644 index d021a766..00000000 --- a/SQLite/SQLite.xcconfig +++ /dev/null @@ -1,28 +0,0 @@ -APPLICATION_EXTENSION_API_ONLY = YES -SWIFT_WHOLE_MODULE_OPTIMIZATION = YES - -MODULEMAP_FILE = $(SRCROOT)/SQLite/module.modulemap - -// Universal Framework - -SUPPORTED_PLATFORMS = iphoneos iphonesimulator macosx - -VALID_ARCHS[sdk=iphone*] = arm64 armv7 armv7s -VALID_ARCHS[sdk=macosx*] = i386 x86_64 - -LD_RUNPATH_SEARCH_PATHS[sdk=iphone*] = $(inherited) @executable_path/Frameworks @loader_path/Frameworks -LD_RUNPATH_SEARCH_PATHS[sdk=macosx*] = $(inherited) @executable_path/../Frameworks @loader_path/Frameworks @loader_path/../Frameworks - -FRAMEWORK_SEARCH_PATHS[sdk=iphone*] = $(inherited) $(SDKROOT)/Developer/Library/Frameworks -FRAMEWORK_SEARCH_PATHS[sdk=macosx*] = $(inherited) $(DEVELOPER_FRAMEWORKS_DIR) - -// Platform-specific - -// iOS -CODE_SIGN_IDENTITY[sdk=iphoneos*] = iPhone Developer -IPHONEOS_DEPLOYMENT_TARGET[sdk=iphone*] = 8.0 -TARGETED_DEVICE_FAMILY[sdk=iphone*] = 1,2 - -// OS X -FRAMEWORK_VERSION[sdk=macosx*] = A -COMBINE_HIDPI_IMAGES[sdk=macosx*] = YES \ No newline at end of file diff --git a/SQLite/Schema.swift b/SQLite/Schema.swift deleted file mode 100644 index a445e676..00000000 --- a/SQLite/Schema.swift +++ /dev/null @@ -1,488 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -public extension Database { - - public func create( - #table: Query, - temporary: Bool = false, - ifNotExists: Bool = false, - @noescape _ block: SchemaBuilder -> Void - ) -> Statement { - var builder = SchemaBuilder(table) - block(builder) - let create = createSQL("TABLE", temporary, false, ifNotExists, table.tableName.unaliased.SQL) - let columns = Expression.join(", ", builder.columns).compile() - return run("\(create) (\(columns))") - } - - public func create(#table: Query, temporary: Bool = false, ifNotExists: Bool = false, from: Query) -> Statement { - let create = createSQL("TABLE", temporary, false, ifNotExists, table.tableName.unaliased.SQL) - let expression = from.selectExpression - return run("\(create) AS \(expression.SQL)", expression.bindings) - } - - public func create(#vtable: Query, ifNotExists: Bool = false, using: Expression) -> Statement { - let create = createSQL("VIRTUAL TABLE", false, false, ifNotExists, vtable.tableName.unaliased.SQL) - return run("\(create) USING \(using.SQL)") - } - - public func rename(table tableName: String, to table: Query) -> Statement { - return run("ALTER TABLE \(quote(identifier: tableName)) RENAME TO \(table.tableName.unaliased.SQL)") - } - - public func alter( - #table: Query, - add column: Expression, - check: Expression? = nil, - defaultValue: V - ) -> Statement { - return alter(table, define(column, nil, false, false, check, Expression(value: defaultValue), nil)) - } - - public func alter( - #table: Query, - add column: Expression, - check: Expression? = nil, - defaultValue: V? = nil - ) -> Statement { - let value = defaultValue.map { Expression(value: $0) } - return alter(table, define(Expression(column), nil, true, false, check, value, nil)) - } - - public func alter( - #table: Query, - add column: Expression, - check: Expression? = nil, - references: Expression - ) -> Statement { - return alter(table, define(Expression(column), nil, true, false, check, nil, [ - Expression(literal: "REFERENCES"), namespace(references) - ])) - } - - public func alter( - #table: Query, - add column: Expression, - check: Expression? = nil, - defaultValue: V, - collate: Collation - ) -> Statement { - return alter(table, define(Expression(column), nil, false, false, check, Expression(value: defaultValue), [ - Expression(literal: "COLLATE"), Expression(collate.description) - ])) - } - - public func alter( - #table: Query, - add column: Expression, - check: Expression? = nil, - defaultValue: V? = nil, - collate: Collation - ) -> Statement { - let value = defaultValue.map { Expression(value: $0) } - return alter(table, define(Expression(column), nil, true, false, check, value, [ - Expression(literal: "COLLATE"), Expression(collate.description) - ])) - } - - private func alter(table: Query, _ definition: Expressible) -> Statement { - return run("ALTER TABLE \(table.tableName.unaliased.SQL) ADD COLUMN \(definition.expression.compile())") - } - - public func drop(#table: Query, ifExists: Bool = false) -> Statement { - return run(dropSQL("TABLE", ifExists, table.tableName.unaliased.SQL)) - } - - public func create( - index table: Query, - unique: Bool = false, - ifNotExists: Bool = false, - on columns: Expressible... - ) -> Statement { - let tableName = table.tableName.unaliased.SQL - let create = createSQL("INDEX", false, unique, ifNotExists, indexName(tableName, on: columns)) - let joined = Expression.join(", ", columns.map { $0.expression.ordered }) - return run("\(create) ON \(tableName) (\(joined.compile()))") - } - - public func drop(index table: Query, ifExists: Bool = false, on columns: Expressible...) -> Statement { - return run(dropSQL("INDEX", ifExists, indexName(table.tableName.unaliased.SQL, on: columns))) - } - - private func indexName(table: String, on columns: [Expressible]) -> String { - let string = join(" ", ["index", table, "on"] + columns.map { $0.expression.SQL }) - return quote(identifier: reduce(string, "") { underscored, character in - if character == "\"" { return underscored } - if "A"..."Z" ~= character || "a"..."z" ~= character { return underscored + String(character) } - return underscored + "_" - }) - } - - public func create(#view: Query, temporary: Bool = false, ifNotExists: Bool = false, from: Query) -> Statement { - let create = createSQL("VIEW", temporary, false, ifNotExists, view.tableName.unaliased.SQL) - let expression = from.selectExpression - return run("\(create) AS \(expression.SQL)", expression.bindings) - } - - public func drop(#view: Query, ifExists: Bool = false) -> Statement { - return run(dropSQL("VIEW", ifExists, view.tableName.unaliased.SQL)) - } - -} - -public final class SchemaBuilder { - - let table: Query - var columns = [Expressible]() - - private init(_ table: Query) { - self.table = table - } - - // MARK: - Column Constraints - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: Expression? - ) { - column(name, nil, false, unique, check, value.map { wrap("", $0) }) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: V? = nil - ) { - column(name, nil, false, unique, check, value.map { Expression(value: $0) }) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: Expression? - ) { - column(Expression(name), nil, true, unique, check, value.map { wrap("", $0) }) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: V? = nil - ) { - column(Expression(name), nil, true, unique, check, value.map { Expression(value: $0) }) - } - - public func column( - name: Expression, - primaryKey: Bool, - check: Expression? = nil, - defaultValue value: Expression? = nil - ) { - column(name, primaryKey ? .Default : nil, false, false, check, value.map { wrap("", $0) }, nil) - } - - // MARK: - INTEGER Columns - - // MARK: PRIMARY KEY - - public enum PrimaryKey { - - case Default - - case Autoincrement - - } - - public func column( - name: Expression, - primaryKey: PrimaryKey?, - check: Expression? = nil - ) { - column(name, primaryKey, false, false, check, nil, nil) - } - - // MARK: REFERENCES - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - references: Expression - ) { - assertForeignKeysEnabled() - let expressions: [Expressible] = [Expression(literal: "REFERENCES"), namespace(references)] - column(name, nil, false, unique, check, nil, expressions) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - references: Query - ) { - return column( - name, - unique: unique, - check: check, - references: Expression(references.tableName) - ) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - references: Expression - ) { - assertForeignKeysEnabled() - let expressions: [Expressible] = [Expression(literal: "REFERENCES"), namespace(references)] - column(Expression(name), nil, true, unique, check, nil, expressions) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - references: Query - ) { - return column( - name, - unique: unique, - check: check, - references: Expression(references.tableName) - ) - } - - // MARK: TEXT Columns (COLLATE) - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: Expression?, - collate: Collation - ) { - let expressions: [Expressible] = [Expression(literal: "COLLATE \(collate)")] - column(name, nil, false, unique, check, value, expressions) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: V? = nil, - collate: Collation - ) { - column(name, unique: unique, check: check, defaultValue: value.map { Expression(value: $0) }, collate: collate) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue value: Expression?, - collate: Collation - ) { - column(Expression(name), unique: unique, check: check, defaultValue: value, collate: collate) - } - - public func column( - name: Expression, - unique: Bool = false, - check: Expression? = nil, - defaultValue: V? = nil, - collate: Collation - ) { - let value = defaultValue.map { Expression(value: $0) } - column(Expression(name), unique: unique, check: check, defaultValue: value, collate: collate) - } - - // MARK: - - - private func column( - name: Expression, - _ primaryKey: PrimaryKey?, - _ null: Bool, - _ unique: Bool, - _ check: Expression?, - _ defaultValue: Expression?, - _ expressions: [Expressible]? = nil - ) { - columns.append(define(name, primaryKey, null, unique, check, defaultValue, expressions)) - } - - // MARK: - Table Constraints - - public func primaryKey(column: Expression) { - primaryKey([column]) - } - - public func primaryKey(columnA: Expression, _ B: Expression) { - primaryKey([columnA, B]) - } - - public func primaryKey(columnA: Expression, _ B: Expression, _ C: Expression) { - primaryKey([columnA, B, C]) - } - - private func primaryKey(composite: [Expressible]) { - let primaryKey = Expression.join(", ", composite) - columns.append(Expression(literal: "PRIMARY KEY(\(primaryKey.SQL))", primaryKey.bindings)) - } - - public func unique(column: Expressible...) { - let unique = Expression.join(", ", column) - columns.append(Expression(literal: "UNIQUE(\(unique.SQL))", unique.bindings)) - } - - public func check(condition: Expression) { - columns.append(Expression(literal: "CHECK (\(condition.SQL))", condition.bindings)) - } - - public enum Dependency: String { - - case NoAction = "NO ACTION" - - case Restrict = "RESTRICT" - - case SetNull = "SET NULL" - - case SetDefault = "SET DEFAULT" - - case Cascade = "CASCADE" - - } - - public func foreignKey( - column: Expression, - references: Expression, - update: Dependency? = nil, - delete: Dependency? = nil - ) { - assertForeignKeysEnabled() - var parts: [Expressible] = [Expression(literal: "FOREIGN KEY(\(column.SQL)) REFERENCES", column.bindings)] - parts.append(namespace(references)) - if let update = update { parts.append(Expression(literal: "ON UPDATE \(update.rawValue)")) } - if let delete = delete { parts.append(Expression(literal: "ON DELETE \(delete.rawValue)")) } - columns.append(Expression.join(" ", parts)) - } - - public func foreignKey( - column: Expression, - references: Expression, - update: Dependency? = nil, - delete: Dependency? = nil - ) { - assertForeignKeysEnabled() - foreignKey(Expression(column), references: references, update: update, delete: delete) - } - - public func foreignKey( - columns: (Expression, Expression), - references: (Expression, Expression), - update: Dependency? = nil, - delete: Dependency? = nil - ) { - let compositeA = Expression(Expression.join(", ", [columns.0, columns.1])) - let compositeB = Expression(Expression.join(", ", [references.0, references.1])) - foreignKey(compositeA, references: compositeB, update: update, delete: delete) - } - - public func foreignKey( - columns: (Expression, Expression, Expression), - references: (Expression, Expression, Expression), - update: Dependency? = nil, - delete: Dependency? = nil - ) { - let compositeA = Expression(Expression.join(", ", [columns.0, columns.1, columns.2])) - let compositeB = Expression(Expression.join(", ", [references.0, references.1, references.2])) - foreignKey(compositeA, references: compositeB, update: update, delete: delete) - } - - private func assertForeignKeysEnabled() { - assert(table.database.foreignKeys, "foreign key constraints are disabled (run `db.foreignKeys = true`)") - } - -} - -private func namespace(column: Expressible) -> Expressible { - let expression = column.expression - if !contains(expression.SQL, ".") { return expression } - let reference = reduce(expression.SQL, "") { SQL, character in - let string = String(character) - return SQL + (string == "." ? "(" : string) - } - return Expression(literal: "\(reference))", expression.bindings) -} - -private func define( - column: Expression, - primaryKey: SchemaBuilder.PrimaryKey?, - null: Bool, - unique: Bool, - check: Expression?, - defaultValue: Expression?, - expressions: [Expressible]? -) -> Expressible { - var parts: [Expressible] = [Expression(column), Expression(literal: V.declaredDatatype)] - if let primaryKey = primaryKey { - parts.append(Expression(literal: "PRIMARY KEY")) - if primaryKey == .Autoincrement { parts.append(Expression(literal: "AUTOINCREMENT")) } - } - if !null { parts.append(Expression(literal: "NOT NULL")) } - if unique { parts.append(Expression(literal: "UNIQUE")) } - if let check = check { parts.append(Expression(literal: "CHECK (\(check.SQL))", check.bindings)) } - if let value = defaultValue { parts.append(Expression(literal: "DEFAULT \(value.SQL)", value.bindings)) } - if let expressions = expressions { parts += expressions } - return Expression.join(" ", parts) -} - -private func createSQL( - type: String, - temporary: Bool, - unique: Bool, - ifNotExists: Bool, - name: String -) -> String { - var parts: [String] = ["CREATE"] - if temporary { parts.append("TEMPORARY") } - if unique { parts.append("UNIQUE") } - parts.append(type) - if ifNotExists { parts.append("IF NOT EXISTS") } - parts.append(name) - return Swift.join(" ", parts) -} - -private func dropSQL(type: String, ifExists: Bool, name: String) -> String { - var parts: [String] = ["DROP \(type)"] - if ifExists { parts.append("IF EXISTS") } - parts.append(name) - return Swift.join(" ", parts) -} diff --git a/SQLite/Statement.swift b/SQLite/Statement.swift deleted file mode 100644 index a819a43d..00000000 --- a/SQLite/Statement.swift +++ /dev/null @@ -1,313 +0,0 @@ -// -// SQLite.swift -// https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. -// - -internal let SQLITE_STATIC = sqlite3_destructor_type(COpaquePointer(bitPattern: 0)) -internal let SQLITE_TRANSIENT = sqlite3_destructor_type(COpaquePointer(bitPattern: -1)) - -/// A single SQL statement. -public final class Statement { - - private var handle: COpaquePointer = nil - - private let database: Database - - /// A cursor pointing to the current row. - public lazy var row: Cursor = Cursor(self) - - internal init(_ database: Database, _ SQL: String) { - self.database = database - database.try { sqlite3_prepare_v2(database.handle, SQL, -1, &self.handle, nil) } - } - - deinit { sqlite3_finalize(handle) } - - public lazy var columnCount: Int = Int(sqlite3_column_count(self.handle)) - - public lazy var columnNames: [String] = (0.. Statement { - return bind(values) - } - - /// Binds a list of parameters to a statement. - /// - /// :param: values A list of parameters to bind to the statement. - /// - /// :returns: The statement object (useful for chaining). - public func bind(values: [Binding?]) -> Statement { - if values.isEmpty { return self } - reset() - assert(values.count == Int(sqlite3_bind_parameter_count(handle)), "\(sqlite3_bind_parameter_count(handle)) values expected, \(values.count) passed") - for idx in 1...values.count { bind(values[idx - 1], atIndex: idx) } - return self - } - - /// Binds a dictionary of named parameters to a statement. - /// - /// :param: values A dictionary of named parameters to bind to the - /// statement. - /// - /// :returns: The statement object (useful for chaining). - public func bind(values: [String: Binding?]) -> Statement { - reset() - for (name, value) in values { - let idx = sqlite3_bind_parameter_index(handle, name) - assert(idx > 0, "parameter not found: \(name)") - bind(value, atIndex: Int(idx)) - } - return self - } - - private func bind(value: Binding?, atIndex idx: Int) { - if value == nil { - try { sqlite3_bind_null(self.handle, Int32(idx)) } - } else if let value = value as? Blob { - try { sqlite3_bind_blob(self.handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT) } - } else if let value = value as? Double { - try { sqlite3_bind_double(self.handle, Int32(idx), value) } - } else if let value = value as? Int64 { - try { sqlite3_bind_int64(self.handle, Int32(idx), value) } - } else if let value = value as? String { - try { sqlite3_bind_text(self.handle, Int32(idx), value, -1, SQLITE_TRANSIENT) } - } else if let value = value as? Bool { - bind(value.datatypeValue, atIndex: idx) - } else if let value = value as? Int { - bind(value.datatypeValue, atIndex: idx) - } else if let value = value { - fatalError("tried to bind unexpected value \(value)") - } - } - - // MARK: - Run - - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The statement object (useful for chaining). - public func run(bindings: Binding?...) -> Statement { - if !bindings.isEmpty { return run(bindings) } - reset(clearBindings: false) - while step() {} - return self - } - - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The statement object (useful for chaining). - public func run(bindings: [Binding?]) -> Statement { - return bind(bindings).run() - } - - /// :param: bindings A dictionary of named parameters to bind to the - /// statement. - /// - /// :returns: The statement object (useful for chaining). - public func run(bindings: [String: Binding?]) -> Statement { - return bind(bindings).run() - } - - // MARK: - Scalar - - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The first value of the first row returned. - public func scalar(bindings: Binding?...) -> Binding? { - if !bindings.isEmpty { return scalar(bindings) } - reset(clearBindings: false) - step() - return row[0] - } - - /// :param: bindings A list of parameters to bind to the statement. - /// - /// :returns: The first value of the first row returned. - public func scalar(bindings: [Binding?]) -> Binding? { - return bind(bindings).scalar() - } - - /// :param: bindings A dictionary of named parameters to bind to the - /// statement. - /// - /// :returns: The first value of the first row returned. - public func scalar(bindings: [String: Binding?]) -> Binding? { - return bind(bindings).scalar() - } - - // MARK: - - - public func step() -> Bool { - try { sqlite3_step(self.handle) } - return status == SQLITE_ROW - } - - private func reset(clearBindings: Bool = true) { - (status, reason) = (SQLITE_OK, nil) - sqlite3_reset(handle) - if (clearBindings) { sqlite3_clear_bindings(handle) } - } - - // MARK: - Error Handling - - /// :returns: Whether or not a statement has produced an error. - public var failed: Bool { - return reason != nil - } - - /// :returns: The reason for an error. - public var reason: String? - - private var status: Int32 = SQLITE_OK - - private func try(block: () -> Int32) { - if failed { return } - database.perform { - self.status = block() - if let error = self.database.lastError { - self.reason = error - assert(self.status == SQLITE_CONSTRAINT || self.status == SQLITE_INTERRUPT, "\(error)") - } - } - } - -} - -// MARK: - SequenceType -extension Statement: SequenceType { - - public func generate() -> Statement { - reset(clearBindings: false) - return self - } - -} - -// MARK: - GeneratorType -extension Statement: GeneratorType { - - /// :returns: The next row from the result set (or nil). - public func next() -> [Binding?]? { - return step() ? Array(row) : nil - } - -} - -// MARK: - Printable -extension Statement: Printable { - - public var description: String { - return String.fromCString(sqlite3_sql(handle))! - } - -} - -/// If `lhs` fails, return it. Otherwise, execute `rhs` and return its -/// associated statement. -public func && (lhs: Statement, @autoclosure rhs: () -> Statement) -> Statement { - if lhs.status == SQLITE_OK { lhs.run() } - return lhs.failed ? lhs : rhs() -} - -/// If `lhs` succeeds, return it. Otherwise, execute `rhs` and return its -/// associated statement. -public func || (lhs: Statement, @autoclosure rhs: () -> Statement) -> Statement { - if lhs.status == SQLITE_OK { lhs.run() } - return lhs.failed ? rhs() : lhs -} - -/// Cursors provide direct access to a statement’s current row. -public struct Cursor { - - private let handle: COpaquePointer - - private let columnCount: Int - - private init(_ statement: Statement) { - handle = statement.handle - columnCount = statement.columnCount - } - - public subscript(idx: Int) -> Blob { - let bytes = sqlite3_column_blob(handle, Int32(idx)) - let length = sqlite3_column_bytes(handle, Int32(idx)) - return Blob(bytes: bytes, length: Int(length)) - } - - public subscript(idx: Int) -> Double { - return sqlite3_column_double(handle, Int32(idx)) - } - - public subscript(idx: Int) -> Int64 { - return sqlite3_column_int64(handle, Int32(idx)) - } - - public subscript(idx: Int) -> String { - return String.fromCString(UnsafePointer(sqlite3_column_text(handle, Int32(idx)))) ?? "" - } - - public subscript(idx: Int) -> Bool { - return Bool.fromDatatypeValue(self[idx]) - } - - public subscript(idx: Int) -> Int { - return Int.fromDatatypeValue(self[idx]) - } - -} - -// MARK: - SequenceType -extension Cursor: SequenceType { - - public subscript(idx: Int) -> Binding? { - switch sqlite3_column_type(handle, Int32(idx)) { - case SQLITE_BLOB: - return self[idx] as Blob - case SQLITE_FLOAT: - return self[idx] as Double - case SQLITE_INTEGER: - return self[idx] as Int64 - case SQLITE_NULL: - return nil - case SQLITE_TEXT: - return self[idx] as String - case let type: - fatalError("unsupported column type: \(type)") - } - } - - public func generate() -> GeneratorOf { - var idx = 0 - return GeneratorOf { - idx >= self.columnCount ? Optional.None : self[idx++] - } - } - -} diff --git a/SQLiteCipher Tests/CipherTests.swift b/SQLiteCipher Tests/CipherTests.swift deleted file mode 100644 index d96d20af..00000000 --- a/SQLiteCipher Tests/CipherTests.swift +++ /dev/null @@ -1,23 +0,0 @@ -import XCTest -import SQLite - -class CipherTests: SQLiteTestCase { - - override func setUp() { - db.key("hello") - createUsersTable() - insertUser("alice") - - super.setUp() - } - - func test_key() { - XCTAssertEqual(1, users.count) - } - - func test_rekey() { - db.rekey("world") - XCTAssertEqual(1, users.count) - } - -} \ No newline at end of file diff --git a/SQLiteCipher.swift.podspec b/SQLiteCipher.swift.podspec new file mode 100644 index 00000000..33279807 --- /dev/null +++ b/SQLiteCipher.swift.podspec @@ -0,0 +1,18 @@ +require_relative 'Supporting Files/podspec.rb' + +Pod::Spec.new do |spec| + spec.name = 'SQLiteCipher.swift' + spec.version = '1.0.0.pre' + spec.summary = 'The SQLCipher flavor of SQLite.swift.' + + spec.description = <<-DESC + SQLiteCipher.swift is SQLite.swift built on top of SQLCipher. + DESC + + apply_shared_config spec, 'SQLiteCipher' + + spec.dependency 'SQLCipher' + spec.xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) SQLITE_HAS_CODEC=1' + } +end diff --git a/SQLiteCipher/Cipher.swift b/Source/Cipher/Cipher.swift similarity index 80% rename from SQLiteCipher/Cipher.swift rename to Source/Cipher/Cipher.swift index 3fd2f60c..48778b68 100644 --- a/SQLiteCipher/Cipher.swift +++ b/Source/Cipher/Cipher.swift @@ -1,7 +1,7 @@ // // SQLite.swift // https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. +// Copyright © 2014-2015 Stephen Celis. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,14 +22,14 @@ // THE SOFTWARE. // -extension Database { +extension Connection { - public func key(key: String) { - try { sqlite3_key(self.handle, key, Int32(count(key.utf8))) } + public func key(key: String) throws { + try check(sqlite3_key(handle, key, Int32(key.utf8.count))) } - public func rekey(key: String) { - try { sqlite3_rekey(self.handle, key, Int32(count(key.utf8))) } + public func rekey(key: String) throws { + try check(sqlite3_rekey(handle, key, Int32(key.utf8.count))) } } diff --git a/Source/Core/Blob.swift b/Source/Core/Blob.swift new file mode 100644 index 00000000..1b30ffa1 --- /dev/null +++ b/Source/Core/Blob.swift @@ -0,0 +1,61 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public struct Blob { + + public let bytes: [UInt8] + + public init(bytes: [UInt8]) { + self.bytes = bytes + } + + public init(bytes: UnsafePointer, length: Int) { + self.init(bytes: [UInt8](UnsafeBufferPointer( + start: UnsafePointer(bytes), count: length + ))) + } + + public func toHex() -> String { + return bytes.map { + ($0 < 16 ? "0" : "") + String($0, radix: 16, uppercase: false) + }.joinWithSeparator("") + } + +} + +extension Blob : CustomStringConvertible { + + public var description: String { + return "x'\(toHex())'" + } + +} + +extension Blob : Equatable { + +} + +public func ==(lhs: Blob, rhs: Blob) -> Bool { + return lhs.bytes == rhs.bytes +} diff --git a/Source/Core/Connection.swift b/Source/Core/Connection.swift new file mode 100644 index 00000000..b8ce7170 --- /dev/null +++ b/Source/Core/Connection.swift @@ -0,0 +1,676 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +import Dispatch + +/// A connection to SQLite. +public final class Connection { + + /// The location of a SQLite database. + public enum Location { + + /// An in-memory database (equivalent to `.URI(":memory:")`). + /// + /// See: + case InMemory + + /// A temporary, file-backed database (equivalent to `.URI("")`). + /// + /// See: + case Temporary + + /// A database located at the given URI filename (or path). + /// + /// See: + /// + /// - Parameter filename: A URI filename + case URI(String) + } + + public var handle: COpaquePointer { return _handle } + + private var _handle: COpaquePointer = nil + + /// Initializes a new SQLite connection. + /// + /// - Parameters: + /// + /// - location: The location of the database. Creates a new database if it + /// doesn’t already exist (unless in read-only mode). + /// + /// Default: `.InMemory`. + /// + /// - readonly: Whether or not to open the database in a read-only state. + /// + /// Default: `false`. + /// + /// - Returns: A new database connection. + public init(_ location: Location = .InMemory, readonly: Bool = false) throws { + let flags = readonly ? SQLITE_OPEN_READONLY : SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE + try check(sqlite3_open_v2(location.description, &_handle, flags | SQLITE_OPEN_FULLMUTEX, nil)) + dispatch_queue_set_specific(queue, Connection.queueKey, queueContext, nil) + } + + /// Initializes a new connection to a database. + /// + /// - Parameters: + /// + /// - filename: The location of the database. Creates a new database if + /// it doesn’t already exist (unless in read-only mode). + /// + /// - readonly: Whether or not to open the database in a read-only state. + /// + /// Default: `false`. + /// + /// - Throws: `Result.Error` iff a connection cannot be established. + /// + /// - Returns: A new database connection. + public convenience init(_ filename: String, readonly: Bool = false) throws { + try self.init(.URI(filename), readonly: readonly) + } + + deinit { + sqlite3_close_v2(handle) + } + + // MARK: - + + /// Whether or not the database was opened in a read-only state. + public var readonly: Bool { return sqlite3_db_readonly(handle, nil) == 1 } + + /// The last rowid inserted into the database via this connection. + public var lastInsertRowid: Int64? { + let rowid = sqlite3_last_insert_rowid(handle) + return rowid > 0 ? rowid : nil + } + + /// The last number of changes (inserts, updates, or deletes) made to the + /// database via this connection. + public var changes: Int { + return Int(sqlite3_changes(handle)) + } + + /// The total number of changes (inserts, updates, or deletes) made to the + /// database via this connection. + public var totalChanges: Int { + return Int(sqlite3_total_changes(handle)) + } + + // MARK: - Execute + + /// Executes a batch of SQL statements. + /// + /// - Parameter SQL: A batch of zero or more semicolon-separated SQL + /// statements. + /// + /// - Throws: `Result.Error` if query execution fails. + public func execute(SQL: String) throws { + try sync { try check(sqlite3_exec(self.handle, SQL, nil, nil, nil)) } + } + + // MARK: - Prepare + + /// Prepares a single SQL statement (with optional parameter bindings). + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: A prepared statement. + @warn_unused_result public func prepare(statement: String, _ bindings: Binding?...) -> Statement { + if !bindings.isEmpty { return prepare(statement, bindings) } + return Statement(self, statement) + } + + /// Prepares a single SQL statement and binds parameters to it. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: A prepared statement. + @warn_unused_result public func prepare(statement: String, _ bindings: [Binding?]) -> Statement { + return prepare(statement).bind(bindings) + } + + /// Prepares a single SQL statement and binds parameters to it. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A dictionary of named parameters to bind to the statement. + /// + /// - Returns: A prepared statement. + @warn_unused_result public func prepare(statement: String, _ bindings: [String: Binding?]) -> Statement { + return prepare(statement).bind(bindings) + } + + // MARK: - Run + + /// Runs a single SQL statement (with optional parameter bindings). + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + public func run(statement: String, _ bindings: Binding?...) throws -> Statement { + return try run(statement, bindings) + } + + /// Prepares, binds, and runs a single SQL statement. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + public func run(statement: String, _ bindings: [Binding?]) throws -> Statement { + return try prepare(statement).run(bindings) + } + + /// Prepares, binds, and runs a single SQL statement. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A dictionary of named parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement. + public func run(statement: String, _ bindings: [String: Binding?]) throws -> Statement { + return try prepare(statement).run(bindings) + } + + // MARK: - Scalar + + /// Runs a single SQL statement (with optional parameter bindings), + /// returning the first value of the first row. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result public func scalar(statement: String, _ bindings: Binding?...) -> Binding? { + return scalar(statement, bindings) + } + + /// Runs a single SQL statement (with optional parameter bindings), + /// returning the first value of the first row. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A list of parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result public func scalar(statement: String, _ bindings: [Binding?]) -> Binding? { + return prepare(statement).scalar(bindings) + } + + /// Runs a single SQL statement (with optional parameter bindings), + /// returning the first value of the first row. + /// + /// - Parameters: + /// + /// - statement: A single SQL statement. + /// + /// - bindings: A dictionary of named parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result public func scalar(statement: String, _ bindings: [String: Binding?]) -> Binding? { + return prepare(statement).scalar(bindings) + } + + // MARK: - Transactions + + /// The mode in which a transaction acquires a lock. + public enum TransactionMode : String { + + /// Defers locking the database till the first read/write executes. + case Deferred = "DEFERRED" + + /// Immediately acquires a reserved lock on the database. + case Immediate = "IMMEDIATE" + + /// Immediately acquires an exclusive lock on all databases. + case Exclusive = "EXCLUSIVE" + + } + + // TODO: Consider not requiring a throw to roll back? + /// Runs a transaction with the given mode. + /// + /// - Note: Transactions cannot be nested. To nest transactions, see + /// `savepoint()`, instead. + /// + /// - Parameters: + /// + /// - mode: The mode in which a transaction acquires a lock. + /// + /// Default: `.Deferred` + /// + /// - block: A closure to run SQL statements within the transaction. + /// The transaction will be committed when the block returns. The block + /// must throw to roll the transaction back. + /// + /// - Throws: `Result.Error`, and rethrows. + public func transaction(mode: TransactionMode = .Deferred, block: () throws -> Void) throws { + try transaction("BEGIN \(mode.rawValue) TRANSACTION", block, "COMMIT TRANSACTION", or: "ROLLBACK TRANSACTION") + } + + // TODO: Consider not requiring a throw to roll back? + // TODO: Consider removing ability to set a name? + /// Runs a transaction with the given savepoint name (if omitted, it will + /// generate a UUID). + /// + /// - SeeAlso: `transaction()`. + /// + /// - Parameters: + /// + /// - savepointName: A unique identifier for the savepoint (optional). + /// + /// - block: A closure to run SQL statements within the transaction. + /// The savepoint will be released (committed) when the block returns. + /// The block must throw to roll the savepoint back. + /// + /// - Throws: `SQLite.Result.Error`, and rethrows. + public func savepoint(name: String = NSUUID().UUIDString, block: () throws -> Void) throws { + let name = name.quote("'") + let savepoint = "SAVEPOINT \(name)" + + try transaction(savepoint, block, "RELEASE \(savepoint)", or: "ROLLBACK TO \(savepoint)") + } + + private func transaction(begin: String, _ block: () throws -> Void, _ commit: String, or rollback: String) throws { + return try sync { + try self.run(begin) + do { + try block() + } catch { + try self.run(rollback) + throw error + } + try self.run(commit) + } + } + + /// Interrupts any long-running queries. + public func interrupt() { + sqlite3_interrupt(handle) + } + + // MARK: - Handlers + + /// The number of seconds a connection will attempt to retry a statement + /// after encountering a busy signal (lock). + public var busyTimeout: Double = 0 { + didSet { + sqlite3_busy_timeout(handle, Int32(busyTimeout * 1_000)) + } + } + + /// Sets a handler to call after encountering a busy signal (lock). + /// + /// - Parameter callback: This block is executed during a lock in which a + /// busy error would otherwise be returned. It’s passed the number of + /// times it’s been called for this lock. If it returns `true`, it will + /// try again. If it returns `false`, no further attempts will be made. + public func busyHandler(callback: ((tries: Int) -> Bool)?) { + guard let callback = callback else { + sqlite3_busy_handler(handle, nil, nil) + busyHandler = nil + return + } + + let box: BusyHandler = { + callback(tries: Int($0)) ? 1 : 0 + } + sqlite3_busy_handler(handle, { callback, tries in + unsafeBitCast(callback, BusyHandler.self)(tries) + }, unsafeBitCast(box, UnsafeMutablePointer.self)) + busyHandler = box + } + private typealias BusyHandler = @convention(block) Int32 -> Int32 + private var busyHandler: BusyHandler? + + /// Sets a handler to call when a statement is executed with the compiled + /// SQL. + /// + /// - Parameter callback: This block is invoked when a statement is executed + /// with the compiled SQL as its argument. + /// + /// db.trace { SQL in print(SQL) } + public func trace(callback: (String -> Void)?) { + guard let callback = callback else { + sqlite3_trace(handle, nil, nil) + trace = nil + return + } + + let box: Trace = { callback(String.fromCString($0)!) } + sqlite3_trace(handle, { callback, SQL in + unsafeBitCast(callback, Trace.self)(SQL) + }, unsafeBitCast(box, UnsafeMutablePointer.self)) + trace = box + } + private typealias Trace = @convention(block) UnsafePointer -> Void + private var trace: Trace? + + /// Registers a callback to be invoked whenever a row is inserted, updated, + /// or deleted in a rowid table. + /// + /// - Parameter callback: A callback invoked with the `Operation` (one of + /// `.Insert`, `.Update`, or `.Delete`), database name, table name, and + /// rowid. + public func updateHook(callback: ((operation: Operation, db: String, table: String, rowid: Int64) -> Void)?) { + guard let callback = callback else { + sqlite3_update_hook(handle, nil, nil) + updateHook = nil + return + } + + let box: UpdateHook = { + callback( + operation: Operation(rawValue: $0), + db: String.fromCString($1)!, + table: String.fromCString($2)!, + rowid: $3 + ) + } + sqlite3_update_hook(handle, { callback, operation, db, table, rowid in + unsafeBitCast(callback, UpdateHook.self)(operation, db, table, rowid) + }, unsafeBitCast(box, UnsafeMutablePointer.self)) + updateHook = box + } + private typealias UpdateHook = @convention(block) (Int32, UnsafePointer, UnsafePointer, Int64) -> Void + private var updateHook: UpdateHook? + + /// Registers a callback to be invoked whenever a transaction is committed. + /// + /// - Parameter callback: A callback invoked whenever a transaction is + /// committed. If this callback throws, the transaction will be rolled + /// back. + public func commitHook(callback: (() throws -> Void)?) { + guard let callback = callback else { + sqlite3_commit_hook(handle, nil, nil) + commitHook = nil + return + } + + let box: CommitHook = { + do { + try callback() + } catch { + return 1 + } + return 0 + } + sqlite3_commit_hook(handle, { callback in + unsafeBitCast(callback, CommitHook.self)() + }, unsafeBitCast(box, UnsafeMutablePointer.self)) + commitHook = box + } + private typealias CommitHook = @convention(block) () -> Int32 + private var commitHook: CommitHook? + + /// Registers a callback to be invoked whenever a transaction rolls back. + /// + /// - Parameter callback: A callback invoked when a transaction is rolled + /// back. + public func rollbackHook(callback: (() -> Void)?) { + guard let callback = callback else { + sqlite3_rollback_hook(handle, nil, nil) + rollbackHook = nil + return + } + + let box: RollbackHook = { callback() } + sqlite3_rollback_hook(handle, { callback in + unsafeBitCast(callback, RollbackHook.self)() + }, unsafeBitCast(box, UnsafeMutablePointer.self)) + rollbackHook = box + } + private typealias RollbackHook = @convention(block) () -> Void + private var rollbackHook: RollbackHook? + + /// Creates or redefines a custom SQL function. + /// + /// - Parameters: + /// + /// - function: The name of the function to create or redefine. + /// + /// - argumentCount: The number of arguments that the function takes. If + /// `nil`, the function may take any number of arguments. + /// + /// Default: `nil` + /// + /// - deterministic: Whether or not the function is deterministic (_i.e._ + /// the function always returns the same result for a given input). + /// + /// Default: `false` + /// + /// - block: A block of code to run when the function is called. The block + /// is called with an array of raw SQL values mapped to the function’s + /// parameters and should return a raw SQL value (or nil). + public func createFunction(function: String, argumentCount: UInt? = nil, deterministic: Bool = false, _ block: (args: [Binding?]) -> Binding?) { + let argc = argumentCount.map { Int($0) } ?? -1 + let box: Function = { context, argc, argv in + let arguments: [Binding?] = (0...self), { context, argc, value in + unsafeBitCast(sqlite3_user_data(context), Function.self)(context, argc, value) + }, nil, nil, nil) + if functions[function] == nil { self.functions[function] = [:] } + functions[function]?[argc] = box + } + private typealias Function = @convention(block) (COpaquePointer, Int32, UnsafeMutablePointer) -> Void + private var functions = [String: [Int: Function]]() + + /// The return type of a collation comparison function. + public typealias ComparisonResult = NSComparisonResult + + /// Defines a new collating sequence. + /// + /// - Parameters: + /// + /// - collation: The name of the collation added. + /// + /// - block: A collation function that takes two strings and returns the + /// comparison result. + public func createCollation(collation: String, _ block: (lhs: String, rhs: String) -> ComparisonResult) { + let box: Collation = { lhs, rhs in + Int32(block(lhs: String.fromCString(UnsafePointer(lhs))!, rhs: String.fromCString(UnsafePointer(rhs))!).rawValue) + } + try! check(sqlite3_create_collation_v2(handle, collation, SQLITE_UTF8, unsafeBitCast(box, UnsafeMutablePointer.self), { callback, _, lhs, _, rhs in + unsafeBitCast(callback, Collation.self)(lhs, rhs) + }, nil)) + collations[collation] = box + } + private typealias Collation = @convention(block) (UnsafePointer, UnsafePointer) -> Int32 + private var collations = [String: Collation]() + + // MARK: - Error Handling + + func sync(block: () throws -> T) rethrows -> T { + var success: T? + var failure: ErrorType? + + let box: () -> Void = { + do { + success = try block() + } catch { + failure = error + } + } + + if dispatch_get_specific(Connection.queueKey) == queueContext { + box() + } else { + dispatch_sync(queue, box) // FIXME: rdar://problem/21389236 + } + + if let failure = failure { + try { () -> Void in throw failure }() + } + + return success! + } + + private var queue = dispatch_queue_create("SQLite.Database", DISPATCH_QUEUE_SERIAL) + + private static let queueKey = unsafeBitCast(Connection.self, UnsafePointer.self) + + private lazy var queueContext: UnsafeMutablePointer = unsafeBitCast(self, UnsafeMutablePointer.self) + +} + +extension Connection : CustomStringConvertible { + + public var description: String { + return String.fromCString(sqlite3_db_filename(handle, nil))! + } + +} + +extension Connection.Location : CustomStringConvertible { + + public var description: String { + switch self { + case .InMemory: + return ":memory:" + case .Temporary: + return "" + case .URI(let URI): + return URI + } + } + +} + +/// An SQL operation passed to update callbacks. +public enum Operation { + + /// An INSERT operation. + case Insert + + /// An UPDATE operation. + case Update + + /// A DELETE operation. + case Delete + + private init(rawValue: Int32) { + switch rawValue { + case SQLITE_INSERT: + self = .Insert + case SQLITE_UPDATE: + self = .Update + case SQLITE_DELETE: + self = .Delete + default: + fatalError("unhandled operation code: \(rawValue)") + } + } + +} + +public enum Result : ErrorType { + + private static let successCodes: Set = [SQLITE_OK, SQLITE_ROW, SQLITE_DONE] + + case Error(code: Int32, statement: Statement?) + + init?(errorCode: Int32, statement: Statement? = nil) { + guard !Result.successCodes.contains(errorCode) else { + return nil + } + self = Error(code: errorCode, statement: statement) + } + +} + +extension Result : CustomStringConvertible { + + public var description: String { + switch self { + case .Error(let code, _): + return String.fromCString(sqlite3_errstr(code))! + } + } + +} diff --git a/SQLite/RTree.swift b/Source/Core/SQLite-Bridging.h similarity index 70% rename from SQLite/RTree.swift rename to Source/Core/SQLite-Bridging.h index 9ff0f6c3..d15e8d56 100644 --- a/SQLite/RTree.swift +++ b/Source/Core/SQLite-Bridging.h @@ -1,7 +1,7 @@ // // SQLite.swift // https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. +// Copyright © 2014-2015 Stephen Celis. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,8 +22,16 @@ // THE SOFTWARE. // -public func rtree(primaryKey: Expression, columns: Expression...) -> Expression { - var definitions: [Expressible] = [primaryKey] - definitions.extend(columns.map { $0 }) - return wrap(__FUNCTION__, Expression.join(", ", definitions)) -} +@import Foundation; + +#ifndef COCOAPODS +#import "sqlite3.h" +#endif + +typedef struct SQLiteHandle SQLiteHandle; // CocoaPods workaround + +NS_ASSUME_NONNULL_BEGIN +typedef NSString * _Nullable (^_SQLiteTokenizerNextCallback)(const char * input, int * inputOffset, int * inputLength); +int _SQLiteRegisterTokenizer(SQLiteHandle * db, const char * module, const char * tokenizer, _Nullable _SQLiteTokenizerNextCallback callback); +NS_ASSUME_NONNULL_END + diff --git a/SQLite/SQLite-Bridging.m b/Source/Core/SQLite-Bridging.m similarity index 60% rename from SQLite/SQLite-Bridging.m rename to Source/Core/SQLite-Bridging.m index 26b6897d..ac5e54db 100644 --- a/SQLite/SQLite-Bridging.m +++ b/Source/Core/SQLite-Bridging.m @@ -1,7 +1,7 @@ // // SQLite.swift // https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. +// Copyright © 2014-2015 Stephen Celis. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -24,85 +24,11 @@ #import "SQLite-Bridging.h" -#import "fts3_tokenizer.h" - -static int __SQLiteBusyHandler(void * context, int tries) { - return ((__bridge _SQLiteBusyHandlerCallback)context)(tries); -} - -int _SQLiteBusyHandler(SQLiteHandle * handle, _SQLiteBusyHandlerCallback callback) { - if (callback) { - return sqlite3_busy_handler((sqlite3 *)handle, __SQLiteBusyHandler, (__bridge void *)callback); - } else { - return sqlite3_busy_handler((sqlite3 *)handle, 0, 0); - } -} - -static void __SQLiteTrace(void * context, const char * SQL) { - ((__bridge _SQLiteTraceCallback)context)(SQL); -} - -void _SQLiteTrace(SQLiteHandle * handle, _SQLiteTraceCallback callback) { - if (callback) { - sqlite3_trace((sqlite3 *)handle, __SQLiteTrace, (__bridge void *)callback); - } else { - sqlite3_trace((sqlite3 *)handle, 0, 0); - } -} - -static void __SQLiteUpdateHook(void * context, int operation, const char * db, const char * table, long long rowid) { - ((__bridge _SQLiteUpdateHookCallback)context)(operation, db, table, rowid); -} - -void _SQLiteUpdateHook(SQLiteHandle * handle, _SQLiteUpdateHookCallback callback) { - sqlite3_update_hook((sqlite3 *)handle, __SQLiteUpdateHook, (__bridge void *)callback); -} - -static int __SQLiteCommitHook(void * context) { - return ((__bridge _SQLiteCommitHookCallback)context)(); -} - -void _SQLiteCommitHook(SQLiteHandle * handle, _SQLiteCommitHookCallback callback) { - sqlite3_commit_hook((sqlite3 *)handle, __SQLiteCommitHook, (__bridge void *)callback); -} - -static void __SQLiteRollbackHook(void * context) { - ((__bridge _SQLiteRollbackHookCallback)context)(); -} - -void _SQLiteRollbackHook(SQLiteHandle * handle, _SQLiteRollbackHookCallback callback) { - sqlite3_rollback_hook((sqlite3 *)handle, __SQLiteRollbackHook, (__bridge void *)callback); -} - -static void __SQLiteCreateFunction(sqlite3_context * context, int argc, sqlite3_value ** argv) { - ((__bridge _SQLiteCreateFunctionCallback)sqlite3_user_data(context))((SQLiteContext *)context, argc, (SQLiteValue **)argv); -} - -int _SQLiteCreateFunction(SQLiteHandle * handle, const char * name, int argc, int deterministic, _SQLiteCreateFunctionCallback callback) { - if (callback) { - int flags = SQLITE_UTF8; - if (deterministic) { -#ifdef SQLITE_DETERMINISTIC - flags |= SQLITE_DETERMINISTIC; +#ifdef COCOAPODS +#import "sqlite3.h" #endif - } - return sqlite3_create_function_v2((sqlite3 *)handle, name, -1, flags, (__bridge void *)callback, &__SQLiteCreateFunction, 0, 0, 0); - } else { - return sqlite3_create_function_v2((sqlite3 *)handle, name, 0, 0, 0, 0, 0, 0, 0); - } -} - -static int __SQLiteCreateCollation(void * context, int len_lhs, const void * lhs, int len_rhs, const void * rhs) { - return ((__bridge _SQLiteCreateCollationCallback)context)(lhs, rhs); -} -int _SQLiteCreateCollation(SQLiteHandle * handle, const char * name, _SQLiteCreateCollationCallback callback) { - if (callback) { - return sqlite3_create_collation_v2((sqlite3 *)handle, name, SQLITE_UTF8, (__bridge void *)callback, &__SQLiteCreateCollation, 0); - } else { - return sqlite3_create_collation_v2((sqlite3 *)handle, name, 0, 0, 0, 0); - } -} +#import "fts3_tokenizer.h" #pragma mark - FTS @@ -126,7 +52,7 @@ static int __SQLiteTokenizerCreate(int argc, const char * const * argv, sqlite3_ if (!tokenizer) { return SQLITE_NOMEM; } - memset(tokenizer, 0, sizeof(* tokenizer)); // FIXME: needed? + memset(tokenizer, 0, sizeof(* tokenizer)); NSString * key = [NSString stringWithUTF8String:argv[0]]; tokenizer->callback = [__SQLiteTokenizerMap objectForKey:key]; diff --git a/Source/Core/Statement.swift b/Source/Core/Statement.swift new file mode 100644 index 00000000..f135d2cc --- /dev/null +++ b/Source/Core/Statement.swift @@ -0,0 +1,279 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +/// A single SQL statement. +public final class Statement { + + private var handle: COpaquePointer = nil + + private let connection: Connection + + init(_ connection: Connection, _ SQL: String) { + self.connection = connection + try! check(sqlite3_prepare_v2(connection.handle, SQL, -1, &handle, nil)) + } + + deinit { + sqlite3_finalize(handle) + } + + public lazy var columnCount: Int = Int(sqlite3_column_count(self.handle)) + + public lazy var columnNames: [String] = (0.. Statement { + return bind(values) + } + + /// Binds a list of parameters to a statement. + /// + /// - Parameter values: A list of parameters to bind to the statement. + /// + /// - Returns: The statement object (useful for chaining). + public func bind(values: [Binding?]) -> Statement { + if values.isEmpty { return self } + reset() + guard values.count == Int(sqlite3_bind_parameter_count(handle)) else { + fatalError("\(sqlite3_bind_parameter_count(handle)) values expected, \(values.count) passed") + } + for idx in 1...values.count { bind(values[idx - 1], atIndex: idx) } + return self + } + + /// Binds a dictionary of named parameters to a statement. + /// + /// - Parameter values: A dictionary of named parameters to bind to the + /// statement. + /// + /// - Returns: The statement object (useful for chaining). + public func bind(values: [String: Binding?]) -> Statement { + reset() + for (name, value) in values { + let idx = sqlite3_bind_parameter_index(handle, name) + guard idx > 0 else { + fatalError("parameter not found: \(name)") + } + bind(value, atIndex: Int(idx)) + } + return self + } + + private func bind(value: Binding?, atIndex idx: Int) { + if value == nil { + sqlite3_bind_null(handle, Int32(idx)) + } else if let value = value as? Blob { + sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.bytes.count), SQLITE_TRANSIENT) + } else if let value = value as? Double { + sqlite3_bind_double(handle, Int32(idx), value) + } else if let value = value as? Int64 { + sqlite3_bind_int64(handle, Int32(idx), value) + } else if let value = value as? String { + sqlite3_bind_text(handle, Int32(idx), value, -1, SQLITE_TRANSIENT) + } else if let value = value as? Int { + self.bind(value.datatypeValue, atIndex: idx) + } else if let value = value as? Bool { + self.bind(value.datatypeValue, atIndex: idx) + } else if let value = value { + fatalError("tried to bind unexpected value \(value)") + } + } + + /// - Parameter bindings: A list of parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement object (useful for chaining). + public func run(bindings: Binding?...) throws -> Statement { + guard bindings.isEmpty else { + return try run(bindings) + } + + reset(clearBindings: false) + repeat {} while try step() + return self + } + + /// - Parameter bindings: A list of parameters to bind to the statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement object (useful for chaining). + public func run(bindings: [Binding?]) throws -> Statement { + return try bind(bindings).run() + } + + /// - Parameter bindings: A dictionary of named parameters to bind to the + /// statement. + /// + /// - Throws: `Result.Error` if query execution fails. + /// + /// - Returns: The statement object (useful for chaining). + public func run(bindings: [String: Binding?]) throws -> Statement { + return try bind(bindings).run() + } + + /// - Parameter bindings: A list of parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result public func scalar(bindings: Binding?...) -> Binding? { + guard bindings.isEmpty else { + return scalar(bindings) + } + + reset(clearBindings: false) + try! step() + return row[0] + } + + /// - Parameter bindings: A list of parameters to bind to the statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result public func scalar(bindings: [Binding?]) -> Binding? { + return bind(bindings).scalar() + } + + + /// - Parameter bindings: A dictionary of named parameters to bind to the + /// statement. + /// + /// - Returns: The first value of the first row returned. + @warn_unused_result public func scalar(bindings: [String: Binding?]) -> Binding? { + return bind(bindings).scalar() + } + + public func step() throws -> Bool { + return try connection.sync { try check(sqlite3_step(self.handle)) == SQLITE_ROW } + } + + private func reset(clearBindings shouldClear: Bool = true) { + sqlite3_reset(handle) + if (shouldClear) { sqlite3_clear_bindings(handle) } + } + +} + +extension Statement : SequenceType { + + public func generate() -> Statement { + reset(clearBindings: false) + return self + } + +} + +extension Statement : GeneratorType { + + public func next() -> [Binding?]? { + return try! step() ? Array(row) : nil + } + +} + +extension Statement : CustomStringConvertible { + + public var description: String { + return String.fromCString(sqlite3_sql(handle))! + } + +} + +public struct Cursor { + + private let handle: COpaquePointer + + private let columnCount: Int + + private init(_ statement: Statement) { + handle = statement.handle + columnCount = statement.columnCount + } + + public subscript(idx: Int) -> Double { + return sqlite3_column_double(handle, Int32(idx)) + } + + public subscript(idx: Int) -> Int64 { + return sqlite3_column_int64(handle, Int32(idx)) + } + + public subscript(idx: Int) -> String { + return String.fromCString(UnsafePointer(sqlite3_column_text(handle, Int32(idx)))) ?? "" + } + + public subscript(idx: Int) -> Blob { + let bytes = sqlite3_column_blob(handle, Int32(idx)) + let length = Int(sqlite3_column_bytes(handle, Int32(idx))) + return Blob(bytes: bytes, length: length) + } + + // MARK: - + + public subscript(idx: Int) -> Bool { + return Bool.fromDatatypeValue(self[idx]) + } + + public subscript(idx: Int) -> Int { + return Int.fromDatatypeValue(self[idx]) + } + +} + +/// Cursors provide direct access to a statement’s current row. +extension Cursor : SequenceType { + + public subscript(idx: Int) -> Binding? { + switch sqlite3_column_type(handle, Int32(idx)) { + case SQLITE_BLOB: + return self[idx] as Blob + case SQLITE_FLOAT: + return self[idx] as Double + case SQLITE_INTEGER: + return self[idx] as Int64 + case SQLITE_NULL: + return nil + case SQLITE_TEXT: + return self[idx] as String + case let type: + fatalError("unsupported column type: \(type)") + } + } + + public func generate() -> AnyGenerator { + var idx = 0 + return anyGenerator { + idx >= self.columnCount ? Optional.None : self[idx++] + } + } + +} diff --git a/SQLite/Value.swift b/Source/Core/Value.swift similarity index 59% rename from SQLite/Value.swift rename to Source/Core/Value.swift index 567320bf..96e80371 100644 --- a/SQLite/Value.swift +++ b/Source/Core/Value.swift @@ -1,7 +1,7 @@ // // SQLite.swift // https://github.com/stephencelis/SQLite.swift -// Copyright (c) 2014-2015 Stephen Celis. +// Copyright © 2014-2015 Stephen Celis. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -22,20 +22,20 @@ // THE SOFTWARE. // -/// Binding is a protocol that SQLite.swift uses internally to directly map -/// SQLite types to Swift types. +/// - Warning: `Binding` is a protocol that SQLite.swift uses internally to +/// directly map SQLite types to Swift types. /// -/// Do not conform custom types to the Binding protocol. See the Value protocol, -/// instead. +/// Do not conform custom types to the Binding protocol. See the `Value` +/// protocol, instead. public protocol Binding {} -public protocol Number: Binding {} +public protocol Number : Binding {} -public protocol Value { +public protocol Value : Expressible { // extensions cannot have inheritance clauses typealias ValueType = Self - typealias Datatype: Binding + typealias Datatype : Binding static var declaredDatatype: String { get } @@ -45,107 +45,57 @@ public protocol Value { } -private let hexChars: [Character] = Array("0123456789abcdef") +extension Double : Number, Value { -public struct Blob: Equatable, Printable { + public static let declaredDatatype = "REAL" - public let data: [UInt8] - - public init(data: [UInt8]) { - self.data = data - } - - public init(bytes: UnsafePointer, length: Int) { - self.data = [UInt8](UnsafeBufferPointer( - start: UnsafePointer(bytes), - count: length - )) - } - - public func toHex() -> String { - var string: String = "" - string.reserveCapacity(data.count*2) - for byte in data { - let a = hexChars[Int(byte >> 4)] - let b = hexChars[Int(byte & 0xF)] - string.append(a) - string.append(b) - } - return string - } - - public var description: String { - return "x'\(toHex())'" - } - -} - -public func ==(lhs: Blob, rhs: Blob) -> Bool { - return lhs.data == rhs.data -} - - -extension Blob { - public var bytes: UnsafePointer { - return UnsafePointer(data) - } - - public var length: Int { - return data.count - } -} - -extension Blob: Binding, Value { - - public static var declaredDatatype = "BLOB" - - public static func fromDatatypeValue(datatypeValue: Blob) -> Blob { + public static func fromDatatypeValue(datatypeValue: Double) -> Double { return datatypeValue } - public var datatypeValue: Blob { + public var datatypeValue: Double { return self } } -extension Double: Number, Value { +extension Int64 : Number, Value { - public static var declaredDatatype = "REAL" + public static let declaredDatatype = "INTEGER" - public static func fromDatatypeValue(datatypeValue: Double) -> Double { + public static func fromDatatypeValue(datatypeValue: Int64) -> Int64 { return datatypeValue } - public var datatypeValue: Double { + public var datatypeValue: Int64 { return self } } -extension Int64: Number, Value { +extension String : Binding, Value { - public static var declaredDatatype = "INTEGER" + public static let declaredDatatype = "TEXT" - public static func fromDatatypeValue(datatypeValue: Int64) -> Int64 { + public static func fromDatatypeValue(datatypeValue: String) -> String { return datatypeValue } - public var datatypeValue: Int64 { + public var datatypeValue: String { return self } } -extension String: Binding, Value { +extension Blob : Binding, Value { - public static var declaredDatatype = "TEXT" + public static let declaredDatatype = "BLOB" - public static func fromDatatypeValue(datatypeValue: String) -> String { + public static func fromDatatypeValue(datatypeValue: Blob) -> Blob { return datatypeValue } - public var datatypeValue: String { + public var datatypeValue: Blob { return self } @@ -153,7 +103,7 @@ extension String: Binding, Value { // MARK: - -extension Bool: Binding, Value { +extension Bool : Binding, Value { public static var declaredDatatype = Int64.declaredDatatype @@ -167,7 +117,7 @@ extension Bool: Binding, Value { } -extension Int: Binding, Value { +extension Int : Number, Value { public static var declaredDatatype = Int64.declaredDatatype diff --git a/SQLite/fts3_tokenizer.h b/Source/Core/fts3_tokenizer.h similarity index 100% rename from SQLite/fts3_tokenizer.h rename to Source/Core/fts3_tokenizer.h diff --git a/Source/Extensions/FTS4.swift b/Source/Extensions/FTS4.swift new file mode 100644 index 00000000..13da8b97 --- /dev/null +++ b/Source/Extensions/FTS4.swift @@ -0,0 +1,156 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension Module { + + @warn_unused_result public static func FTS4(column: Expressible, _ more: Expressible...) -> Module { + return FTS4([column] + more) + } + + @warn_unused_result public static func FTS4(var columns: [Expressible] = [], tokenize tokenizer: Tokenizer? = nil) -> Module { + if let tokenizer = tokenizer { + columns.append("=".join([Expression(literal: "tokenize"), Expression(literal: tokenizer.description)])) + } + return Module(name: "fts4", arguments: columns) + } + +} + +extension VirtualTable { + + /// Builds an expression appended with a `MATCH` query against the given + /// pattern. + /// + /// let emails = VirtualTable("emails") + /// + /// emails.filter(emails.match("Hello")) + /// // SELECT * FROM "emails" WHERE "emails" MATCH 'Hello' + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: An expression appended with a `MATCH` query against the given + /// pattern. + @warn_unused_result public func match(pattern: String) -> Expression { + return "MATCH".infix(tableName(), pattern) + } + + @warn_unused_result public func match(pattern: Expression) -> Expression { + return "MATCH".infix(tableName(), pattern) + } + + @warn_unused_result public func match(pattern: Expression) -> Expression { + return "MATCH".infix(tableName(), pattern) + } + + /// Builds a copy of the query with a `WHERE … MATCH` clause. + /// + /// let emails = VirtualTable("emails") + /// + /// emails.match("Hello") + /// // SELECT * FROM "emails" WHERE "emails" MATCH 'Hello' + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A query with the given `WHERE … MATCH` clause applied. + @warn_unused_result public func match(pattern: String) -> QueryType { + return filter(match(pattern)) + } + + @warn_unused_result public func match(pattern: Expression) -> QueryType { + return filter(match(pattern)) + } + + @warn_unused_result public func match(pattern: Expression) -> QueryType { + return filter(match(pattern)) + } + +} + +public struct Tokenizer { + + public static let Simple = Tokenizer("simple") + + public static let Porter = Tokenizer("porter") + + @warn_unused_result public static func Unicode61(removeDiacritics removeDiacritics: Bool? = nil, tokenchars: Set = [], separators: Set = []) -> Tokenizer { + var arguments = [String]() + + if let removeDiacritics = removeDiacritics { + arguments.append("removeDiacritics=\(removeDiacritics ? 1 : 0)".quote()) + } + + if !tokenchars.isEmpty { + let joined = tokenchars.map { String($0) }.joinWithSeparator("") + arguments.append("tokenchars=\(joined)".quote()) + } + + if !separators.isEmpty { + let joined = separators.map { String($0) }.joinWithSeparator("") + arguments.append("separators=\(joined)".quote()) + } + + return Tokenizer("unicode61", arguments) + } + + @warn_unused_result public static func Custom(name: String) -> Tokenizer { + return Tokenizer(Tokenizer.moduleName.quote(), [name.quote()]) + } + + public let name: String + + public let arguments: [String] + + private init(_ name: String, _ arguments: [String] = []) { + self.name = name + self.arguments = arguments + } + + private static let moduleName = "SQLite.swift" + +} + +extension Tokenizer : CustomStringConvertible { + + public var description: String { + return ([name] + arguments).joinWithSeparator(" ") + } + +} + +extension Connection { + + public func registerTokenizer(submoduleName: String, next: String -> (String, Range)?) throws { + try check(_SQLiteRegisterTokenizer(handle, Tokenizer.moduleName, submoduleName) { input, offset, length in + let string = String.fromCString(input)! + if let (token, range) = next(string) { + let view = string.utf8 + offset.memory += string.substringToIndex(range.startIndex).utf8.count + length.memory = Int32(range.startIndex.samePositionIn(view).distanceTo(range.endIndex.samePositionIn(view))) + return token + } + return nil + }) + } + +} diff --git a/Source/Extensions/R*Tree.swift b/Source/Extensions/R*Tree.swift new file mode 100644 index 00000000..a5571ea6 --- /dev/null +++ b/Source/Extensions/R*Tree.swift @@ -0,0 +1,37 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension Module { + + @warn_unused_result public static func RTree(primaryKey: Expression, _ pairs: (Expression, Expression)...) -> Module { + var arguments: [Expressible] = [primaryKey] + + for pair in pairs { + arguments.appendContentsOf([pair.0, pair.1] as [Expressible]) + } + + return Module(name: "rtree", arguments: arguments) + } + +} diff --git a/Source/Foundation.swift b/Source/Foundation.swift new file mode 100644 index 00000000..ef39d3bf --- /dev/null +++ b/Source/Foundation.swift @@ -0,0 +1,104 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension NSData : Value { + + public class var declaredDatatype: String { + return Blob.declaredDatatype + } + + public class func fromDatatypeValue(dataValue: Blob) -> NSData { + return NSData(bytes: dataValue.bytes, length: dataValue.bytes.count) + } + + public var datatypeValue: Blob { + return Blob(bytes: bytes, length: length) + } + +} + +extension NSDate : Value { + + public class var declaredDatatype: String { + return String.declaredDatatype + } + + public class func fromDatatypeValue(stringValue: String) -> NSDate { + return dateFormatter.dateFromString(stringValue)! + } + + public var datatypeValue: String { + return dateFormatter.stringFromDate(self) + } + +} + +/// A global date formatter used to serialize and deserialize `NSDate` objects. +/// If multiple date formats are used in an application’s database(s), use a +/// custom `Value` type per additional format. +public var dateFormatter: NSDateFormatter = { + let formatter = NSDateFormatter() + formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS" + formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX") + formatter.timeZone = NSTimeZone(forSecondsFromGMT: 0) + return formatter +}() + +// FIXME: rdar://problem/18673897 // subscript… + +extension QueryType { + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + +} + +extension Row { + + public subscript(column: Expression) -> NSData { + return get(column) + } + public subscript(column: Expression) -> NSData? { + return get(column) + } + + public subscript(column: Expression) -> NSDate { + return get(column) + } + public subscript(column: Expression) -> NSDate? { + return get(column) + } + +} \ No newline at end of file diff --git a/Source/Helpers.swift b/Source/Helpers.swift new file mode 100644 index 00000000..78a63564 --- /dev/null +++ b/Source/Helpers.swift @@ -0,0 +1,130 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public typealias Star = (Expression?, Expression?) -> Expression + +public func *(_: Expression?, _: Expression?) -> Expression { + return Expression(literal: "*") +} + +public protocol _OptionalType { + + typealias WrappedType + +} + +extension Optional : _OptionalType { + + public typealias WrappedType = Wrapped + +} + +func check(resultCode: Int32, statement: Statement? = nil) throws -> Int32 { + if let error = Result(errorCode: resultCode, statement: statement) { + throw error + } + + return resultCode +} + +// let SQLITE_STATIC = unsafeBitCast(0, sqlite3_destructor_type.self) +let SQLITE_TRANSIENT = unsafeBitCast(-1, sqlite3_destructor_type.self) + +extension String { + + @warn_unused_result func quote(mark: Character = "\"") -> String { + let escaped = characters.reduce("") { string, character in + string + (character == mark ? "\(mark)\(mark)" : "\(character)") + } + return "\(mark)\(escaped)\(mark)" + } + + @warn_unused_result func join(expressions: [Expressible]) -> Expressible { + var (template, bindings) = ([String](), [Binding?]()) + for expressible in expressions { + let expression = expressible.expression + template.append(expression.template) + bindings.appendContentsOf(expression.bindings) + } + return Expression(template.joinWithSeparator(self), bindings) + } + + @warn_unused_result func infix(lhs: Expressible, _ rhs: Expressible, wrap: Bool = true) -> Expression { + let expression = Expression(" \(self) ".join([lhs, rhs]).expression) + guard wrap else { + return expression + } + return "".wrap(expression) + } + + @warn_unused_result func prefix(expressions: Expressible) -> Expressible { + return "\(self) ".wrap(expressions) as Expression + } + + @warn_unused_result func prefix(expressions: [Expressible]) -> Expressible { + return "\(self) ".wrap(expressions) as Expression + } + + @warn_unused_result func wrap(expression: Expressible) -> Expression { + return Expression("\(self)(\(expression.expression.template))", expression.expression.bindings) + } + + @warn_unused_result func wrap(expressions: [Expressible]) -> Expression { + return wrap(", ".join(expressions)) + } + +} + +@warn_unused_result func infix(lhs: Expressible, _ rhs: Expressible, wrap: Bool = true, function: String = __FUNCTION__) -> Expression { + return function.infix(lhs, rhs, wrap: wrap) +} + +@warn_unused_result func wrap(expression: Expressible, function: String = __FUNCTION__) -> Expression { + return function.wrap(expression) +} + +@warn_unused_result func wrap(expressions: [Expressible], function: String = __FUNCTION__) -> Expression { + return function.wrap(", ".join(expressions)) +} + +@warn_unused_result func transcode(literal: Binding?) -> String { + if let literal = literal { + if let literal = literal as? NSData { + let buf = UnsafeBufferPointer(start: UnsafePointer(literal.bytes), count: literal.length) + let hex = buf.map { String(format: "%02x", $0) }.joinWithSeparator("") + return "x'\(hex)'" + } + if let literal = literal as? String { return literal.quote("'") } + return "\(literal)" + } + return "NULL" +} + +@warn_unused_result func value(v: Binding) -> A { + return A.fromDatatypeValue(v as! A.Datatype) as! A +} + +@warn_unused_result func value(v: Binding?) -> A { + return value(v!) +} diff --git a/Source/Typed/AggregateFunctions.swift b/Source/Typed/AggregateFunctions.swift new file mode 100644 index 00000000..aa482e41 --- /dev/null +++ b/Source/Typed/AggregateFunctions.swift @@ -0,0 +1,251 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension ExpressionType where UnderlyingType : Value { + + /// Builds a copy of the expression prefixed with the `DISTINCT` keyword. + /// + /// let name = Expression("name") + /// name.distinct + /// // DISTINCT "name" + /// + /// - Returns: A copy of the expression prefixed with the `DISTINCT` + /// keyword. + public var distinct: Expression { + return Expression("DISTINCT \(template)", bindings) + } + + /// Builds a copy of the expression wrapped with the `count` aggregate + /// function. + /// + /// let name = Expression("name") + /// name.count + /// // count("name") + /// name.distinct.count + /// // count(DISTINCT "name") + /// + /// - Returns: A copy of the expression wrapped with the `count` aggregate + /// function. + public var count: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value { + + /// Builds a copy of the expression prefixed with the `DISTINCT` keyword. + /// + /// let name = Expression("name") + /// name.distinct + /// // DISTINCT "name" + /// + /// - Returns: A copy of the expression prefixed with the `DISTINCT` + /// keyword. + public var distinct: Expression { + return Expression("DISTINCT \(template)", bindings) + } + + /// Builds a copy of the expression wrapped with the `count` aggregate + /// function. + /// + /// let name = Expression("name") + /// name.count + /// // count("name") + /// name.distinct.count + /// // count(DISTINCT "name") + /// + /// - Returns: A copy of the expression wrapped with the `count` aggregate + /// function. + public var count: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType : protocol { + + /// Builds a copy of the expression wrapped with the `max` aggregate + /// function. + /// + /// let age = Expression("age") + /// age.max + /// // max("age") + /// + /// - Returns: A copy of the expression wrapped with the `max` aggregate + /// function. + public var max: Expression { + return wrap(self) + } + + /// Builds a copy of the expression wrapped with the `min` aggregate + /// function. + /// + /// let age = Expression("age") + /// age.min + /// // min("age") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var min: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : protocol { + + /// Builds a copy of the expression wrapped with the `max` aggregate + /// function. + /// + /// let age = Expression("age") + /// age.max + /// // max("age") + /// + /// - Returns: A copy of the expression wrapped with the `max` aggregate + /// function. + public var max: Expression { + return wrap(self) + } + + /// Builds a copy of the expression wrapped with the `min` aggregate + /// function. + /// + /// let age = Expression("age") + /// age.min + /// // min("age") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var min: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype : Number { + + /// Builds a copy of the expression wrapped with the `avg` aggregate + /// function. + /// + /// let salary = Expression("salary") + /// salary.average + /// // avg("salary") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var average: Expression { + return "avg".wrap(self) + } + + /// Builds a copy of the expression wrapped with the `sum` aggregate + /// function. + /// + /// let salary = Expression("salary") + /// salary.sum + /// // sum("salary") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var sum: Expression { + return wrap(self) + } + + /// Builds a copy of the expression wrapped with the `total` aggregate + /// function. + /// + /// let salary = Expression("salary") + /// salary.total + /// // total("salary") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var total: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value, UnderlyingType.WrappedType.Datatype : Number { + + /// Builds a copy of the expression wrapped with the `avg` aggregate + /// function. + /// + /// let salary = Expression("salary") + /// salary.average + /// // avg("salary") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var average: Expression { + return "avg".wrap(self) + } + + /// Builds a copy of the expression wrapped with the `sum` aggregate + /// function. + /// + /// let salary = Expression("salary") + /// salary.sum + /// // sum("salary") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var sum: Expression { + return wrap(self) + } + + /// Builds a copy of the expression wrapped with the `total` aggregate + /// function. + /// + /// let salary = Expression("salary") + /// salary.total + /// // total("salary") + /// + /// - Returns: A copy of the expression wrapped with the `min` aggregate + /// function. + public var total: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType == Int { + + @warn_unused_result static func count(star: Star) -> Expression { + return wrap(star(nil, nil)) + } + +} + +/// Builds an expression representing `count(*)` (when called with the `*` +/// function literal). +/// +/// count(*) +/// // count(*) +/// +/// - Returns: An expression returning `count(*)` (when called with the `*` +/// function literal). +@warn_unused_result public func count(star: Star) -> Expression { + return Expression.count(star) +} diff --git a/Source/Typed/Collation.swift b/Source/Typed/Collation.swift new file mode 100644 index 00000000..5a632055 --- /dev/null +++ b/Source/Typed/Collation.swift @@ -0,0 +1,69 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +/// A collating function used to compare to strings. +/// +/// - SeeAlso: +public enum Collation { + + /// Compares string by raw data. + case Binary + + /// Like binary, but folds uppercase ASCII letters into their lowercase + /// equivalents. + case Nocase + + /// Like binary, but strips trailing space. + case Rtrim + + /// A custom collating sequence identified by the given string, registered + /// using `Database.create(collation:…)` + case Custom(String) + +} + +extension Collation : Expressible { + + public var expression: Expression { + return Expression(literal: description) + } + +} + +extension Collation : CustomStringConvertible { + + public var description : String { + switch self { + case Binary: + return "BINARY" + case Nocase: + return "NOCASE" + case Rtrim: + return "RTRIM" + case Custom(let collation): + return collation.quote() + } + } + +} diff --git a/Source/Typed/CoreFunctions.swift b/Source/Typed/CoreFunctions.swift new file mode 100644 index 00000000..b929e40a --- /dev/null +++ b/Source/Typed/CoreFunctions.swift @@ -0,0 +1,680 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension ExpressionType where UnderlyingType : Number { + + /// Builds a copy of the expression wrapped with the `abs` function. + /// + /// let x = Expression("x") + /// x.absoluteValue + /// // abs("x") + /// + /// - Returns: A copy of the expression wrapped with the `abs` function. + public var absoluteValue : Expression { + return "abs".wrap(self) + } + +} + +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Number { + + /// Builds a copy of the expression wrapped with the `abs` function. + /// + /// let x = Expression("x") + /// x.absoluteValue + /// // abs("x") + /// + /// - Returns: A copy of the expression wrapped with the `abs` function. + public var absoluteValue : Expression { + return "abs".wrap(self) + } + +} + +extension ExpressionType where UnderlyingType == Double { + + /// Builds a copy of the expression wrapped with the `round` function. + /// + /// let salary = Expression("salary") + /// salary.round() + /// // round("salary") + /// salary.round(2) + /// // round("salary", 2) + /// + /// - Returns: A copy of the expression wrapped with the `round` function. + @warn_unused_result public func round(precision: Int? = nil) -> Expression { + guard let precision = precision else { + return wrap([self]) + } + return wrap([self, Int(precision)]) + } + +} + +extension ExpressionType where UnderlyingType == Double? { + + /// Builds a copy of the expression wrapped with the `round` function. + /// + /// let salary = Expression("salary") + /// salary.round() + /// // round("salary") + /// salary.round(2) + /// // round("salary", 2) + /// + /// - Returns: A copy of the expression wrapped with the `round` function. + @warn_unused_result public func round(precision: Int? = nil) -> Expression { + guard let precision = precision else { + return wrap(self) + } + return wrap([self, Int(precision)]) + } + +} + +extension ExpressionType where UnderlyingType : Value, UnderlyingType.Datatype == Int64 { + + /// Builds an expression representing the `random` function. + /// + /// Expression.random() + /// // random() + /// + /// - Returns: An expression calling the `random` function. + @warn_unused_result public static func random() -> Expression { + return "random".wrap([]) + } + +} + +extension ExpressionType where UnderlyingType == NSData { + + /// Builds an expression representing the `randomblob` function. + /// + /// Expression.random(16) + /// // randomblob(16) + /// + /// - Parameter length: Length in bytes. + /// + /// - Returns: An expression calling the `randomblob` function. + @warn_unused_result public static func random(length: Int) -> Expression { + return "randomblob".wrap([]) + } + + /// Builds an expression representing the `zeroblob` function. + /// + /// Expression.allZeros(16) + /// // zeroblob(16) + /// + /// - Parameter length: Length in bytes. + /// + /// - Returns: An expression calling the `zeroblob` function. + @warn_unused_result public static func allZeros(length: Int) -> Expression { + return "zeroblob".wrap([]) + } + + /// Builds a copy of the expression wrapped with the `length` function. + /// + /// let data = Expression("data") + /// data.length + /// // length("data") + /// + /// - Returns: A copy of the expression wrapped with the `length` function. + public var length: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType == NSData? { + + /// Builds a copy of the expression wrapped with the `length` function. + /// + /// let data = Expression("data") + /// data.length + /// // length("data") + /// + /// - Returns: A copy of the expression wrapped with the `length` function. + public var length: Expression { + return wrap(self) + } + +} + +extension ExpressionType where UnderlyingType == String { + + /// Builds a copy of the expression wrapped with the `length` function. + /// + /// let name = Expression("name") + /// name.length + /// // length("name") + /// + /// - Returns: A copy of the expression wrapped with the `length` function. + public var length: Expression { + return wrap(self) + } + + /// Builds a copy of the expression wrapped with the `lower` function. + /// + /// let name = Expression("name") + /// name.lowercaseString + /// // lower("name") + /// + /// - Returns: A copy of the expression wrapped with the `lower` function. + public var lowercaseString: Expression { + return "lower".wrap(self) + } + + /// Builds a copy of the expression wrapped with the `upper` function. + /// + /// let name = Expression("name") + /// name.uppercaseString + /// // lower("name") + /// + /// - Returns: A copy of the expression wrapped with the `upper` function. + public var uppercaseString: Expression { + return "upper".wrap(self) + } + + /// Builds a copy of the expression appended with a `LIKE` query against the + /// given pattern. + /// + /// let email = Expression("email") + /// email.like("%@example.com") + /// // "email" LIKE '%@example.com' + /// email.like("99\\%@%", escape: "\\") + /// // "email" LIKE '99\%@%' ESCAPE '\' + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - escape: An (optional) character designated for escaping + /// pattern-matching characters (*i.e.*, the `%` and `_` characters). + /// + /// - Returns: A copy of the expression appended with a `LIKE` query against + /// the given pattern. + @warn_unused_result public func like(pattern: String, escape character: Character? = nil) -> Expression { + guard let character = character else { + return "LIKE".infix(self, pattern) + } + return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) + } + + /// Builds a copy of the expression appended with a `GLOB` query against the + /// given pattern. + /// + /// let path = Expression("path") + /// path.glob("*.png") + /// // "path" GLOB '*.png' + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression appended with a `GLOB` query against + /// the given pattern. + @warn_unused_result public func glob(pattern: String) -> Expression { + return "GLOB".infix(self, pattern) + } + + /// Builds a copy of the expression appended with a `MATCH` query against + /// the given pattern. + /// + /// let title = Expression("title") + /// title.match("swift AND programming") + /// // "title" MATCH 'swift AND programming' + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression appended with a `MATCH` query + /// against the given pattern. + @warn_unused_result public func match(pattern: String) -> Expression { + return "MATCH".infix(self, pattern) + } + + /// Builds a copy of the expression appended with a `REGEXP` query against + /// the given pattern. + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression appended with a `REGEXP` query + /// against the given pattern. + @warn_unused_result public func regexp(pattern: String) -> Expression { + return "REGEXP".infix(self, pattern) + } + + /// Builds a copy of the expression appended with a `COLLATE` clause with + /// the given sequence. + /// + /// let name = Expression("name") + /// name.collate(.Nocase) + /// // "name" COLLATE NOCASE + /// + /// - Parameter collation: A collating sequence. + /// + /// - Returns: A copy of the expression appended with a `COLLATE` clause + /// with the given sequence. + @warn_unused_result public func collate(collation: Collation) -> Expression { + return "COLLATE".infix(self, collation) + } + + /// Builds a copy of the expression wrapped with the `ltrim` function. + /// + /// let name = Expression("name") + /// name.ltrim() + /// // ltrim("name") + /// name.ltrim([" ", "\t"]) + /// // ltrim("name", ' \t') + /// + /// - Parameter characters: A set of characters to trim. + /// + /// - Returns: A copy of the expression wrapped with the `ltrim` function. + @warn_unused_result public func ltrim(characters: Set? = nil) -> Expression { + guard let characters = characters else { + return wrap(self) + } + return wrap([self, String(characters)]) + } + + /// Builds a copy of the expression wrapped with the `rtrim` function. + /// + /// let name = Expression("name") + /// name.rtrim() + /// // rtrim("name") + /// name.rtrim([" ", "\t"]) + /// // rtrim("name", ' \t') + /// + /// - Parameter characters: A set of characters to trim. + /// + /// - Returns: A copy of the expression wrapped with the `rtrim` function. + @warn_unused_result public func rtrim(characters: Set? = nil) -> Expression { + guard let characters = characters else { + return wrap(self) + } + return wrap([self, String(characters)]) + } + + /// Builds a copy of the expression wrapped with the `trim` function. + /// + /// let name = Expression("name") + /// name.trim() + /// // trim("name") + /// name.trim([" ", "\t"]) + /// // trim("name", ' \t') + /// + /// - Parameter characters: A set of characters to trim. + /// + /// - Returns: A copy of the expression wrapped with the `trim` function. + @warn_unused_result public func trim(characters: Set? = nil) -> Expression { + guard let characters = characters else { + return wrap([self]) + } + return wrap([self, String(characters)]) + } + + /// Builds a copy of the expression wrapped with the `replace` function. + /// + /// let email = Expression("email") + /// email.replace("@mac.com", with: "@icloud.com") + /// // replace("email", '@mac.com', '@icloud.com') + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - replacement: The replacement string. + /// + /// - Returns: A copy of the expression wrapped with the `replace` function. + @warn_unused_result public func replace(pattern: String, with replacement: String) -> Expression { + return "replace".wrap([self, pattern, replacement]) + } + + @warn_unused_result public func substring(location: Int, length: Int? = nil) -> Expression { + guard let length = length else { + return "substr".wrap([self, location]) + } + return "substr".wrap([self, location, length]) + } + + public subscript(range: Range) -> Expression { + return substring(range.startIndex, length: range.endIndex - range.startIndex) + } + +} + +extension ExpressionType where UnderlyingType == String? { + + /// Builds a copy of the expression wrapped with the `length` function. + /// + /// let name = Expression("name") + /// name.length + /// // length("name") + /// + /// - Returns: A copy of the expression wrapped with the `length` function. + public var length: Expression { + return wrap(self) + } + + /// Builds a copy of the expression wrapped with the `lower` function. + /// + /// let name = Expression("name") + /// name.lowercaseString + /// // lower("name") + /// + /// - Returns: A copy of the expression wrapped with the `lower` function. + public var lowercaseString: Expression { + return "lower".wrap(self) + } + + /// Builds a copy of the expression wrapped with the `upper` function. + /// + /// let name = Expression("name") + /// name.uppercaseString + /// // lower("name") + /// + /// - Returns: A copy of the expression wrapped with the `upper` function. + public var uppercaseString: Expression { + return "upper".wrap(self) + } + + /// Builds a copy of the expression appended with a `LIKE` query against the + /// given pattern. + /// + /// let email = Expression("email") + /// email.like("%@example.com") + /// // "email" LIKE '%@example.com' + /// email.like("99\\%@%", escape: "\\") + /// // "email" LIKE '99\%@%' ESCAPE '\' + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - escape: An (optional) character designated for escaping + /// pattern-matching characters (*i.e.*, the `%` and `_` characters). + /// + /// - Returns: A copy of the expression appended with a `LIKE` query against + /// the given pattern. + @warn_unused_result public func like(pattern: String, escape character: Character? = nil) -> Expression { + guard let character = character else { + return "LIKE".infix(self, pattern) + } + return Expression("(\(template) LIKE ? ESCAPE ?)", bindings + [pattern, String(character)]) + } + + /// Builds a copy of the expression appended with a `GLOB` query against the + /// given pattern. + /// + /// let path = Expression("path") + /// path.glob("*.png") + /// // "path" GLOB '*.png' + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression appended with a `GLOB` query against + /// the given pattern. + @warn_unused_result public func glob(pattern: String) -> Expression { + return "GLOB".infix(self, pattern) + } + + /// Builds a copy of the expression appended with a `MATCH` query against + /// the given pattern. + /// + /// let title = Expression("title") + /// title.match("swift AND programming") + /// // "title" MATCH 'swift AND programming' + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression appended with a `MATCH` query + /// against the given pattern. + @warn_unused_result public func match(pattern: String) -> Expression { + return "MATCH".infix(self, pattern) + } + + /// Builds a copy of the expression appended with a `REGEXP` query against + /// the given pattern. + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression appended with a `REGEXP` query + /// against the given pattern. + @warn_unused_result public func regexp(pattern: String) -> Expression { + return "REGEXP".infix(self, pattern) + } + + /// Builds a copy of the expression appended with a `COLLATE` clause with + /// the given sequence. + /// + /// let name = Expression("name") + /// name.collate(.Nocase) + /// // "name" COLLATE NOCASE + /// + /// - Parameter collation: A collating sequence. + /// + /// - Returns: A copy of the expression appended with a `COLLATE` clause + /// with the given sequence. + @warn_unused_result public func collate(collation: Collation) -> Expression { + return "COLLATE".infix(self, collation) + } + + /// Builds a copy of the expression wrapped with the `ltrim` function. + /// + /// let name = Expression("name") + /// name.ltrim() + /// // ltrim("name") + /// name.ltrim([" ", "\t"]) + /// // ltrim("name", ' \t') + /// + /// - Parameter characters: A set of characters to trim. + /// + /// - Returns: A copy of the expression wrapped with the `ltrim` function. + @warn_unused_result public func ltrim(characters: Set? = nil) -> Expression { + guard let characters = characters else { + return wrap(self) + } + return wrap([self, String(characters)]) + } + + /// Builds a copy of the expression wrapped with the `rtrim` function. + /// + /// let name = Expression("name") + /// name.rtrim() + /// // rtrim("name") + /// name.rtrim([" ", "\t"]) + /// // rtrim("name", ' \t') + /// + /// - Parameter characters: A set of characters to trim. + /// + /// - Returns: A copy of the expression wrapped with the `rtrim` function. + @warn_unused_result public func rtrim(characters: Set? = nil) -> Expression { + guard let characters = characters else { + return wrap(self) + } + return wrap([self, String(characters)]) + } + + /// Builds a copy of the expression wrapped with the `trim` function. + /// + /// let name = Expression("name") + /// name.trim() + /// // trim("name") + /// name.trim([" ", "\t"]) + /// // trim("name", ' \t') + /// + /// - Parameter characters: A set of characters to trim. + /// + /// - Returns: A copy of the expression wrapped with the `trim` function. + @warn_unused_result public func trim(characters: Set? = nil) -> Expression { + guard let characters = characters else { + return wrap(self) + } + return wrap([self, String(characters)]) + } + + /// Builds a copy of the expression wrapped with the `replace` function. + /// + /// let email = Expression("email") + /// email.replace("@mac.com", with: "@icloud.com") + /// // replace("email", '@mac.com', '@icloud.com') + /// + /// - Parameters: + /// + /// - pattern: A pattern to match. + /// + /// - replacement: The replacement string. + /// + /// - Returns: A copy of the expression wrapped with the `replace` function. + @warn_unused_result public func replace(pattern: String, with replacement: String) -> Expression { + return "replace".wrap([self, pattern, replacement]) + } + + /// Builds a copy of the expression wrapped with the `substr` function. + /// + /// let title = Expression("title") + /// title.substr(-100) + /// // substr("title", -100) + /// title.substr(0, length: 100) + /// // substr("title", 0, 100) + /// + /// - Parameters: + /// + /// - location: The substring’s start index. + /// + /// - length: An optional substring length. + /// + /// - Returns: A copy of the expression wrapped with the `substr` function. + @warn_unused_result public func substring(location: Int, length: Int? = nil) -> Expression { + guard let length = length else { + return "substr".wrap([self, location]) + } + return "substr".wrap([self, location, length]) + } + + /// Builds a copy of the expression wrapped with the `substr` function. + /// + /// let title = Expression("title") + /// title[0..<100] + /// // substr("title", 0, 100) + /// + /// - Parameter range: The character index range of the substring. + /// + /// - Returns: A copy of the expression wrapped with the `substr` function. + public subscript(range: Range) -> Expression { + return substring(range.startIndex, length: range.endIndex - range.startIndex) + } + +} + +extension CollectionType where Generator.Element : Value, Index.Distance == Int { + + /// Builds a copy of the expression prepended with an `IN` check against the + /// collection. + /// + /// let name = Expression("name") + /// ["alice", "betty"].contains(name) + /// // "name" IN ('alice', 'betty') + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression prepended with an `IN` check against + /// the collection. + @warn_unused_result public func contains(expression: Expression) -> Expression { + let templates = [String](count: count, repeatedValue: "?").joinWithSeparator(", ") + return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + } + + /// Builds a copy of the expression prepended with an `IN` check against the + /// collection. + /// + /// let name = Expression("name") + /// ["alice", "betty"].contains(name) + /// // "name" IN ('alice', 'betty') + /// + /// - Parameter pattern: A pattern to match. + /// + /// - Returns: A copy of the expression prepended with an `IN` check against + /// the collection. + @warn_unused_result public func contains(expression: Expression) -> Expression { + let templates = [String](count: count, repeatedValue: "?").joinWithSeparator(", ") + return "IN".infix(expression, Expression("(\(templates))", map { $0.datatypeValue })) + } + +} + +/// Builds a copy of the given expressions wrapped with the `ifnull` function. +/// +/// let name = Expression("name") +/// name ?? "An Anonymous Coward" +/// // ifnull("name", 'An Anonymous Coward') +/// +/// - Parameters: +/// +/// - optional: An optional expression. +/// +/// - defaultValue: A fallback value for when the optional expression is +/// `nil`. +/// +/// - Returns: A copy of the given expressions wrapped with the `ifnull` +/// function. +public func ??(optional: Expression, defaultValue: V) -> Expression { + return "ifnull".wrap([optional, defaultValue]) +} + +/// Builds a copy of the given expressions wrapped with the `ifnull` function. +/// +/// let nick = Expression("nick") +/// let name = Expression("name") +/// nick ?? name +/// // ifnull("nick", "name") +/// +/// - Parameters: +/// +/// - optional: An optional expression. +/// +/// - defaultValue: A fallback expression for when the optional expression is +/// `nil`. +/// +/// - Returns: A copy of the given expressions wrapped with the `ifnull` +/// function. +public func ??(optional: Expression, defaultValue: Expression) -> Expression { + return "ifnull".wrap([optional, defaultValue]) +} + +/// Builds a copy of the given expressions wrapped with the `ifnull` function. +/// +/// let nick = Expression("nick") +/// let name = Expression("name") +/// nick ?? name +/// // ifnull("nick", "name") +/// +/// - Parameters: +/// +/// - optional: An optional expression. +/// +/// - defaultValue: A fallback expression for when the optional expression is +/// `nil`. +/// +/// - Returns: A copy of the given expressions wrapped with the `ifnull` +/// function. +public func ??(optional: Expression, defaultValue: Expression) -> Expression { + return "ifnull".wrap([optional, defaultValue]) +} diff --git a/Source/Typed/CustomFunctions.swift b/Source/Typed/CustomFunctions.swift new file mode 100644 index 00000000..068d0340 --- /dev/null +++ b/Source/Typed/CustomFunctions.swift @@ -0,0 +1,136 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public extension Connection { + + /// Creates or redefines a custom SQL function. + /// + /// - Parameters: + /// + /// - function: The name of the function to create or redefine. + /// + /// - deterministic: Whether or not the function is deterministic (_i.e._ + /// the function always returns the same result for a given input). + /// + /// Default: `false` + /// + /// - block: A block of code to run when the function is called. + /// The assigned types must be explicit. + /// + /// - Returns: A closure returning an SQL expression to call the function. + public func createFunction(function: String, deterministic: Bool = false, _ block: () -> Z) throws -> (() -> Expression) { + let fn = try createFunction(function, 0, deterministic) { _ in block() } + return { fn([]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: () -> Z?) throws -> (() -> Expression) { + let fn = try createFunction(function, 0, deterministic) { _ in block() } + return { fn([]) } + } + + // MARK: - + + public func createFunction(function: String, deterministic: Bool = false, _ block: A -> Z) throws -> (Expression -> Expression) { + let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } + return { arg in fn([arg]) } + } + + public func createFunction(function function: String, deterministic: Bool = false, _ block: A? -> Z) throws -> (Expression -> Expression) { + let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } + return { arg in fn([arg]) } + } + + public func createFunction(function function: String, deterministic: Bool = false, _ block: A -> Z?) throws -> (Expression -> Expression) { + let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0])) } + return { arg in fn([arg]) } + } + + public func createFunction(function function: String, deterministic: Bool = false, _ block: A? -> Z?) throws -> (Expression -> Expression) { + let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value)) } + return { arg in fn([arg]) } + } + + // MARK: - + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B) -> Z) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), value(args[1])) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B) -> Z) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), value(args[1])) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B?) -> Z) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), args[1].map(value)) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B) -> Z?) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), value(args[1])) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), args[1].map(value)) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B) -> Z?) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), value(args[1])) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(value(args[0]), args[1].map(value)) } + return { a, b in fn([a, b]) } + } + + public func createFunction(function: String, deterministic: Bool = false, _ block: (A?, B?) -> Z?) throws -> (Expression, Expression) -> Expression { + let fn = try createFunction(function, 1, deterministic) { args in block(args[0].map(value), args[1].map(value)) } + return { a, b in fn([a, b]) } + } + + // MARK: - + + private func createFunction(function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: [Binding?] -> Z) throws -> ([Expressible] -> Expression) { + createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in + block(arguments).datatypeValue + } + return { arguments in + function.quote().wrap(", ".join(arguments)) + } + } + + private func createFunction(function: String, _ argumentCount: UInt, _ deterministic: Bool, _ block: [Binding?] -> Z?) throws -> ([Expressible] -> Expression) { + createFunction(function, argumentCount: argumentCount, deterministic: deterministic) { arguments in + block(arguments)?.datatypeValue + } + return { arguments in + function.quote().wrap(", ".join(arguments)) + } + } + +} diff --git a/Source/Typed/Expression.swift b/Source/Typed/Expression.swift new file mode 100644 index 00000000..551831d7 --- /dev/null +++ b/Source/Typed/Expression.swift @@ -0,0 +1,139 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public protocol ExpressionType : Expressible { // extensions cannot have inheritance clauses + + typealias UnderlyingType = Void + + var template: String { get } + var bindings: [Binding?] { get } + + init(_ template: String, _ bindings: [Binding?]) + +} + +extension ExpressionType { + + public init(literal: String) { + self.init(literal, []) + } + + public init(_ identifier: String) { + self.init(literal: identifier.quote()) + } + + public init(_ expression: U) { + self.init(expression.template, expression.bindings) + } + +} + +/// An `Expression` represents a raw SQL fragment and any associated bindings. +public struct Expression : ExpressionType { + + public typealias UnderlyingType = Datatype + + public var template: String + public var bindings: [Binding?] + + public init(_ template: String, _ bindings: [Binding?]) { + self.template = template + self.bindings = bindings + } + +} + +public protocol Expressible { + + var expression: Expression { get } + +} + +extension Expressible { + + // naïve compiler for statements that can’t be bound, e.g., CREATE TABLE + // FIXME: use @testable and make internal + public func asSQL() -> String { + let expressed = expression + var idx = 0 + return expressed.template.characters.reduce("") { template, character in + return template + (character == "?" ? transcode(expressed.bindings[idx++]) : String(character)) + } + } + +} + +extension ExpressionType { + + public var expression: Expression { + return Expression(template, bindings) + } + + public var asc: Expressible { + return " ".join([self, Expression(literal: "ASC")]) + } + + public var desc: Expressible { + return " ".join([self, Expression(literal: "DESC")]) + } + +} + +extension ExpressionType where UnderlyingType : Value { + + public init(value: UnderlyingType) { + self.init("?", [value.datatypeValue]) + } + +} + +extension ExpressionType where UnderlyingType : _OptionalType, UnderlyingType.WrappedType : Value { + + public static var null: Self { + return self.init(value: nil) + } + + public init(value: UnderlyingType.WrappedType?) { + self.init("?", [value?.datatypeValue]) + } + +} + +extension Value { + + public var expression: Expression { + return Expression(value: self).expression + } + +} + +public let rowid = Expression("ROWID") + +public func cast(expression: Expression) -> Expression { + return Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) +} + +public func cast(expression: Expression) -> Expression { + return Expression("CAST (\(expression.template) AS \(U.declaredDatatype))", expression.bindings) +} diff --git a/Source/Typed/Operators.swift b/Source/Typed/Operators.swift new file mode 100644 index 00000000..816560a5 --- /dev/null +++ b/Source/Typed/Operators.swift @@ -0,0 +1,541 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +// TODO: use `@warn_unused_result` by the time operator functions support it + +public func +(lhs: Expression, rhs: Expression) -> Expression { + return "||".infix(lhs, rhs) +} + +public func +(lhs: Expression, rhs: Expression) -> Expression { + return "||".infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: Expression) -> Expression { + return "||".infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: Expression) -> Expression { + return "||".infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: String) -> Expression { + return "||".infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: String) -> Expression { + return "||".infix(lhs, rhs) +} +public func +(lhs: String, rhs: Expression) -> Expression { + return "||".infix(lhs, rhs) +} +public func +(lhs: String, rhs: Expression) -> Expression { + return "||".infix(lhs, rhs) +} + +// MARK: - + +public func +(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func +(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func -(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func -(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func *(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func *(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func /(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func /(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public prefix func -(rhs: Expression) -> Expression { + return wrap(rhs) +} +public prefix func -(rhs: Expression) -> Expression { + return wrap(rhs) +} + +// MARK: - + +public func %(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func %(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func <<(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <<(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func >>(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >>(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func &(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func &(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func |(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func |(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func ^(lhs: Expression, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: Expression, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: Expression, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: Expression, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: Expression, rhs: V) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: Expression, rhs: V) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: V, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} +public func ^(lhs: V, rhs: Expression) -> Expression { + return (~(lhs & rhs)) & (lhs | rhs) +} + +public prefix func ~(rhs: Expression) -> Expression { + return wrap(rhs) +} +public prefix func ~(rhs: Expression) -> Expression { + return wrap(rhs) +} + +// MARK: - + +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return "=".infix(lhs, rhs) +} +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return "=".infix(lhs, rhs) +} +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return "=".infix(lhs, rhs) +} +public func ==(lhs: Expression, rhs: Expression) -> Expression { + return "=".infix(lhs, rhs) +} +public func ==(lhs: Expression, rhs: V) -> Expression { + return "=".infix(lhs, rhs) +} +public func ==(lhs: Expression, rhs: V?) -> Expression { + guard let rhs = rhs else { return "IS".infix(lhs, Expression(value: nil)) } + return "=".infix(lhs, rhs) +} +public func ==(lhs: V, rhs: Expression) -> Expression { + return "=".infix(lhs, rhs) +} +public func ==(lhs: V?, rhs: Expression) -> Expression { + guard let lhs = lhs else { return "IS".infix(Expression(value: nil), rhs) } + return "=".infix(lhs, rhs) +} + +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func !=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func !=(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func !=(lhs: Expression, rhs: V?) -> Expression { + guard let rhs = rhs else { return "IS NOT".infix(lhs, Expression(value: nil)) } + return infix(lhs, rhs) +} +public func !=(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func !=(lhs: V?, rhs: Expression) -> Expression { + guard let lhs = lhs else { return "IS NOT".infix(Expression(value: nil), rhs) } + return infix(lhs, rhs) +} + +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func >=(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: Expression, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: Expression, rhs: V) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} +public func <=(lhs: V, rhs: Expression) -> Expression { + return infix(lhs, rhs) +} + +public func ~=, V.Datatype == I.Bound>(lhs: I, rhs: Expression) -> Expression { + return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) +} +public func ~=, V.Datatype == I.Bound>(lhs: I, rhs: Expression) -> Expression { + return Expression("\(rhs.template) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end]) +} + +// MARK: - + +public func &&(lhs: Expression, rhs: Expression) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Expression, rhs: Expression) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Expression, rhs: Expression) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Expression, rhs: Expression) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Expression, rhs: Bool) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Expression, rhs: Bool) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Bool, rhs: Expression) -> Expression { + return "AND".infix(lhs, rhs) +} +public func &&(lhs: Bool, rhs: Expression) -> Expression { + return "AND".infix(lhs, rhs) +} + +public func ||(lhs: Expression, rhs: Expression) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Expression, rhs: Expression) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Expression, rhs: Expression) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Expression, rhs: Expression) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Expression, rhs: Bool) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Expression, rhs: Bool) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Bool, rhs: Expression) -> Expression { + return "OR".infix(lhs, rhs) +} +public func ||(lhs: Bool, rhs: Expression) -> Expression { + return "OR".infix(lhs, rhs) +} + +public prefix func !(rhs: Expression) -> Expression { + return "NOT ".wrap(rhs) +} +public prefix func !(rhs: Expression) -> Expression { + return "NOT ".wrap(rhs) +} diff --git a/Source/Typed/Query.swift b/Source/Typed/Query.swift new file mode 100644 index 00000000..6eee17b5 --- /dev/null +++ b/Source/Typed/Query.swift @@ -0,0 +1,1093 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +public protocol QueryType : Expressible { + + var clauses: QueryClauses { get set } + + init(_ name: String, database: String?) + +} + +public protocol SchemaType : QueryType { + + static var identifier: String { get } + +} + +extension SchemaType { + + /// Builds a copy of the query with the `SELECT` clause applied. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// let email = Expression("email") + /// + /// users.select(id, email) + /// // SELECT "id", "email" FROM "users" + /// + /// - Parameter all: A list of expressions to select. + /// + /// - Returns: A query with the given `SELECT` clause applied. + public func select(column: Expressible, _ more: Expressible...) -> Self { + return select(false, [column] + more) + } + + /// Builds a copy of the query with the `SELECT DISTINCT` clause applied. + /// + /// let users = Table("users") + /// let email = Expression("email") + /// + /// users.select(distinct: email) + /// // SELECT DISTINCT "email" FROM "users" + /// + /// - Parameter columns: A list of expressions to select. + /// + /// - Returns: A query with the given `SELECT DISTINCT` clause applied. + public func select(distinct column: Expressible, _ more: Expressible...) -> Self { + return select(true, [column] + more) + } + + /// Builds a copy of the query with the `SELECT` clause applied. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// let email = Expression("email") + /// + /// users.select([id, email]) + /// // SELECT "id", "email" FROM "users" + /// + /// - Parameter all: A list of expressions to select. + /// + /// - Returns: A query with the given `SELECT` clause applied. + public func select(all: [Expressible]) -> Self { + return select(false, all) + } + + /// Builds a copy of the query with the `SELECT DISTINCT` clause applied. + /// + /// let users = Table("users") + /// let email = Expression("email") + /// + /// users.select(distinct: [email]) + /// // SELECT DISTINCT "email" FROM "users" + /// + /// - Parameter columns: A list of expressions to select. + /// + /// - Returns: A query with the given `SELECT DISTINCT` clause applied. + public func select(distinct columns: [Expressible]) -> Self { + return select(true, columns) + } + + /// Builds a copy of the query with the `SELECT *` clause applied. + /// + /// let users = Table("users") + /// + /// users.select(*) + /// // SELECT * FROM "users" + /// + /// - Parameter star: A star literal. + /// + /// - Returns: A query with the given `SELECT *` clause applied. + public func select(star: Star) -> Self { + return select(star(nil, nil)) + } + + /// Builds a copy of the query with the `SELECT DISTINCT *` clause applied. + /// + /// let users = Table("users") + /// + /// users.select(distinct: *) + /// // SELECT DISTINCT * FROM "users" + /// + /// - Parameter star: A star literal. + /// + /// - Returns: A query with the given `SELECT DISTINCT *` clause applied. + public func select(distinct star: Star) -> Self { + return select(distinct: star(nil, nil)) + } + + /// Builds a scalar copy of the query with the `SELECT` clause applied. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// + /// users.select(id) + /// // SELECT "id" FROM "users" + /// + /// - Parameter all: A list of expressions to select. + /// + /// - Returns: A query with the given `SELECT` clause applied. + public func select(column: Expression) -> ScalarQuery { + return select(false, [column]) + } + public func select(column: Expression) -> ScalarQuery { + return select(false, [column]) + } + + /// Builds a scalar copy of the query with the `SELECT DISTINCT` clause + /// applied. + /// + /// let users = Table("users") + /// let email = Expression("email") + /// + /// users.select(distinct: email) + /// // SELECT DISTINCT "email" FROM "users" + /// + /// - Parameter column: A list of expressions to select. + /// + /// - Returns: A query with the given `SELECT DISTINCT` clause applied. + public func select(distinct column: Expression) -> ScalarQuery { + return select(true, [column]) + } + public func select(distinct column: Expression) -> ScalarQuery { + return select(true, [column]) + } + + public var count: ScalarQuery { + return select(Expression.count(*)) + } + +} + +extension QueryType { + + private func select(distinct: Bool, _ columns: [Expressible]) -> Q { + var query = Q.init(clauses.from.name, database: clauses.from.database) + query.clauses = clauses + query.clauses.select = (distinct, columns) + return query + } + + // MARK: JOIN + + /// Adds a `JOIN` clause to the query. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// let posts = Table("posts") + /// let userId = Expression("user_id") + /// + /// users.join(posts, on: posts[userId] == users[id]) + /// // SELECT * FROM "users" INNER JOIN "posts" ON ("posts"."user_id" = "users"."id") + /// + /// - Parameters: + /// + /// - table: A query representing the other table. + /// + /// - condition: A boolean expression describing the join condition. + /// + /// - Returns: A query with the given `JOIN` clause applied. + public func join(table: QueryType, on condition: Expression) -> Self { + return join(table, on: Expression(condition)) + } + + /// Adds a `JOIN` clause to the query. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// let posts = Table("posts") + /// let userId = Expression("user_id") + /// + /// users.join(posts, on: posts[userId] == users[id]) + /// // SELECT * FROM "users" INNER JOIN "posts" ON ("posts"."user_id" = "users"."id") + /// + /// - Parameters: + /// + /// - table: A query representing the other table. + /// + /// - condition: A boolean expression describing the join condition. + /// + /// - Returns: A query with the given `JOIN` clause applied. + public func join(table: QueryType, on condition: Expression) -> Self { + return join(.Inner, table, on: condition) + } + + /// Adds a `JOIN` clause to the query. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// let posts = Table("posts") + /// let userId = Expression("user_id") + /// + /// users.join(.LeftOuter, posts, on: posts[userId] == users[id]) + /// // SELECT * FROM "users" LEFT OUTER JOIN "posts" ON ("posts"."user_id" = "users"."id") + /// + /// - Parameters: + /// + /// - type: The `JOIN` operator. + /// + /// - table: A query representing the other table. + /// + /// - condition: A boolean expression describing the join condition. + /// + /// - Returns: A query with the given `JOIN` clause applied. + public func join(type: JoinType, _ table: QueryType, on condition: Expression) -> Self { + return join(type, table, on: Expression(condition)) + } + + /// Adds a `JOIN` clause to the query. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// let posts = Table("posts") + /// let userId = Expression("user_id") + /// + /// users.join(.LeftOuter, posts, on: posts[userId] == users[id]) + /// // SELECT * FROM "users" LEFT OUTER JOIN "posts" ON ("posts"."user_id" = "users"."id") + /// + /// - Parameters: + /// + /// - type: The `JOIN` operator. + /// + /// - table: A query representing the other table. + /// + /// - condition: A boolean expression describing the join condition. + /// + /// - Returns: A query with the given `JOIN` clause applied. + public func join(type: JoinType, _ table: QueryType, on condition: Expression) -> Self { + var query = self + query.clauses.join.append((type: type, query: table, condition: table.clauses.filters.map { condition && $0 } ?? condition as Expressible)) + return query + } + + // MARK: WHERE + + /// Adds a condition to the query’s `WHERE` clause. + /// + /// let users = Table("users") + /// let id = Expression("id") + /// + /// users.filter(id == 1) + /// // SELECT * FROM "users" WHERE ("id" = 1) + /// + /// - Parameter condition: A boolean expression to filter on. + /// + /// - Returns: A query with the given `WHERE` clause applied. + public func filter(predicate: Expression) -> Self { + return filter(Expression(predicate)) + } + + /// Adds a condition to the query’s `WHERE` clause. + /// + /// let users = Table("users") + /// let age = Expression("age") + /// + /// users.filter(age >= 35) + /// // SELECT * FROM "users" WHERE ("age" >= 35) + /// + /// - Parameter condition: A boolean expression to filter on. + /// + /// - Returns: A query with the given `WHERE` clause applied. + public func filter(predicate: Expression) -> Self { + var query = self + query.clauses.filters = query.clauses.filters.map { $0 && predicate } ?? predicate + return query + } + + // MARK: GROUP BY + + /// Sets a `GROUP BY` clause on the query. + /// + /// - Parameter by: A list of columns to group by. + /// + /// - Returns: A query with the given `GROUP BY` clause applied. + public func group(by: Expressible...) -> Self { + return group(by) + } + + /// Sets a `GROUP BY` clause on the query. + /// + /// - Parameter by: A list of columns to group by. + /// + /// - Returns: A query with the given `GROUP BY` clause applied. + public func group(by: [Expressible]) -> Self { + return group(by, nil) + } + + /// Sets a `GROUP BY`-`HAVING` clause on the query. + /// + /// - Parameters: + /// + /// - by: A column to group by. + /// + /// - having: A condition determining which groups are returned. + /// + /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. + public func group(by: Expressible, having: Expression) -> Self { + return group([by], having: having) + } + + /// Sets a `GROUP BY`-`HAVING` clause on the query. + /// + /// - Parameters: + /// + /// - by: A column to group by. + /// + /// - having: A condition determining which groups are returned. + /// + /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. + public func group(by: Expressible, having: Expression) -> Self { + return group([by], having: having) + } + + /// Sets a `GROUP BY`-`HAVING` clause on the query. + /// + /// - Parameters: + /// + /// - by: A list of columns to group by. + /// + /// - having: A condition determining which groups are returned. + /// + /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. + public func group(by: [Expressible], having: Expression) -> Self { + return group(by, Expression(having)) + } + + /// Sets a `GROUP BY`-`HAVING` clause on the query. + /// + /// - Parameters: + /// + /// - by: A list of columns to group by. + /// + /// - having: A condition determining which groups are returned. + /// + /// - Returns: A query with the given `GROUP BY`–`HAVING` clause applied. + public func group(by: [Expressible], having: Expression) -> Self { + return group(by, having) + } + + private func group(by: [Expressible], _ having: Expression?) -> Self { + var query = self + query.clauses.group = (by, having) + return query + } + + // MARK: ORDER BY + + /// Sets an `ORDER BY` clause on the query. + /// + /// let users = Table("users") + /// let email = Expression("email") + /// let email = Expression("name") + /// + /// users.order(email.desc, name.asc) + /// // SELECT * FROM "users" ORDER BY "email" DESC, "name" ASC + /// + /// - Parameter by: An ordered list of columns and directions to sort by. + /// + /// - Returns: A query with the given `ORDER BY` clause applied. + public func order(by: Expressible...) -> Self { + var query = self + query.clauses.order = by + return query + } + + // MARK: LIMIT/OFFSET + + /// Sets the LIMIT clause (and resets any OFFSET clause) on the query. + /// + /// let users = Table("users") + /// + /// users.limit(20) + /// // SELECT * FROM "users" LIMIT 20 + /// + /// - Parameter length: The maximum number of rows to return (or `nil` to + /// return unlimited rows). + /// + /// - Returns: A query with the given LIMIT clause applied. + public func limit(length: Int?) -> Self { + return limit(length, nil) + } + + /// Sets LIMIT and OFFSET clauses on the query. + /// + /// let users = Table("users") + /// + /// users.limit(20, offset: 20) + /// // SELECT * FROM "users" LIMIT 20 OFFSET 20 + /// + /// - Parameters: + /// + /// - length: The maximum number of rows to return. + /// + /// - offset: The number of rows to skip. + /// + /// - Returns: A query with the given LIMIT and OFFSET clauses applied. + public func limit(length: Int, offset: Int) -> Self { + return limit(length, offset) + } + + // prevents limit(nil, offset: 5) + private func limit(length: Int?, _ offset: Int?) -> Self { + var query = self + query.clauses.limit = length.map { ($0, offset) } + return query + } + + // MARK: - Clauses + // + // MARK: SELECT + + // MARK: - + + private var selectClause: Expressible { + return " ".join([ + Expression(literal: clauses.select.distinct ? "SELECT DISTINCT" : "SELECT"), + ", ".join(clauses.select.columns), + Expression(literal: "FROM"), + tableName(alias: true) + ]) + } + + private var joinClause: Expressible? { + guard !clauses.join.isEmpty else { + return nil + } + + return " ".join(clauses.join.map { type, query, condition in + " ".join([ + Expression(literal: "\(type.rawValue) JOIN"), + query.tableName(), + Expression(literal: "ON"), + condition + ]) + }) + } + + private var whereClause: Expressible? { + guard let filters = clauses.filters else { + return nil + } + + return " ".join([ + Expression(literal: "WHERE"), + filters + ]) + } + + private var groupByClause: Expressible? { + guard let group = clauses.group else { + return nil + } + + let groupByClause = " ".join([ + Expression(literal: "GROUP BY"), + ", ".join(group.by) + ]) + + guard let having = group.having else { + return groupByClause + } + + return " ".join([ + groupByClause, + " ".join([ + Expression(literal: "HAVING"), + having + ]) + ]) + } + + private var orderClause: Expressible? { + guard !clauses.order.isEmpty else { + return nil + } + + return " ".join([ + Expression(literal: "ORDER BY"), + ", ".join(clauses.order) + ]) + } + + private var limitOffsetClause: Expressible? { + guard let limit = clauses.limit else { + return nil + } + + let limitClause = Expression(literal: "LIMIT \(limit.length)") + + guard let offset = limit.offset else { + return limitClause + } + + return " ".join([ + limitClause, + Expression(literal: "OFFSET \(offset)") + ]) + } + + // MARK: - Operations + // + // MARK: INSERT + + public func insert(value: Setter, _ more: Setter...) -> Insert { + return insert([value] + more) + } + + public func insert(values: [Setter]) -> Insert { + return insert(nil, values) + } + + public func insert(or onConflict: OnConflict, _ values: Setter...) -> Insert { + return insert(or: onConflict, values) + } + + public func insert(or onConflict: OnConflict, _ values: [Setter]) -> Insert { + return insert(onConflict, values) + } + + private 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(literal: "INSERT"), + or.map { Expression(literal: "OR \($0.rawValue)") }, + Expression(literal: "INTO"), + tableName(), + "".wrap(insert.columns) as Expression, + Expression(literal: "VALUES"), + "".wrap(insert.values) as Expression, + whereClause + ] + + return Insert(" ".join(clauses.flatMap { $0 }).expression) + } + + /// Runs an `INSERT` statement against the query with `DEFAULT VALUES`. + public func insert() -> Insert { + return Insert(" ".join([ + Expression(literal: "INSERT INTO"), + tableName(), + Expression(literal: "DEFAULT VALUES") + ]).expression) + } + + /// Runs an `INSERT` statement against the query with the results of another + /// query. + /// + /// - Parameter query: A query to `SELECT` results from. + /// + /// - Returns: The number of updated rows and statement. + public func insert(query: QueryType) -> Update { + return Update(" ".join([ + Expression(literal: "INSERT INTO"), + tableName(), + query.expression + ]).expression) + } + + // MARK: UPDATE + + public func update(values: Setter...) -> Update { + return update(values) + } + + public func update(values: [Setter]) -> Update { + let clauses: [Expressible?] = [ + Expression(literal: "UPDATE"), + tableName(), + Expression(literal: "SET"), + ", ".join(values.map { " = ".join([$0.column, $0.value]) }), + whereClause + ] + + return Update(" ".join(clauses.flatMap { $0 }).expression) + } + + // MARK: DELETE + + public func delete() -> Delete { + let clauses: [Expressible?] = [ + Expression(literal: "DELETE FROM"), + tableName(), + whereClause + ] + + return Delete(" ".join(clauses.flatMap { $0 }).expression) + } + + // MARK: EXISTS + + public var exists: Select { + return Select(" ".join([ + Expression(literal: "EXISTS"), + "".wrap(expression) as Expression + ]).expression) + } + + // MARK: - + + /// Prefixes a column expression with the query’s table name or alias. + /// + /// - Parameter column: A column expression. + /// + /// - Returns: A column expression namespaced with the query’s table name or + /// alias. + public func namespace(column: Expression) -> Expression { + return Expression(".".join([tableName(), column]).expression) + } + + // FIXME: rdar://problem/18673897 // subscript… + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + public subscript(column: Expression) -> Expression { + return namespace(column) + } + public subscript(column: Expression) -> Expression { + return namespace(column) + } + + /// Prefixes a star with the query’s table name or alias. + /// + /// - Parameter star: A literal `*`. + /// + /// - Returns: A `*` expression namespaced with the query’s table name or + /// alias. + public subscript(star: Star) -> Expression { + return namespace(star(nil, nil)) + } + + // MARK: - + + // TODO: alias support + func tableName(alias aliased: Bool = false) -> Expressible { + return database(namespace: clauses.from.name) + } + + func database(namespace name: String) -> Expressible { + let name = Expression(name) + + guard let database = clauses.from.database else { + return name + } + + return ".".join([Expression(database), name]) + } + + public var expression: Expression { + let clauses: [Expressible?] = [ + selectClause, + joinClause, + whereClause, + groupByClause, + orderClause, + limitOffsetClause + ] + + return " ".join(clauses.flatMap { $0 }).expression + } + +} + +// TODO: decide: simplify the below with a boxed type instead + +/// Queries a collection of chainable helper functions and expressions to build +/// executable SQL statements. +public struct Table : SchemaType { + + public static let identifier = "TABLE" + + public var clauses: QueryClauses + + public init(_ name: String, database: String? = nil) { + clauses = QueryClauses(name, database: database) + } + +} + +public struct View : SchemaType { + + public static let identifier = "VIEW" + + public var clauses: QueryClauses + + public init(_ name: String, database: String? = nil) { + clauses = QueryClauses(name, database: database) + } + +} + +public struct VirtualTable : SchemaType { + + public static let identifier = "VIRTUAL TABLE" + + public var clauses: QueryClauses + + public init(_ name: String, database: String? = nil) { + clauses = QueryClauses(name, database: database) + } + +} + +// TODO: make `ScalarQuery` work in `QueryType.select()`, `.filter()`, etc. + +public struct ScalarQuery : QueryType { + + public var clauses: QueryClauses + + public init(_ name: String, database: String? = nil) { + clauses = QueryClauses(name, database: database) + } + +} + +// TODO: decide: simplify the below with a boxed type instead + +public struct Select : ExpressionType { + + public var template: String + public var bindings: [Binding?] + + public init(_ template: String, _ bindings: [Binding?]) { + self.template = template + self.bindings = bindings + } + +} + +public struct Insert : ExpressionType { + + public var template: String + public var bindings: [Binding?] + + public init(_ template: String, _ bindings: [Binding?]) { + self.template = template + self.bindings = bindings + } + +} + +public struct Update : ExpressionType { + + public var template: String + public var bindings: [Binding?] + + public init(_ template: String, _ bindings: [Binding?]) { + self.template = template + self.bindings = bindings + } + +} + +public struct Delete : ExpressionType { + + public var template: String + public var bindings: [Binding?] + + public init(_ template: String, _ bindings: [Binding?]) { + self.template = template + self.bindings = bindings + } + +} + +extension Connection { + + public func prepare(query: QueryType) -> AnySequence { + let expression = query.expression + let statement = prepare(expression.template, expression.bindings) + + let columnNames: [String: Int] = { + var (columnNames, idx) = ([String: Int](), 0) + column: for each in query.clauses.select.columns ?? [Expression(literal: "*")] { + var names = each.expression.template.characters.split { $0 == "." }.map(String.init) + let column = names.removeLast() + let namespace = names.joinWithSeparator(".") + + func expandGlob(namespace: Bool) -> QueryType -> Void { + return { query in + var q = query.dynamicType.init(query.clauses.from.name, database: query.clauses.from.database) + q.clauses.select = query.clauses.select + let e = q.expression + var names = self.prepare(e.template, e.bindings).columnNames.map { $0.quote() } + if namespace { names = names.map { "\(query.tableName()).\($0)" } } + for name in names { columnNames[name] = idx++ } + } + } + + if column == "*" { + var select = query + select.clauses.select = (false, [Expression(literal: "*") as Expressible]) + let queries = [select] + query.clauses.join.map { $0.query } + if !namespace.isEmpty { + for q in queries { + if q.tableName().expression.template == namespace { + expandGlob(true)(q) + continue column + } + } + fatalError("no such table: \(namespace)") + } + for q in queries { + expandGlob(query.clauses.join.count > 0)(q) + } + continue + } + + columnNames[each.expression.template] = idx++ + } + return columnNames + }() + + return AnySequence { + anyGenerator { statement.next().map { Row(columnNames, $0) } } + } + } + + public func scalar(query: ScalarQuery) -> V { + let expression = query.expression + return value(scalar(expression.template, expression.bindings)) + } + + public func scalar(query: Select) -> V { + let expression = query.expression + return value(scalar(expression.template, expression.bindings)) + } + + public func pluck(query: QueryType) -> Row? { + return prepare(query.limit(1, query.clauses.limit?.offset)).generate().next() + } + + /// Runs an `Insert` query. + /// + /// - SeeAlso: `QueryType.insert(value:_:)` + /// - SeeAlso: `QueryType.insert(values:)` + /// - SeeAlso: `QueryType.insert(or:_:)` + /// - SeeAlso: `QueryType.insert()` + /// + /// - Parameter query: An insert query. + /// + /// - Returns: The insert’s rowid. + public func run(query: Insert) throws -> Int64 { + let expression = query.expression + return try sync { + try self.run(expression.template, expression.bindings) + return self.lastInsertRowid! + } + } + + /// Runs an `Update` query. + /// + /// - SeeAlso: `QueryType.insert(query:)` + /// - SeeAlso: `QueryType.update(values:)` + /// + /// - Parameter query: An update query. + /// + /// - Returns: The number of updated rows. + public func run(query: Update) throws -> Int { + let expression = query.expression + return try sync { + try self.run(expression.template, expression.bindings) + return self.changes + } + } + + /// Runs a `Delete` query. + /// + /// - SeeAlso: `QueryType.delete()` + /// + /// - Parameter query: A delete query. + /// + /// - Returns: The number of deleted rows. + public func run(query: Delete) throws -> Int { + let expression = query.expression + return try sync { + try self.run(expression.template, expression.bindings) + return self.changes + } + } + +} + +public struct Row { + + private let columnNames: [String: Int] + + private let values: [Binding?] + + private init(_ columnNames: [String: Int], _ values: [Binding?]) { + self.columnNames = columnNames + self.values = values + } + + /// Returns a row’s value for the given column. + /// + /// - Parameter column: An expression representing a column selected in a Query. + /// + /// - Returns: The value for the given column. + public func get(column: Expression) -> V { + return get(Expression(column))! + } + public func get(column: Expression) -> V? { + func valueAtIndex(idx: Int) -> V? { + if let value = values[idx] as? V.Datatype { return (V.fromDatatypeValue(value) as! V) } + return nil + } + + if let idx = columnNames[column.template] { return valueAtIndex(idx) } + + let similar = Array(columnNames.keys).filter { $0.hasSuffix(".\(column.template)") } + if similar.count == 1 { return valueAtIndex(columnNames[similar[0]]!) } + + if similar.count > 1 { + fatalError("ambiguous column '\(column.template)' (please disambiguate: \(similar))") + } + fatalError("no such column '\(column.template)' in columns: \(columnNames.keys.sort())") + } + + // FIXME: rdar://problem/18673897 // subscript… + + public subscript(column: Expression) -> Blob { + return get(column) + } + public subscript(column: Expression) -> Blob? { + return get(column) + } + + public subscript(column: Expression) -> Bool { + return get(column) + } + public subscript(column: Expression) -> Bool? { + return get(column) + } + + public subscript(column: Expression) -> Double { + return get(column) + } + public subscript(column: Expression) -> Double? { + return get(column) + } + + public subscript(column: Expression) -> Int { + return get(column) + } + public subscript(column: Expression) -> Int? { + return get(column) + } + + public subscript(column: Expression) -> Int64 { + return get(column) + } + public subscript(column: Expression) -> Int64? { + return get(column) + } + + public subscript(column: Expression) -> String { + return get(column) + } + public subscript(column: Expression) -> String? { + return get(column) + } + +} + +/// Determines the join operator for a query’s `JOIN` clause. +public enum JoinType : String { + + /// A `CROSS` join. + case Cross = "CROSS" + + /// An `INNER` join. + case Inner = "INNER" + + /// A `LEFT OUTER` join. + case LeftOuter = "LEFT OUTER" + +} + +/// ON CONFLICT resolutions. +public enum OnConflict: String { + + case Replace = "REPLACE" + + case Rollback = "ROLLBACK" + + case Abort = "ABORT" + + case Fail = "FAIL" + + case Ignore = "IGNORE" + +} + +// MARK: - Private + +public struct QueryClauses { + + var select = (distinct: false, columns: [Expression(literal: "*") as Expressible]) + + var from: (name: String, database: String?) + + var join = [(type: JoinType, query: QueryType, condition: Expressible)]() + + var filters: Expression? + + var group: (by: [Expressible], having: Expression?)? + + var order = [Expressible]() + + var limit: (length: Int, offset: Int?)? + + private init(_ name: String, database: String?) { + self.from = (name, database) + } + +} diff --git a/Source/Typed/Schema.swift b/Source/Typed/Schema.swift new file mode 100644 index 00000000..a768b740 --- /dev/null +++ b/Source/Typed/Schema.swift @@ -0,0 +1,513 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +extension SchemaType { + + // MARK: - DROP TABLE / VIEW / VIRTUAL TABLE + + public func drop(ifExists ifExists: Bool = false) -> String { + return drop(Self.identifier, tableName(), ifExists) + } + +} + +extension Table { + + // MARK: - CREATE TABLE + + public func create(temporary temporary: Bool = false, ifNotExists: Bool = false, @noescape block: TableBuilder -> Void) -> String { + let builder = TableBuilder() + + block(builder) + + let clauses: [Expressible?] = [ + create(Table.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists), + "".wrap(builder.definitions) as Expression + ] + + return " ".join(clauses.flatMap { $0 }).asSQL() + } + + public func create(query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String { + let clauses: [Expressible?] = [ + create(Table.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists), + Expression(literal: "AS"), + query + ] + + return " ".join(clauses.flatMap { $0 }).asSQL() + } + + // MARK: - ALTER TABLE … ADD COLUMN + + public func addColumn(name: Expression, check: Expression? = nil, defaultValue: V) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) + } + + public func addColumn(name: Expression, check: Expression, defaultValue: V) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, nil)) + } + + public func addColumn(name: Expression, check: Expression? = nil, defaultValue: V? = nil) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) + } + + public func addColumn(name: Expression, check: Expression, defaultValue: V? = nil) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, nil)) + } + + public func addColumn(name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) + } + + public func addColumn(name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil)) + } + + public func addColumn(name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) + } + + public func addColumn(name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil)) + } + + public func addColumn(name: Expression, check: Expression? = nil, defaultValue: V, collate: Collation) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) + } + + public func addColumn(name: Expression, check: Expression, defaultValue: V, collate: Collation) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, false, false, check, defaultValue, nil, collate)) + } + + public func addColumn(name: Expression, check: Expression? = nil, defaultValue: V? = nil, collate: Collation) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) + } + + public func addColumn(name: Expression, check: Expression, defaultValue: V? = nil, collate: Collation) -> String { + return addColumn(definition(name, V.declaredDatatype, nil, true, false, check, defaultValue, nil, collate)) + } + + private func addColumn(expression: Expressible) -> String { + return " ".join([ + Expression(literal: "ALTER TABLE"), + tableName(), + Expression(literal: "ADD COLUMN"), + expression + ]).asSQL() + } + + // MARK: - ALTER TABLE … RENAME TO + + public func rename(to: Table) -> String { + return rename(to: to) + } + + // MARK: - CREATE INDEX + + public func createIndex(columns: Expressible...) -> String { + return createIndex(columns) + } + + public func createIndex(columns: [Expressible], unique: Bool = false, ifNotExists: Bool = false) -> String { + let clauses: [Expressible?] = [ + create("INDEX", indexName(columns), unique ? .Unique : nil, ifNotExists), + Expression(literal: "ON"), + tableName(), + "".wrap(columns) as Expression + ] + + return " ".join(clauses.flatMap { $0 }).asSQL() + } + + // MARK: - DROP INDEX + + public func dropIndex(columns: Expressible...) -> String { + return dropIndex(columns) + } + + public func dropIndex(columns: [Expressible], ifExists: Bool = false) -> String { + return drop("INDEX", indexName(columns), ifExists) + } + + private func indexName(columns: [Expressible]) -> Expressible { + let string = (["index", clauses.from.name, "on"] + columns.map { $0.expression.template }).joinWithSeparator(" ").lowercaseString + + let index = string.characters.reduce("") { underscored, character in + guard character != "\"" else { + return underscored + } + guard "a"..."z" ~= character || "0"..."9" ~= character else { + return underscored + "_" + } + return underscored + String(character) + } + + return database(namespace: index) + } + +} + +extension View { + + // MARK: - CREATE VIEW + + public func create(query: QueryType, temporary: Bool = false, ifNotExists: Bool = false) -> String { + let clauses: [Expressible?] = [ + create(View.identifier, tableName(), temporary ? .Temporary : nil, ifNotExists), + Expression(literal: "AS"), + query + ] + + return " ".join(clauses.flatMap { $0 }).asSQL() + } + +} + +extension VirtualTable { + + // MARK: - CREATE VIRTUAL TABLE + + public func create(using: Module, ifNotExists: Bool = false) -> String { + let clauses: [Expressible?] = [ + create(VirtualTable.identifier, tableName(), nil, ifNotExists), + Expression(literal: "USING"), + using + ] + + return " ".join(clauses.flatMap { $0 }).asSQL() + } + + // MARK: - ALTER TABLE … RENAME TO + + public func rename(to: VirtualTable) -> String { + return rename(to: to) + } + +} + +public final class TableBuilder { + + private var definitions = [Expressible]() + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: V) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression) { + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V) { + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression) { + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: V) { + column(name, V.declaredDatatype, nil, true, unique, check, defaultValue, nil, nil) + } + + public func column(name: Expression, primaryKey: Bool, check: Expression? = nil, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, primaryKey ? .Default : nil, false, false, check, defaultValue, nil, nil) + } + + public func column(name: Expression, primaryKey: Bool, check: Expression, defaultValue: Expression? = nil) { + column(name, V.declaredDatatype, primaryKey ? .Default : nil, false, false, check, defaultValue, nil, nil) + } + + public func column(name: Expression, primaryKey: PrimaryKey, check: Expression? = nil) { + column(name, V.declaredDatatype, primaryKey, false, false, check, nil, nil, nil) + } + + public func column(name: Expression, primaryKey: PrimaryKey, check: Expression) { + column(name, V.declaredDatatype, primaryKey, false, false, check, nil, nil, nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) { + column(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) { + column(name, V.declaredDatatype, nil, false, unique, check, nil, (table, other), nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, references table: QueryType, _ other: Expression) { + column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, references table: QueryType, _ other: Expression) { + column(name, V.declaredDatatype, nil, true, unique, check, nil, (table, other), nil) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression? = nil, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: Expression, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression? = nil, defaultValue: V, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression? = nil, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: Expression, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + public func column(name: Expression, unique: Bool = false, check: Expression, defaultValue: V, collate: Collation) { + column(name, V.declaredDatatype, nil, false, unique, check, defaultValue, nil, collate) + } + + private func column(name: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) { + definitions.append(definition(name, datatype, primaryKey, null, unique, check, defaultValue, references, collate)) + } + + // MARK: - + + public func primaryKey(column: Expression) { + primaryKey([column]) + } + + public func primaryKey(compositeA: Expression, _ b: Expression) { + primaryKey([compositeA, b]) + } + + public func primaryKey(compositeA: Expression, _ b: Expression, _ c: Expression) { + primaryKey([compositeA, b, c]) + } + + private func primaryKey(composite: [Expressible]) { + definitions.append("PRIMARY KEY".prefix(composite)) + } + + public func unique(columns: Expressible...) { + unique(columns) + } + + public func unique(columns: [Expressible]) { + definitions.append("UNIQUE".prefix(columns)) + } + + public func check(condition: Expression) { + check(Expression(condition)) + } + + public func check(condition: Expression) { + definitions.append("CHECK".prefix(condition)) + } + + public enum Dependency: String { + + case NoAction = "NO ACTION" + + case Restrict = "RESTRICT" + + case SetNull = "SET NULL" + + case SetDefault = "SET DEFAULT" + + case Cascade = "CASCADE" + + } + + public func foreignKey(column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { + foreignKey(column, (table, other), update, delete) + } + + public func foreignKey(column: Expression, references table: QueryType, _ other: Expression, update: Dependency? = nil, delete: Dependency? = nil) { + foreignKey(column, (table, other), update, delete) + } + + public func foreignKey(composite: (Expression, Expression), references table: QueryType, _ other: (Expression, Expression), update: Dependency? = nil, delete: Dependency? = nil) { + let composite = ", ".join([composite.0, composite.1]) + let references = (table, ", ".join([other.0, other.1])) + + foreignKey(composite, references, update, delete) + } + + public func foreignKey(composite: (Expression, Expression, Expression), references table: QueryType, _ other: (Expression, Expression, Expression), update: Dependency? = nil, delete: Dependency? = nil) { + let composite = ", ".join([composite.0, composite.1, composite.2]) + let references = (table, ", ".join([other.0, other.1, other.2])) + + foreignKey(composite, references, update, delete) + } + + private func foreignKey(column: Expressible, _ references: (QueryType, Expressible), _ update: Dependency?, _ delete: Dependency?) { + let clauses: [Expressible?] = [ + "FOREIGN KEY".prefix(column), + reference(references), + update.map { Expression(literal: "ON UPDATE \($0.rawValue)") }, + delete.map { Expression(literal: "ON DELETE \($0.rawValue)") } + ] + + definitions.append(" ".join(clauses.flatMap { $0 })) + } + +} + +public enum PrimaryKey { + + case Default + + case Autoincrement + +} + +public struct Module { + + private let name: String + + private let arguments: [Expressible] + + public init(_ name: String, _ arguments: [Expressible]) { + self.init(name: name.quote(), arguments: arguments) + } + + init(name: String, arguments: [Expressible]) { + self.name = name + self.arguments = arguments + } + +} + +extension Module : Expressible { + + public var expression: Expression { + return name.wrap(arguments) + } + +} + +// MARK: - Private + +private extension QueryType { + + func create(identifier: String, _ name: Expressible, _ modifier: Modifier?, _ ifNotExists: Bool) -> Expressible { + let clauses: [Expressible?] = [ + Expression(literal: "CREATE"), + modifier.map { Expression(literal: $0.rawValue) }, + Expression(literal: identifier), + ifNotExists ? Expression(literal: "IF NOT EXISTS") : nil, + name + ] + + return " ".join(clauses.flatMap { $0 }) + } + + func rename(to to: Self) -> String { + return " ".join([ + Expression(literal: "ALTER TABLE"), + tableName(), + Expression(literal: "RENAME TO"), + Expression(to.clauses.from.name) + ]).asSQL() + } + + func drop(identifier: String, _ name: Expressible, _ ifExists: Bool) -> String { + let clauses: [Expressible?] = [ + Expression(literal: "DROP \(identifier)"), + ifExists ? Expression(literal: "IF EXISTS") : nil, + name + ] + + return " ".join(clauses.flatMap { $0 }).asSQL() + } + +} + +private func definition(column: Expressible, _ datatype: String, _ primaryKey: PrimaryKey?, _ null: Bool, _ unique: Bool, _ check: Expressible?, _ defaultValue: Expressible?, _ references: (QueryType, Expressible)?, _ collate: Collation?) -> Expressible { + let clauses: [Expressible?] = [ + column, + Expression(literal: datatype), + primaryKey.map { Expression(literal: $0 == .Autoincrement ? "PRIMARY KEY AUTOINCREMENT" : "PRIMARY KEY") }, + null ? nil : Expression(literal: "NOT NULL"), + unique ? Expression(literal: "UNIQUE") : nil, + check.map { " ".join([Expression(literal: "CHECK"), $0]) }, + defaultValue.map { "DEFAULT".prefix($0) }, + references.map(reference), + collate.map { " ".join([Expression(literal: "COLLATE"), $0]) } + ] + + return " ".join(clauses.flatMap { $0 }) +} + +private func reference(primary: (QueryType, Expressible)) -> Expressible { + return " ".join([ + Expression(literal: "REFERENCES"), + primary.0.tableName(), + "".wrap(primary.1) as Expression + ]) +} + +private enum Modifier : String { + + case Unique = "UNIQUE" + + case Temporary = "TEMPORARY" + +} diff --git a/Source/Typed/Setter.swift b/Source/Typed/Setter.swift new file mode 100644 index 00000000..d8bf775a --- /dev/null +++ b/Source/Typed/Setter.swift @@ -0,0 +1,275 @@ +// +// SQLite.swift +// https://github.com/stephencelis/SQLite.swift +// Copyright © 2014-2015 Stephen Celis. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +infix operator <- { + associativity left + precedence 135 + assignment +} + +public struct Setter { + + let column: Expressible + let value: Expressible + + private init(column: Expression, value: Expression) { + self.column = column + self.value = value + } + + private init(column: Expression, value: V) { + self.column = column + self.value = value + } + + private init(column: Expression, value: Expression) { + self.column = column + self.value = value + } + + private init(column: Expression, value: Expression) { + self.column = column + self.value = value + } + + private init(column: Expression, value: V?) { + self.column = column + self.value = Expression(value: value) + } + +} + +extension Setter : Expressible { + + public var expression: Expression { + return "=".infix(column, value, wrap: false) + } + +} + +public func <-(column: Expression, value: Expression) -> Setter { + return Setter(column: column, value: value) +} +public func <-(column: Expression, value: V) -> Setter { + return Setter(column: column, value: value) +} +public func <-(column: Expression, value: Expression) -> Setter { + return Setter(column: column, value: value) +} +public func <-(column: Expression, value: Expression) -> Setter { + return Setter(column: column, value: value) +} +public func <-(column: Expression, value: V?) -> Setter { + return Setter(column: column, value: value) +} + +public func +=(column: Expression, value: Expression) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: String) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: Expression) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: Expression) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: String) -> Setter { + return column <- column + value +} + +public func +=(column: Expression, value: Expression) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: V) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: Expression) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: Expression) -> Setter { + return column <- column + value +} +public func +=(column: Expression, value: V) -> Setter { + return column <- column + value +} + +public func -=(column: Expression, value: Expression) -> Setter { + return column <- column - value +} +public func -=(column: Expression, value: V) -> Setter { + return column <- column - value +} +public func -=(column: Expression, value: Expression) -> Setter { + return column <- column - value +} +public func -=(column: Expression, value: Expression) -> Setter { + return column <- column - value +} +public func -=(column: Expression, value: V) -> Setter { + return column <- column - value +} + +public func *=(column: Expression, value: Expression) -> Setter { + return column <- column * value +} +public func *=(column: Expression, value: V) -> Setter { + return column <- column * value +} +public func *=(column: Expression, value: Expression) -> Setter { + return column <- column * value +} +public func *=(column: Expression, value: Expression) -> Setter { + return column <- column * value +} +public func *=(column: Expression, value: V) -> Setter { + return column <- column * value +} + +public func /=(column: Expression, value: Expression) -> Setter { + return column <- column / value +} +public func /=(column: Expression, value: V) -> Setter { + return column <- column / value +} +public func /=(column: Expression, value: Expression) -> Setter { + return column <- column / value +} +public func /=(column: Expression, value: Expression) -> Setter { + return column <- column / value +} +public func /=(column: Expression, value: V) -> Setter { + return column <- column / value +} + +public func %=(column: Expression, value: Expression) -> Setter { + return column <- column % value +} +public func %=(column: Expression, value: V) -> Setter { + return column <- column % value +} +public func %=(column: Expression, value: Expression) -> Setter { + return column <- column % value +} +public func %=(column: Expression, value: Expression) -> Setter { + return column <- column % value +} +public func %=(column: Expression, value: V) -> Setter { + return column <- column % value +} + +public func <<=(column: Expression, value: Expression) -> Setter { + return column <- column << value +} +public func <<=(column: Expression, value: V) -> Setter { + return column <- column << value +} +public func <<=(column: Expression, value: Expression) -> Setter { + return column <- column << value +} +public func <<=(column: Expression, value: Expression) -> Setter { + return column <- column << value +} +public func <<=(column: Expression, value: V) -> Setter { + return column <- column << value +} + +public func >>=(column: Expression, value: Expression) -> Setter { + return column <- column >> value +} +public func >>=(column: Expression, value: V) -> Setter { + return column <- column >> value +} +public func >>=(column: Expression, value: Expression) -> Setter { + return column <- column >> value +} +public func >>=(column: Expression, value: Expression) -> Setter { + return column <- column >> value +} +public func >>=(column: Expression, value: V) -> Setter { + return column <- column >> value +} + +public func &=(column: Expression, value: Expression) -> Setter { + return column <- column & value +} +public func &=(column: Expression, value: V) -> Setter { + return column <- column & value +} +public func &=(column: Expression, value: Expression) -> Setter { + return column <- column & value +} +public func &=(column: Expression, value: Expression) -> Setter { + return column <- column & value +} +public func &=(column: Expression, value: V) -> Setter { + return column <- column & value +} + +public func |=(column: Expression, value: Expression) -> Setter { + return column <- column | value +} +public func |=(column: Expression, value: V) -> Setter { + return column <- column | value +} +public func |=(column: Expression, value: Expression) -> Setter { + return column <- column | value +} +public func |=(column: Expression, value: Expression) -> Setter { + return column <- column | value +} +public func |=(column: Expression, value: V) -> Setter { + return column <- column | value +} + +public func ^=(column: Expression, value: Expression) -> Setter { + return column <- column ^ value +} +public func ^=(column: Expression, value: V) -> Setter { + return column <- column ^ value +} +public func ^=(column: Expression, value: Expression) -> Setter { + return column <- column ^ value +} +public func ^=(column: Expression, value: Expression) -> Setter { + return column <- column ^ value +} +public func ^=(column: Expression, value: V) -> Setter { + return column <- column ^ value +} + +public postfix func ++(column: Expression) -> Setter { + return Expression(column) += 1 +} +public postfix func ++(column: Expression) -> Setter { + return Expression(column) += 1 +} + +public postfix func --(column: Expression) -> Setter { + return Expression(column) -= 1 +} +public postfix func --(column: Expression) -> Setter { + return Expression(column) -= 1 +} diff --git a/Supporting Files/Base.xcconfig b/Supporting Files/Base.xcconfig new file mode 100644 index 00000000..bc31c8a3 --- /dev/null +++ b/Supporting Files/Base.xcconfig @@ -0,0 +1,5 @@ +INFOPLIST_FILE = Supporting Files/$(PRODUCT_NAME)/Info.plist + +PRODUCT_BUNDLE_IDENTIFIER = com.stephencelis.$(PRODUCT_NAME:c99extidentifier) + +APPLICATION_EXTENSION_API_ONLY = YES diff --git a/SQLite/Info.plist b/Supporting Files/SQLite/Info.plist similarity index 79% rename from SQLite/Info.plist rename to Supporting Files/SQLite/Info.plist index 9e2c8628..d3de8eef 100644 --- a/SQLite/Info.plist +++ b/Supporting Files/SQLite/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.stephencelis.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName @@ -15,13 +15,11 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.1 + 1.0 CFBundleSignature ???? CFBundleVersion $(CURRENT_PROJECT_VERSION) - NSHumanReadableCopyright - Copyright © 2014–2015 Stephen Celis. NSPrincipalClass diff --git a/SQLite/SQLite.h b/Supporting Files/SQLite/SQLite.h similarity index 55% rename from SQLite/SQLite.h rename to Supporting Files/SQLite/SQLite.h index 418bb477..f6fa54c8 100644 --- a/SQLite/SQLite.h +++ b/Supporting Files/SQLite/SQLite.h @@ -1,9 +1,9 @@ @import Foundation; //! Project version number for SQLite. -FOUNDATION_EXPORT double SQLite_VersionNumber; +FOUNDATION_EXPORT double SQLiteVersionNumber; //! Project version string for SQLite. -FOUNDATION_EXPORT const unsigned char SQLite_VersionString[]; +FOUNDATION_EXPORT const unsigned char SQLiteVersionString[]; #import diff --git a/Supporting Files/SQLite/SQLite.xcconfig b/Supporting Files/SQLite/SQLite.xcconfig new file mode 100644 index 00000000..05f740a1 --- /dev/null +++ b/Supporting Files/SQLite/SQLite.xcconfig @@ -0,0 +1,5 @@ +#include "../Base.xcconfig" + +PRODUCT_NAME = SQLite + +OTHER_LDFLAGS = -lsqlite3 diff --git a/SQLite/module.modulemap b/Supporting Files/SQLite/module.modulemap similarity index 100% rename from SQLite/module.modulemap rename to Supporting Files/SQLite/module.modulemap diff --git a/Supporting Files/SQLiteCipher/Info.plist b/Supporting Files/SQLiteCipher/Info.plist new file mode 100644 index 00000000..d3de8eef --- /dev/null +++ b/Supporting Files/SQLiteCipher/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/Supporting Files/SQLiteCipher/SQLiteCipher.h b/Supporting Files/SQLiteCipher/SQLiteCipher.h new file mode 100644 index 00000000..f34ba14a --- /dev/null +++ b/Supporting Files/SQLiteCipher/SQLiteCipher.h @@ -0,0 +1,9 @@ +@import Foundation; + +//! Project version number for SQLite. +FOUNDATION_EXPORT double SQLiteVersionNumber; + +//! Project version string for SQLite. +FOUNDATION_EXPORT const unsigned char SQLiteVersionString[]; + +#import diff --git a/Supporting Files/SQLiteCipher/SQLiteCipher.xcconfig b/Supporting Files/SQLiteCipher/SQLiteCipher.xcconfig new file mode 100644 index 00000000..4af62038 --- /dev/null +++ b/Supporting Files/SQLiteCipher/SQLiteCipher.xcconfig @@ -0,0 +1,5 @@ +#include "../Base.xcconfig" + +PRODUCT_NAME = SQLiteCipher + +GCC_PREPROCESSOR_DEFINITIONS = $(inherited) SQLITE_HAS_CODEC=1 diff --git a/Supporting Files/SQLiteCipher/module.modulemap b/Supporting Files/SQLiteCipher/module.modulemap new file mode 100644 index 00000000..0169f9bf --- /dev/null +++ b/Supporting Files/SQLiteCipher/module.modulemap @@ -0,0 +1,12 @@ +framework module SQLiteCipher { + umbrella header "SQLiteCipher.h" + + // Load the SDK header alongside SQLite.swift. Alternate headers: + // + // header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/usr/include/sqlite3.h" + // header "/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/include/sqlite3.h" + header "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/usr/include/sqlite3.h" + + export * + module * { export * } +} diff --git a/Supporting Files/Test.xcconfig b/Supporting Files/Test.xcconfig new file mode 100644 index 00000000..40e5c38b --- /dev/null +++ b/Supporting Files/Test.xcconfig @@ -0,0 +1 @@ +INFOPLIST_FILE = Tests/Info.plist diff --git a/Supporting Files/podspec.rb b/Supporting Files/podspec.rb new file mode 100644 index 00000000..33edda9e --- /dev/null +++ b/Supporting Files/podspec.rb @@ -0,0 +1,25 @@ +$podspec_version = '1.0.0.pre' +$podspec_source_git = 'https://github.com/stephencelis/SQLite.swift.git' + +def apply_shared_config spec, name + spec.version = $podspec_version + spec.source = { git: $podspec_source_git, tag: $podspec_version } + + spec.homepage = 'https://github.com/stephencelis/SQLite.swift' + spec.license = { type: 'MIT', file: 'LICENSE.txt' } + + spec.author = { 'Stephen Celis' => 'stephen@stephencelis.com' } + spec.social_media_url = 'https://twitter.com/stephencelis' + + # TODO: separate SQLiteCipher.swift and share `SQLite` module name + # spec.module_name = 'SQLite' + # spec.module_map = 'Supporting Files/module.modulemap' + spec.module_name = name + spec.module_map = "Supporting Files/#{name}/module.modulemap" + + spec.source_files = [ + "Supporting Files/#{name}/#{name}.h", 'Source/**/*.{swift,c,h,m}' + ] + + spec.private_header_files = 'Source/Core/fts3_tokenizer.h' +end diff --git a/Tests/AggregateFunctionsTests.swift b/Tests/AggregateFunctionsTests.swift new file mode 100644 index 00000000..52d635a0 --- /dev/null +++ b/Tests/AggregateFunctionsTests.swift @@ -0,0 +1,64 @@ +import XCTest +import SQLite + +class AggregateFunctionsTests : XCTestCase { + + func test_distinct_prependsExpressionsWithDistinctKeyword() { + AssertSQL("DISTINCT \"int\"", int.distinct) + AssertSQL("DISTINCT \"intOptional\"", intOptional.distinct) + AssertSQL("DISTINCT \"double\"", double.distinct) + AssertSQL("DISTINCT \"doubleOptional\"", doubleOptional.distinct) + AssertSQL("DISTINCT \"string\"", string.distinct) + AssertSQL("DISTINCT \"stringOptional\"", stringOptional.distinct) + } + + func test_count_wrapsOptionalExpressionsWithCountFunction() { + AssertSQL("count(\"intOptional\")", intOptional.count) + AssertSQL("count(\"doubleOptional\")", doubleOptional.count) + AssertSQL("count(\"stringOptional\")", stringOptional.count) + } + + func test_max_wrapsComparableExpressionsWithMaxFunction() { + AssertSQL("max(\"int\")", int.max) + AssertSQL("max(\"intOptional\")", intOptional.max) + AssertSQL("max(\"double\")", double.max) + AssertSQL("max(\"doubleOptional\")", doubleOptional.max) + AssertSQL("max(\"string\")", string.max) + AssertSQL("max(\"stringOptional\")", stringOptional.max) + } + + func test_min_wrapsComparableExpressionsWithMinFunction() { + AssertSQL("min(\"int\")", int.min) + AssertSQL("min(\"intOptional\")", intOptional.min) + AssertSQL("min(\"double\")", double.min) + AssertSQL("min(\"doubleOptional\")", doubleOptional.min) + AssertSQL("min(\"string\")", string.min) + AssertSQL("min(\"stringOptional\")", stringOptional.min) + } + + func test_average_wrapsNumericExpressionsWithAvgFunction() { + AssertSQL("avg(\"int\")", int.average) + AssertSQL("avg(\"intOptional\")", intOptional.average) + AssertSQL("avg(\"double\")", double.average) + AssertSQL("avg(\"doubleOptional\")", doubleOptional.average) + } + + func test_sum_wrapsNumericExpressionsWithSumFunction() { + AssertSQL("sum(\"int\")", int.sum) + AssertSQL("sum(\"intOptional\")", intOptional.sum) + AssertSQL("sum(\"double\")", double.sum) + AssertSQL("sum(\"doubleOptional\")", doubleOptional.sum) + } + + func test_total_wrapsNumericExpressionsWithTotalFunction() { + AssertSQL("total(\"int\")", int.total) + AssertSQL("total(\"intOptional\")", intOptional.total) + AssertSQL("total(\"double\")", double.total) + AssertSQL("total(\"doubleOptional\")", doubleOptional.total) + } + + func test_count_withStar_wrapsStarWithCountFunction() { + AssertSQL("count(*)", count(*)) + } + +} diff --git a/Tests/BlobTests.swift b/Tests/BlobTests.swift new file mode 100644 index 00000000..ba0a7c54 --- /dev/null +++ b/Tests/BlobTests.swift @@ -0,0 +1,12 @@ +import XCTest +import SQLite + +class BlobTests : XCTestCase { + + func test_toHex() { + let blob = Blob(bytes: [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 150, 250, 255]) + + XCTAssertEqual(blob.toHex(), "000a141e28323c46505a6496faff") + } + +} diff --git a/Tests/CipherTests.swift b/Tests/CipherTests.swift new file mode 100644 index 00000000..78e3ac1b --- /dev/null +++ b/Tests/CipherTests.swift @@ -0,0 +1,26 @@ +import XCTest +import SQLiteCipher + +class CipherTests: XCTestCase { + + let db = try! Connection() + + override func setUp() { + try! db.key("hello") + + try! db.run("CREATE TABLE foo (bar TEXT)") + try! db.run("INSERT INTO foo (bar) VALUES ('world')") + + super.setUp() + } + + func test_key() { + XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM foo") as! Int64) + } + + func test_rekey() { + try! db.rekey("goodbye") + XCTAssertEqual(1, try! db.scalar("SELECT count(*) FROM foo") as! Int64) + } + +} diff --git a/Tests/ConnectionTests.swift b/Tests/ConnectionTests.swift new file mode 100644 index 00000000..efa28746 --- /dev/null +++ b/Tests/ConnectionTests.swift @@ -0,0 +1,289 @@ +import XCTest +import SQLite + +class ConnectionTests : SQLiteTestCase { + + override func setUp() { + super.setUp() + + CreateUsersTable() + } + + func test_init_withInMemory_returnsInMemoryConnection() { + let db = try! Connection(.InMemory) + XCTAssertEqual("", db.description) + } + + func test_init_returnsInMemoryByDefault() { + let db = try! Connection() + XCTAssertEqual("", db.description) + } + + func test_init_withTemporary_returnsTemporaryConnection() { + let db = try! Connection(.Temporary) + XCTAssertEqual("", db.description) + } + + func test_init_withURI_returnsURIConnection() { + let db = try! Connection(.URI("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3")) + XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) + } + + func test_init_withString_returnsURIConnection() { + let db = try! Connection("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3") + XCTAssertEqual("\(NSTemporaryDirectory())/SQLite.swift Tests.sqlite3", db.description) + } + + func test_readonly_returnsFalseOnReadWriteConnections() { + XCTAssertFalse(db.readonly) + } + + func test_readonly_returnsTrueOnReadOnlyConnections() { + let db = try! Connection(readonly: true) + XCTAssertTrue(db.readonly) + } + + func test_lastInsertRowid_returnsNilOnNewConnections() { + XCTAssert(db.lastInsertRowid == nil) + } + + func test_lastInsertRowid_returnsLastIdAfterInserts() { + try! InsertUser("alice") + XCTAssertEqual(1, db.lastInsertRowid!) + } + + func test_changes_returnsZeroOnNewConnections() { + XCTAssertEqual(0, db.changes) + } + + func test_changes_returnsNumberOfChanges() { + try! InsertUser("alice") + XCTAssertEqual(1, db.changes) + try! InsertUser("betsy") + XCTAssertEqual(1, db.changes) + } + + func test_totalChanges_returnsTotalNumberOfChanges() { + XCTAssertEqual(0, db.totalChanges) + try! InsertUser("alice") + XCTAssertEqual(1, db.totalChanges) + try! InsertUser("betsy") + XCTAssertEqual(2, db.totalChanges) + } + + func test_prepare_preparesAndReturnsStatements() { + _ = db.prepare("SELECT * FROM users WHERE admin = 0") + _ = db.prepare("SELECT * FROM users WHERE admin = ?", 0) + _ = db.prepare("SELECT * FROM users WHERE admin = ?", [0]) + _ = db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) + } + + func test_run_preparesRunsAndReturnsStatements() { + try! db.run("SELECT * FROM users WHERE admin = 0") + try! db.run("SELECT * FROM users WHERE admin = ?", 0) + try! db.run("SELECT * FROM users WHERE admin = ?", [0]) + try! db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) + AssertSQL("SELECT * FROM users WHERE admin = 0", 4) + } + + func test_scalar_preparesRunsAndReturnsScalarValues() { + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as? Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as? Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as? Int64) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as? Int64) + AssertSQL("SELECT count(*) FROM users WHERE admin = 0", 4) + } + + func test_transaction_executesBeginDeferred() { + try! db.transaction(.Deferred) {} + + AssertSQL("BEGIN DEFERRED TRANSACTION") + } + + func test_transaction_executesBeginImmediate() { + try! db.transaction(.Immediate) {} + + AssertSQL("BEGIN IMMEDIATE TRANSACTION") + } + + func test_transaction_executesBeginExclusive() { + try! db.transaction(.Exclusive) {} + + AssertSQL("BEGIN EXCLUSIVE TRANSACTION") + } + + func test_transaction_beginsAndCommitsTransactions() { + let stmt = db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + + try! db.transaction { + try stmt.run() + } + + AssertSQL("BEGIN DEFERRED TRANSACTION") + AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')") + AssertSQL("COMMIT TRANSACTION") + AssertSQL("ROLLBACK TRANSACTION", 0) + } + + func test_transaction_beginsAndRollsTransactionsBack() { + let stmt = db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + + do { + try db.transaction { + try stmt.run() + try stmt.run() + } + } catch { + } + + AssertSQL("BEGIN DEFERRED TRANSACTION") + AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2) + AssertSQL("ROLLBACK TRANSACTION") + AssertSQL("COMMIT TRANSACTION", 0) + } + + func test_savepoint_beginsAndCommitsSavepoints() { + let db = self.db + + try! db.savepoint("1") { + try db.savepoint("2") { + try db.run("INSERT INTO users (email) VALUES (?)", "alice@example.com") + } + } + + AssertSQL("SAVEPOINT '1'") + AssertSQL("SAVEPOINT '2'") + AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')") + AssertSQL("RELEASE SAVEPOINT '2'") + AssertSQL("RELEASE SAVEPOINT '1'") + AssertSQL("ROLLBACK TO SAVEPOINT '2'", 0) + AssertSQL("ROLLBACK TO SAVEPOINT '1'", 0) + } + + func test_savepoint_beginsAndRollsSavepointsBack() { + let db = self.db + let stmt = db.prepare("INSERT INTO users (email) VALUES (?)", "alice@example.com") + + do { + try db.savepoint("1") { + try db.savepoint("2") { + try stmt.run() + try stmt.run() + try stmt.run() + } + try db.savepoint("2") { + try stmt.run() + try stmt.run() + try stmt.run() + } + } + } catch { + } + + AssertSQL("SAVEPOINT '1'") + AssertSQL("SAVEPOINT '2'") + AssertSQL("INSERT INTO users (email) VALUES ('alice@example.com')", 2) + AssertSQL("ROLLBACK TO SAVEPOINT '2'") + AssertSQL("ROLLBACK TO SAVEPOINT '1'") + AssertSQL("RELEASE SAVEPOINT '2'", 0) + AssertSQL("RELEASE SAVEPOINT '1'", 0) + } + + func test_updateHook_setsUpdateHook() { + async { done in + db.updateHook { operation, db, table, rowid in + XCTAssertEqual(Operation.Insert, operation) + XCTAssertEqual("main", db) + XCTAssertEqual("users", table) + XCTAssertEqual(1, rowid) + done() + } + try! InsertUser("alice") + } + } + + func test_commitHook_setsCommitHook() { + async { done in + db.commitHook { + done() + } + try! db.transaction { + try InsertUser("alice") + } + XCTAssertEqual(1, db.scalar("SELECT count(*) FROM users") as? Int64) + } + } + + func test_rollbackHook_setsRollbackHook() { + async { done in + db.rollbackHook(done) + do { + try db.transaction { + try self.InsertUser("alice") + try self.InsertUser("alice") // throw + } + } catch { + } + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) + } + } + + func test_commitHook_withRollback_rollsBack() { + async { done in + db.commitHook { + throw NSError(domain: "com.stephencelis.SQLiteTests", code: 1, userInfo: nil) + } + db.rollbackHook(done) + do { + try db.transaction { + try self.InsertUser("alice") + } + } catch { + } + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users") as? Int64) + } + } + + func test_createFunction_withArrayArguments() { + db.createFunction("hello") { $0[0].map { "Hello, \($0)!" } } + + XCTAssertEqual("Hello, world!", db.scalar("SELECT hello('world')") as? String) + XCTAssert(db.scalar("SELECT hello(NULL)") == nil) + } + + func test_createFunction_createsQuotableFunction() { + db.createFunction("hello world") { $0[0].map { "Hello, \($0)!" } } + + XCTAssertEqual("Hello, world!", db.scalar("SELECT \"hello world\"('world')") as? String) + XCTAssert(db.scalar("SELECT \"hello world\"(NULL)") == nil) + } + + func test_createCollation_createsCollation() { + db.createCollation("NODIACRITIC") { lhs, rhs in + return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) + } + XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE NODIACRITIC", "cafe", "café") as? Int64) + } + + func test_createCollation_createsQuotableCollation() { + db.createCollation("NO DIACRITIC") { lhs, rhs in + return lhs.compare(rhs, options: .DiacriticInsensitiveSearch) + } + XCTAssertEqual(1, db.scalar("SELECT ? = ? COLLATE \"NO DIACRITIC\"", "cafe", "café") as? Int64) + } + + func test_interrupt_interruptsLongRunningQuery() { + try! InsertUsers("abcdefghijklmnopqrstuvwxyz".characters.map { String($0) }) + db.createFunction("sleep") { args in + usleep(UInt32((args[0] as? Double ?? Double(args[0] as? Int64 ?? 1)) * 1_000_000)) + return nil + } + + let stmt = db.prepare("SELECT *, sleep(?) FROM users", 0.1) + try! stmt.run() + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(10 * NSEC_PER_MSEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), db.interrupt) + AssertThrows(try stmt.run()) + } + +} diff --git a/Tests/CoreFunctionsTests.swift b/Tests/CoreFunctionsTests.swift new file mode 100644 index 00000000..8f7460d5 --- /dev/null +++ b/Tests/CoreFunctionsTests.swift @@ -0,0 +1,136 @@ +import XCTest +import SQLite + +class CoreFunctionsTests : XCTestCase { + + func test_round_wrapsDoubleExpressionsWithRoundFunction() { + AssertSQL("round(\"double\")", double.round()) + AssertSQL("round(\"doubleOptional\")", doubleOptional.round()) + + AssertSQL("round(\"double\", 1)", double.round(1)) + AssertSQL("round(\"doubleOptional\", 2)", doubleOptional.round(2)) + } + + func test_random_generatesExpressionWithRandomFunction() { + AssertSQL("random()", Expression.random()) + AssertSQL("random()", Expression.random()) + } + + func test_length_wrapsStringExpressionWithLengthFunction() { + AssertSQL("length(\"string\")", string.length) + AssertSQL("length(\"stringOptional\")", stringOptional.length) + } + + func test_lowercaseString_wrapsStringExpressionWithLowerFunction() { + AssertSQL("lower(\"string\")", string.lowercaseString) + AssertSQL("lower(\"stringOptional\")", stringOptional.lowercaseString) + } + + func test_uppercaseString_wrapsStringExpressionWithUpperFunction() { + AssertSQL("upper(\"string\")", string.uppercaseString) + AssertSQL("upper(\"stringOptional\")", stringOptional.uppercaseString) + } + + func test_like_buildsExpressionWithLikeOperator() { + AssertSQL("(\"string\" LIKE 'a%')", string.like("a%")) + AssertSQL("(\"stringOptional\" LIKE 'b%')", stringOptional.like("b%")) + + AssertSQL("(\"string\" LIKE '%\\%' ESCAPE '\\')", string.like("%\\%", escape: "\\")) + AssertSQL("(\"stringOptional\" LIKE '_\\_' ESCAPE '\\')", stringOptional.like("_\\_", escape: "\\")) + } + + func test_glob_buildsExpressionWithGlobOperator() { + AssertSQL("(\"string\" GLOB 'a*')", string.glob("a*")) + AssertSQL("(\"stringOptional\" GLOB 'b*')", stringOptional.glob("b*")) + } + + func test_match_buildsExpressionWithMatchOperator() { + AssertSQL("(\"string\" MATCH 'a*')", string.match("a*")) + AssertSQL("(\"stringOptional\" MATCH 'b*')", stringOptional.match("b*")) + } + + func test_regexp_buildsExpressionWithRegexpOperator() { + AssertSQL("(\"string\" REGEXP '^.+@.+\\.com$')", string.regexp("^.+@.+\\.com$")) + AssertSQL("(\"stringOptional\" REGEXP '^.+@.+\\.net$')", stringOptional.regexp("^.+@.+\\.net$")) + } + + func test_collate_buildsExpressionWithCollateOperator() { + AssertSQL("(\"string\" COLLATE BINARY)", string.collate(.Binary)) + AssertSQL("(\"string\" COLLATE NOCASE)", string.collate(.Nocase)) + AssertSQL("(\"string\" COLLATE RTRIM)", string.collate(.Rtrim)) + AssertSQL("(\"string\" COLLATE \"CUSTOM\")", string.collate(.Custom("CUSTOM"))) + + AssertSQL("(\"stringOptional\" COLLATE BINARY)", stringOptional.collate(.Binary)) + AssertSQL("(\"stringOptional\" COLLATE NOCASE)", stringOptional.collate(.Nocase)) + AssertSQL("(\"stringOptional\" COLLATE RTRIM)", stringOptional.collate(.Rtrim)) + AssertSQL("(\"stringOptional\" COLLATE \"CUSTOM\")", stringOptional.collate(.Custom("CUSTOM"))) + } + + func test_ltrim_wrapsStringWithLtrimFunction() { + AssertSQL("ltrim(\"string\")", string.ltrim()) + AssertSQL("ltrim(\"stringOptional\")", stringOptional.ltrim()) + + AssertSQL("ltrim(\"string\", ' ')", string.ltrim([" "])) + AssertSQL("ltrim(\"stringOptional\", ' ')", stringOptional.ltrim([" "])) + } + + func test_ltrim_wrapsStringWithRtrimFunction() { + AssertSQL("rtrim(\"string\")", string.rtrim()) + AssertSQL("rtrim(\"stringOptional\")", stringOptional.rtrim()) + + AssertSQL("rtrim(\"string\", ' ')", string.rtrim([" "])) + AssertSQL("rtrim(\"stringOptional\", ' ')", stringOptional.rtrim([" "])) + } + + func test_ltrim_wrapsStringWithTrimFunction() { + AssertSQL("trim(\"string\")", string.trim()) + AssertSQL("trim(\"stringOptional\")", stringOptional.trim()) + + AssertSQL("trim(\"string\", ' ')", string.trim([" "])) + AssertSQL("trim(\"stringOptional\", ' ')", stringOptional.trim([" "])) + } + + func test_replace_wrapsStringWithReplaceFunction() { + AssertSQL("replace(\"string\", '@example.com', '@example.net')", string.replace("@example.com", with: "@example.net")) + AssertSQL("replace(\"stringOptional\", '@example.net', '@example.com')", stringOptional.replace("@example.net", with: "@example.com")) + } + + func test_substring_wrapsStringWithSubstrFunction() { + AssertSQL("substr(\"string\", 1, 2)", string.substring(1, length: 2)) + AssertSQL("substr(\"stringOptional\", 2, 1)", stringOptional.substring(2, length: 1)) + } + + func test_subscriptWithRange_wrapsStringWithSubstrFunction() { + AssertSQL("substr(\"string\", 1, 2)", string[1..<3]) + AssertSQL("substr(\"stringOptional\", 2, 1)", stringOptional[2..<3]) + } + + func test_nilCoalescingOperator_wrapsOptionalsWithIfnullFunction() { + AssertSQL("ifnull(\"intOptional\", 1)", intOptional ?? 1) + // AssertSQL("ifnull(\"doubleOptional\", 1.0)", doubleOptional ?? 1) // rdar://problem/21677256 + XCTAssertEqual("ifnull(\"doubleOptional\", 1.0)", (doubleOptional ?? 1).asSQL()) + AssertSQL("ifnull(\"stringOptional\", 'literal')", stringOptional ?? "literal") + + AssertSQL("ifnull(\"intOptional\", \"int\")", intOptional ?? int) + AssertSQL("ifnull(\"doubleOptional\", \"double\")", doubleOptional ?? double) + AssertSQL("ifnull(\"stringOptional\", \"string\")", stringOptional ?? string) + + AssertSQL("ifnull(\"intOptional\", \"intOptional\")", intOptional ?? intOptional) + AssertSQL("ifnull(\"doubleOptional\", \"doubleOptional\")", doubleOptional ?? doubleOptional) + AssertSQL("ifnull(\"stringOptional\", \"stringOptional\")", stringOptional ?? stringOptional) + } + + func test_absoluteValue_wrapsNumberWithAbsFucntion() { + AssertSQL("abs(\"int\")", int.absoluteValue) + AssertSQL("abs(\"intOptional\")", intOptional.absoluteValue) + + AssertSQL("abs(\"double\")", double.absoluteValue) + AssertSQL("abs(\"doubleOptional\")", doubleOptional.absoluteValue) + } + + func test_contains_buildsExpressionWithInOperator() { + AssertSQL("(\"string\" IN ('hello', 'world'))", ["hello", "world"].contains(string)) + AssertSQL("(\"stringOptional\" IN ('hello', 'world'))", ["hello", "world"].contains(stringOptional)) + } + +} diff --git a/Tests/CustomFunctionsTests.swift b/Tests/CustomFunctionsTests.swift new file mode 100644 index 00000000..67150ccf --- /dev/null +++ b/Tests/CustomFunctionsTests.swift @@ -0,0 +1,6 @@ +import XCTest +import SQLite + +class CustomFunctionsTests : XCTestCase { + +} diff --git a/Tests/ExpressionTests.swift b/Tests/ExpressionTests.swift new file mode 100644 index 00000000..036e10ce --- /dev/null +++ b/Tests/ExpressionTests.swift @@ -0,0 +1,6 @@ +import XCTest +import SQLite + +class ExpressionTests : XCTestCase { + +} diff --git a/Tests/FTS4Tests.swift b/Tests/FTS4Tests.swift new file mode 100644 index 00000000..a94d9073 --- /dev/null +++ b/Tests/FTS4Tests.swift @@ -0,0 +1,77 @@ +import XCTest +import SQLite + +class FTS4Tests : XCTestCase { + + func test_create_onVirtualTable_withFTS4_compilesCreateVirtualTableExpression() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4()", + virtualTable.create(.FTS4()) + ) + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\")", + virtualTable.create(.FTS4(string)) + ) + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=simple)", + virtualTable.create(.FTS4(tokenize: .Simple)) + ) + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(\"string\", tokenize=porter)", + virtualTable.create(.FTS4([string], tokenize: .Porter)) + ) + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=0\")", + virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: false))) + ) + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING fts4(tokenize=unicode61 \"removeDiacritics=1\" \"tokenchars=.\" \"separators=X\")", + virtualTable.create(.FTS4(tokenize: .Unicode61(removeDiacritics: true, tokenchars: ["."], separators: ["X"]))) + ) + } + + func test_match_onVirtualTableAsExpression_compilesMatchExpression() { + AssertSQL("(\"virtual_table\" MATCH 'string')", virtualTable.match("string") as Expression) + AssertSQL("(\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as Expression) + AssertSQL("(\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as Expression) + } + + func test_match_onVirtualTableAsQueryType_compilesMatchExpression() { + AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH 'string')", virtualTable.match("string") as QueryType) + AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"string\")", virtualTable.match(string) as QueryType) + AssertSQL("SELECT * FROM \"virtual_table\" WHERE (\"virtual_table\" MATCH \"stringOptional\")", virtualTable.match(stringOptional) as QueryType) + } + +} + +class FTS4IntegrationTests : SQLiteTestCase { + + func test_registerTokenizer_registersTokenizer() { + let emails = VirtualTable("emails") + let subject = Expression("subject") + let body = Expression("body") + + let locale = CFLocaleCopyCurrent() + let tokenizerName = "tokenizer" + let tokenizer = CFStringTokenizerCreate(nil, "", CFRangeMake(0, 0), UInt(kCFStringTokenizerUnitWord), locale) + try! db.registerTokenizer(tokenizerName) { string in + CFStringTokenizerSetString(tokenizer, string, CFRangeMake(0, CFStringGetLength(string))) + if CFStringTokenizerAdvanceToNextToken(tokenizer) == .None { + return nil + } + let range = CFStringTokenizerGetCurrentTokenRange(tokenizer) + let input = CFStringCreateWithSubstring(kCFAllocatorDefault, string, range) + let token = CFStringCreateMutableCopy(nil, range.length, input) + CFStringLowercase(token, locale) + CFStringTransform(token, nil, kCFStringTransformStripDiacritics, false) + return (token as String, string.rangeOfString(input as String)!) + } + + try! db.run(emails.create(.FTS4([subject, body], tokenize: .Custom(tokenizerName)))) + AssertSQL("CREATE VIRTUAL TABLE \"emails\" USING fts4(\"subject\", \"body\", tokenize=\"SQLite.swift\" \"tokenizer\")") + + try! db.run(emails.insert(subject <- "Aún más cáfe!")) + XCTAssertEqual(1, db.scalar(emails.filter(emails.match("aun")).count)) + } + +} diff --git a/SQLite Tests/Info.plist b/Tests/Info.plist similarity index 90% rename from SQLite Tests/Info.plist rename to Tests/Info.plist index 00b831da..ba72822e 100644 --- a/SQLite Tests/Info.plist +++ b/Tests/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.stephencelis.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/Tests/OperatorsTests.swift b/Tests/OperatorsTests.swift new file mode 100644 index 00000000..86aa34ed --- /dev/null +++ b/Tests/OperatorsTests.swift @@ -0,0 +1,286 @@ +import XCTest +import SQLite + +class OperatorsTests : XCTestCase { + + func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() { + AssertSQL("(\"string\" || \"string\")", string + string) + AssertSQL("(\"string\" || \"stringOptional\")", string + stringOptional) + AssertSQL("(\"stringOptional\" || \"string\")", stringOptional + string) + AssertSQL("(\"stringOptional\" || \"stringOptional\")", stringOptional + stringOptional) + AssertSQL("(\"string\" || 'literal')", string + "literal") + AssertSQL("(\"stringOptional\" || 'literal')", stringOptional + "literal") + AssertSQL("('literal' || \"string\")", "literal" + string) + AssertSQL("('literal' || \"stringOptional\")", "literal" + stringOptional) + } + + func test_numberExpression_plusNumberExpression_buildsAdditiveNumberExpression() { + AssertSQL("(\"int\" + \"int\")", int + int) + AssertSQL("(\"int\" + \"intOptional\")", int + intOptional) + AssertSQL("(\"intOptional\" + \"int\")", intOptional + int) + AssertSQL("(\"intOptional\" + \"intOptional\")", intOptional + intOptional) + AssertSQL("(\"int\" + 1)", int + 1) + AssertSQL("(\"intOptional\" + 1)", intOptional + 1) + AssertSQL("(1 + \"int\")", 1 + int) + AssertSQL("(1 + \"intOptional\")", 1 + intOptional) + + AssertSQL("(\"double\" + \"double\")", double + double) + AssertSQL("(\"double\" + \"doubleOptional\")", double + doubleOptional) + AssertSQL("(\"doubleOptional\" + \"double\")", doubleOptional + double) + AssertSQL("(\"doubleOptional\" + \"doubleOptional\")", doubleOptional + doubleOptional) + AssertSQL("(\"double\" + 1.0)", double + 1) + AssertSQL("(\"doubleOptional\" + 1.0)", doubleOptional + 1) + AssertSQL("(1.0 + \"double\")", 1 + double) + AssertSQL("(1.0 + \"doubleOptional\")", 1 + doubleOptional) + } + + func test_numberExpression_minusNumberExpression_buildsSubtractiveNumberExpression() { + AssertSQL("(\"int\" - \"int\")", int - int) + AssertSQL("(\"int\" - \"intOptional\")", int - intOptional) + AssertSQL("(\"intOptional\" - \"int\")", intOptional - int) + AssertSQL("(\"intOptional\" - \"intOptional\")", intOptional - intOptional) + AssertSQL("(\"int\" - 1)", int - 1) + AssertSQL("(\"intOptional\" - 1)", intOptional - 1) + AssertSQL("(1 - \"int\")", 1 - int) + AssertSQL("(1 - \"intOptional\")", 1 - intOptional) + + AssertSQL("(\"double\" - \"double\")", double - double) + AssertSQL("(\"double\" - \"doubleOptional\")", double - doubleOptional) + AssertSQL("(\"doubleOptional\" - \"double\")", doubleOptional - double) + AssertSQL("(\"doubleOptional\" - \"doubleOptional\")", doubleOptional - doubleOptional) + AssertSQL("(\"double\" - 1.0)", double - 1) + AssertSQL("(\"doubleOptional\" - 1.0)", doubleOptional - 1) + AssertSQL("(1.0 - \"double\")", 1 - double) + AssertSQL("(1.0 - \"doubleOptional\")", 1 - doubleOptional) + } + + func test_numberExpression_timesNumberExpression_buildsMultiplicativeNumberExpression() { + AssertSQL("(\"int\" * \"int\")", int * int) + AssertSQL("(\"int\" * \"intOptional\")", int * intOptional) + AssertSQL("(\"intOptional\" * \"int\")", intOptional * int) + AssertSQL("(\"intOptional\" * \"intOptional\")", intOptional * intOptional) + AssertSQL("(\"int\" * 1)", int * 1) + AssertSQL("(\"intOptional\" * 1)", intOptional * 1) + AssertSQL("(1 * \"int\")", 1 * int) + AssertSQL("(1 * \"intOptional\")", 1 * intOptional) + + AssertSQL("(\"double\" * \"double\")", double * double) + AssertSQL("(\"double\" * \"doubleOptional\")", double * doubleOptional) + AssertSQL("(\"doubleOptional\" * \"double\")", doubleOptional * double) + AssertSQL("(\"doubleOptional\" * \"doubleOptional\")", doubleOptional * doubleOptional) + AssertSQL("(\"double\" * 1.0)", double * 1) + AssertSQL("(\"doubleOptional\" * 1.0)", doubleOptional * 1) + AssertSQL("(1.0 * \"double\")", 1 * double) + AssertSQL("(1.0 * \"doubleOptional\")", 1 * doubleOptional) + } + + func test_numberExpression_dividedByNumberExpression_buildsDivisiveNumberExpression() { + AssertSQL("(\"int\" / \"int\")", int / int) + AssertSQL("(\"int\" / \"intOptional\")", int / intOptional) + AssertSQL("(\"intOptional\" / \"int\")", intOptional / int) + AssertSQL("(\"intOptional\" / \"intOptional\")", intOptional / intOptional) + AssertSQL("(\"int\" / 1)", int / 1) + AssertSQL("(\"intOptional\" / 1)", intOptional / 1) + AssertSQL("(1 / \"int\")", 1 / int) + AssertSQL("(1 / \"intOptional\")", 1 / intOptional) + + AssertSQL("(\"double\" / \"double\")", double / double) + AssertSQL("(\"double\" / \"doubleOptional\")", double / doubleOptional) + AssertSQL("(\"doubleOptional\" / \"double\")", doubleOptional / double) + AssertSQL("(\"doubleOptional\" / \"doubleOptional\")", doubleOptional / doubleOptional) + AssertSQL("(\"double\" / 1.0)", double / 1) + AssertSQL("(\"doubleOptional\" / 1.0)", doubleOptional / 1) + AssertSQL("(1.0 / \"double\")", 1 / double) + AssertSQL("(1.0 / \"doubleOptional\")", 1 / doubleOptional) + } + + func test_numberExpression_prefixedWithMinus_buildsInvertedNumberExpression() { + AssertSQL("-(\"int\")", -int) + AssertSQL("-(\"intOptional\")", -intOptional) + + AssertSQL("-(\"double\")", -double) + AssertSQL("-(\"doubleOptional\")", -doubleOptional) + } + + func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() { + AssertSQL("(\"int\" % \"int\")", int % int) + AssertSQL("(\"int\" % \"intOptional\")", int % intOptional) + AssertSQL("(\"intOptional\" % \"int\")", intOptional % int) + AssertSQL("(\"intOptional\" % \"intOptional\")", intOptional % intOptional) + AssertSQL("(\"int\" % 1)", int % 1) + AssertSQL("(\"intOptional\" % 1)", intOptional % 1) + AssertSQL("(1 % \"int\")", 1 % int) + AssertSQL("(1 % \"intOptional\")", 1 % intOptional) + } + + func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() { + AssertSQL("(\"int\" << \"int\")", int << int) + AssertSQL("(\"int\" << \"intOptional\")", int << intOptional) + AssertSQL("(\"intOptional\" << \"int\")", intOptional << int) + AssertSQL("(\"intOptional\" << \"intOptional\")", intOptional << intOptional) + AssertSQL("(\"int\" << 1)", int << 1) + AssertSQL("(\"intOptional\" << 1)", intOptional << 1) + AssertSQL("(1 << \"int\")", 1 << int) + AssertSQL("(1 << \"intOptional\")", 1 << intOptional) + } + + func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() { + AssertSQL("(\"int\" >> \"int\")", int >> int) + AssertSQL("(\"int\" >> \"intOptional\")", int >> intOptional) + AssertSQL("(\"intOptional\" >> \"int\")", intOptional >> int) + AssertSQL("(\"intOptional\" >> \"intOptional\")", intOptional >> intOptional) + AssertSQL("(\"int\" >> 1)", int >> 1) + AssertSQL("(\"intOptional\" >> 1)", intOptional >> 1) + AssertSQL("(1 >> \"int\")", 1 >> int) + AssertSQL("(1 >> \"intOptional\")", 1 >> intOptional) + } + + func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() { + AssertSQL("(\"int\" & \"int\")", int & int) + AssertSQL("(\"int\" & \"intOptional\")", int & intOptional) + AssertSQL("(\"intOptional\" & \"int\")", intOptional & int) + AssertSQL("(\"intOptional\" & \"intOptional\")", intOptional & intOptional) + AssertSQL("(\"int\" & 1)", int & 1) + AssertSQL("(\"intOptional\" & 1)", intOptional & 1) + AssertSQL("(1 & \"int\")", 1 & int) + AssertSQL("(1 & \"intOptional\")", 1 & intOptional) + } + + func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() { + AssertSQL("(\"int\" | \"int\")", int | int) + AssertSQL("(\"int\" | \"intOptional\")", int | intOptional) + AssertSQL("(\"intOptional\" | \"int\")", intOptional | int) + AssertSQL("(\"intOptional\" | \"intOptional\")", intOptional | intOptional) + AssertSQL("(\"int\" | 1)", int | 1) + AssertSQL("(\"intOptional\" | 1)", intOptional | 1) + AssertSQL("(1 | \"int\")", 1 | int) + AssertSQL("(1 | \"intOptional\")", 1 | intOptional) + } + + func test_integerExpression_bitwiseExclusiveOrIntegerExpression_buildsOredIntegerExpression() { + AssertSQL("(~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^ int) + AssertSQL("(~((\"int\" & \"intOptional\")) & (\"int\" | \"intOptional\"))", int ^ intOptional) + AssertSQL("(~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^ int) + AssertSQL("(~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^ intOptional) + AssertSQL("(~((\"int\" & 1)) & (\"int\" | 1))", int ^ 1) + AssertSQL("(~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^ 1) + AssertSQL("(~((1 & \"int\")) & (1 | \"int\"))", 1 ^ int) + AssertSQL("(~((1 & \"intOptional\")) & (1 | \"intOptional\"))", 1 ^ intOptional) + } + + func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() { + AssertSQL("~(\"int\")", ~int) + AssertSQL("~(\"intOptional\")", ~intOptional) + } + + func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" = \"bool\")", bool == bool) + AssertSQL("(\"bool\" = \"boolOptional\")", bool == boolOptional) + AssertSQL("(\"boolOptional\" = \"bool\")", boolOptional == bool) + AssertSQL("(\"boolOptional\" = \"boolOptional\")", boolOptional == boolOptional) + AssertSQL("(\"bool\" = 1)", bool == true) + AssertSQL("(\"boolOptional\" = 1)", boolOptional == true) + AssertSQL("(1 = \"bool\")", true == bool) + AssertSQL("(1 = \"boolOptional\")", true == boolOptional) + + AssertSQL("(\"boolOptional\" IS NULL)", boolOptional == nil) + AssertSQL("(NULL IS \"boolOptional\")", nil == boolOptional) + } + + func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" != \"bool\")", bool != bool) + AssertSQL("(\"bool\" != \"boolOptional\")", bool != boolOptional) + AssertSQL("(\"boolOptional\" != \"bool\")", boolOptional != bool) + AssertSQL("(\"boolOptional\" != \"boolOptional\")", boolOptional != boolOptional) + AssertSQL("(\"bool\" != 1)", bool != true) + AssertSQL("(\"boolOptional\" != 1)", boolOptional != true) + AssertSQL("(1 != \"bool\")", true != bool) + AssertSQL("(1 != \"boolOptional\")", true != boolOptional) + + AssertSQL("(\"boolOptional\" IS NOT NULL)", boolOptional != nil) + AssertSQL("(NULL IS NOT \"boolOptional\")", nil != boolOptional) + } + + func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" > \"bool\")", bool > bool) + AssertSQL("(\"bool\" > \"boolOptional\")", bool > boolOptional) + AssertSQL("(\"boolOptional\" > \"bool\")", boolOptional > bool) + AssertSQL("(\"boolOptional\" > \"boolOptional\")", boolOptional > boolOptional) + AssertSQL("(\"bool\" > 1)", bool > true) + AssertSQL("(\"boolOptional\" > 1)", boolOptional > true) + AssertSQL("(1 > \"bool\")", true > bool) + AssertSQL("(1 > \"boolOptional\")", true > boolOptional) + } + + func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" >= \"bool\")", bool >= bool) + AssertSQL("(\"bool\" >= \"boolOptional\")", bool >= boolOptional) + AssertSQL("(\"boolOptional\" >= \"bool\")", boolOptional >= bool) + AssertSQL("(\"boolOptional\" >= \"boolOptional\")", boolOptional >= boolOptional) + AssertSQL("(\"bool\" >= 1)", bool >= true) + AssertSQL("(\"boolOptional\" >= 1)", boolOptional >= true) + AssertSQL("(1 >= \"bool\")", true >= bool) + AssertSQL("(1 >= \"boolOptional\")", true >= boolOptional) + } + + func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" < \"bool\")", bool < bool) + AssertSQL("(\"bool\" < \"boolOptional\")", bool < boolOptional) + AssertSQL("(\"boolOptional\" < \"bool\")", boolOptional < bool) + AssertSQL("(\"boolOptional\" < \"boolOptional\")", boolOptional < boolOptional) + AssertSQL("(\"bool\" < 1)", bool < true) + AssertSQL("(\"boolOptional\" < 1)", boolOptional < true) + AssertSQL("(1 < \"bool\")", true < bool) + AssertSQL("(1 < \"boolOptional\")", true < boolOptional) + } + + func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() { + AssertSQL("(\"bool\" <= \"bool\")", bool <= bool) + AssertSQL("(\"bool\" <= \"boolOptional\")", bool <= boolOptional) + AssertSQL("(\"boolOptional\" <= \"bool\")", boolOptional <= bool) + AssertSQL("(\"boolOptional\" <= \"boolOptional\")", boolOptional <= boolOptional) + AssertSQL("(\"bool\" <= 1)", bool <= true) + AssertSQL("(\"boolOptional\" <= 1)", boolOptional <= true) + AssertSQL("(1 <= \"bool\")", true <= bool) + AssertSQL("(1 <= \"boolOptional\")", true <= boolOptional) + } + + func test_patternMatchingOperator_withComparableInterval_buildsBetweenBooleanExpression() { + AssertSQL("\"int\" BETWEEN 0 AND 5", 0...5 ~= int) + AssertSQL("\"intOptional\" BETWEEN 0 AND 5", 0...5 ~= intOptional) + } + + func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() { + AssertSQL("(\"bool\" AND \"bool\")", bool && bool) + AssertSQL("(\"bool\" AND \"boolOptional\")", bool && boolOptional) + AssertSQL("(\"boolOptional\" AND \"bool\")", boolOptional && bool) + AssertSQL("(\"boolOptional\" AND \"boolOptional\")", boolOptional && boolOptional) + AssertSQL("(\"bool\" AND 1)", bool && true) + AssertSQL("(\"boolOptional\" AND 1)", boolOptional && true) + AssertSQL("(1 AND \"bool\")", true && bool) + AssertSQL("(1 AND \"boolOptional\")", true && boolOptional) + } + + func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() { + AssertSQL("(\"bool\" OR \"bool\")", bool || bool) + AssertSQL("(\"bool\" OR \"boolOptional\")", bool || boolOptional) + AssertSQL("(\"boolOptional\" OR \"bool\")", boolOptional || bool) + AssertSQL("(\"boolOptional\" OR \"boolOptional\")", boolOptional || boolOptional) + AssertSQL("(\"bool\" OR 1)", bool || true) + AssertSQL("(\"boolOptional\" OR 1)", boolOptional || true) + AssertSQL("(1 OR \"bool\")", true || bool) + AssertSQL("(1 OR \"boolOptional\")", true || boolOptional) + } + + func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() { + AssertSQL("NOT (\"bool\")", !bool) + AssertSQL("NOT (\"boolOptional\")", !boolOptional) + } + + func test_precedencePreserved() { + let n = Expression(value: 1) + AssertSQL("(((1 = 1) AND (1 = 1)) OR (1 = 1))", (n == n && n == n) || n == n) + AssertSQL("((1 = 1) AND ((1 = 1) OR (1 = 1)))", n == n && (n == n || n == n)) + } + +} \ No newline at end of file diff --git a/Tests/QueryTests.swift b/Tests/QueryTests.swift new file mode 100644 index 00000000..b4275543 --- /dev/null +++ b/Tests/QueryTests.swift @@ -0,0 +1,281 @@ +import XCTest +import SQLite + +class QueryTests : XCTestCase { + + let users = Table("users") + let id = Expression("id") + let email = Expression("email") + let age = Expression("age") + let admin = Expression("admin") + + let posts = Table("posts") + let userId = Expression("user_id") + let categoryId = Expression("category_id") + let published = Expression("published") + + let categories = Table("categories") + let tag = Expression("tag") + + func test_select_withExpression_compilesSelectClause() { + AssertSQL("SELECT \"email\" FROM \"users\"", users.select(email)) + } + + func test_select_withVariadicExpressions_compilesSelectClause() { + AssertSQL("SELECT \"email\", count(*) FROM \"users\"", users.select(email, count(*))) + } + + func test_select_withStar_resetsSelectClause() { + AssertSQL("SELECT * FROM \"users\"", users.select(email).select(*)) + } + + func test_selectDistinct_withExpression_compilesSelectClause() { + AssertSQL("SELECT DISTINCT \"age\" FROM \"users\"", users.select(distinct: age)) + } + + func test_selectDistinct_withStar_compilesSelectClause() { + AssertSQL("SELECT DISTINCT * FROM \"users\"", users.select(distinct: *)) + } + + func test_join_compilesJoinClause() { + AssertSQL( + "SELECT * FROM \"users\" INNER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", + users.join(posts, on: posts[userId] == users[id]) + ) + } + + func test_join_withExplicitType_compilesJoinClauseWithType() { + AssertSQL( + "SELECT * FROM \"users\" LEFT OUTER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", + users.join(.LeftOuter, posts, on: posts[userId] == users[id]) + ) + + AssertSQL( + "SELECT * FROM \"users\" CROSS JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\")", + users.join(.Cross, posts, on: posts[userId] == users[id]) + ) + } + + func test_join_withTableCondition_compilesJoinClauseWithTableCondition() { + AssertSQL( + "SELECT * FROM \"users\" INNER JOIN \"posts\" ON ((\"posts\".\"user_id\" = \"users\".\"id\") AND \"published\")", + users.join(posts.filter(published), on: posts[userId] == users[id]) + ) + } + + func test_join_whenChained_compilesAggregateJoinClause() { + AssertSQL( + "SELECT * FROM \"users\" " + + "INNER JOIN \"posts\" ON (\"posts\".\"user_id\" = \"users\".\"id\") " + + "INNER JOIN \"categories\" ON (\"categories\".\"id\" = \"posts\".\"category_id\")", + users.join(posts, on: posts[userId] == users[id]).join(categories, on: categories[id] == posts[categoryId]) + ) + } + + func test_filter_compilesWhereClause() { + AssertSQL("SELECT * FROM \"users\" WHERE (\"admin\" = 1)", users.filter(admin == true)) + } + + func test_filter_whenChained_compilesAggregateWhereClause() { + AssertSQL( + "SELECT * FROM \"users\" WHERE ((\"age\" >= 35) AND \"admin\")", + users.filter(age >= 35).filter(admin) + ) + } + + func test_group_withSingleExpressionName_compilesGroupClause() { + AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\"", + users.group(age)) + } + + func test_group_withVariadicExpressionNames_compilesGroupClause() { + AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\", \"admin\"", users.group(age, admin)) + } + + func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() { + AssertSQL("SELECT * FROM \"users\" GROUP BY \"age\" HAVING (\"age\" >= 30)", users.group(age, having: age >= 30)) + } + + func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() { + AssertSQL( + "SELECT * FROM \"users\" GROUP BY \"age\", \"admin\" HAVING (\"age\" >= 30)", + users.group([age, admin], having: age >= 30) + ) + } + + func test_order_withSingleExpressionName_compilesOrderClause() { + AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(age)) + } + + func test_order_withVariadicExpressionNames_compilesOrderClause() { + AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\", \"email\"", users.order(age, email)) + } + + func test_order_withExpressionAndSortDirection_compilesOrderClause() { +// AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age.desc, email.asc)) + } + + func test_order_whenChained_resetsOrderClause() { + AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\"", users.order(email).order(age)) + } + + func test_reverse_withoutOrder_ordersByRowIdDescending() { +// AssertSQL("SELECT * FROM \"users\" ORDER BY \"ROWID\" DESC", users.reverse()) + } + + func test_reverse_withOrder_reversesOrder() { +// AssertSQL("SELECT * FROM \"users\" ORDER BY \"age\" DESC, \"email\" ASC", users.order(age, email.desc).reverse()) + } + + func test_limit_compilesLimitClause() { + AssertSQL("SELECT * FROM \"users\" LIMIT 5", users.limit(5)) + } + + func test_limit_withOffset_compilesOffsetClause() { + AssertSQL("SELECT * FROM \"users\" LIMIT 5 OFFSET 5", users.limit(5, offset: 5)) + } + + func test_limit_whenChained_overridesLimit() { + let query = users.limit(5) + + AssertSQL("SELECT * FROM \"users\" LIMIT 10", query.limit(10)) + AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) + } + + func test_limit_whenChained_withOffset_overridesOffset() { + let query = users.limit(5, offset: 5) + + AssertSQL("SELECT * FROM \"users\" LIMIT 10 OFFSET 20", query.limit(10, offset: 20)) + AssertSQL("SELECT * FROM \"users\"", query.limit(nil)) + } + + func test_insert_compilesInsertExpression() { + AssertSQL( + "INSERT INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)", + users.insert(email <- "alice@example.com", age <- 30) + ) + } + + func test_insert_withOnConflict_compilesInsertOrOnConflictExpression() { + AssertSQL( + "INSERT OR REPLACE INTO \"users\" (\"email\", \"age\") VALUES ('alice@example.com', 30)", + users.insert(or: .Replace, email <- "alice@example.com", age <- 30) + ) + } + + func test_insert_compilesInsertExpressionWithDefaultValues() { + AssertSQL("INSERT INTO \"users\" DEFAULT VALUES", users.insert()) + } + + func test_insert_withQuery_compilesInsertExpressionWithSelectStatement() { + let emails = Table("emails") + + AssertSQL( + "INSERT INTO \"emails\" SELECT \"email\" FROM \"users\" WHERE \"admin\"", + emails.insert(users.select(email).filter(admin)) + ) + } + + func test_update_compilesUpdateExpression() { + AssertSQL( + "UPDATE \"users\" SET \"age\" = 30, \"admin\" = 1 WHERE (\"id\" = 1)", + users.filter(id == 1).update(age <- 30, admin <- true) + ) + } + + func test_delete_compilesDeleteExpression() { + AssertSQL( + "DELETE FROM \"users\" WHERE (\"id\" = 1)", + users.filter(id == 1).delete() + ) + } + + func test_delete_compilesExistsExpression() { + AssertSQL( + "EXISTS (SELECT * FROM \"users\")", + users.exists + ) + } + + func test_count_returnsCountExpression() { + AssertSQL("SELECT count(*) FROM \"users\"", users.count) + } + + func test_scalar_returnsScalarExpression() { + AssertSQL("SELECT count(\"intOptional\") FROM \"users\"", users.select(intOptional.count)) + } + + func test_subscript_withExpression_returnsNamespacedExpression() { + let query = Table("query") + + AssertSQL("\"query\".\"blob\"", query[data]) + AssertSQL("\"query\".\"blobOptional\"", query[dataOptional]) + + AssertSQL("\"query\".\"bool\"", query[bool]) + AssertSQL("\"query\".\"boolOptional\"", query[boolOptional]) + + AssertSQL("\"query\".\"date\"", query[date]) + AssertSQL("\"query\".\"dateOptional\"", query[dateOptional]) + + AssertSQL("\"query\".\"double\"", query[double]) + AssertSQL("\"query\".\"doubleOptional\"", query[doubleOptional]) + + AssertSQL("\"query\".\"int\"", query[int]) + AssertSQL("\"query\".\"intOptional\"", query[intOptional]) + + AssertSQL("\"query\".\"int64\"", query[int64]) + AssertSQL("\"query\".\"int64Optional\"", query[int64Optional]) + + AssertSQL("\"query\".\"string\"", query[string]) + AssertSQL("\"query\".\"stringOptional\"", query[stringOptional]) + + AssertSQL("\"query\".*", query[*]) + } + + func test_tableNamespacedByDatabase() { + let table = Table("table", database: "attached") + + AssertSQL("SELECT * FROM \"attached\".\"table\"", table) + } + +} + +class QueryIntegrationTests : SQLiteTestCase { + + let email = Expression("email") + + override func setUp() { + super.setUp() + + CreateUsersTable() + } + + // MARK: - + + func test_select() { + for _ in db.prepare(users) { + // FIXME + } + } + + func test_scalar() { + let count = db.scalar(users.count) + XCTAssertEqual(0, count) + } + + func test_insert() { + let id = try! db.run(users.insert(email <- "alice@example.com")) + XCTAssertEqual(1, id) + } + + func test_update() { + let changes = try! db.run(users.update(email <- "alice@example.com")) + XCTAssertEqual(0, changes) + } + + func test_delete() { + let changes = try! db.run(users.delete()) + XCTAssertEqual(0, changes) + } + +} diff --git a/Tests/R*TreeTests.swift b/Tests/R*TreeTests.swift new file mode 100644 index 00000000..7147533e --- /dev/null +++ b/Tests/R*TreeTests.swift @@ -0,0 +1,17 @@ +import XCTest +import SQLite + +class RTreeTests : XCTestCase { + + func test_create_onVirtualTable_withRTree_createVirtualTableExpression() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING rtree(\"int64\", \"double\", \"double\")", + virtualTable.create(.RTree(int64, (double, double))) + ) + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING rtree(\"int64\", \"double\", \"double\", \"double\", \"double\")", + virtualTable.create(.RTree(int64, (double, double), (double, double))) + ) + } + +} \ No newline at end of file diff --git a/Tests/SchemaTests.swift b/Tests/SchemaTests.swift new file mode 100644 index 00000000..a269aee6 --- /dev/null +++ b/Tests/SchemaTests.swift @@ -0,0 +1,767 @@ +import XCTest +import SQLite + +class SchemaTests : XCTestCase { + + func test_drop_compilesDropTableExpression() { + XCTAssertEqual("DROP TABLE \"table\"", table.drop()) + XCTAssertEqual("DROP TABLE IF EXISTS \"table\"", table.drop(ifExists: true)) + } + + func test_drop_compilesDropVirtualTableExpression() { + XCTAssertEqual("DROP VIRTUAL TABLE \"virtual_table\"", virtualTable.drop()) + XCTAssertEqual("DROP VIRTUAL TABLE IF EXISTS \"virtual_table\"", virtualTable.drop(ifExists: true)) + } + + func test_drop_compilesDropViewExpression() { + XCTAssertEqual("DROP VIEW \"view\"", _view.drop()) + XCTAssertEqual("DROP VIEW IF EXISTS \"view\"", _view.drop(ifExists: true)) + } + + func test_create_withBuilder_compilesCreateTableExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (" + + "\"blob\" BLOB NOT NULL, " + + "\"blobOptional\" BLOB, " + + "\"double\" REAL NOT NULL, " + + "\"doubleOptional\" REAL, " + + "\"int64\" INTEGER NOT NULL, " + + "\"int64Optional\" INTEGER, " + + "\"string\" TEXT NOT NULL, " + + "\"stringOptional\" TEXT" + + ")", + table.create { t in + t.column(data) + t.column(dataOptional) + t.column(double) + t.column(doubleOptional) + t.column(int64) + t.column(int64Optional) + t.column(string) + t.column(stringOptional) + } + ) + XCTAssertEqual( + "CREATE TEMPORARY TABLE \"table\" (\"int64\" INTEGER NOT NULL)", + table.create(temporary: true) { $0.column(int64) } + ) + XCTAssertEqual( + "CREATE TABLE IF NOT EXISTS \"table\" (\"int64\" INTEGER NOT NULL)", + table.create(ifNotExists: true) { $0.column(int64) } + ) + XCTAssertEqual( + "CREATE TEMPORARY TABLE IF NOT EXISTS \"table\" (\"int64\" INTEGER NOT NULL)", + table.create(temporary: true, ifNotExists: true) { $0.column(int64) } + ) + } + + func test_create_withQuery_compilesCreateTableExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" AS SELECT \"int64\" FROM \"view\"", + table.create(_view.select(int64)) + ) + } + + // thoroughness test for ambiguity + func test_column_compilesColumnDefinitionExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL)", + table.create { t in t.column(int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE)", + table.create { t in t.column(int64, unique: true) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0))", + table.create { t in t.column(int64, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL DEFAULT (\"int64\"))", + table.create { t in t.column(int64, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL DEFAULT (0))", + table.create { t in t.column(int64, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0))", + table.create { t in t.column(int64, unique: true, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64, unique: true, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE DEFAULT (\"int64\"))", + table.create { t in t.column(int64, unique: true, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE DEFAULT (0))", + table.create { t in t.column(int64, unique: true, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0))", + table.create { t in t.column(int64, unique: true, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64, unique: true, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64, unique: true, check: int64 > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64, unique: true, check: int64Optional > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) DEFAULT (0))", + table.create { t in t.column(int64, unique: true, check: int64 > 0, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (0))", + table.create { t in t.column(int64, unique: true, check: int64Optional > 0, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64, check: int64 > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64, check: int64Optional > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) DEFAULT (0))", + table.create { t in t.column(int64, check: int64 > 0, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) DEFAULT (0))", + table.create { t in t.column(int64, check: int64Optional > 0, defaultValue: 0) } + ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64\" > 0))", + table.create { t in t.column(int64, primaryKey: true, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64, primaryKey: true, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL DEFAULT (\"int64\"))", + table.create { t in t.column(int64, primaryKey: true, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64\" > 0))", + table.create { t in t.column(int64, primaryKey: true, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64, primaryKey: true, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64, primaryKey: true, check: int64 > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY NOT NULL CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64, primaryKey: true, check: int64Optional > 0, defaultValue: int64) } + ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER)", + table.create { t in t.column(int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE)", + table.create { t in t.column(int64Optional, unique: true) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0))", + table.create { t in t.column(int64Optional, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64Optional, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER DEFAULT (\"int64\"))", + table.create { t in t.column(int64Optional, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER DEFAULT (\"int64Optional\"))", + table.create { t in t.column(int64Optional, defaultValue: int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER DEFAULT (0))", + table.create { t in t.column(int64Optional, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0))", + table.create { t in t.column(int64Optional, unique: true, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE DEFAULT (\"int64\"))", + table.create { t in t.column(int64Optional, unique: true, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE DEFAULT (\"int64Optional\"))", + table.create { t in t.column(int64Optional, unique: true, defaultValue: int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE DEFAULT (0))", + table.create { t in t.column(int64Optional, unique: true, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0))", + table.create { t in t.column(int64Optional, unique: true, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) DEFAULT (\"int64Optional\"))", + table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, defaultValue: int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (\"int64Optional\"))", + table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, defaultValue: int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) DEFAULT (0))", + table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) DEFAULT (0))", + table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64Optional, check: int64 > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) DEFAULT (\"int64\"))", + table.create { t in t.column(int64Optional, check: int64Optional > 0, defaultValue: int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) DEFAULT (\"int64Optional\"))", + table.create { t in t.column(int64Optional, check: int64 > 0, defaultValue: int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) DEFAULT (\"int64Optional\"))", + table.create { t in t.column(int64Optional, check: int64Optional > 0, defaultValue: int64Optional) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) DEFAULT (0))", + table.create { t in t.column(int64Optional, check: int64 > 0, defaultValue: 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) DEFAULT (0))", + table.create { t in t.column(int64Optional, check: int64Optional > 0, defaultValue: 0) } + ) + } + + func test_column_withIntegerExpression_compilesPrimaryKeyAutoincrementColumnDefinitionExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + table.create { t in t.column(int64, primaryKey: .Autoincrement) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (\"int64\" > 0))", + table.create { t in t.column(int64, primaryKey: .Autoincrement, check: int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL CHECK (\"int64Optional\" > 0))", + table.create { t in t.column(int64, primaryKey: .Autoincrement, check: int64Optional > 0) } + ) + } + + func test_column_withIntegerExpression_compilesReferentialColumnDefinitionExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, unique: true, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, check: int64 > 0, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, check: int64Optional > 0, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, unique: true, check: int64 > 0, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64, unique: true, check: int64Optional > 0, references: table, int64) } + ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, unique: true, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, check: int64 > 0, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, check: int64Optional > 0, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, unique: true, check: int64 > 0, references: table, int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\"))", + table.create { t in t.column(int64Optional, unique: true, check: int64Optional > 0, references: table, int64) } + ) + } + + func test_column_withStringExpression_compilesCollatedColumnDefinitionExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL COLLATE RTRIM)", + table.create { t in t.column(string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"string\" != '') COLLATE RTRIM)", + table.create { t in t.column(string, check: string != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') COLLATE RTRIM)", + table.create { t in t.column(string, check: stringOptional != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(string, defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(string, defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, check: string != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, check: stringOptional != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, check: string != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, check: stringOptional != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, check: string != "", defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(string, unique: true, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(string, check: string != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(string, check: stringOptional != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(string, check: string != "", defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(string, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) } + ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL COLLATE RTRIM)", + table.create { t in t.column(stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: string != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: stringOptional != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT (\"stringOptional\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, defaultValue: stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: string != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT (\"stringOptional\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, defaultValue: stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: string != "", defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL UNIQUE CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, unique: true, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: string != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"string\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: string, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: string != "", defaultValue: stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT (\"stringOptional\") COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: stringOptional, collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: string != "", defaultValue: "string", collate: .Rtrim) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (\"stringOptional\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM)", + table.create { t in t.column(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) } + ) + } + + func test_primaryKey_compilesPrimaryKeyExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (PRIMARY KEY (\"int64\"))", + table.create { t in t.primaryKey(int64) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (PRIMARY KEY (\"int64\", \"string\"))", + table.create { t in t.primaryKey(int64, string) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (PRIMARY KEY (\"int64\", \"string\", \"double\"))", + table.create { t in t.primaryKey(int64, string, double) } + ) + } + + func test_unique_compilesUniqueExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (UNIQUE (\"int64\"))", + table.create { t in t.unique(int64) } + ) + } + + func test_check_compilesCheckExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (CHECK ((\"int64\" > 0)))", + table.create { t in t.check(int64 > 0) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (CHECK ((\"int64Optional\" > 0)))", + table.create { t in t.check(int64Optional > 0) } + ) + } + + func test_foreignKey_compilesForeignKeyExpression() { + XCTAssertEqual( + "CREATE TABLE \"table\" (FOREIGN KEY (\"string\") REFERENCES \"table\" (\"string\"))", + table.create { t in t.foreignKey(string, references: table, string) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (FOREIGN KEY (\"stringOptional\") REFERENCES \"table\" (\"string\"))", + table.create { t in t.foreignKey(stringOptional, references: table, string) } + ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (FOREIGN KEY (\"string\") REFERENCES \"table\" (\"string\") ON UPDATE CASCADE ON DELETE SET NULL)", + table.create { t in t.foreignKey(string, references: table, string, update: .Cascade, delete: .SetNull) } + ) + + XCTAssertEqual( + "CREATE TABLE \"table\" (FOREIGN KEY (\"string\", \"string\") REFERENCES \"table\" (\"string\", \"string\"))", + table.create { t in t.foreignKey((string, string), references: table, (string, string)) } + ) + XCTAssertEqual( + "CREATE TABLE \"table\" (FOREIGN KEY (\"string\", \"string\", \"string\") REFERENCES \"table\" (\"string\", \"string\", \"string\"))", + table.create { t in t.foreignKey((string, string, string), references: table, (string, string, string)) } + ) + } + + func test_addColumn_compilesAlterTableExpression() { + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL DEFAULT (1)", + table.addColumn(int64, defaultValue: 1) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) DEFAULT (1)", + table.addColumn(int64, check: int64 > 0, defaultValue: 1) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) DEFAULT (1)", + table.addColumn(int64, check: int64Optional > 0, defaultValue: 1) + ) + + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER", + table.addColumn(int64Optional) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64\" > 0)", + table.addColumn(int64Optional, check: int64 > 0) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0)", + table.addColumn(int64Optional, check: int64Optional > 0) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER DEFAULT (1)", + table.addColumn(int64Optional, defaultValue: 1) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64\" > 0) DEFAULT (1)", + table.addColumn(int64Optional, check: int64 > 0, defaultValue: 1) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) DEFAULT (1)", + table.addColumn(int64Optional, check: int64Optional > 0, defaultValue: 1) + ) + } + + func test_addColumn_withIntegerExpression_compilesReferentialAlterTableExpression() { + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL REFERENCES \"table\" (\"int64\")", + table.addColumn(int64, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL UNIQUE REFERENCES \"table\" (\"int64\")", + table.addColumn(int64, unique: true, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64, check: int64 > 0, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64, check: int64Optional > 0, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64, unique: true, check: int64 > 0, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64\" INTEGER NOT NULL UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64, unique: true, check: int64Optional > 0, references: table, int64) + ) + + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER REFERENCES \"table\" (\"int64\")", + table.addColumn(int64Optional, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER UNIQUE REFERENCES \"table\" (\"int64\")", + table.addColumn(int64Optional, unique: true, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64Optional, check: int64 > 0, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64Optional, check: int64Optional > 0, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER UNIQUE CHECK (\"int64\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64Optional, unique: true, check: int64 > 0, references: table, int64) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"int64Optional\" INTEGER UNIQUE CHECK (\"int64Optional\" > 0) REFERENCES \"table\" (\"int64\")", + table.addColumn(int64Optional, unique: true, check: int64Optional > 0, references: table, int64) + ) + } + + func test_addColumn_withStringExpression_compilesCollatedAlterTableExpression() { + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"string\" TEXT NOT NULL DEFAULT ('string') COLLATE RTRIM", + table.addColumn(string, defaultValue: "string", collate: .Rtrim) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"string\" TEXT NOT NULL CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM", + table.addColumn(string, check: string != "", defaultValue: "string", collate: .Rtrim) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"string\" TEXT NOT NULL CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM", + table.addColumn(string, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) + ) + + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT COLLATE RTRIM", + table.addColumn(stringOptional, collate: .Rtrim) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"string\" != '') COLLATE RTRIM", + table.addColumn(stringOptional, check: string != "", collate: .Rtrim) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"stringOptional\" != '') COLLATE RTRIM", + table.addColumn(stringOptional, check: stringOptional != "", collate: .Rtrim) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"string\" != '') DEFAULT ('string') COLLATE RTRIM", + table.addColumn(stringOptional, check: string != "", defaultValue: "string", collate: .Rtrim) + ) + XCTAssertEqual( + "ALTER TABLE \"table\" ADD COLUMN \"stringOptional\" TEXT CHECK (\"stringOptional\" != '') DEFAULT ('string') COLLATE RTRIM", + table.addColumn(stringOptional, check: stringOptional != "", defaultValue: "string", collate: .Rtrim) + ) + } + + func test_rename_compilesAlterTableRenameToExpression() { + XCTAssertEqual("ALTER TABLE \"old\" RENAME TO \"table\"", Table("old").rename(table)) + } + + func test_createIndex_compilesCreateIndexExpression() { + XCTAssertEqual("CREATE INDEX \"index_table_on_int64\" ON \"table\" (\"int64\")", table.createIndex(int64)) + + XCTAssertEqual( + "CREATE UNIQUE INDEX \"index_table_on_int64\" ON \"table\" (\"int64\")", + table.createIndex([int64], unique: true) + ) + XCTAssertEqual( + "CREATE INDEX IF NOT EXISTS \"index_table_on_int64\" ON \"table\" (\"int64\")", + table.createIndex([int64], ifNotExists: true) + ) + XCTAssertEqual( + "CREATE UNIQUE INDEX IF NOT EXISTS \"index_table_on_int64\" ON \"table\" (\"int64\")", + table.createIndex([int64], unique: true, ifNotExists: true) + ) + } + + func test_dropIndex_compilesCreateIndexExpression() { + XCTAssertEqual("DROP INDEX \"index_table_on_int64\"", table.dropIndex(int64)) + XCTAssertEqual("DROP INDEX IF EXISTS \"index_table_on_int64\"", table.dropIndex([int64], ifExists: true)) + } + + func test_create_onView_compilesCreateViewExpression() { + XCTAssertEqual( + "CREATE VIEW \"view\" AS SELECT \"int64\" FROM \"table\"", + _view.create(table.select(int64)) + ) + XCTAssertEqual( + "CREATE TEMPORARY VIEW \"view\" AS SELECT \"int64\" FROM \"table\"", + _view.create(table.select(int64), temporary: true) + ) + XCTAssertEqual( + "CREATE VIEW IF NOT EXISTS \"view\" AS SELECT \"int64\" FROM \"table\"", + _view.create(table.select(int64), ifNotExists: true) + ) + XCTAssertEqual( + "CREATE TEMPORARY VIEW IF NOT EXISTS \"view\" AS SELECT \"int64\" FROM \"table\"", + _view.create(table.select(int64), temporary: true, ifNotExists: true) + ) + } + + func test_create_onVirtualTable_compilesCreateVirtualTableExpression() { + XCTAssertEqual( + "CREATE VIRTUAL TABLE \"virtual_table\" USING \"custom\"('foo', 'bar')", + virtualTable.create(Module("custom", ["foo", "bar"])) + ) + } + + func test_rename_onVirtualTable_compilesAlterTableRenameToExpression() { + XCTAssertEqual( + "ALTER TABLE \"old\" RENAME TO \"virtual_table\"", + VirtualTable("old").rename(virtualTable) + ) + } + +} diff --git a/Tests/SetterTests.swift b/Tests/SetterTests.swift new file mode 100644 index 00000000..d4f189d7 --- /dev/null +++ b/Tests/SetterTests.swift @@ -0,0 +1,137 @@ +import XCTest +import SQLite + +class SetterTests : XCTestCase { + + func test_setterAssignmentOperator_buildsSetter() { + AssertSQL("\"int\" = \"int\"", int <- int) + AssertSQL("\"int\" = 1", int <- 1) + AssertSQL("\"intOptional\" = \"int\"", intOptional <- int) + AssertSQL("\"intOptional\" = \"intOptional\"", intOptional <- intOptional) + AssertSQL("\"intOptional\" = 1", intOptional <- 1) + AssertSQL("\"intOptional\" = NULL", intOptional <- nil) + } + + func test_plusEquals_withStringExpression_buildsSetter() { + AssertSQL("\"string\" = (\"string\" || \"string\")", string += string) + AssertSQL("\"string\" = (\"string\" || 'literal')", string += "literal") + AssertSQL("\"stringOptional\" = (\"stringOptional\" || \"string\")", stringOptional += string) + AssertSQL("\"stringOptional\" = (\"stringOptional\" || \"stringOptional\")", stringOptional += stringOptional) + AssertSQL("\"stringOptional\" = (\"stringOptional\" || 'literal')", stringOptional += "literal") + } + + func test_plusEquals_withNumberExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" + \"int\")", int += int) + AssertSQL("\"int\" = (\"int\" + 1)", int += 1) + AssertSQL("\"intOptional\" = (\"intOptional\" + \"int\")", intOptional += int) + AssertSQL("\"intOptional\" = (\"intOptional\" + \"intOptional\")", intOptional += intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional += 1) + + AssertSQL("\"double\" = (\"double\" + \"double\")", double += double) + AssertSQL("\"double\" = (\"double\" + 1.0)", double += 1) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"double\")", doubleOptional += double) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + \"doubleOptional\")", doubleOptional += doubleOptional) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" + 1.0)", doubleOptional += 1) + } + + func test_minusEquals_withNumberExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" - \"int\")", int -= int) + AssertSQL("\"int\" = (\"int\" - 1)", int -= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" - \"int\")", intOptional -= int) + AssertSQL("\"intOptional\" = (\"intOptional\" - \"intOptional\")", intOptional -= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional -= 1) + + AssertSQL("\"double\" = (\"double\" - \"double\")", double -= double) + AssertSQL("\"double\" = (\"double\" - 1.0)", double -= 1) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"double\")", doubleOptional -= double) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - \"doubleOptional\")", doubleOptional -= doubleOptional) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" - 1.0)", doubleOptional -= 1) + } + + func test_timesEquals_withNumberExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" * \"int\")", int *= int) + AssertSQL("\"int\" = (\"int\" * 1)", int *= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" * \"int\")", intOptional *= int) + AssertSQL("\"intOptional\" = (\"intOptional\" * \"intOptional\")", intOptional *= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" * 1)", intOptional *= 1) + + AssertSQL("\"double\" = (\"double\" * \"double\")", double *= double) + AssertSQL("\"double\" = (\"double\" * 1.0)", double *= 1) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"double\")", doubleOptional *= double) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * \"doubleOptional\")", doubleOptional *= doubleOptional) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" * 1.0)", doubleOptional *= 1) + } + + func test_dividedByEquals_withNumberExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" / \"int\")", int /= int) + AssertSQL("\"int\" = (\"int\" / 1)", int /= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" / \"int\")", intOptional /= int) + AssertSQL("\"intOptional\" = (\"intOptional\" / \"intOptional\")", intOptional /= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" / 1)", intOptional /= 1) + + AssertSQL("\"double\" = (\"double\" / \"double\")", double /= double) + AssertSQL("\"double\" = (\"double\" / 1.0)", double /= 1) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"double\")", doubleOptional /= double) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / \"doubleOptional\")", doubleOptional /= doubleOptional) + AssertSQL("\"doubleOptional\" = (\"doubleOptional\" / 1.0)", doubleOptional /= 1) + } + + func test_moduloEquals_withIntegerExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" % \"int\")", int %= int) + AssertSQL("\"int\" = (\"int\" % 1)", int %= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" % \"int\")", intOptional %= int) + AssertSQL("\"intOptional\" = (\"intOptional\" % \"intOptional\")", intOptional %= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" % 1)", intOptional %= 1) + } + + func test_leftShiftEquals_withIntegerExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" << \"int\")", int <<= int) + AssertSQL("\"int\" = (\"int\" << 1)", int <<= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" << \"int\")", intOptional <<= int) + AssertSQL("\"intOptional\" = (\"intOptional\" << \"intOptional\")", intOptional <<= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" << 1)", intOptional <<= 1) + } + + func test_rightShiftEquals_withIntegerExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" >> \"int\")", int >>= int) + AssertSQL("\"int\" = (\"int\" >> 1)", int >>= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" >> \"int\")", intOptional >>= int) + AssertSQL("\"intOptional\" = (\"intOptional\" >> \"intOptional\")", intOptional >>= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" >> 1)", intOptional >>= 1) + } + + func test_bitwiseAndEquals_withIntegerExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" & \"int\")", int &= int) + AssertSQL("\"int\" = (\"int\" & 1)", int &= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" & \"int\")", intOptional &= int) + AssertSQL("\"intOptional\" = (\"intOptional\" & \"intOptional\")", intOptional &= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" & 1)", intOptional &= 1) + } + + func test_bitwiseOrEquals_withIntegerExpression_buildsSetter() { + AssertSQL("\"int\" = (\"int\" | \"int\")", int |= int) + AssertSQL("\"int\" = (\"int\" | 1)", int |= 1) + AssertSQL("\"intOptional\" = (\"intOptional\" | \"int\")", intOptional |= int) + AssertSQL("\"intOptional\" = (\"intOptional\" | \"intOptional\")", intOptional |= intOptional) + AssertSQL("\"intOptional\" = (\"intOptional\" | 1)", intOptional |= 1) + } + + func test_bitwiseExclusiveOrEquals_withIntegerExpression_buildsSetter() { + AssertSQL("\"int\" = (~((\"int\" & \"int\")) & (\"int\" | \"int\"))", int ^= int) + AssertSQL("\"int\" = (~((\"int\" & 1)) & (\"int\" | 1))", int ^= 1) + AssertSQL("\"intOptional\" = (~((\"intOptional\" & \"int\")) & (\"intOptional\" | \"int\"))", intOptional ^= int) + AssertSQL("\"intOptional\" = (~((\"intOptional\" & \"intOptional\")) & (\"intOptional\" | \"intOptional\"))", intOptional ^= intOptional) + AssertSQL("\"intOptional\" = (~((\"intOptional\" & 1)) & (\"intOptional\" | 1))", intOptional ^= 1) + } + + func test_postfixPlus_withIntegerValue_buildsSetter() { + AssertSQL("\"int\" = (\"int\" + 1)", int++) + AssertSQL("\"intOptional\" = (\"intOptional\" + 1)", intOptional++) + } + + func test_postfixMinus_withIntegerValue_buildsSetter() { + AssertSQL("\"int\" = (\"int\" - 1)", int--) + AssertSQL("\"intOptional\" = (\"intOptional\" - 1)", intOptional--) + } + +} diff --git a/Tests/StatementTests.swift b/Tests/StatementTests.swift new file mode 100644 index 00000000..44a7ae17 --- /dev/null +++ b/Tests/StatementTests.swift @@ -0,0 +1,6 @@ +import XCTest +import SQLite + +class StatementTests : XCTestCase { + +} diff --git a/Tests/TestHelpers.swift b/Tests/TestHelpers.swift new file mode 100644 index 00000000..e41af0f8 --- /dev/null +++ b/Tests/TestHelpers.swift @@ -0,0 +1,115 @@ +import XCTest + +import SQLite + +class SQLiteTestCase : XCTestCase { + + var trace = [String: Int]() + + let db = try! Connection() + + let users = Table("users") + + override func setUp() { + super.setUp() + + db.trace { SQL in + print(SQL) + self.trace[SQL] = (self.trace[SQL] ?? 0) + 1 + } + } + + func CreateUsersTable() { + try! db.execute( + "CREATE TABLE \"users\" (" + + "id INTEGER PRIMARY KEY, " + + "email TEXT NOT NULL UNIQUE, " + + "age INTEGER, " + + "salary REAL, " + + "admin BOOLEAN NOT NULL DEFAULT 0 CHECK (admin IN (0, 1)), " + + "manager_id INTEGER, " + + "FOREIGN KEY(manager_id) REFERENCES users(id)" + + ")" + ) + } + + func InsertUsers(names: String...) throws { + try InsertUsers(names) + } + + func InsertUsers(names: [String]) throws { + for name in names { try InsertUser(name) } + } + + func InsertUser(name: String, age: Int? = nil, admin: Bool = false) throws -> Statement { + return try db.run( + "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", + "\(name)@example.com", age?.datatypeValue, admin.datatypeValue + ) + } + + func AssertSQL(SQL: String, _ executions: Int = 1, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { + XCTAssertEqual( + executions, trace[SQL] ?? 0, + message ?? SQL, + file: file, line: line + ) + } + + func AssertSQL(SQL: String, _ statement: Statement, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { + try! statement.run() + AssertSQL(SQL, 1, message, file: file, line: line) + if let count = trace[SQL] { trace[SQL] = count - 1 } + } + +// func AssertSQL(SQL: String, _ query: Query, _ message: String? = nil, file: String = __FILE__, line: UInt = __LINE__) { +// for _ in query {} +// AssertSQL(SQL, 1, message, file: file, line: line) +// if let count = trace[SQL] { trace[SQL] = count - 1 } +// } + + func async(expect description: String = "async", timeout: Double = 5, @noescape block: (() -> Void) -> Void) { + let expectation = expectationWithDescription(description) + block(expectation.fulfill) + waitForExpectationsWithTimeout(timeout, handler: nil) + } + +} + +let bool = Expression("bool") +let boolOptional = Expression("boolOptional") + +let data = Expression("blob") +let dataOptional = Expression("blobOptional") + +let date = Expression("date") +let dateOptional = Expression("dateOptional") + +let double = Expression("double") +let doubleOptional = Expression("doubleOptional") + +let int = Expression("int") +let intOptional = Expression("intOptional") + +let int64 = Expression("int64") +let int64Optional = Expression("int64Optional") + +let string = Expression("string") +let stringOptional = Expression("stringOptional") + +func AssertSQL(@autoclosure expression1: () -> String, @autoclosure _ expression2: () -> Expressible, file: String = __FILE__, line: UInt = __LINE__) { + XCTAssertEqual(expression1(), expression2().asSQL(), file: file, line: line) +} + +func AssertThrows(@autoclosure expression: () throws -> T, file: String = __FILE__, line: UInt = __LINE__) { + do { + try expression() + XCTFail("expression expected to throw", file: file, line: line) + } catch { + XCTAssert(true, file: file, line: line) + } +} + +let table = Table("table") +let virtualTable = VirtualTable("virtual_table") +let _view = View("view") // avoid Mac XCTestCase collision diff --git a/Tests/ValueTests.swift b/Tests/ValueTests.swift new file mode 100644 index 00000000..bda2b4b3 --- /dev/null +++ b/Tests/ValueTests.swift @@ -0,0 +1,6 @@ +import XCTest +import SQLite + +class ValueTests : XCTestCase { + +} diff --git a/Vendor/sqlcipher b/Vendor/sqlcipher index fafb0d05..c01b94fb 160000 --- a/Vendor/sqlcipher +++ b/Vendor/sqlcipher @@ -1 +1 @@ -Subproject commit fafb0d05bdae394037ac495fe7261573f5f675eb +Subproject commit c01b94fb6a6b54e00b9839e7314893f72c9dab22