From fc61295b214333b4327a64c2a7a9e91f9efcea72 Mon Sep 17 00:00:00 2001 From: Stephen Celis Date: Tue, 3 Feb 2015 20:17:26 -0800 Subject: [PATCH] Remove Bool support from thin SQLite layer SQLite doesn't really support BOOLEAN values (they're just INTEGERs), so let's remove support for it in the thin layer. Bool now conforms to Value, which means the type-safe layer can use it freely and more safely. Signed-off-by: Stephen Celis --- SQLite Common Tests/DatabaseTests.swift | 44 ++++++++++++------------ SQLite Common Tests/SchemaTests.swift | 4 +-- SQLite Common Tests/StatementTests.swift | 37 +++++++------------- SQLite Common Tests/TestHelper.swift | 4 +-- SQLite Common/Expression.swift | 28 ++++++++------- SQLite Common/Statement.swift | 9 +---- SQLite Common/Value.swift | 10 +++--- SQLite.playground/section-6.swift | 2 +- 8 files changed, 61 insertions(+), 77 deletions(-) diff --git a/SQLite Common Tests/DatabaseTests.swift b/SQLite Common Tests/DatabaseTests.swift index 4683f8d..5e634ba 100644 --- a/SQLite Common Tests/DatabaseTests.swift +++ b/SQLite Common Tests/DatabaseTests.swift @@ -50,26 +50,26 @@ class DatabaseTests: XCTestCase { func test_prepare_preparesAndReturnsStatements() { db.prepare("SELECT * FROM users WHERE admin = 0") - db.prepare("SELECT * FROM users WHERE admin = ?", false) - db.prepare("SELECT * FROM users WHERE admin = ?", [false]) - db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": false]) + db.prepare("SELECT * FROM users WHERE admin = ?", 0) + db.prepare("SELECT * FROM users WHERE admin = ?", [0]) + db.prepare("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) // no-op assert-nothing-asserted } func test_run_preparesRunsAndReturnsStatements() { ExpectExecutions(db, ["SELECT * FROM users WHERE admin = 0": 4]) { db in db.run("SELECT * FROM users WHERE admin = 0") - db.run("SELECT * FROM users WHERE admin = ?", false) - db.run("SELECT * FROM users WHERE admin = ?", [false]) - db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": false]) + db.run("SELECT * FROM users WHERE admin = ?", 0) + db.run("SELECT * FROM users WHERE admin = ?", [0]) + db.run("SELECT * FROM users WHERE admin = $admin", ["$admin": 0]) } } func test_scalar_preparesRunsAndReturnsScalarValues() { XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = 0") as Int) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", false) as Int) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [false]) as Int) - XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": false]) as Int) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", 0) as Int) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = ?", [0]) as Int) + XCTAssertEqual(0, db.scalar("SELECT count(*) FROM users WHERE admin = $admin", ["$admin": 0]) as Int) } func test_transaction_beginsAndCommitsStatements() { @@ -80,34 +80,34 @@ class DatabaseTests: XCTestCase { ] ExpectExecutions(db, fulfilled) { db in let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - db.transaction(stmt.bind("alice@example.com", true)) + db.transaction(stmt.bind("alice@example.com", 1)) } } func test_transaction_executesBeginDeferred() { ExpectExecutions(db, ["BEGIN DEFERRED TRANSACTION": 1]) { db in let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - db.transaction(.Deferred, stmt.bind("alice@example.com", true)) + db.transaction(.Deferred, stmt.bind("alice@example.com", 1)) } } func test_transaction_executesBeginImmediate() { ExpectExecutions(db, ["BEGIN IMMEDIATE TRANSACTION": 1]) { db in let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - db.transaction(.Immediate, stmt.bind("alice@example.com", true)) + db.transaction(.Immediate, stmt.bind("alice@example.com", 1)) } } func test_transaction_executesBeginExclusive() { ExpectExecutions(db, ["BEGIN EXCLUSIVE TRANSACTION": 1]) { db in let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - db.transaction(.Exclusive, stmt.bind("alice@example.com", true)) + db.transaction(.Exclusive, stmt.bind("alice@example.com", 1)) } } func test_transaction_rollsBackOnFailure() { let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - db.transaction(stmt.bind("alice@example.com", true)) + db.transaction(stmt.bind("alice@example.com", 1)) let fulfilled = [ "COMMIT TRANSACTION": 0, "ROLLBACK TRANSACTION": 1, @@ -116,8 +116,8 @@ class DatabaseTests: XCTestCase { var txn: Statement! ExpectExecutions(db, fulfilled) { db in txn = db.transaction( - stmt.bind("alice@example.com", true), - stmt.bind("alice@example.com", true) + stmt.bind("alice@example.com", 1), + stmt.bind("alice@example.com", 1) ) return } @@ -162,14 +162,14 @@ class DatabaseTests: XCTestCase { ExpectExecutions(db, fulfilled) { db in db.savepoint( db.savepoint( - stmt.run("alice@example.com", true), - stmt.run("alice@example.com", true), - stmt.run("alice@example.com", true) + stmt.run("alice@example.com", 1), + stmt.run("alice@example.com", 1), + stmt.run("alice@example.com", 1) ), db.savepoint( - stmt.run("alice@example.com", true), - stmt.run("alice@example.com", true), - stmt.run("alice@example.com", true) + stmt.run("alice@example.com", 1), + stmt.run("alice@example.com", 1), + stmt.run("alice@example.com", 1) ) ) return diff --git a/SQLite Common Tests/SchemaTests.swift b/SQLite Common Tests/SchemaTests.swift index ba47e76..d937595 100644 --- a/SQLite Common Tests/SchemaTests.swift +++ b/SQLite Common Tests/SchemaTests.swift @@ -78,7 +78,7 @@ class SchemaTests: XCTestCase { } func test_createTable_column_withCheck_buildsCheckClause() { - ExpectExecution(db, "CREATE TABLE \"users\" (\"admin\" BOOLEAN NOT NULL CHECK (\"admin\" IN (0, 1)))", + ExpectExecution(db, "CREATE TABLE \"users\" (\"admin\" INTEGER NOT NULL CHECK (\"admin\" IN (0, 1)))", db.create(table: users) { t in t.column(admin, check: contains([false, true], admin)) } @@ -175,7 +175,7 @@ class SchemaTests: XCTestCase { func test_createTable_check_buildsCheckTableConstraint() { let users = self.users - ExpectExecution(db, "CREATE TABLE \"users\" (\"admin\" BOOLEAN NOT NULL, CHECK (\"admin\" IN (0, 1)))", + ExpectExecution(db, "CREATE TABLE \"users\" (\"admin\" INTEGER NOT NULL, CHECK (\"admin\" IN (0, 1)))", db.create(table: users) { t in t.column(admin) t.check(contains([false, true], admin)) diff --git a/SQLite Common Tests/StatementTests.swift b/SQLite Common Tests/StatementTests.swift index 0f9bd12..7107d20 100644 --- a/SQLite Common Tests/StatementTests.swift +++ b/SQLite Common Tests/StatementTests.swift @@ -12,30 +12,30 @@ class StatementTests: XCTestCase { } func test_bind_withVariadicParameters_bindsParameters() { - let stmt = db.prepare("SELECT ?, ?, ?, ?, ?") - ExpectExecutions(db, ["SELECT 0, 1, 2.0, '3', x'34'": 1]) { _ in + let stmt = db.prepare("SELECT ?, ?, ?, ?") + ExpectExecutions(db, ["SELECT 1, 2.0, '3', x'34'": 1]) { _ in withBlob { blob in - stmt.bind(false, 1, 2.0, "3", blob).run() + stmt.bind(1, 2.0, "3", blob).run() return } } } func test_bind_withArrayOfParameters_bindsParameters() { - let stmt = db.prepare("SELECT ?, ?, ?, ?, ?") - ExpectExecutions(db, ["SELECT 0, 1, 2.0, '3', x'34'": 1]) { _ in + let stmt = db.prepare("SELECT ?, ?, ?, ?") + ExpectExecutions(db, ["SELECT 1, 2.0, '3', x'34'": 1]) { _ in withBlob { blob in - stmt.bind([false, 1, 2.0, "3", blob]).run() + stmt.bind([1, 2.0, "3", blob]).run() return } } } func test_bind_withNamedParameters_bindsParameters() { - let stmt = db.prepare("SELECT ?1, ?2, ?3, ?4, ?5") - ExpectExecutions(db, ["SELECT 0, 1, 2.0, '3', x'34'": 1]) { _ in + let stmt = db.prepare("SELECT ?1, ?2, ?3, ?4") + ExpectExecutions(db, ["SELECT 1, 2.0, '3', x'34'": 1]) { _ in withBlob { blob in - stmt.bind(["?1": false, "?2": 1, "?3": 2.0, "?4": "3", "?5": blob]).run() + stmt.bind(["?1": 1, "?2": 2.0, "?3": "3", "?4": blob]).run() return } } @@ -51,14 +51,6 @@ class StatementTests: XCTestCase { } } - func test_bind_withBool_bindsInt() { - let stmt = db.prepare("SELECT ?") - ExpectExecutions(db, ["SELECT 0": 1, "SELECT 1": 1]) { _ in - stmt.bind(false).run() - stmt.bind(true).run() - } - } - func test_bind_withDouble_bindsDouble() { let stmt = db.prepare("SELECT ?") ExpectExecutions(db, ["SELECT 2.0": 1]) { _ in @@ -93,13 +85,13 @@ class StatementTests: XCTestCase { func test_run_withVariadicParameters() { let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - stmt.run("alice@example.com", true) + stmt.run("alice@example.com", 1) XCTAssertEqual(1, db.totalChanges) } func test_run_withArrayOfParameters() { let stmt = db.prepare("INSERT INTO users (email, admin) VALUES (?, ?)") - stmt.run(["alice@example.com", true]) + stmt.run(["alice@example.com", 1]) XCTAssertEqual(1, db.totalChanges) } @@ -107,7 +99,7 @@ class StatementTests: XCTestCase { let stmt = db.prepare( "INSERT INTO users (email, admin) VALUES ($email, $admin)" ) - stmt.run(["$email": "alice@example.com", "$admin": true]) + stmt.run(["$email": "alice@example.com", "$admin": 1]) XCTAssertEqual(1, db.totalChanges) } @@ -149,11 +141,6 @@ class StatementTests: XCTestCase { XCTAssertEqual(0, count.scalar(22) as Int) } - func test_scalar_boolReturnValue() { - InsertUser(db, "alice", admin: true) - XCTAssertEqual(true, db.scalar("SELECT admin FROM users") as Bool) - } - func test_scalar_doubleReturnValue() { XCTAssertEqual(2.0, db.scalar("SELECT 2.0") as Double) } diff --git a/SQLite Common Tests/TestHelper.swift b/SQLite Common Tests/TestHelper.swift index b9d36e6..c9aef70 100644 --- a/SQLite Common Tests/TestHelper.swift +++ b/SQLite Common Tests/TestHelper.swift @@ -20,10 +20,10 @@ func CreateUsersTable(db: Database) { db.trace(Trace) } -func InsertUser(db: Database, name: String, age: Int? = nil, admin: Bool? = false) -> Statement { +func InsertUser(db: Database, name: String, age: Int? = nil, admin: Bool = false) -> Statement { return db.run( "INSERT INTO \"users\" (email, age, admin) values (?, ?, ?)", - ["\(name)@example.com", age, admin] + ["\(name)@example.com", age, admin.datatypeValue] ) } diff --git a/SQLite Common/Expression.swift b/SQLite Common/Expression.swift index 1ce0986..c022c7a 100644 --- a/SQLite Common/Expression.swift +++ b/SQLite Common/Expression.swift @@ -84,7 +84,8 @@ public protocol Expressible { extension Bool: Expressible { public var expression: Expression<()> { - return Expression(binding: self) + // FIXME: rdar://TODO segfaults during archive // return Expression(value: self) + return Expression(binding: datatypeValue) } } @@ -437,19 +438,23 @@ public func && (lhs: Expression, rhs: Expression) -> Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } public func && (lhs: Expression, rhs: Expression) -> Expression { return infix("AND", lhs, rhs) } -public func && (lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(binding: rhs) } -public func && (lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(binding: rhs) } -public func && (lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs) && rhs } -public func && (lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs) && rhs } +// FIXME: rdar://TODO segfaults during archive // ... Expression(value: lhs) +public func && (lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(binding: rhs.datatypeValue) } +public func && (lhs: Expression, rhs: Bool) -> Expression { return lhs && Expression(binding: rhs.datatypeValue) } +// FIXME: rdar://TODO segfaults during archive // ... Expression(value: rhs) +public func && (lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs.datatypeValue) && rhs } +public func && (lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs.datatypeValue) && rhs } public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } public func || (lhs: Expression, rhs: Expression) -> Expression { return infix("OR", lhs, rhs) } -public func || (lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(binding: rhs) } -public func || (lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(binding: rhs) } -public func || (lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs) || rhs } -public func || (lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs) || rhs } +// FIXME: rdar://TODO segfaults during archive // ... Expression(value: lhs) +public func || (lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(binding: rhs.datatypeValue) } +public func || (lhs: Expression, rhs: Bool) -> Expression { return lhs || Expression(binding: rhs.datatypeValue) } +// FIXME: rdar://TODO segfaults during archive // ... Expression(value: rhs) +public func || (lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs.datatypeValue) || rhs } +public func || (lhs: Bool, rhs: Expression) -> Expression { return Expression(binding: lhs.datatypeValue) || rhs } public prefix func ! (rhs: Expression) -> Expression { return wrap("NOT ", rhs) } public prefix func ! (rhs: Expression) -> Expression { return wrap("NOT ", rhs) } @@ -655,11 +660,11 @@ public func * (Expression?, Expression?) -> Expression<()> { public func contains(values: [V], column: Expression) -> Expression { let templates = join(", ", [String](count: values.count, repeatedValue: "?")) - return infix("IN", column, Expression(literal: "(\(templates))", values.map { $0 })) + return infix("IN", column, Expression(literal: "(\(templates))", values.map { $0.datatypeValue })) } public func contains(values: [V?], column: Expression) -> Expression { let templates = join(", ", [String](count: values.count, repeatedValue: "?")) - return infix("IN", column, Expression(literal: "(\(templates))", values.map { $0 })) + return infix("IN", column, Expression(literal: "(\(templates))", values.map { $0?.datatypeValue })) } // MARK: - Modifying @@ -829,7 +834,6 @@ public postfix func -- (column: Expression) -> Setter { internal func transcode(literal: Binding?) -> String { if let literal = literal { if let literal = literal as? String { return quote(literal: literal) } - if let literal = literal as? Bool { return literal ? "1" : "0" } return "\(literal)" } return "NULL" diff --git a/SQLite Common/Statement.swift b/SQLite Common/Statement.swift index f05ca9f..2b6e701 100644 --- a/SQLite Common/Statement.swift +++ b/SQLite Common/Statement.swift @@ -86,8 +86,6 @@ public final class Statement { private func bind(value: Binding?, atIndex idx: Int) { if let value = value as? Blob { try(sqlite3_bind_blob(handle, Int32(idx), value.bytes, Int32(value.length), SQLITE_TRANSIENT)) - } else if let value = value as? Bool { - bind(value ? 1 : 0, atIndex: idx) } else if let value = value as? Double { try(sqlite3_bind_double(handle, Int32(idx), value)) } else if let value = value as? Int { @@ -221,12 +219,7 @@ extension Statement: GeneratorType { case SQLITE_FLOAT: return Double(sqlite3_column_double(self.handle, Int32(idx))) case SQLITE_INTEGER: - let int = Int(sqlite3_column_int64(self.handle, Int32(idx))) - var bool = false - if let type = String.fromCString(sqlite3_column_decltype(self.handle, Int32(idx))) { - bool = type.hasPrefix("BOOL") - } - return bool ? int != 0 : int + return Int(sqlite3_column_int64(self.handle, Int32(idx))) case SQLITE_NULL: return nil case SQLITE_TEXT: diff --git a/SQLite Common/Value.swift b/SQLite Common/Value.swift index 19039b6..49cfde5 100644 --- a/SQLite Common/Value.swift +++ b/SQLite Common/Value.swift @@ -86,18 +86,18 @@ extension Blob: Binding, Value { } -extension Bool: Binding, Value { +extension Bool: Value { - public typealias Datatype = Bool + public typealias Datatype = Int - public static var declaredDatatype = "BOOLEAN" + public static var declaredDatatype = Int.declaredDatatype public static func fromDatatypeValue(datatypeValue: Datatype) -> Bool { - return datatypeValue + return datatypeValue != 0 } public var datatypeValue: Datatype { - return self + return self ? 1 : 0 } } diff --git a/SQLite.playground/section-6.swift b/SQLite.playground/section-6.swift index b3fd3a1..b9afc57 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@acme.com": true, "betsy@acme.com": false] { +for (email, admin) in ["alice@acme.com": 1, "betsy@acme.com": 0] { stmt.run(email, admin) }