From 825d6b287a1d15e6a017ecea727fa7a89f1478af Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 11:31:00 -0700
Subject: [PATCH 0001/1063] Fix download link
Signed-off-by: Stephen Celis
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 6f10c6d8..0ca5b858 100644
--- a/README.md
+++ b/README.md
@@ -86,7 +86,7 @@ To install SQLite.swift:
[4.1]: https://developer.apple.com/xcode/downloads/
[4.2]: http://git-scm.com/book/en/Git-Tools-Submodules
-[4.3]: https://github.com/stephencelis/SQLite.swift/master.zip
+[4.3]: https://github.com/stephencelis/SQLite.swift/archive/master.zip
## Communication
From 60a935ca46250db68a2b2cb54ec8dc6bdba61fba Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 11:33:15 -0700
Subject: [PATCH 0002/1063] Add a better example of iteration row access
We should show how we can access data where we can (rather than hide
behind a println).
Signed-off-by: Stephen Celis
---
README.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/README.md b/README.md
index 0ca5b858..3c88df19 100644
--- a/README.md
+++ b/README.md
@@ -39,9 +39,9 @@ db.lastChanges // {Some 1}
db.lastID // {Some 2}
for row in db.prepare("SELECT id, email FROM users") {
- println(row)
- // [Optional(1), Optional("betsy@example.com")]
- // [Optional(2), Optional("alice@example.com")]
+ println("id: \(row[0]), email: \(row[1])")
+ // id: Optional(1), email: Optional("betsy@example.com")
+ // id: Optional(2), email: Optional("alice@example.com")
}
db.scalar("SELECT count(*) FROM users") // {Some 2}
From 300fe421374e0d01671fc6c2088368a988062782 Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 11:34:35 -0700
Subject: [PATCH 0003/1063] Documentation fixes
- Remove private transaction function documentation.
- Add public savepoint function documentation.
- Clarity: savepoints aren't quite transactions.
Signed-off-by: Stephen Celis
---
SQLite Common/Database.swift | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift
index e946d9cc..ecebf334 100644
--- a/SQLite Common/Database.swift
+++ b/SQLite Common/Database.swift
@@ -232,16 +232,6 @@ public final class Database {
return transaction(mode, statements)
}
- /// Runs a series of statements in a transaction. The first statement to
- /// fail will short-circuit the rest and roll back the changes. A successful
- /// transaction will automatically be committed.
- ///
- /// :param: mode The mode in which the transaction will acquire a
- /// lock.
- ///
- /// :param: statements Statements to run in the transaction.
- ///
- /// :returns: The last statement executed, successful or not.
private func transaction(mode: TransactionMode, _ statements: [@autoclosure () -> Statement]) -> Statement {
var transaction = run("BEGIN \(mode.rawValue) TRANSACTION")
// FIXME: rdar://18479820 // for statement in statements { transaction = transaction && statement() }
@@ -259,7 +249,7 @@ public final class Database {
/// fail will short-circuit the rest and roll back the changes. A successful
/// savepoint will automatically be committed.
///
- /// :param: statements Statements to run in the transaction.
+ /// :param: statements Statements to run in the savepoint.
///
/// :returns: The last statement executed, successful or not.
public func savepoint(statements: (@autoclosure () -> Statement)...) -> Statement {
@@ -268,6 +258,15 @@ public final class Database {
return transaction
}
+ /// Runs a series of statements in a new savepoint. The first statement to
+ /// fail will short-circuit the rest and roll back the changes. A successful
+ /// savepoint will automatically be committed.
+ ///
+ /// :param: name The name of the savepoint.
+ ///
+ /// :param: statements Statements to run in the savepoint.
+ ///
+ /// :returns: The last statement executed, successful or not.
public func savepoint(name: String, _ statements: (@autoclosure () -> Statement)...) -> Statement {
return savepoint(name, statements)
}
From cc9344c6def4fd4f3b0b650fda50dc9a67925ef8 Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 11:36:15 -0700
Subject: [PATCH 0004/1063] Playground cleanup
Just a little bit of editing.
Signed-off-by: Stephen Celis
---
SQLite.playground/Documentation/fragment-3.html | 2 +-
SQLite.playground/Documentation/fragment-4.html | 2 +-
SQLite.playground/Documentation/fragment-6.html | 2 +-
SQLite.playground/Documentation/fragment-8.html | 2 +-
SQLite.playground/section-10.swift | 2 +-
5 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/SQLite.playground/Documentation/fragment-3.html b/SQLite.playground/Documentation/fragment-3.html
index 62f6ae76..33944b25 100644
--- a/SQLite.playground/Documentation/fragment-3.html
+++ b/SQLite.playground/Documentation/fragment-3.html
@@ -4,7 +4,7 @@
- Prepared statements can bind and escape input values safely. In this case, both email and admin column values are marked and bound with different values over two executions.
+ Prepared statements can bind and escape input values safely. In this case, both 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; lastChanges returns the number of changes from the last executed statement that modified the database; lastID returns the row ID of the last insert.
diff --git a/SQLite.playground/Documentation/fragment-4.html b/SQLite.playground/Documentation/fragment-4.html
index e8cd165d..988c0027 100644
--- a/SQLite.playground/Documentation/fragment-4.html
+++ b/SQLite.playground/Documentation/fragment-4.html
@@ -5,6 +5,6 @@
Querying
- Statement objects act as Swift sequences and generators. We can iterate over a select statement’s rows directly using a for–in loop.
+ Statement objects act as both sequences and generators. We can iterate over a select statement’s rows directly using a for–in loop.
- Using the transaction and savepoint functions, we can run a series of statements, commiting the changes to the database if all succeed, or bailing out early and rolling back on failure. In the following example we prepare two statements: one to insert a manager into the database, and one—given a manager’s row ID—to insert a managed user into the database.
+ 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 row ID—to insert a managed user into the database.
- Our database has a uniqueness constraint on email address, so let’s try inserting Fiona, who also claims to be managing Emery.
+ 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.
diff --git a/SQLite.playground/section-10.swift b/SQLite.playground/section-10.swift
index 2d8b38d8..316672a1 100644
--- a/SQLite.playground/section-10.swift
+++ b/SQLite.playground/section-10.swift
@@ -1,3 +1,3 @@
for row in db.prepare("SELECT id, email FROM users") {
- println(row)
+ println("id: \(row[0]), email: \(row[1])")
}
From 6961bfeb66f58ba352768fcd561c4d1e793762dd Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Wed, 8 Oct 2014 06:50:02 -0700
Subject: [PATCH 0005/1063] Add lazy, chained query builder
This commit brings the expressiveness of Active Record query building to
SQLite.swift.
Signed-off-by: Stephen Celis
---
SQLite Common Tests/QueryTests.swift | 355 ++++++++++++++++
SQLite Common/Database.swift | 6 +
SQLite Common/Query.swift | 579 +++++++++++++++++++++++++++
SQLite.xcodeproj/project.pbxproj | 12 +
4 files changed, 952 insertions(+)
create mode 100644 SQLite Common Tests/QueryTests.swift
create mode 100644 SQLite Common/Query.swift
diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift
new file mode 100644
index 00000000..736a8114
--- /dev/null
+++ b/SQLite Common Tests/QueryTests.swift
@@ -0,0 +1,355 @@
+import XCTest
+import SQLite
+
+class QueryTests: XCTestCase {
+
+ let db = Database()
+ var users: Query { return db["users"] }
+
+ override func setUp() {
+ super.setUp()
+
+ CreateUsersTable(db)
+ }
+
+ func test_select_withString_compilesSelectClause() {
+ let query = users.select("email")
+
+ let SQL = "SELECT email FROM users"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_select_withVariadicStrings_compilesSelectClause() {
+ let query = users.select("email", "count(*)")
+
+ let SQL = "SELECT email, count(*) FROM users"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_join_compilesJoinClause() {
+ let query = users.join("users AS managers", on: "users.manager_id = managers.id")
+
+ let SQL = "SELECT * FROM users INNER JOIN users AS managers ON users.manager_id = managers.id"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_join_withExplicityType_compilesJoinClauseWithType() {
+ let query = users.join(.LeftOuter, "users AS managers", on: "users.manager_id = managers.id")
+
+ let SQL = "SELECT * FROM users LEFT OUTER JOIN users AS managers ON users.manager_id = managers.id"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_join_whenChained_compilesAggregateJoinClause() {
+ let query = users
+ .join("users AS managers", on: "users.manager_id = managers.id")
+ .join("users AS managed", on: "managed.manager_id = users.id")
+
+ let SQL = "SELECT * FROM users " +
+ "INNER JOIN users AS managers ON users.manager_id = managers.id " +
+ "INNER JOIN users AS managed ON managed.manager_id = users.id"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_filter_withoutBindings_compilesWhereClause() {
+ let query = users.filter("admin = 1")
+
+ let SQL = "SELECT * FROM users WHERE admin = 1"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_filter_withExplicitBindings_compilesWhereClause() {
+ let query = users.filter("admin = ?", true)
+
+ let SQL = "SELECT * FROM users WHERE admin = 1"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_filter_withImplicitBindingsDictionary_compilesWhereClause() {
+ let query = users.filter(["email": "alice@example.com", "age": 30])
+
+ let SQL = "SELECT * FROM users " +
+ "WHERE email = 'alice@example.com' " +
+ "AND age = 30"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_filter_withArrayBindings_compilesWhereClause() {
+ let query = users.filter(["id": [1, 2]])
+
+ let SQL = "SELECT * FROM users WHERE id IN (1, 2)"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_filter_withRangeBindings_compilesWhereClause() {
+ let query = users.filter(["age": 20..<30])
+
+ let SQL = "SELECT * FROM users WHERE age BETWEEN 20 AND 30"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ 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"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_group_withSingleColumnName_compilesGroupClause() {
+ let query = users.group("age")
+
+ let SQL = "SELECT * FROM users GROUP BY age"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_group_withVariadicColumnNames_compilesGroupClause() {
+ let query = users.group("age", "admin")
+
+ let SQL = "SELECT * FROM users GROUP BY age, admin"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_group_withColumnNameAndHavingBindings_compilesGroupClause() {
+ let query = users.group("age", having: "age >= ?", 30)
+
+ let SQL = "SELECT * FROM users GROUP BY age HAVING age >= 30"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_group_withColumnNamesAndHavingBindings_compilesGroupClause() {
+ let query = users.group(["age", "admin"], having: "age >= ?", 30)
+
+ let SQL = "SELECT * FROM users GROUP BY age, admin HAVING age >= 30"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_order_withSingleColumnName_compilesOrderClause() {
+ let query = users.order("age")
+
+ let SQL = "SELECT * FROM users ORDER BY age ASC"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_order_withVariadicColumnNames_compilesOrderClause() {
+ let query = users.order("age", "email")
+
+ let SQL = "SELECT * FROM users ORDER BY age ASC, email ASC"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_order_withColumnAndSortDirection_compilesOrderClause() {
+ let query = users.order("age", .Desc)
+
+ let SQL = "SELECT * FROM users ORDER BY age DESC"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_order_withColumnSortDirectionTuple_compilesOrderClause() {
+ let query = users.order(("age", .Desc))
+
+ let SQL = "SELECT * FROM users ORDER BY age DESC"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_order_withVariadicColumnSortDirectionTuples_compilesOrderClause() {
+ let query = users.order(("age", .Desc), ("email", .Asc))
+
+ let SQL = "SELECT * FROM users ORDER BY age DESC, email ASC"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_order_whenChained_compilesAggregateOrderClause() {
+ let query = users.order("age").order("email")
+
+ let SQL = "SELECT * FROM users ORDER BY age ASC, email ASC"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_reverse_reversesOrder() {
+ let query = users.order(("age", .Desc), ("email", .Asc))
+
+ let SQL = "SELECT * FROM users ORDER BY age ASC, email DESC"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in reverse(query) {} }
+ }
+
+ func test_reverse_withoutOrder_reversesOrderByRowID() {
+ let SQL = "SELECT * FROM users ORDER BY users.ROWID DESC"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in reverse(self.users) {} }
+ }
+
+ func test_limit_compilesLimitClause() {
+ let query = users.limit(5)
+
+ let SQL = "SELECT * FROM users LIMIT 5"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_limit_withOffset_compilesOffsetClause() {
+ let query = users.limit(5, offset: 5)
+
+ let SQL = "SELECT * FROM users LIMIT 5 OFFSET 5"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_limit_whenChained_overridesLimit() {
+ let query = users.limit(5).limit(10)
+
+ var SQL = "SELECT * FROM users LIMIT 10"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+
+ SQL = "SELECT * FROM users"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.limit(nil) {} }
+ }
+
+ func test_limit_whenChained_withOffset_overridesOffset() {
+ let query = users.limit(5, offset: 5).limit(10, offset: 10)
+
+ var SQL = "SELECT * FROM users LIMIT 10 OFFSET 10"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+
+ SQL = "SELECT * FROM users"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.limit(nil) {} }
+ }
+
+ func test_SQL_compilesProperly() {
+ let query = users
+ .select("email", "count(email) AS count")
+ .filter("age >= ?", 21)
+ .group("age", having: "count > ?", 1)
+ .order("email", .Desc)
+ .limit(1, offset: 2)
+
+ let SQL = "SELECT email, count(email) AS count FROM users " +
+ "WHERE age >= 21 " +
+ "GROUP BY age HAVING count > 1 " +
+ "ORDER BY email DESC " +
+ "LIMIT 1 " +
+ "OFFSET 2"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ }
+
+ func test_first_withAnEmptyQuery_returnsNil() {
+ XCTAssert(users.first == nil)
+ }
+
+ func test_first_returnsTheFirstRow() {
+ InsertUsers(db, "alice", "betsy")
+ ExpectExecutions(db, ["SELECT * FROM users LIMIT 1": 1]) { _ in
+ XCTAssertEqual(1, self.users.first!["id"] as Int)
+ }
+ }
+
+ func test_last_withAnEmptyQuery_returnsNil() {
+ XCTAssert(users.last == nil)
+ }
+
+ func test_last_returnsTheLastRow() {
+ InsertUsers(db, "alice", "betsy")
+ ExpectExecutions(db, ["SELECT * FROM users ORDER BY users.ROWID DESC LIMIT 1": 1]) { _ in
+ XCTAssertEqual(2, self.users.last!["id"] as Int)
+ }
+ }
+
+ func test_last_withAnOrderedQuery_reversesOrder() {
+ ExpectExecutions(db, ["SELECT * FROM users ORDER BY age DESC LIMIT 1": 1]) { _ in
+ self.users.order("age").last
+ return
+ }
+ }
+
+ func test_isEmpty_returnsWhetherOrNotTheQueryIsEmpty() {
+ ExpectExecutions(db, ["SELECT * FROM users LIMIT 1": 2]) { _ in
+ XCTAssertTrue(self.users.isEmpty)
+ InsertUser(self.db, "alice")
+ XCTAssertFalse(self.users.isEmpty)
+ }
+ }
+
+ func test_insert_insertsRows() {
+ let SQL = "INSERT INTO users (email, age) VALUES ('alice@example.com', 30)"
+ ExpectExecutions(db, [SQL: 1]) { _ in
+ XCTAssertEqual(1, self.users.insert(["email": "alice@example.com", "age": 30]).ID!)
+ }
+
+ XCTAssert(users.insert(["email": "alice@example.com", "age": 30]).ID == nil)
+ }
+
+ func test_update_updatesRows() {
+ InsertUsers(db, "alice", "betsy")
+ InsertUser(db, "dolly", admin: true)
+
+ XCTAssertEqual(2, users.filter(["admin": false]).update(["age": 30, "admin": true]).changes)
+ XCTAssertEqual(0, users.filter(["admin": false]).update(["age": 30, "admin": true]).changes)
+ }
+
+ func test_delete_deletesRows() {
+ InsertUser(db, "alice", age: 20)
+ XCTAssertEqual(0, users.filter(["email": "betsy@example.com"]).delete().changes)
+
+ InsertUser(db, "betsy", age: 30)
+ XCTAssertEqual(2, users.delete().changes)
+ XCTAssertEqual(0, users.delete().changes)
+ }
+
+ func test_count_returnsCount() {
+ XCTAssertEqual(0, users.count)
+
+ InsertUser(db, "alice")
+ XCTAssertEqual(1, users.count)
+ XCTAssertEqual(0, users.filter("age IS NOT NULL").count)
+ }
+
+ func test_count_withColumn_returnsCount() {
+ InsertUser(db, "alice", age: 20)
+ InsertUser(db, "betsy", age: 20)
+ InsertUser(db, "cindy")
+
+ XCTAssertEqual(2, users.count("age"))
+ XCTAssertEqual(1, users.count("DISTINCT age"))
+ }
+
+ func test_max_returnsMaximum() {
+ XCTAssert(users.max("age") == nil)
+
+ InsertUser(db, "alice", age: 20)
+ InsertUser(db, "betsy", age: 30)
+ XCTAssertEqual(30, users.max("age") as Int)
+ }
+
+ func test_max_returnsMinimum() {
+ XCTAssert(users.min("age") == nil)
+
+ InsertUser(db, "alice", age: 20)
+ InsertUser(db, "betsy", age: 30)
+ XCTAssertEqual(20, users.min("age") as Int)
+ }
+
+ func test_average_returnsAverage() {
+ XCTAssert(users.average("age") == nil)
+
+ InsertUser(db, "alice", age: 20)
+ InsertUser(db, "betsy", age: 30)
+ XCTAssertEqual(25.0, users.average("age")!)
+ }
+
+ func test_sum_returnsSum() {
+ XCTAssert(users.sum("age") == nil)
+
+ InsertUser(db, "alice", age: 20)
+ InsertUser(db, "betsy", age: 30)
+ XCTAssertEqual(50, users.sum("age") as Int)
+ }
+
+ func test_total_returnsTotal() {
+ XCTAssertEqual(0.0, users.total("age"))
+
+ InsertUser(db, "alice", age: 20)
+ InsertUser(db, "betsy", age: 30)
+ XCTAssertEqual(50.0, users.total("age"))
+ }
+
+}
diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift
index ecebf334..e0778ea3 100644
--- a/SQLite Common/Database.swift
+++ b/SQLite Common/Database.swift
@@ -330,6 +330,12 @@ public final class Database {
if block() != SQLITE_OK { assertionFailure("\(lastError)") }
}
+ // MARK: - Query Building
+
+ public subscript(tableName: String) -> Query {
+ return Query(self, tableName)
+ }
+
}
extension Database: DebugPrintable {
diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift
new file mode 100644
index 00000000..06c6867e
--- /dev/null
+++ b/SQLite Common/Query.swift
@@ -0,0 +1,579 @@
+//
+// SQLite.Query
+// Copyright (c) 2014 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 using a collection of
+/// chainable helper functions.
+public struct Query {
+
+ private var database: Database
+
+ internal init(_ database: Database, _ tableName: String) {
+ self.database = database
+ self.tableName = tableName
+ self.columnNames = ["*"]
+ }
+
+ // 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"
+
+ }
+
+ /// Determines the direction in which rows are returned for a query’s ORDER
+ /// BY clause.
+ public enum SortDirection: String {
+
+ /// Ascending order (smaller to larger values).
+ case Asc = "ASC"
+
+ /// Descending order (larger to smaller values).
+ case Desc = "DESC"
+
+ }
+
+ private var columnNames: [String]
+ private var tableName: String
+ private var joins = [JoinType, String, String]()
+ private var conditions: String?
+ private var bindings = [Datatype?]()
+ private var groupByHaving: ([String], String?, [Datatype?])?
+ private var order = [String, SortDirection]()
+ private var limit: Int?
+ private var offset: Int?
+
+ // MARK: -
+
+ /// Sets a SELECT clause on the query.
+ ///
+ /// :param: columnNames A list of columns to retrieve.
+ ///
+ /// :returns: A query with the given SELECT clause applied.
+ public func select(columnNames: String...) -> Query {
+ var query = self
+ query.columnNames = columnNames
+ return query
+ }
+
+ /// Adds an INNER JOIN clause to the query.
+ ///
+ /// :param: tableName The table being joined.
+ ///
+ /// :param: constraint The condition in which the table is joined.
+ ///
+ /// :returns: A query with the given INNER JOIN clause applied.
+ public func join(tableName: String, on constraint: String) -> Query {
+ return join(.Inner, tableName, on: constraint)
+ }
+
+ /// Adds a JOIN clause to the query.
+ ///
+ /// :param: type The JOIN operator used .
+ ///
+ /// :param: tableName The table being joined.
+ ///
+ /// :param: constraint The condition in which the table is joined.
+ ///
+ /// :returns: A query with the given JOIN clause applied.
+ public func join(type: JoinType, _ tableName: String, on constraint: String) -> Query {
+ var query = self
+ query.joins.append(type, tableName, constraint)
+ return query
+ }
+
+ /// Adds a condition to the query’s WHERE clause.
+ ///
+ /// :param: condition A dictionary of conditions where the keys map to
+ /// column names and the columns must equal the given
+ /// values.
+ ///
+ /// :returns: A query with the given condition applied.
+ public func filter(condition: [String: Datatype?]) -> Query {
+ var query = self
+ for (column, value) in condition {
+ if let value = value {
+ query = query.filter("\(column) = ?", value)
+ } else {
+ query = query.filter("\(column) IS NULL")
+ }
+ }
+ return query
+ }
+
+ /// Adds a condition to the query’s WHERE clause.
+ ///
+ /// :param: condition A dictionary of conditions where the keys map to
+ /// column names and the columns must be in the lists of
+ /// given values.
+ ///
+ /// :returns: A query with the given condition applied.
+ public func filter(condition: [String: [Datatype?]]) -> Query {
+ var query = self
+ for (column, values) in condition {
+ let templates = Swift.join(", ", [String](count: values.count, repeatedValue: "?"))
+ query = query.filter("\(column) IN (\(templates))", values)
+ }
+ return query
+ }
+
+ /// Adds a condition to the query’s WHERE clause.
+ ///
+ /// :param: condition A dictionary of conditions where the keys map to
+ /// column names and the columns must be in the ranges of
+ /// given values.
+ ///
+ /// :returns: A query with the given condition applied.
+ public func filter(condition: [String: Range]) -> Query {
+ var query = self
+ for (column, value) in condition {
+ query = query.filter("\(column) BETWEEN ? AND ?", value.startIndex, value.endIndex)
+ }
+ return query
+ }
+
+ /// Adds a condition to the query’s WHERE clause.
+ ///
+ /// :param: condition A string condition with optional "?"-parameterized
+ /// bindings.
+ ///
+ /// :param: bindings A list of parameters to bind to the WHERE clause.
+ ///
+ /// :returns: A query with the given condition applied.
+ public func filter(condition: String, _ bindings: Datatype?...) -> Query {
+ return filter(condition, bindings)
+ }
+
+ /// Adds a condition to the query’s WHERE clause.
+ ///
+ /// :param: condition A string condition with optional "?"-parameterized
+ /// bindings.
+ ///
+ /// :param: bindings A list of parameters to bind to the WHERE clause.
+ ///
+ /// :returns: A query with the given condition applied.
+ public func filter(condition: String, _ bindings: [Datatype?]) -> Query {
+ var query = self
+ if let conditions = query.conditions {
+ query.conditions = "\(conditions) AND \(condition)"
+ } else {
+ query.conditions = condition
+ }
+ query.bindings += bindings
+ return query
+ }
+
+ /// 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: String...) -> Query {
+ return group(by, bindings)
+ }
+
+ /// Sets a GROUP BY-HAVING clause on the query.
+ ///
+ /// :param: by A column to group by.
+ ///
+ /// :param: having A condition determining which groups are returned.
+ ///
+ /// :param: bindings A list of parameters to bind to the HAVING clause.
+ ///
+ /// :returns: A query with the given GROUP BY clause applied.
+ public func group(by: String, having: String, _ bindings: Datatype?...) -> Query {
+ return group([by], having: having, bindings)
+ }
+
+ /// 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.
+ ///
+ /// :param: bindings A list of parameters to bind to the HAVING clause.
+ ///
+ /// :returns: A query with the given GROUP BY clause applied.
+ public func group(by: [String], having: String, _ bindings: Datatype?...) -> Query {
+ return group(by, having: having, bindings)
+ }
+
+ private func group(by: [String], having: String? = nil, _ bindings: [Datatype?]) -> Query {
+ var query = self
+ query.groupByHaving = (by, having, bindings)
+ return query
+ }
+
+ /// Adds sorting instructions to the query’s ORDER BY clause.
+ ///
+ /// :param: by A list of columns to order by with an ascending sort.
+ ///
+ /// :returns: A query with the given sorting instructions applied.
+ public func order(by: String...) -> Query {
+ return order(by)
+ }
+
+ private func order(by: [String]) -> Query {
+ return order(by.map { ($0, .Asc) })
+ }
+
+ /// Adds sorting instructions to the query’s ORDER BY clause.
+ ///
+ /// :param: by A column to order by.
+ ///
+ /// :param: direction The direction in which the column is ordered.
+ ///
+ /// :returns: A query with the given sorting instructions applied.
+ public func order(by: String, _ direction: SortDirection) -> Query {
+ return order([(by, direction)])
+ }
+
+ /// Adds sorting instructions to the query’s ORDER BY clause.
+ ///
+ /// :param: by An ordered list of columns and directions to sort them by.
+ ///
+ /// :returns: A query with the given sorting instructions applied.
+ public func order(by: (String, SortDirection)...) -> Query {
+ return order(by)
+ }
+
+ private func order(by: [(String, SortDirection)]) -> 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)
+ }
+
+ // prevent limit(nil, offset: 5)
+ private func limit(#to: Int?, offset: Int? = nil) -> Query {
+ var query = self
+ (query.limit, query.offset) = (to, offset)
+ return query
+ }
+
+ // MARK: - Compiling Statements
+
+ private var selectStatement: Statement {
+ let columnNames = Swift.join(", ", self.columnNames)
+
+ var parts = ["SELECT \(columnNames) FROM \(tableName)"]
+ joinClause.map(parts.append)
+ whereClause.map(parts.append)
+ groupClause.map(parts.append)
+ orderClause.map(parts.append)
+ limitClause.map(parts.append)
+
+ var bindings = self.bindings
+ if let (_, _, values) = groupByHaving { bindings += values }
+
+ return database.prepare(Swift.join(" ", parts), bindings)
+ }
+
+ private func insertStatement(values: [String: Datatype?]) -> Statement {
+ var (parts, bindings) = (["INSERT INTO \(tableName)"], self.bindings)
+ let valuesClause = Swift.join(", ", map(values) { columnName, value in
+ bindings.append(value)
+ return columnName
+ })
+ let templates = Swift.join(", ", [String](count: values.count, repeatedValue: "?"))
+ parts.append("(\(valuesClause)) VALUES (\(templates))")
+ return database.prepare(Swift.join(" ", parts), bindings)
+ }
+
+ private func updateStatement(values: [String: Datatype?]) -> Statement {
+ var (parts, bindings) = (["UPDATE \(tableName)"], [Datatype?]())
+ let valuesClause = Swift.join(", ", map(values) { columnName, value in
+ bindings.append(value)
+ return "\(columnName) = ?"
+ })
+ parts.append("SET \(valuesClause)")
+ whereClause.map(parts.append)
+ return database.prepare(Swift.join(" ", parts), bindings + self.bindings)
+ }
+
+ private var deleteStatement: Statement {
+ var parts = ["DELETE FROM \(tableName)"]
+ whereClause.map(parts.append)
+ return database.prepare(Swift.join(" ", parts), bindings)
+ }
+
+ // MARK: -
+
+ private var joinClause: String? {
+ if joins.count == 0 { return nil }
+ let clause = joins.map { "\($0.rawValue) JOIN \($1) ON \($2)" }
+ return Swift.join(" ", clause)
+ }
+
+ private var whereClause: String? {
+ if let conditions = conditions { return "WHERE \(conditions)" }
+ return nil
+ }
+
+ private var groupClause: String? {
+ if let (groupBy, having, _) = groupByHaving {
+ let groups = Swift.join(", ", groupBy)
+ var clause = ["GROUP BY \(groups)"]
+ having.map { clause.append("HAVING \($0)") }
+ return Swift.join(" ", clause)
+ }
+ return nil
+ }
+
+ private var orderClause: String? {
+ if order.count == 0 { return nil }
+ let mapped = order.map { "\($0.0) \($0.1.rawValue)" }
+ let joined = Swift.join(", ", mapped)
+ return "ORDER BY \(joined)"
+ }
+
+ private var limitClause: String? {
+ if let to = limit {
+ var clause = ["LIMIT \(to)"]
+ offset.map { clause.append("OFFSET \($0)") }
+ return Swift.join(" ", clause)
+ }
+ return nil
+ }
+
+ // MARK: - Array
+
+ public typealias Element = [String: Datatype?]
+
+ /// The first result (or nil if the query has no results).
+ public var first: Element? { return limit(1).generate().next() }
+
+ /// The last result (or nil if the query has no results).
+ public var last: Element? { return reverse(self).first }
+
+ /// Returns true if the query has no results.
+ public var isEmpty: Bool { return first == nil }
+
+ // MARK: - Modifying
+
+ /// Runs an INSERT statement with the given row of values.
+ ///
+ /// :param: values A dictionary of column names to values.
+ ///
+ /// :returns: The statement.
+ public func insert(values: [String: Datatype?]) -> Statement {
+ return insert(values).statement
+ }
+
+ /// Runs an INSERT statement with the given row of values.
+ ///
+ /// :param: values A dictionary of column names to values.
+ ///
+ /// :returns: The row ID.
+ public func insert(values: [String: Datatype?]) -> Int? {
+ return insert(values).ID
+ }
+
+ /// Runs an INSERT statement with the given row of values.
+ ///
+ /// :param: values A dictionary of column names to values.
+ ///
+ /// :returns: The row ID and statement.
+ public func insert(values: [String: Datatype?]) -> (ID: Int?, statement: Statement) {
+ let statement = insertStatement(values).run()
+ return (statement.failed ? nil : database.lastID, statement)
+ }
+
+ /// Runs an UPDATE statement against the query with the given values.
+ ///
+ /// :param: values A dictionary of column names to values.
+ ///
+ /// :returns: The statement.
+ public func update(values: [String: Datatype?]) -> Statement {
+ return update(values).statement
+ }
+
+ /// Runs an UPDATE statement against the query with the given values.
+ ///
+ /// :param: values A dictionary of column names to values.
+ ///
+ /// :returns: The number of updated rows.
+ public func update(values: [String: Datatype?]) -> Int {
+ return update(values).changes
+ }
+
+ /// Runs an UPDATE statement against the query with the given values.
+ ///
+ /// :param: values A dictionary of column names to values.
+ ///
+ /// :returns: The number of updated rows and statement.
+ public func update(values: [String: Datatype?]) -> (changes: Int, statement: Statement) {
+ let statement = updateStatement(values).run()
+ return (statement.failed ? 0 : database.lastChanges ?? 0, statement)
+ }
+
+ /// Runs an DELETE statement against the query.
+ ///
+ /// :returns: The statement.
+ public func delete() -> Statement {
+ return delete().statement
+ }
+
+ /// Runs an DELETE statement against the query.
+ ///
+ /// :returns: The number of deleted rows.
+ public func delete() -> Int {
+ return delete().changes
+ }
+
+ /// Runs an DELETE statement against the query.
+ ///
+ /// :returns: The number of deleted rows and statement.
+ public func delete() -> (changes: Int, statement: Statement) {
+ let statement = deleteStatement.run()
+ return (statement.failed ? 0 : database.lastChanges ?? 0, statement)
+ }
+
+ // MARK: - Aggregate Functions
+
+ /// Runs count(*) against the query and returns it.
+ public var count: Int { return count("*") }
+
+ /// Runs count() against the query.
+ ///
+ /// :param: columnName The column used for the calculation.
+ ///
+ /// :returns: The number of rows matching the given column.
+ public func count(columnName: String) -> Int {
+ return calculate("count", columnName) as Int
+ }
+
+ /// Runs max() against the query.
+ ///
+ /// :param: columnName The column used for the calculation.
+ ///
+ /// :returns: The largest value of the given column.
+ public func max(columnName: String) -> Datatype? {
+ return calculate("max", columnName)
+ }
+
+ /// Runs min() against the query.
+ ///
+ /// :param: columnName The column used for the calculation.
+ ///
+ /// :returns: The smallest value of the given column.
+ public func min(columnName: String) -> Datatype? {
+ return calculate("min", columnName)
+ }
+
+ /// Runs avg() against the query.
+ ///
+ /// :param: columnName The column used for the calculation.
+ /// :returns: The average value of the given column.
+ public func average(columnName: String) -> Double? {
+ return calculate("avg", columnName) as? Double
+ }
+
+ /// Runs sum() against the query.
+ ///
+ /// :param: columnName The column used for the calculation.
+ ///
+ /// :returns: The sum of the given column’s values.
+ public func sum(columnName: String) -> Datatype? {
+ return calculate("sum", columnName)
+ }
+
+ /// Runs total() against the query.
+ ///
+ /// :param: columnName The column used for the calculation.
+ ///
+ /// :returns: The total of the given column’s values.
+ public func total(columnName: String) -> Double {
+ return calculate("total", columnName) as Double
+ }
+
+ private func calculate(function: String, _ columnName: String) -> Datatype? {
+ return select("\(function)(\(columnName))").selectStatement.scalar()
+ }
+
+}
+
+// MARK: - SequenceType
+extension Query: SequenceType {
+
+ public typealias Generator = QueryGenerator
+
+ public func generate() -> Generator { return Generator(selectStatement) }
+
+}
+
+// MARK: - GeneratorType
+public struct QueryGenerator: GeneratorType {
+
+ public typealias Element = [String: Datatype?]
+
+ private var statement: Statement
+
+ private init(_ statement: Statement) { self.statement = statement }
+
+ public func next() -> Element? {
+ statement.next()
+ return statement.values
+ }
+
+}
+
+/// Reverses the order of a given query.
+///
+/// :param: query A query object to reverse.
+///
+/// :returns: A reversed query.
+public func reverse(query: Query) -> Query {
+ if query.order.count == 0 { return query.order("\(query.tableName).ROWID", .Desc) }
+
+ var reversed = query
+ reversed.order = query.order.map { ($0, $1 == .Asc ? .Desc : .Asc) }
+ return reversed
+}
diff --git a/SQLite.xcodeproj/project.pbxproj b/SQLite.xcodeproj/project.pbxproj
index ea7038de..d75fa8eb 100644
--- a/SQLite.xcodeproj/project.pbxproj
+++ b/SQLite.xcodeproj/project.pbxproj
@@ -25,6 +25,10 @@
DC37743C19C8D6C0004FCF85 /* Statement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC37743A19C8D6C0004FCF85 /* Statement.swift */; };
DC37744319C8DC91004FCF85 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744219C8DC91004FCF85 /* libsqlite3.dylib */; };
DC37744519C8DCA1004FCF85 /* libsqlite3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = DC37744419C8DCA1004FCF85 /* libsqlite3.dylib */; };
+ DCAD429719E2E0F1004A51DF /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; };
+ DCAD429819E2E0F1004A51DF /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429619E2E0F1004A51DF /* Query.swift */; };
+ DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429919E2EE50004A51DF /* QueryTests.swift */; };
+ DCAD429B19E2EE50004A51DF /* QueryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCAD429919E2EE50004A51DF /* QueryTests.swift */; };
DCF37F8219DDAC2D001534AA /* DatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF37F8119DDAC2D001534AA /* DatabaseTests.swift */; };
DCF37F8319DDAC2D001534AA /* DatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF37F8119DDAC2D001534AA /* DatabaseTests.swift */; };
DCF37F8519DDAF3F001534AA /* StatementTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DCF37F8419DDAF3F001534AA /* StatementTests.swift */; };
@@ -72,6 +76,8 @@
DC37744419C8DCA1004FCF85 /* libsqlite3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libsqlite3.dylib; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk/usr/lib/libsqlite3.dylib; sourceTree = DEVELOPER_DIR; };
DC37744719C8F50B004FCF85 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; };
DCAAE66D19D8A71B00158FEF /* SQLite.playground */ = {isa = PBXFileReference; lastKnownFileType = file.playground; path = SQLite.playground; sourceTree = ""; };
+ DCAD429619E2E0F1004A51DF /* Query.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Query.swift; sourceTree = ""; };
+ DCAD429919E2EE50004A51DF /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.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; path = StatementTests.swift; sourceTree = ""; };
DCF37F8719DDAF79001534AA /* TestHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestHelper.swift; sourceTree = ""; };
@@ -129,6 +135,7 @@
DCF37F8719DDAF79001534AA /* TestHelper.swift */,
DCF37F8119DDAC2D001534AA /* DatabaseTests.swift */,
DCF37F8419DDAF3F001534AA /* StatementTests.swift */,
+ DCAD429919E2EE50004A51DF /* QueryTests.swift */,
);
name = "SQLite Tests";
path = "SQLite Common Tests";
@@ -241,6 +248,7 @@
children = (
DC37743419C8D626004FCF85 /* Database.swift */,
DC37743719C8D693004FCF85 /* Datatype.swift */,
+ DCAD429619E2E0F1004A51DF /* Query.swift */,
DC37743A19C8D6C0004FCF85 /* Statement.swift */,
DC0FA83419D87DE3009F3A35 /* Bridging */,
DC37743319C8CFCE004FCF85 /* Supporting Files */,
@@ -434,6 +442,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ DCAD429719E2E0F1004A51DF /* Query.swift in Sources */,
DC37743B19C8D6C0004FCF85 /* Statement.swift in Sources */,
DC37743519C8D626004FCF85 /* Database.swift in Sources */,
DC37743819C8D693004FCF85 /* Datatype.swift in Sources */,
@@ -445,6 +454,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ DCAD429A19E2EE50004A51DF /* QueryTests.swift in Sources */,
DCF37F8219DDAC2D001534AA /* DatabaseTests.swift in Sources */,
DCF37F8519DDAF3F001534AA /* StatementTests.swift in Sources */,
DCF37F8819DDAF79001534AA /* TestHelper.swift in Sources */,
@@ -455,6 +465,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ DCAD429819E2E0F1004A51DF /* Query.swift in Sources */,
DC37743C19C8D6C0004FCF85 /* Statement.swift in Sources */,
DC37743619C8D626004FCF85 /* Database.swift in Sources */,
DC37743919C8D693004FCF85 /* Datatype.swift in Sources */,
@@ -466,6 +477,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ DCAD429B19E2EE50004A51DF /* QueryTests.swift in Sources */,
DCF37F8319DDAC2D001534AA /* DatabaseTests.swift in Sources */,
DCF37F8619DDAF3F001534AA /* StatementTests.swift in Sources */,
DCF37F8919DDAF79001534AA /* TestHelper.swift in Sources */,
From 8438d8049ebe78955cdb444e270f0015d9f386dc Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 11:27:46 -0700
Subject: [PATCH 0006/1063] Update playground with Query examples
Edits throughout. Let's also change "example.com" to a better example.
There's no need for boring domain names in unnetworked code.
Also: an indentation fix for one of the fragment files. Consistency!
Signed-off-by: Stephen Celis
---
.../Documentation/fragment-1.html | 4 +--
.../Documentation/fragment-10.html | 9 +++++++
.../Documentation/fragment-11.html | 12 +++++++++
.../Documentation/fragment-12.html | 9 +++++++
.../Documentation/fragment-13.html | 12 +++++++++
.../Documentation/fragment-14.html | 21 +++++++++++++++
.../Documentation/fragment-15.html | 9 +++++++
.../Documentation/fragment-16.html | 9 +++++++
.../Documentation/fragment-17.html | 24 +++++++++++++++++
.../Documentation/fragment-18.html | 9 +++++++
.../Documentation/fragment-19.html | 12 +++++++++
.../Documentation/fragment-2.html | 2 +-
.../Documentation/fragment-20.html | 13 ++++++++++
.../Documentation/fragment-3.html | 4 +--
.../Documentation/fragment-6.html | 2 +-
.../Documentation/fragment-9.html | 26 +++++++++----------
SQLite.playground/contents.xcplayground | 22 ++++++++++++++++
SQLite.playground/section-16.swift | 4 +--
SQLite.playground/section-18.swift | 4 +--
SQLite.playground/section-20.swift | 1 +
SQLite.playground/section-22.swift | 3 +++
SQLite.playground/section-24.swift | 4 +++
SQLite.playground/section-26.swift | 4 +++
SQLite.playground/section-28.swift | 9 +++++++
SQLite.playground/section-30.swift | 1 +
SQLite.playground/section-32.swift | 2 ++
SQLite.playground/section-34.swift | 6 +++++
SQLite.playground/section-36.swift | 4 +++
SQLite.playground/section-38.swift | 2 ++
SQLite.playground/section-4.swift | 1 +
SQLite.playground/section-40.swift | 1 +
SQLite.playground/section-6.swift | 2 +-
32 files changed, 223 insertions(+), 24 deletions(-)
create mode 100644 SQLite.playground/Documentation/fragment-10.html
create mode 100644 SQLite.playground/Documentation/fragment-11.html
create mode 100644 SQLite.playground/Documentation/fragment-12.html
create mode 100644 SQLite.playground/Documentation/fragment-13.html
create mode 100644 SQLite.playground/Documentation/fragment-14.html
create mode 100644 SQLite.playground/Documentation/fragment-15.html
create mode 100644 SQLite.playground/Documentation/fragment-16.html
create mode 100644 SQLite.playground/Documentation/fragment-17.html
create mode 100644 SQLite.playground/Documentation/fragment-18.html
create mode 100644 SQLite.playground/Documentation/fragment-19.html
create mode 100644 SQLite.playground/Documentation/fragment-20.html
create mode 100644 SQLite.playground/section-20.swift
create mode 100644 SQLite.playground/section-22.swift
create mode 100644 SQLite.playground/section-24.swift
create mode 100644 SQLite.playground/section-26.swift
create mode 100644 SQLite.playground/section-28.swift
create mode 100644 SQLite.playground/section-30.swift
create mode 100644 SQLite.playground/section-32.swift
create mode 100644 SQLite.playground/section-34.swift
create mode 100644 SQLite.playground/section-36.swift
create mode 100644 SQLite.playground/section-38.swift
create mode 100644 SQLite.playground/section-40.swift
diff --git a/SQLite.playground/Documentation/fragment-1.html b/SQLite.playground/Documentation/fragment-1.html
index 253ad69f..2d7b55c7 100644
--- a/SQLite.playground/Documentation/fragment-1.html
+++ b/SQLite.playground/Documentation/fragment-1.html
@@ -6,9 +6,9 @@
- This implicitly opens a database in ":memory:". To open a database at a specific location, pass the path as a parameter during instantiation, e.g., Database("path/to/database.sqlite3"). Pass nil or an empty string ("") to open a temporary, disk-backed database.
+ This implicitly opens a database in ":memory:". To open a database at a specific location, pass the path as a parameter during instantiation, e.g., Database("path/to/database.sqlite3"). Pass nil or an empty string ("") to open a temporary, disk-backed database, instead.
- Once we instantiate a database connection, we can execute SQL statements against it. Let’s create a table.
+ Once we instantiate a database connection, we can execute SQL statements directly against it. Let’s create a table.
+ From here, we can build a variety of queries. For example, we can build and run an INSERT statement by passing a dictionary of values to the query’s insert function. Let’s add a few new rows this way.
+
+ The insert function can return an ID (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.
+
+ 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.
+
+ You may notice that iteration works a little differently here. Rather than arrays of rows, we are given dictionaries of column names to row values. This gives us a little more powerful of a mapping to work with and pass around.
+
+
+ Queries can be used and reused, and can quickly return rows, counts and other aggregate values.
+
+ Alongside filter, we can use the select, join, group, order, and limit functions to compose rich queries with relative safety and ease. Let’s say we want to order our results by email, then age, and return no more than three rows.
+
+ 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.
+
+ 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.
+
+
+ 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.
+
+
diff --git a/SQLite.playground/Documentation/fragment-2.html b/SQLite.playground/Documentation/fragment-2.html
index 48bbe656..acf1bb08 100644
--- a/SQLite.playground/Documentation/fragment-2.html
+++ b/SQLite.playground/Documentation/fragment-2.html
@@ -7,6 +7,6 @@
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 instantiate a statement and insert a couple rows.
+ It’s generally safer to prepare SQL statements individually. Let’s instantiate a Statement object and insert a couple rows.
- Prepared statements can bind and escape input values safely. In this case, both email and admin columns are bound with different values over two executions.
+ 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; lastChanges returns the number of changes from the last executed statement that modified the database; lastID returns the row ID of the last insert.
+ 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; lastChanges returns the number of changes from the last statement that modified the database; lastID returns the row ID of the last insert.
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 of the rows at once.
+ Also try using the Array initializer to return an array of all rows at once.
- 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).
-
-
-
& More…
-
- We’ve only explored the surface to SQLite.swift. Dive into the code to discover more!
-
+
+ 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).
+
+
+
Query Building
+
+ In order to both minimize the risk of SQL syntax errors and maximize our ability to quickly access data, SQLite.swift exposes a query-building interface via the Query struct. We can access this interface by subscripting our database connection with a table name.
+
diff --git a/SQLite.playground/contents.xcplayground b/SQLite.playground/contents.xcplayground
index d01a62fd..22659c1e 100644
--- a/SQLite.playground/contents.xcplayground
+++ b/SQLite.playground/contents.xcplayground
@@ -20,6 +20,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/SQLite.playground/section-16.swift b/SQLite.playground/section-16.swift
index 5af49d6e..5dea58c8 100644
--- a/SQLite.playground/section-16.swift
+++ b/SQLite.playground/section-16.swift
@@ -1,5 +1,5 @@
db.transaction(
- sr.run("dolly@example.com"),
- jr.run("emery@example.com", db.lastID)
+ sr.run("dolly@acme.com"),
+ jr.run("emery@acme.com", db.lastID)
)
count.scalar()
diff --git a/SQLite.playground/section-18.swift b/SQLite.playground/section-18.swift
index f7a017d0..e361ea74 100644
--- a/SQLite.playground/section-18.swift
+++ b/SQLite.playground/section-18.swift
@@ -1,6 +1,6 @@
let txn = db.transaction(
- sr.run("fiona@example.com"),
- jr.run("emery@example.com", db.lastID)
+ sr.run("fiona@acme.com"),
+ jr.run("emery@acme.com", db.lastID)
)
count.scalar()
diff --git a/SQLite.playground/section-20.swift b/SQLite.playground/section-20.swift
new file mode 100644
index 00000000..624a7098
--- /dev/null
+++ b/SQLite.playground/section-20.swift
@@ -0,0 +1 @@
+let users = db["users"]
diff --git a/SQLite.playground/section-22.swift b/SQLite.playground/section-22.swift
new file mode 100644
index 00000000..08b03fa3
--- /dev/null
+++ b/SQLite.playground/section-22.swift
@@ -0,0 +1,3 @@
+users.insert(["email": "giles@acme.com", "age": 42, "admin": true]).ID
+users.insert(["email": "haley@acme.com", "age": 30, "admin": true]).ID
+users.insert(["email": "inigo@acme.com", "age": 24, "admin": false]).ID
diff --git a/SQLite.playground/section-24.swift b/SQLite.playground/section-24.swift
new file mode 100644
index 00000000..e5be28c9
--- /dev/null
+++ b/SQLite.playground/section-24.swift
@@ -0,0 +1,4 @@
+db.transaction(
+ users.insert(["email": "julie@acme.com"]),
+ users.insert(["email": "kelly@acme.com", "manager_id": db.lastID])
+)
diff --git a/SQLite.playground/section-26.swift b/SQLite.playground/section-26.swift
new file mode 100644
index 00000000..104bdcb3
--- /dev/null
+++ b/SQLite.playground/section-26.swift
@@ -0,0 +1,4 @@
+// SELECT * FROM users
+for user in users {
+ println(user["email"])
+}
diff --git a/SQLite.playground/section-28.swift b/SQLite.playground/section-28.swift
new file mode 100644
index 00000000..273a695d
--- /dev/null
+++ b/SQLite.playground/section-28.swift
@@ -0,0 +1,9 @@
+// SELECT * FROM users LIMIT 1
+users.first
+
+// SELECT count(*) FROM users
+users.count
+
+users.min("age")
+users.max("age")
+users.average("age")
diff --git a/SQLite.playground/section-30.swift b/SQLite.playground/section-30.swift
new file mode 100644
index 00000000..113d778a
--- /dev/null
+++ b/SQLite.playground/section-30.swift
@@ -0,0 +1 @@
+let admins = users.filter(["admin": true])
diff --git a/SQLite.playground/section-32.swift b/SQLite.playground/section-32.swift
new file mode 100644
index 00000000..86b2bf20
--- /dev/null
+++ b/SQLite.playground/section-32.swift
@@ -0,0 +1,2 @@
+// SELECT count(*) FROM users WHERE admin = 1
+admins.count
diff --git a/SQLite.playground/section-34.swift b/SQLite.playground/section-34.swift
new file mode 100644
index 00000000..9f4c2743
--- /dev/null
+++ b/SQLite.playground/section-34.swift
@@ -0,0 +1,6 @@
+let ordered = admins.order("email", "age").limit(3)
+
+// SELECT * FROM users WHERE admin = 1 ORDER BY email ASC, age ASC LIMIT 3
+for admin in ordered {
+ println(admin)
+}
diff --git a/SQLite.playground/section-36.swift b/SQLite.playground/section-36.swift
new file mode 100644
index 00000000..469e6ce6
--- /dev/null
+++ b/SQLite.playground/section-36.swift
@@ -0,0 +1,4 @@
+let agelessAdmins = admins.filter(["age": nil])
+
+// SELECT count(*) FROM users WHERE admin = 1 AND age IS NULL
+agelessAdmins.count
diff --git a/SQLite.playground/section-38.swift b/SQLite.playground/section-38.swift
new file mode 100644
index 00000000..fafcc454
--- /dev/null
+++ b/SQLite.playground/section-38.swift
@@ -0,0 +1,2 @@
+// UPDATE users SET admin = 0 WHERE admin = 1 AND age IS NULL
+agelessAdmins.update(["admin": false]).changes
diff --git a/SQLite.playground/section-4.swift b/SQLite.playground/section-4.swift
index e220eebb..f8807857 100644
--- a/SQLite.playground/section-4.swift
+++ b/SQLite.playground/section-4.swift
@@ -2,6 +2,7 @@ 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)" +
diff --git a/SQLite.playground/section-40.swift b/SQLite.playground/section-40.swift
new file mode 100644
index 00000000..24b77750
--- /dev/null
+++ b/SQLite.playground/section-40.swift
@@ -0,0 +1 @@
+users.filter(["email": "alice@acme.com"]).delete().changes
diff --git a/SQLite.playground/section-6.swift b/SQLite.playground/section-6.swift
index 620fbba0..b3fd3a1f 100644
--- a/SQLite.playground/section-6.swift
+++ b/SQLite.playground/section-6.swift
@@ -1,4 +1,4 @@
let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)")
-for (email, admin) in ["alice@example.com": true, "betsy@example.com": false] {
+for (email, admin) in ["alice@acme.com": true, "betsy@acme.com": false] {
stmt.run(email, admin)
}
From 83837bfa2da8a48c7ee4548a932df10cf3489314 Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 11:56:51 -0700
Subject: [PATCH 0007/1063] Tabs vs. spaces, the eternal struggle
This is what I get for switching editors.
Signed-off-by: Stephen Celis
---
SQLite.playground/Documentation/fragment-12.html | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/SQLite.playground/Documentation/fragment-12.html b/SQLite.playground/Documentation/fragment-12.html
index 610468e1..0341e633 100644
--- a/SQLite.playground/Documentation/fragment-12.html
+++ b/SQLite.playground/Documentation/fragment-12.html
@@ -3,7 +3,7 @@
-
- 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.
-
+
+ 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.
+
From d74732d916e28fe6b46d9488d4d8a396003d4a3c Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 12:10:24 -0700
Subject: [PATCH 0008/1063] I don't need space
Signed-off-by: Stephen Celis
---
SQLite Common/Query.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift
index 06c6867e..5692e9da 100644
--- a/SQLite Common/Query.swift
+++ b/SQLite Common/Query.swift
@@ -97,7 +97,7 @@ public struct Query {
/// Adds a JOIN clause to the query.
///
- /// :param: type The JOIN operator used .
+ /// :param: type The JOIN operator used.
///
/// :param: tableName The table being joined.
///
From 09d3fbd7f4583ceefff53017982e6cc28f3fd817 Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 12:36:25 -0700
Subject: [PATCH 0009/1063] Better wording
Signed-off-by: Stephen Celis
---
SQLite Common/Query.swift | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift
index 5692e9da..713e390a 100644
--- a/SQLite Common/Query.swift
+++ b/SQLite Common/Query.swift
@@ -21,8 +21,8 @@
// THE SOFTWARE.
//
-/// A query object. Used to build SQL statements using a collection of
-/// chainable helper functions.
+/// A query object. Used to build SQL statements with a collection of chainable
+/// helper functions.
public struct Query {
private var database: Database
From e87a6a3f466e933b0737379f210a79cffcff67f0 Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 18:12:36 -0700
Subject: [PATCH 0010/1063] Fix README SQL syntax error
Yet another case for the Query builder.
Signed-off-by: Stephen Celis
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 3c88df19..2dd0d2a1 100644
--- a/README.md
+++ b/README.md
@@ -46,7 +46,7 @@ for row in db.prepare("SELECT id, email FROM users") {
db.scalar("SELECT count(*) FROM users") // {Some 2}
-let jr = db.prepare("INSERT INTO users (email, manager_id) VALUES (? ?)")
+let jr = db.prepare("INSERT INTO users (email, manager_id) VALUES (?, ?)")
db.transaction(
stmt.run("dolly@example.com"),
jr.run("emery@example.com", db.lastID)
From 91ac404f198a4add56364e426c9325d1b8b88fbb Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 18:13:00 -0700
Subject: [PATCH 0011/1063] Reorganize README
Features come first.
Signed-off-by: Stephen Celis
---
README.md | 21 +++++++++++----------
1 file changed, 11 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index 2dd0d2a1..faa0dbac 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,17 @@ A pure [Swift][1.1] framework wrapping [SQLite3][1.2].
[1.3]: https://github.com/stephencelis/SQLite.swift
+## Features
+
+ - A lightweight, uncomplicated query and parameter binding interface
+ - A flexible, chainable query builder
+ - Safe, automatically-typed data access
+ - Transactions with implicit commit/rollback
+ - Developer-friendly error handling and debugging
+ - Well-documented
+ - Extensively tested
+
+
## Usage
Explore interactively from the Xcode project’s playground.
@@ -54,16 +65,6 @@ db.transaction(
```
-## Features
-
- - Uncomplicated query and parameter binding interface
- - Safe, automatically-typed data access
- - Implicit commit/rollback interface
- - Developer-friendly error handling and debugging
- - Well-documented
- - Extensively tested
-
-
## Installation
_Note: SQLite.swift requires Swift 1.1 (available in [Xcode 6.1][4.1])._
From 2bbdf2cc9149a108de766d5eb1b0b920f7b225b6 Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 18:13:18 -0700
Subject: [PATCH 0012/1063] Add SQLite.Values typealias
It represents a dictionary of values, you know, `[String: Datatype?]`.
Handy for preparing inserts, updates and the like:
let alice: Values = ["email": "alice@example.com"]
users.insert(alice)
Signed-off-by: Stephen Celis
---
SQLite Common/Query.swift | 29 ++++++++++++++---------------
1 file changed, 14 insertions(+), 15 deletions(-)
diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift
index 713e390a..c98c7179 100644
--- a/SQLite Common/Query.swift
+++ b/SQLite Common/Query.swift
@@ -21,6 +21,9 @@
// THE SOFTWARE.
//
+/// A dictionary mapping column names to values.
+public typealias Values = [String: Datatype?]
+
/// A query object. Used to build SQL statements with a collection of chainable
/// helper functions.
public struct Query {
@@ -316,7 +319,7 @@ public struct Query {
return database.prepare(Swift.join(" ", parts), bindings)
}
- private func insertStatement(values: [String: Datatype?]) -> Statement {
+ private func insertStatement(values: Values) -> Statement {
var (parts, bindings) = (["INSERT INTO \(tableName)"], self.bindings)
let valuesClause = Swift.join(", ", map(values) { columnName, value in
bindings.append(value)
@@ -327,7 +330,7 @@ public struct Query {
return database.prepare(Swift.join(" ", parts), bindings)
}
- private func updateStatement(values: [String: Datatype?]) -> Statement {
+ private func updateStatement(values: Values) -> Statement {
var (parts, bindings) = (["UPDATE \(tableName)"], [Datatype?]())
let valuesClause = Swift.join(", ", map(values) { columnName, value in
bindings.append(value)
@@ -385,13 +388,11 @@ public struct Query {
// MARK: - Array
- public typealias Element = [String: Datatype?]
-
/// The first result (or nil if the query has no results).
- public var first: Element? { return limit(1).generate().next() }
+ public var first: Values? { return limit(1).generate().next() }
/// The last result (or nil if the query has no results).
- public var last: Element? { return reverse(self).first }
+ public var last: Values? { return reverse(self).first }
/// Returns true if the query has no results.
public var isEmpty: Bool { return first == nil }
@@ -403,7 +404,7 @@ public struct Query {
/// :param: values A dictionary of column names to values.
///
/// :returns: The statement.
- public func insert(values: [String: Datatype?]) -> Statement {
+ public func insert(values: Values) -> Statement {
return insert(values).statement
}
@@ -412,7 +413,7 @@ public struct Query {
/// :param: values A dictionary of column names to values.
///
/// :returns: The row ID.
- public func insert(values: [String: Datatype?]) -> Int? {
+ public func insert(values: Values) -> Int? {
return insert(values).ID
}
@@ -421,7 +422,7 @@ public struct Query {
/// :param: values A dictionary of column names to values.
///
/// :returns: The row ID and statement.
- public func insert(values: [String: Datatype?]) -> (ID: Int?, statement: Statement) {
+ public func insert(values: Values) -> (ID: Int?, statement: Statement) {
let statement = insertStatement(values).run()
return (statement.failed ? nil : database.lastID, statement)
}
@@ -431,7 +432,7 @@ public struct Query {
/// :param: values A dictionary of column names to values.
///
/// :returns: The statement.
- public func update(values: [String: Datatype?]) -> Statement {
+ public func update(values: Values) -> Statement {
return update(values).statement
}
@@ -440,7 +441,7 @@ public struct Query {
/// :param: values A dictionary of column names to values.
///
/// :returns: The number of updated rows.
- public func update(values: [String: Datatype?]) -> Int {
+ public func update(values: Values) -> Int {
return update(values).changes
}
@@ -449,7 +450,7 @@ public struct Query {
/// :param: values A dictionary of column names to values.
///
/// :returns: The number of updated rows and statement.
- public func update(values: [String: Datatype?]) -> (changes: Int, statement: Statement) {
+ public func update(values: Values) -> (changes: Int, statement: Statement) {
let statement = updateStatement(values).run()
return (statement.failed ? 0 : database.lastChanges ?? 0, statement)
}
@@ -552,13 +553,11 @@ extension Query: SequenceType {
// MARK: - GeneratorType
public struct QueryGenerator: GeneratorType {
- public typealias Element = [String: Datatype?]
-
private var statement: Statement
private init(_ statement: Statement) { self.statement = statement }
- public func next() -> Element? {
+ public func next() -> Values? {
statement.next()
return statement.values
}
From 40eae84f655f3f8bb7b0c7c39aa7266d855f85d1 Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 20:27:37 -0700
Subject: [PATCH 0013/1063] Add basic query building example to README
Signed-off-by: Stephen Celis
---
README.md | 26 ++++++++++++++++++++++++++
1 file changed, 26 insertions(+)
diff --git a/README.md b/README.md
index faa0dbac..51be4759 100644
--- a/README.md
+++ b/README.md
@@ -64,6 +64,32 @@ db.transaction(
)
```
+SQLite.swift also exposes a powerful query building interface.
+
+``` swift
+let products = db["products"]
+
+let available = products.filter("quantity > ?", 0).order("name").limit(5)
+for product in available {
+ println(product["name"])
+}
+// SELECT * FROM products WHERE quantity > 0 ORDER BY name LIMIT 5
+
+products.count // SELECT count(*) FROM products
+products.average("price") // SELECT avg(price) FROM products
+
+if let id = products.insert(["name": "Sprocket", "price": 19.99]) {
+ println("inserted \(id)")
+}
+// INSERT INTO products (name, price) VALUES ('Sprocket', 19.99)
+
+let updates: Int = products.filter(["id": id]).update(["quantity": 20])
+// UPDATE products SET quantity = 20 WHERE id = 42
+
+let deletes: Int = products.filter(["quantity": 0]).delete()
+// DELETE FROM products WHERE quantity = 0
+```
+
## Installation
From 24a5643901b33457ab6235dc11ef1f6b4d0cbf42 Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 20:28:02 -0700
Subject: [PATCH 0014/1063] Simplify short one-liners
Signed-off-by: Stephen Celis
---
SQLite Common/Query.swift | 24 ++++++------------------
1 file changed, 6 insertions(+), 18 deletions(-)
diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift
index c98c7179..41998bd4 100644
--- a/SQLite Common/Query.swift
+++ b/SQLite Common/Query.swift
@@ -404,18 +404,14 @@ public struct Query {
/// :param: values A dictionary of column names to values.
///
/// :returns: The statement.
- public func insert(values: Values) -> Statement {
- return insert(values).statement
- }
+ public func insert(values: Values) -> Statement { return insert(values).statement }
/// Runs an INSERT statement with the given row of values.
///
/// :param: values A dictionary of column names to values.
///
/// :returns: The row ID.
- public func insert(values: Values) -> Int? {
- return insert(values).ID
- }
+ public func insert(values: Values) -> Int? { return insert(values).ID }
/// Runs an INSERT statement with the given row of values.
///
@@ -432,18 +428,14 @@ public struct Query {
/// :param: values A dictionary of column names to values.
///
/// :returns: The statement.
- public func update(values: Values) -> Statement {
- return update(values).statement
- }
+ public func update(values: Values) -> Statement { return update(values).statement }
/// Runs an UPDATE statement against the query with the given values.
///
/// :param: values A dictionary of column names to values.
///
/// :returns: The number of updated rows.
- public func update(values: Values) -> Int {
- return update(values).changes
- }
+ public func update(values: Values) -> Int { return update(values).changes }
/// Runs an UPDATE statement against the query with the given values.
///
@@ -458,16 +450,12 @@ public struct Query {
/// Runs an DELETE statement against the query.
///
/// :returns: The statement.
- public func delete() -> Statement {
- return delete().statement
- }
+ public func delete() -> Statement { return delete().statement }
/// Runs an DELETE statement against the query.
///
/// :returns: The number of deleted rows.
- public func delete() -> Int {
- return delete().changes
- }
+ public func delete() -> Int { return delete().changes }
/// Runs an DELETE statement against the query.
///
From a7f300ff75b4a8c268a3ba2060d405aff3692239 Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Tue, 14 Oct 2014 15:02:27 -0700
Subject: [PATCH 0015/1063] Don't re-append internal query bindings
Signed-off-by: Stephen Celis
---
SQLite Common/Query.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SQLite Common/Query.swift b/SQLite Common/Query.swift
index 41998bd4..c3c48771 100644
--- a/SQLite Common/Query.swift
+++ b/SQLite Common/Query.swift
@@ -200,7 +200,7 @@ public struct Query {
///
/// :returns: A query with the given GROUP BY clause applied.
public func group(by: String...) -> Query {
- return group(by, bindings)
+ return group(by, [])
}
/// Sets a GROUP BY-HAVING clause on the query.
From 3b3d07f1a496cd813184c6d80a20d4d7011a4d67 Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Tue, 14 Oct 2014 19:50:42 -0700
Subject: [PATCH 0016/1063] Fix test typo
Signed-off-by: Stephen Celis
---
SQLite Common Tests/QueryTests.swift | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift
index 736a8114..d501a6a7 100644
--- a/SQLite Common Tests/QueryTests.swift
+++ b/SQLite Common Tests/QueryTests.swift
@@ -320,7 +320,7 @@ class QueryTests: XCTestCase {
XCTAssertEqual(30, users.max("age") as Int)
}
- func test_max_returnsMinimum() {
+ func test_min_returnsMinimum() {
XCTAssert(users.min("age") == nil)
InsertUser(db, "alice", age: 20)
From 8bebe4b8f306be6c2c3926b2d14006911070213e Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Tue, 14 Oct 2014 19:50:55 -0700
Subject: [PATCH 0017/1063] Remove extra Datatype extensions
Let's stick with the core and make it reliable. People can always cast
values before sending them to SQLite queries if they differ.
Signed-off-by: Stephen Celis
---
SQLite Common/Datatype.swift | 56 ------------------------------------
1 file changed, 56 deletions(-)
diff --git a/SQLite Common/Datatype.swift b/SQLite Common/Datatype.swift
index f2fac835..d7cabbf7 100644
--- a/SQLite Common/Datatype.swift
+++ b/SQLite Common/Datatype.swift
@@ -59,30 +59,6 @@ extension Int: Datatype {
}
-extension Int16: Datatype {
-
- public func bindTo(statement: Statement, atIndex idx: Int) {
- statement.bind(int: Int(self), atIndex: idx)
- }
-
-}
-
-extension Int32: Datatype {
-
- public func bindTo(statement: Statement, atIndex idx: Int) {
- statement.bind(int: Int(self), atIndex: idx)
- }
-
-}
-
-extension Int64: Datatype {
-
- public func bindTo(statement: Statement, atIndex idx: Int) {
- statement.bind(int: Int(self), atIndex: idx)
- }
-
-}
-
extension String: Datatype {
public func bindTo(statement: Statement, atIndex idx: Int) {
@@ -90,35 +66,3 @@ extension String: Datatype {
}
}
-
-extension UInt: Datatype {
-
- public func bindTo(statement: Statement, atIndex idx: Int) {
- statement.bind(int: Int(self), atIndex: idx)
- }
-
-}
-
-extension UInt16: Datatype {
-
- public func bindTo(statement: Statement, atIndex idx: Int) {
- statement.bind(int: Int(self), atIndex: idx)
- }
-
-}
-
-extension UInt32: Datatype {
-
- public func bindTo(statement: Statement, atIndex idx: Int) {
- statement.bind(int: Int(self), atIndex: idx)
- }
-
-}
-
-extension UInt64: Datatype {
-
- public func bindTo(statement: Statement, atIndex idx: Int) {
- statement.bind(int: Int(self), atIndex: idx)
- }
-
-}
From 4896eae274b4ee0c422a6a2ef058221693375ad4 Mon Sep 17 00:00:00 2001
From: Stephen Celis
Date: Mon, 13 Oct 2014 23:47:44 -0700
Subject: [PATCH 0018/1063] Add type-safe SQL expression builder
A bit more declarative sugar for building queries, e.g.:
// given
let users = db["users"]
let admin = Expression("admin")
let age = Expression("age")
users.filter(admin && age >= 30).order(age.desc)
// SELECT * FROM users WHERE (admin) AND (age >= 30) ORDER BY age DESC
users.group(age, having: count(age) == 1)
// SELECT * FROM users GROUP BY age HAVING count(age) = 1
Everything is an expression now, and depending on the type can interact
differently. This means we can now use bindings everywhere with little
effort.
Signed-off-by: Stephen Celis
---
SQLite Common Tests/ExpressionTests.swift | 423 +++++++++++++++++
SQLite Common Tests/QueryTests.swift | 266 +++++------
SQLite Common Tests/StatementTests.swift | 2 +-
SQLite Common Tests/TestHelper.swift | 1 +
SQLite Common/Database.swift | 38 +-
SQLite Common/Expression.swift | 420 ++++++++++++++++
SQLite Common/Query.swift | 449 ++++++------------
SQLite Common/Statement.swift | 32 +-
SQLite Common/{Datatype.swift => Value.swift} | 22 +-
SQLite.xcodeproj/project.pbxproj | 34 +-
10 files changed, 1193 insertions(+), 494 deletions(-)
create mode 100644 SQLite Common Tests/ExpressionTests.swift
create mode 100644 SQLite Common/Expression.swift
rename SQLite Common/{Datatype.swift => Value.swift} (84%)
diff --git a/SQLite Common Tests/ExpressionTests.swift b/SQLite Common Tests/ExpressionTests.swift
new file mode 100644
index 00000000..159edfdb
--- /dev/null
+++ b/SQLite Common Tests/ExpressionTests.swift
@@ -0,0 +1,423 @@
+import XCTest
+import SQLite
+
+class ExpressionTests: XCTestCase {
+
+ let db = Database()
+ var users: Query { return db["users"] }
+
+ override func setUp() {
+ super.setUp()
+
+ CreateUsersTable(db)
+ }
+
+ func test_stringExpressionPlusStringExpression_buildsConcatenatingStringExpression() {
+ let string = Expression(value: "Hello")
+ ExpectExecutions(db, ["SELECT 'Hello' || 'Hello' FROM users": 3]) { _ in
+ for _ in self.users.select(string + string) {}
+ for _ in self.users.select(string + "Hello") {}
+ for _ in self.users.select("Hello" + string) {}
+ }
+ }
+
+ func test_integerExpression_plusIntegerExpression_buildsAdditiveIntegerExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 + 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int + int) {}
+ for _ in self.users.select(int + 2) {}
+ for _ in self.users.select(2 + int) {}
+ }
+ }
+
+ func test_doubleExpression_plusDoubleExpression_buildsAdditiveDoubleExpression() {
+ let double = Expression(value: 2.0)
+ ExpectExecutions(db, ["SELECT 2.0 + 2.0 FROM users": 3]) { _ in
+ for _ in self.users.select(double + double) {}
+ for _ in self.users.select(double + 2.0) {}
+ for _ in self.users.select(2.0 + double) {}
+ }
+ }
+
+ func test_integerExpression_minusIntegerExpression_buildsSubtractiveIntegerExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 - 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int - int) {}
+ for _ in self.users.select(int - 2) {}
+ for _ in self.users.select(2 - int) {}
+ }
+ }
+
+ func test_doubleExpression_minusDoubleExpression_buildsSubtractiveDoubleExpression() {
+ let double = Expression(value: 2.0)
+ ExpectExecutions(db, ["SELECT 2.0 - 2.0 FROM users": 3]) { _ in
+ for _ in self.users.select(double - double) {}
+ for _ in self.users.select(double - 2.0) {}
+ for _ in self.users.select(2.0 - double) {}
+ }
+ }
+
+ func test_integerExpression_timesIntegerExpression_buildsMultiplicativeIntegerExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 * 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int * int) {}
+ for _ in self.users.select(int * 2) {}
+ for _ in self.users.select(2 * int) {}
+ }
+ }
+
+ func test_doubleExpression_timesDoubleExpression_buildsMultiplicativeDoubleExpression() {
+ let double = Expression(value: 2.0)
+ ExpectExecutions(db, ["SELECT 2.0 * 2.0 FROM users": 3]) { _ in
+ for _ in self.users.select(double * double) {}
+ for _ in self.users.select(double * 2.0) {}
+ for _ in self.users.select(2.0 * double) {}
+ }
+ }
+
+ func test_integerExpression_dividedByIntegerExpression_buildsDivisiveIntegerExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 / 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int / int) {}
+ for _ in self.users.select(int / 2) {}
+ for _ in self.users.select(2 / int) {}
+ }
+ }
+
+ func test_doubleExpression_dividedByDoubleExpression_buildsDivisiveDoubleExpression() {
+ let double = Expression(value: 2.0)
+ ExpectExecutions(db, ["SELECT 2.0 / 2.0 FROM users": 3]) { _ in
+ for _ in self.users.select(double / double) {}
+ for _ in self.users.select(double / 2.0) {}
+ for _ in self.users.select(2.0 / double) {}
+ }
+ }
+
+ func test_integerExpression_moduloIntegerExpression_buildsModuloIntegerExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 % 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int % int) {}
+ for _ in self.users.select(int % 2) {}
+ for _ in self.users.select(2 % int) {}
+ }
+ }
+
+ func test_integerExpression_bitShiftLeftIntegerExpression_buildsLeftShiftedIntegerExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 << 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int << int) {}
+ for _ in self.users.select(int << 2) {}
+ for _ in self.users.select(2 << int) {}
+ }
+ }
+
+ func test_integerExpression_bitShiftRightIntegerExpression_buildsRightShiftedIntegerExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 >> 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int >> int) {}
+ for _ in self.users.select(int >> 2) {}
+ for _ in self.users.select(2 >> int) {}
+ }
+ }
+
+ func test_integerExpression_bitwiseAndIntegerExpression_buildsAndedIntegerExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 & 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int & int) {}
+ for _ in self.users.select(int & 2) {}
+ for _ in self.users.select(2 & int) {}
+ }
+ }
+
+ func test_integerExpression_bitwiseOrIntegerExpression_buildsOredIntegerExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 | 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int | int) {}
+ for _ in self.users.select(int | 2) {}
+ for _ in self.users.select(2 | int) {}
+ }
+ }
+
+ func test_bitwiseNot_integerExpression_buildsComplementIntegerExpression() {
+ let int = Expression(value: 2)
+ let query = users.select(~int)
+ ExpectExecutions(db, ["SELECT ~(2) FROM users": 1]) { _ in for _ in query {} }
+ }
+
+ func test_equalityOperator_withEquatableExpressions_buildsBooleanExpression() {
+ let bool = Expression(value: true)
+ ExpectExecutions(db, ["SELECT 1 = 1 FROM users": 3]) { _ in
+ for _ in self.users.select(bool == bool) {}
+ for _ in self.users.select(bool == true) {}
+ for _ in self.users.select(true == bool) {}
+ }
+ }
+
+ func test_inequalityOperator_withEquatableExpressions_buildsBooleanExpression() {
+ let bool = Expression(value: true)
+ ExpectExecutions(db, ["SELECT 1 != 1 FROM users": 3]) { _ in
+ for _ in self.users.select(bool != bool) {}
+ for _ in self.users.select(bool != true) {}
+ for _ in self.users.select(true != bool) {}
+ }
+ }
+
+ func test_greaterThanOperator_withComparableExpressions_buildsBooleanExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 > 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int > int) {}
+ for _ in self.users.select(int > 2) {}
+ for _ in self.users.select(2 > int) {}
+ }
+ }
+
+ func test_greaterThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 >= 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int >= int) {}
+ for _ in self.users.select(int >= 2) {}
+ for _ in self.users.select(2 >= int) {}
+ }
+ }
+
+ func test_lessThanOperator_withComparableExpressions_buildsBooleanExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 < 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int < int) {}
+ for _ in self.users.select(int < 2) {}
+ for _ in self.users.select(2 < int) {}
+ }
+ }
+
+ func test_lessThanOrEqualToOperator_withComparableExpressions_buildsBooleanExpression() {
+ let int = Expression(value: 2)
+ ExpectExecutions(db, ["SELECT 2 <= 2 FROM users": 3]) { _ in
+ for _ in self.users.select(int <= int) {}
+ for _ in self.users.select(int <= 2) {}
+ for _ in self.users.select(2 <= int) {}
+ }
+ }
+
+ func test_unaryMinusOperator_withIntegerExpression_buildsNegativeIntegerExpression() {
+ let int = Expression(value: 2)
+ let query = users.select(-int)
+ ExpectExecution(db, query, "-(2)")
+ }
+
+ func test_unaryMinusOperator_withDoubleExpression_buildsNegativeDoubleExpression() {
+ let double = Expression(value: 2.0)
+ let query = users.select(-double)
+ ExpectExecution(db, query, "-(2.0)")
+ }
+
+ func test_betweenOperator_withComparableExpression_buildsBetweenBooleanExpression() {
+ let int = Expression(value: 2)
+ let query = users.select(0...5 ~= int)
+ ExpectExecution(db, query, "2 BETWEEN 0 AND 5")
+ }
+
+ func test_likeOperator_withStringExpression_buildsLikeExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(like("%ello", string))
+ ExpectExecution(db, query, "'Hello' LIKE '%ello'")
+ }
+
+ func test_globOperator_withStringExpression_buildsGlobExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(glob("*ello", string))
+ ExpectExecution(db, query, "'Hello' GLOB '*ello'")
+ }
+
+ func test_matchOperator_withStringExpression_buildsMatchExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(match("ello", string))
+ ExpectExecution(db, query, "'Hello' MATCH 'ello'")
+ }
+
+ func test_doubleAndOperator_withBooleanExpressions_buildsCompoundExpression() {
+ let bool = Expression(value: true)
+ let query = users.select(bool && bool)
+ ExpectExecution(db, query, "(1) AND (1)")
+ }
+
+ func test_doubleOrOperator_withBooleanExpressions_buildsCompoundExpression() {
+ let bool = Expression(value: true)
+ let query = users.select(bool || bool)
+ ExpectExecution(db, query, "(1) OR (1)")
+ }
+
+ func test_unaryNotOperator_withBooleanExpressions_buildsNotExpression() {
+ let bool = Expression(value: true)
+ let query = users.select(!bool)
+ ExpectExecution(db, query, "NOT (1)")
+ }
+
+ func test_absFunction_withNumberExpressions_buildsAbsExpression() {
+ let int = Expression(value: -2)
+ let query = users.select(abs(int))
+ ExpectExecution(db, query, "abs(-2)")
+ }
+
+ func test_coalesceFunction_withValueExpressions_buildsCoalesceExpression() {
+ let int1 = Expression(value: nil)
+ let int2 = Expression(value: nil)
+ let int3 = Expression(value: 3)
+ let query = users.select(coalesce(int1, int2, int3))
+ ExpectExecution(db, query, "coalesce(NULL, NULL, 3)")
+ }
+
+ func test_ifNullFunction_withValueExpressionAndValue_buildsIfNullExpression() {
+ let int = Expression(value: nil)
+ ExpectExecution(db, users.select(ifnull(int, 1)), "ifnull(NULL, 1)")
+ ExpectExecution(db, users.select(int ?? 1), "ifnull(NULL, 1)")
+ }
+
+ func test_lengthFunction_withValueExpression_buildsLengthIntExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(length(string))
+ ExpectExecution(db, query, "length('Hello')")
+ }
+
+ func test_lowerFunction_withStringExpression_buildsLowerStringExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(lower(string))
+ ExpectExecution(db, query, "lower('Hello')")
+ }
+
+ func test_ltrimFunction_withStringExpression_buildsTrimmedStringExpression() {
+ let string = Expression(value: " Hello")
+ let query = users.select(ltrim(string))
+ ExpectExecution(db, query, "ltrim(' Hello')")
+ }
+
+ func test_ltrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(ltrim(string, "H"))
+ ExpectExecution(db, query, "ltrim('Hello', 'H')")
+ }
+
+ func test_randomFunction_buildsRandomIntExpression() {
+ let query = users.select(random)
+ ExpectExecution(db, query, "random()")
+ }
+
+ func test_replaceFunction_withStringExpressionAndFindReplaceStrings_buildsReplacedStringExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(replace(string, "He", "E"))
+ ExpectExecution(db, query, "replace('Hello', 'He', 'E')")
+ }
+
+ func test_roundFunction_withDoubleExpression_buildsRoundedDoubleExpression() {
+ let double = Expression(value: 3.14159)
+ let query = users.select(round(double))
+ ExpectExecution(db, query, "round(3.14159)")
+ }
+
+ func test_roundFunction_withDoubleExpressionAndPrecision_buildsRoundedDoubleExpression() {
+ let double = Expression(value: 3.14159)
+ let query = users.select(round(double, 2))
+ ExpectExecution(db, query, "round(3.14159, 2)")
+ }
+
+ func test_rtrimFunction_withStringExpression_buildsTrimmedStringExpression() {
+ let string = Expression(value: "Hello ")
+ let query = users.select(rtrim(string))
+ ExpectExecution(db, query, "rtrim('Hello ')")
+ }
+
+ func test_rtrimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(rtrim(string, "lo"))
+ ExpectExecution(db, query, "rtrim('Hello', 'lo')")
+ }
+
+ func test_substrFunction_withStringExpressionAndStartIndex_buildsSubstringExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(substr(string, 1))
+ ExpectExecution(db, query, "substr('Hello', 1)")
+ }
+
+ func test_substrFunction_withStringExpressionPositionAndLength_buildsSubstringExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(substr(string, 1, 2))
+ ExpectExecution(db, query, "substr('Hello', 1, 2)")
+ }
+
+ func test_substrFunction_withStringExpressionAndRange_buildsSubstringExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(substr(string, 1..<3))
+ ExpectExecution(db, query, "substr('Hello', 1, 2)")
+ }
+
+ func test_trimFunction_withStringExpression_buildsTrimmedStringExpression() {
+ let string = Expression(value: " Hello ")
+ let query = users.select(trim(string))
+ ExpectExecution(db, query, "trim(' Hello ')")
+ }
+
+ func test_trimFunction_withStringExpressionAndReplacementCharacters_buildsTrimmedStringExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(trim(string, "lo"))
+ ExpectExecution(db, query, "trim('Hello', 'lo')")
+ }
+
+ func test_upperFunction_withStringExpression_buildsLowerStringExpression() {
+ let string = Expression(value: "Hello")
+ let query = users.select(upper(string))
+ ExpectExecution(db, query, "upper('Hello')")
+ }
+
+ let id = Expression("id")
+ let email = Expression("email")
+ let salary = Expression("salary")
+ let admin = Expression("admin")
+
+ func test_countFunction_withExpression_buildsCountExpression() {
+ ExpectExecution(db, users.select(count(id)), "count(id)")
+ ExpectExecution(db, users.select(count(email)), "count(email)")
+ ExpectExecution(db, users.select(count(salary)), "count(salary)")
+ ExpectExecution(db, users.select(count(admin)), "count(admin)")
+ }
+
+ func test_countFunction_withStar_buildsCountExpression() {
+ ExpectExecution(db, users.select(count(*)), "count(*)")
+ }
+
+ func test_maxFunction_withExpression_buildsMaxExpression() {
+ ExpectExecution(db, users.select(max(id)), "max(id)")
+ ExpectExecution(db, users.select(max(email)), "max(email)")
+ ExpectExecution(db, users.select(max(salary)), "max(salary)")
+ ExpectExecution(db, users.select(max(admin)), "max(admin)")
+ }
+
+ func test_minFunction_withExpression_buildsMinExpression() {
+ ExpectExecution(db, users.select(min(id)), "min(id)")
+ ExpectExecution(db, users.select(min(email)), "min(email)")
+ ExpectExecution(db, users.select(min(salary)), "min(salary)")
+ ExpectExecution(db, users.select(min(admin)), "min(admin)")
+ }
+
+ func test_averageFunction_withExpression_buildsAverageExpression() {
+ ExpectExecution(db, users.select(average(id)), "avg(id)")
+ ExpectExecution(db, users.select(average(salary)), "avg(salary)")
+ }
+
+ func test_sumFunction_withExpression_buildsSumExpression() {
+ ExpectExecution(db, users.select(sum(id)), "sum(id)")
+ ExpectExecution(db, users.select(sum(salary)), "sum(salary)")
+ }
+
+ func test_totalFunction_withExpression_buildsTotalExpression() {
+ ExpectExecution(db, users.select(total(id)), "total(id)")
+ ExpectExecution(db, users.select(total(salary)), "total(salary)")
+ }
+
+ func test_containsFunction_withValueExpressionAndValueArray_buildsInExpression() {
+ ExpectExecution(db, users.select(contains([1, 2, 3], id)), "id IN (1, 2, 3)")
+ }
+
+}
+
+func ExpectExecution(db: Database, query: Query, SQL: String) {
+ ExpectExecutions(db, ["SELECT \(SQL) FROM users": 1]) { _ in for _ in query {} }
+}
diff --git a/SQLite Common Tests/QueryTests.swift b/SQLite Common Tests/QueryTests.swift
index d501a6a7..9a67fa52 100644
--- a/SQLite Common Tests/QueryTests.swift
+++ b/SQLite Common Tests/QueryTests.swift
@@ -6,181 +6,162 @@ class QueryTests: XCTestCase {
let db = Database()
var users: Query { return db["users"] }
+ let id = Expression("id")
+ let email = Expression("email")
+ let age = Expression("age")
+ let admin = Expression("admin")
+ let manager_id = Expression("manager_id")
+
override func setUp() {
super.setUp()
CreateUsersTable(db)
}
- func test_select_withString_compilesSelectClause() {
- let query = users.select("email")
+ func test_select_withExpression_compilesSelectClause() {
+ let query = users.select(email)
let SQL = "SELECT email FROM users"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_select_withVariadicStrings_compilesSelectClause() {
- let query = users.select("email", "count(*)")
+ func test_select_withVariadicExpressions_compilesSelectClause() {
+ let query = users.select(email, count(*))
let SQL = "SELECT email, count(*) FROM users"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_join_compilesJoinClause() {
- let query = users.join("users AS managers", on: "users.manager_id = managers.id")
+ func test_select_withStar_resetsSelectClause() {
+ let query = users.select(email)
- let SQL = "SELECT * FROM users INNER JOIN users AS managers ON users.manager_id = managers.id"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
+ let SQL = "SELECT * FROM users"
+ ExpectExecutions(db, [SQL: 1]) { _ in for _ in query.select(*) {} }
}
- func test_join_withExplicityType_compilesJoinClauseWithType() {
- let query = users.join(.LeftOuter, "users AS managers", on: "users.manager_id = managers.id")
+ func test_join_compilesJoinClause() {
+ let managers = db["users AS managers"]
+ let managers_id = Expression("managers.id")
+ let users_manager_id = Expression("users.manager_id")
- let SQL = "SELECT * FROM users LEFT OUTER JOIN users AS managers ON users.manager_id = managers.id"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
+ let query = users.join(managers, on: managers_id == users_manager_id)
- func test_join_whenChained_compilesAggregateJoinClause() {
- let query = users
- .join("users AS managers", on: "users.manager_id = managers.id")
- .join("users AS managed", on: "managed.manager_id = users.id")
-
- let SQL = "SELECT * FROM users " +
- "INNER JOIN users AS managers ON users.manager_id = managers.id " +
- "INNER JOIN users AS managed ON managed.manager_id = users.id"
+ let SQL = "SELECT * FROM users INNER JOIN users AS managers ON managers.id = users.manager_id"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_filter_withoutBindings_compilesWhereClause() {
- let query = users.filter("admin = 1")
+ func test_join_withExplicitType_compilesJoinClauseWithType() {
+ let managers = db["users AS managers"]
+ let managers_id = Expression("managers.id")
+ let users_manager_id = Expression("users.manager_id")
- let SQL = "SELECT * FROM users WHERE admin = 1"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
+ let query = users.join(.LeftOuter, managers, on: managers_id == users_manager_id)
- func test_filter_withExplicitBindings_compilesWhereClause() {
- let query = users.filter("admin = ?", true)
-
- let SQL = "SELECT * FROM users WHERE admin = 1"
+ let SQL = "SELECT * FROM users LEFT OUTER JOIN users AS managers ON managers.id = users.manager_id"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_filter_withImplicitBindingsDictionary_compilesWhereClause() {
- let query = users.filter(["email": "alice@example.com", "age": 30])
+ func test_join_withTableCondition_compilesJoinClauseWithTableCondition() {
+ let admin = Expression("managers.admin")
+ let managers = db["users AS managers"].filter(admin)
+ let managers_id = Expression("managers.id")
+ let users_manager_id = Expression("users.manager_id")
+
+ let query = users.join(managers, on: managers_id == users_manager_id)
let SQL = "SELECT * FROM users " +
- "WHERE email = 'alice@example.com' " +
- "AND age = 30"
+ "INNER JOIN users AS managers " +
+ "ON (managers.id = users.manager_id) " +
+ "AND (managers.admin)"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_filter_withArrayBindings_compilesWhereClause() {
- let query = users.filter(["id": [1, 2]])
+ func test_join_whenChained_compilesAggregateJoinClause() {
+ let managers = db["users AS managers"]
+ let managers_id = Expression("managers.id")
+ let users_manager_id = Expression("users.manager_id")
+
+ let managed = db["users AS managed"]
+ let managed_manager_id = Expression("users.id")
+ let users_id = Expression("managed.manager_id")
+
+ let query = users
+ .join(managers, on: managers_id == users_manager_id)
+ .join(managed, on: managed_manager_id == users_id)
- let SQL = "SELECT * FROM users WHERE id IN (1, 2)"
+ let SQL = "SELECT * FROM users " +
+ "INNER JOIN users AS managers ON managers.id = users.manager_id " +
+ "INNER JOIN users AS managed ON users.id = managed.manager_id"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_filter_withRangeBindings_compilesWhereClause() {
- let query = users.filter(["age": 20..<30])
+ func test_filter_compilesWhereClause() {
+ let query = users.filter(admin == true)
- let SQL = "SELECT * FROM users WHERE age BETWEEN 20 AND 30"
+ let SQL = "SELECT * FROM users WHERE admin = 1"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
func test_filter_whenChained_compilesAggregateWhereClause() {
let query = users
- .filter("email = ?", "alice@example.com")
- .filter("age >= ?", 21)
+ .filter(email == "alice@example.com")
+ .filter(age >= 21)
let SQL = "SELECT * FROM users " +
- "WHERE email = 'alice@example.com' " +
- "AND age >= 21"
+ "WHERE (email = 'alice@example.com') " +
+ "AND (age >= 21)"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_group_withSingleColumnName_compilesGroupClause() {
- let query = users.group("age")
+ func test_group_withSingleExpressionName_compilesGroupClause() {
+ let query = users.group(age)
let SQL = "SELECT * FROM users GROUP BY age"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_group_withVariadicColumnNames_compilesGroupClause() {
- let query = users.group("age", "admin")
+ func test_group_withVariadicExpressionNames_compilesGroupClause() {
+ let query = users.group(age, admin)
let SQL = "SELECT * FROM users GROUP BY age, admin"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_group_withColumnNameAndHavingBindings_compilesGroupClause() {
- let query = users.group("age", having: "age >= ?", 30)
+ func test_group_withExpressionNameAndHavingBindings_compilesGroupClause() {
+ let query = users.group(age, having: age >= 30)
let SQL = "SELECT * FROM users GROUP BY age HAVING age >= 30"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_group_withColumnNamesAndHavingBindings_compilesGroupClause() {
- let query = users.group(["age", "admin"], having: "age >= ?", 30)
+ func test_group_withExpressionNamesAndHavingBindings_compilesGroupClause() {
+ let query = users.group([age, admin], having: age >= 30)
let SQL = "SELECT * FROM users GROUP BY age, admin HAVING age >= 30"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_order_withSingleColumnName_compilesOrderClause() {
- let query = users.order("age")
+ func test_order_withSingleExpressionName_compilesOrderClause() {
+ let query = users.order(age)
- let SQL = "SELECT * FROM users ORDER BY age ASC"
+ let SQL = "SELECT * FROM users ORDER BY age"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_order_withVariadicColumnNames_compilesOrderClause() {
- let query = users.order("age", "email")
+ func test_order_withVariadicExpressionNames_compilesOrderClause() {
+ let query = users.order(age, email)
- let SQL = "SELECT * FROM users ORDER BY age ASC, email ASC"
+ let SQL = "SELECT * FROM users ORDER BY age, email"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_order_withColumnAndSortDirection_compilesOrderClause() {
- let query = users.order("age", .Desc)
-
- let SQL = "SELECT * FROM users ORDER BY age DESC"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_order_withColumnSortDirectionTuple_compilesOrderClause() {
- let query = users.order(("age", .Desc))
-
- let SQL = "SELECT * FROM users ORDER BY age DESC"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_order_withVariadicColumnSortDirectionTuples_compilesOrderClause() {
- let query = users.order(("age", .Desc), ("email", .Asc))
+ func test_order_withExpressionAndSortDirection_compilesOrderClause() {
+ let query = users.order(age.desc, email.asc)
let SQL = "SELECT * FROM users ORDER BY age DESC, email ASC"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
}
- func test_order_whenChained_compilesAggregateOrderClause() {
- let query = users.order("age").order("email")
-
- let SQL = "SELECT * FROM users ORDER BY age ASC, email ASC"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
- }
-
- func test_reverse_reversesOrder() {
- let query = users.order(("age", .Desc), ("email", .Asc))
-
- let SQL = "SELECT * FROM users ORDER BY age ASC, email DESC"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in reverse(query) {} }
- }
-
- func test_reverse_withoutOrder_reversesOrderByRowID() {
- let SQL = "SELECT * FROM users ORDER BY users.ROWID DESC"
- ExpectExecutions(db, [SQL: 1]) { _ in for _ in reverse(self.users) {} }
- }
-
func test_limit_compilesLimitClause() {
let query = users.limit(5)
@@ -216,17 +197,26 @@ class QueryTests: XCTestCase {
}
func test_SQL_compilesProperly() {
+ let admin = Expression("managers.admin")
+ let managers = db["users AS managers"].filter(admin == true)
+ let managers_id = Expression("managers.id")
+ let users_manager_id = Expression("users.manager_id")
+ let email = Expression("users.email")
+ let age = Expression("users.age")
+
let query = users
- .select("email", "count(email) AS count")
- .filter("age >= ?", 21)
- .group("age", having: "count > ?", 1)
- .order("email", .Desc)
+ .select(email, count(email))
+ .join(.LeftOuter, managers, on: managers_id == users_manager_id)
+ .filter(21..<32 ~= age)
+ .group(age, having: count(email) > 1)
+ .order(email.desc)
.limit(1, offset: 2)
- let SQL = "SELECT email, count(email) AS count FROM users " +
- "WHERE age >= 21 " +
- "GROUP BY age HAVING count > 1 " +
- "ORDER BY email DESC " +
+ let SQL = "SELECT users.email, count(users.email) 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.email) > 1 " +
+ "ORDER BY users.email DESC " +
"LIMIT 1 " +
"OFFSET 2"
ExpectExecutions(db, [SQL: 1]) { _ in for _ in query {} }
@@ -243,24 +233,6 @@ class QueryTests: XCTestCase {
}
}
- func test_last_withAnEmptyQuery_returnsNil() {
- XCTAssert(users.last == nil)
- }
-
- func test_last_returnsTheLastRow() {
- InsertUsers(db, "alice", "betsy")
- ExpectExecutions(db, ["SELECT * FROM users ORDER BY users.ROWID DESC LIMIT 1": 1]) { _ in
- XCTAssertEqual(2, self.users.last!["id"] as Int)
- }
- }
-
- func test_last_withAnOrderedQuery_reversesOrder() {
- ExpectExecutions(db, ["SELECT * FROM users ORDER BY age DESC LIMIT 1": 1]) { _ in
- self.users.order("age").last
- return
- }
- }
-
func test_isEmpty_returnsWhetherOrNotTheQueryIsEmpty() {
ExpectExecutions(db, ["SELECT * FROM users LIMIT 1": 2]) { _ in
XCTAssertTrue(self.users.isEmpty)
@@ -272,23 +244,35 @@ class QueryTests: XCTestCase {
func test_insert_insertsRows() {
let SQL = "INSERT INTO users (email, age) VALUES ('alice@example.com', 30)"
ExpectExecutions(db, [SQL: 1]) { _ in
- XCTAssertEqual(1, self.users.insert(["email": "alice@example.com", "age": 30]).ID!)
+ XCTAssertEqual(1, self.users.insert { u in
+ u.set(self.email, "alice@example.com")
+ u.set(self.age, 30)
+ }.ID!)
}
- XCTAssert(users.insert(["email": "alice@example.com", "age": 30]).ID == nil)
+ XCTAssert(users.insert { u in
+ u.set(self.email, "alice@example.com")
+ u.set(self.age, 30)
+ }.ID == nil)
}
func test_update_updatesRows() {
InsertUsers(db, "alice", "betsy")
InsertUser(db, "dolly", admin: true)
- XCTAssertEqual(2, users.filter(["admin": false]).update(["age": 30, "admin": true]).changes)
- XCTAssertEqual(0, users.filter(["admin": false]).update(["age": 30, "admin": true]).changes)
+ XCTAssertEqual(2, users.filter(!admin).update { u in
+ u.set(self.age, 30)
+ u.set(self.admin, true)
+ }.changes)
+ XCTAssertEqual(0, users.filter(!admin).update { u in
+ u.set(self.age, 30)
+ u.set(self.admin, true)
+ }.changes)
}
func test_delete_deletesRows() {
InsertUser(db, "alice", age: 20)
- XCTAssertEqual(0, users.filter(["email": "betsy@example.com"]).delete().changes)
+ XCTAssertEqual(0, users.filter(email == "betsy@example.com").delete().changes)
InsertUser(db, "betsy", age: 30)
XCTAssertEqual(2, users.delete().changes)
@@ -300,56 +284,56 @@ class QueryTests: XCTestCase {
InsertUser(db, "alice")
XCTAssertEqual(1, users.count)
- XCTAssertEqual(0, users.filter("age IS NOT NULL").count)
+ XCTAssertEqual(0, users.filter(age != nil).count)
}
- func test_count_withColumn_returnsCount() {
+ func test_count_withExpression_returnsCount() {
InsertUser(db, "alice", age: 20)
InsertUser(db, "betsy", age: 20)
InsertUser(db, "cindy")
- XCTAssertEqual(2, users.count("age"))
- XCTAssertEqual(1, users.count("DISTINCT age"))
+ XCTAssertEqual(2, users.count(age))
+ XCTAssertEqual(1, users.count(Expression("DISTINCT age")))
}
- func test_max_returnsMaximum() {
- XCTAssert(users.max("age") == nil)
+ func test_max_withInt_returnsMaximumInt() {
+ XCTAssert(users.max(age) == nil)
InsertUser(db, "alice", age: 20)
InsertUser(db, "betsy", age: 30)
- XCTAssertEqual(30, users.max("age") as Int)
+ XCTAssertEqual(30, users.max(age)!)
}
- func test_min_returnsMinimum() {
- XCTAssert(users.min("age") == nil)
+ func test_min_withInt_returnsMinimumInt() {
+ XCTAssert(users.min(age) == nil)
InsertUser(db, "alice", age: 20)
InsertUser(db, "betsy", age: 30)
- XCTAssertEqual(20, users.min("age") as Int)
+ XCTAssertEqual(20, users.min(age)!)
}
- func test_average_returnsAverage() {
- XCTAssert(users.average("age") == nil)
+ func test_averageWithInt_returnsDouble() {
+ XCTAssert(users.average(age) == nil)
InsertUser(db, "alice", age: 20)
InsertUser(db, "betsy", age: 30)
- XCTAssertEqual(25.0, users.average("age")!)
+ XCTAssertEqual(25.0, users.average(age)!)
}
func test_sum_returnsSum() {
- XCTAssert(users.sum("age") == nil)
+ XCTAssert(users.sum(age) == nil)
InsertUser(db, "alice", age: 20)
InsertUser(db, "betsy", age: 30)
- XCTAssertEqual(50, users.sum("age") as Int)
+ XCTAssertEqual(50, users.sum(age)!)
}
func test_total_returnsTotal() {
- XCTAssertEqual(0.0, users.total("age"))
+ XCTAssertEqual(0.0, users.total(age))
InsertUser(db, "alice", age: 20)
InsertUser(db, "betsy", age: 30)
- XCTAssertEqual(50.0, users.total("age"))
+ XCTAssertEqual(50.0, users.total(age))
}
}
diff --git a/SQLite Common Tests/StatementTests.swift b/SQLite Common Tests/StatementTests.swift
index d74208f9..453f8e19 100644
--- a/SQLite Common Tests/StatementTests.swift
+++ b/SQLite Common Tests/StatementTests.swift
@@ -171,7 +171,7 @@ class StatementTests: XCTestCase {
XCTAssertEqual("alice@example.com", row[1] as String)
}
- func test_values_returnsDictionaryOfColumnsToValues() {
+ func test_values_returnsDictionaryOfExpressionsToValues() {
InsertUser(db, "alice")
let stmt = db.prepare("SELECT id, \"email\" FROM users")
stmt.next()
diff --git a/SQLite Common Tests/TestHelper.swift b/SQLite Common Tests/TestHelper.swift
index 89ab014b..ef80a310 100644
--- a/SQLite Common Tests/TestHelper.swift
+++ b/SQLite Common Tests/TestHelper.swift
@@ -11,6 +11,7 @@ func CreateUsersTable(db: Database) {
"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)" +
diff --git a/SQLite Common/Database.swift b/SQLite Common/Database.swift
index e0778ea3..5ce16cb7 100644
--- a/SQLite Common/Database.swift
+++ b/SQLite Common/Database.swift
@@ -1,3 +1,4 @@
+
//
// SQLite.Database
// Copyright (c) 2014 Stephen Celis.
@@ -21,6 +22,11 @@
// THE SOFTWARE.
//
+func quote(#literal: String) -> String {
+ let escaped = join("''", split(literal) { $0 == "'" })
+ return "'\(escaped)'"
+}
+
/// A connection (handle) to a SQLite database.
public final class Database {
@@ -85,7 +91,7 @@ public final class Database {
/// :param: bindings A list of parameters to bind to the statement.
///
/// :returns: A prepared statement.
- public func prepare(statement: String, _ bindings: Datatype?...) -> Statement {
+ public func prepare(statement: String, _ bindings: Value?...) -> Statement {
if !bindings.isEmpty { return prepare(statement, bindings) }
var statementHandle: COpaquePointer = nil
@@ -100,7 +106,7 @@ public final class Database {
/// :param: bindings A list of parameters to bind to the statement.
///
/// :returns: A prepared statement.
- public func prepare(statement: String, _ bindings: [Datatype?]) -> Statement {
+ public func prepare(statement: String, _ bindings: [Value?]) -> Statement {
return prepare(statement).bind(bindings)
}
@@ -112,7 +118,7 @@ public final class Database {
/// statement.
///
/// :returns: A prepared statement.
- public func prepare(statement: String, _ bindings: [String: Datatype?]) -> Statement {
+ public func prepare(statement: String, _ bindings: [String: Value?]) -> Statement {
return prepare(statement).bind(bindings)
}
@@ -125,7 +131,7 @@ public final class Database {
/// :param: bindings A list of parameters to bind to the statement.
///
/// :returns: The statement.
- public func run(statement: String, _ bindings: Datatype?...) -> Statement {
+ public func run(statement: String, _ bindings: Value?...) -> Statement {
return run(statement, bindings)
}
@@ -136,7 +142,7 @@ public final class Database {
/// :param: bindings A list of parameters to bind to the statement.
///
/// :returns: The statement.
- public func run(statement: String, _ bindings: [Datatype?]) -> Statement {
+ public func run(statement: String, _ bindings: [Value?]) -> Statement {
return prepare(statement).run(bindings)
}
@@ -148,7 +154,7 @@ public final class Database {
/// statement.
///
/// :returns: The statement.
- public func run(statement: String, _ bindings: [String: Datatype?]) -> Statement {
+ public func run(statement: String, _ bindings: [String: Value?]) -> Statement {
return prepare(statement).run(bindings)
}
@@ -162,7 +168,7 @@ public final class Database {
/// :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: Datatype?...) -> Datatype? {
+ public func scalar(statement: String, _ bindings: Value?...) -> Value? {
return scalar(statement, bindings)
}
@@ -174,7 +180,7 @@ public final class Database {
/// :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: [Datatype?]) -> Datatype? {
+ public func scalar(statement: String, _ bindings: [Value?]) -> Value? {
return prepare(statement).scalar(bindings)
}
@@ -187,7 +193,7 @@ public final class Database {
/// statement.
///
/// :returns: The first value of the first row returned.
- public func scalar(statement: String, _ bindings: [String: Datatype?]) -> Datatype? {
+ public func scalar(statement: String, _ bindings: [String: Value?]) -> Value? {
return prepare(statement).scalar(bindings)
}
@@ -272,12 +278,12 @@ public final class Database {
}
private func savepoint(name: String, _ statements: [@autoclosure () -> Statement]) -> Statement {
- let quotedName = join("''", split(name) { $0 == "'" })
- var savepoint = run("SAVEPOINT '\(quotedName)'")
+ let quotedName = quote(literal: name)
+ var savepoint = run("SAVEPOINT \(quotedName)")
// FIXME: rdar://18479820 // for statement in statements { savepoint = savepoint && statement() }
for idx in 0.. Query {
- return Query(self, tableName)
- }
-
}
extension Database: DebugPrintable {
diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift
new file mode 100644
index 00000000..6443bf8f
--- /dev/null
+++ b/SQLite Common/Expression.swift
@@ -0,0 +1,420 @@
+//
+// SQLite.Expression
+// Copyright (c) 2014 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 Expression {
+
+ internal var SQL: String
+ internal var bindings: [Value?]
+
+ public init(_ SQL: String = "", _ bindings: [Value?] = []) {
+ (self.SQL, self.bindings) = (SQL, bindings)
+ }
+
+ public init(value: Expression) {
+ self.init(value.SQL, value.bindings)
+ }
+
+ public init(value: Value?) {
+ self.init("?", [value])
+ }
+
+}
+
+public protocol Expressible {
+
+ var expression: Expression<()> { get }
+
+}
+
+extension Bool: Expressible {
+
+ public var expression: Expression<()> {
+ return Expression(value: self)
+ }
+
+}
+
+extension Double: Expressible {
+
+ public var expression: Expression<()> {
+ return Expression(value: self)
+ }
+
+}
+
+extension Int: Expressible {
+
+ public var expression: Expression<()> {
+ return Expression(value: self)
+ }
+
+}
+
+extension String: Expressible {
+
+ public var expression: Expression<()> {
+ return Expression(value: self)
+ }
+
+}
+
+extension Query: Expressible {
+
+ public var expression: Expression<()> {
+ return Expression(tableName)
+ }
+
+}
+
+extension Expression: Expressible {
+
+ public var expression: Expression<()> {
+ return Expression<()>(SQL, bindings)
+ }
+
+ public var asc: Expression<()> {
+ return join(" ", [self, Expression("ASC")])
+ }
+
+ public var desc: Expression<()> {
+ return join(" ", [self, Expression("DESC")])
+ }
+
+}
+
+// MARK: - Expressions
+
+public func +(lhs: Expression, rhs: Expression) -> Expression {
+ return infix("||", lhs, rhs)
+}
+public func +(lhs: Expression, rhs: String) -> Expression { return lhs + Expression(value: rhs) }
+public func +(lhs: String, 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: T) -> Expression { return lhs + Expression(value: rhs) }
+public func +(lhs: T, 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: T) -> Expression { return lhs - Expression(value: rhs) }
+public func -(lhs: T, 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: T) -> Expression { return lhs * Expression(value: rhs) }
+public func *(lhs: T, 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: T) -> Expression { return lhs / Expression(value: rhs) }
+public func /(lhs: T, 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: Int) -> Expression { return lhs % Expression(value: rhs) }
+public func %(lhs: Int, 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: Int) -> Expression { return lhs << Expression(value: rhs) }
+public func <<(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) << rhs }
+
+public func >>(lhs: Expression, rhs: Expression) -> Expression {
+ return Expression("\(lhs.SQL) \(__FUNCTION__) \(rhs.SQL)", lhs.bindings + rhs.bindings)
+}
+public func >>(lhs: Expression, rhs: Int) -> Expression { return lhs >> Expression(value: rhs) }
+public func >>(lhs: Int, 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: Int) -> Expression { return lhs & Expression(value: rhs) }
+public func &(lhs: Int, 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: Int) -> Expression { return lhs | Expression(value: rhs) }
+public func |(lhs: Int, rhs: Expression) -> Expression { return Expression(value: lhs) | rhs }
+
+public prefix func ~(rhs: Expression) -> Expression {
+ return wrap(__FUNCTION__, rhs)
+}
+
+// MARK: - Predicates
+
+public func ==(lhs: Expression, rhs: Expression) -> Expression {
+ return infix("=", lhs, rhs)
+}
+public func ==(lhs: Expression, rhs: T?) -> Expression {
+ if let rhs = rhs { return lhs == Expression(value: rhs) }
+ return Expression("\(lhs.SQL) IS ?", lhs.bindings + [nil])
+}
+public func ==(lhs: T?, rhs: Expression) -> Expression {
+ if let lhs = lhs { return Expression(value: lhs) == rhs }
+ return Expression("? IS \(rhs.SQL)", [nil] + rhs.bindings)
+}
+
+public func !=(lhs: Expression, rhs: Expression) -> Expression {
+ return infix(__FUNCTION__, lhs, rhs)
+}
+public func !=(lhs: Expression, rhs: T?) -> Expression {
+ if let rhs = rhs { return lhs != Expression(value: rhs) }
+ return Expression("\(lhs.SQL) IS NOT ?", lhs.bindings + [nil])
+}
+public func !=(lhs: T?, rhs: Expression) -> Expression {
+ if let lhs = lhs { return Expression(value: lhs) != rhs }
+ return Expression("? IS NOT \(rhs.SQL)", [nil] + rhs.bindings)
+}
+
+public func >(lhs: Expression, rhs: Expression) -> Expression {
+ return infix(__FUNCTION__, lhs, rhs)
+}
+public func >(lhs: Expression, rhs: T) -> Expression {
+ return lhs > Expression(value: rhs)
+}
+public func >(lhs: T, 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: T) -> Expression {
+ return lhs >= Expression(value: rhs)
+}
+public func >=(lhs: T, 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: T) -> Expression {
+ return lhs < Expression(value: rhs)
+}
+public func <(lhs: T, 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: T) -> Expression {
+ return lhs <= Expression(value: rhs)
+}
+public func <=(lhs: T, rhs: Expression) -> Expression {
+ return Expression(value: lhs) <= rhs
+}
+
+public prefix func -(rhs: Expression) -> Expression {
+ return wrap(__FUNCTION__, rhs)
+}
+
+public func ~=(lhs: I, rhs: Expression) -> Expression {
+ return Expression("\(rhs.SQL) BETWEEN ? AND ?", rhs.bindings + [lhs.start, lhs.end])
+}
+
+// MARK: Operators
+
+public func like(string: String, expression: Expression) -> Expression