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 <stephen@stephencelis.com>
This commit is contained in:
Stephen Celis 2015-02-03 20:17:26 -08:00
Родитель d52d3af9cf
Коммит fc61295b21
8 изменённых файлов: 61 добавлений и 77 удалений

Просмотреть файл

@ -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

Просмотреть файл

@ -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))

Просмотреть файл

@ -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)
}

Просмотреть файл

@ -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]
)
}

Просмотреть файл

@ -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<Bool>, rhs: Expression<Bool>) -> Expression<Bool
public func && (lhs: Expression<Bool>, rhs: Expression<Bool?>) -> Expression<Bool?> { return infix("AND", lhs, rhs) }
public func && (lhs: Expression<Bool?>, rhs: Expression<Bool>) -> Expression<Bool?> { return infix("AND", lhs, rhs) }
public func && (lhs: Expression<Bool?>, rhs: Expression<Bool?>) -> Expression<Bool?> { return infix("AND", lhs, rhs) }
public func && (lhs: Expression<Bool>, rhs: Bool) -> Expression<Bool> { return lhs && Expression(binding: rhs) }
public func && (lhs: Expression<Bool?>, rhs: Bool) -> Expression<Bool?> { return lhs && Expression(binding: rhs) }
public func && (lhs: Bool, rhs: Expression<Bool>) -> Expression<Bool> { return Expression(binding: lhs) && rhs }
public func && (lhs: Bool, rhs: Expression<Bool?>) -> Expression<Bool?> { return Expression(binding: lhs) && rhs }
// FIXME: rdar://TODO segfaults during archive // ... Expression(value: lhs)
public func && (lhs: Expression<Bool>, rhs: Bool) -> Expression<Bool> { return lhs && Expression(binding: rhs.datatypeValue) }
public func && (lhs: Expression<Bool?>, rhs: Bool) -> Expression<Bool?> { return lhs && Expression(binding: rhs.datatypeValue) }
// FIXME: rdar://TODO segfaults during archive // ... Expression(value: rhs)
public func && (lhs: Bool, rhs: Expression<Bool>) -> Expression<Bool> { return Expression(binding: lhs.datatypeValue) && rhs }
public func && (lhs: Bool, rhs: Expression<Bool?>) -> Expression<Bool?> { return Expression(binding: lhs.datatypeValue) && rhs }
public func || (lhs: Expression<Bool>, rhs: Expression<Bool>) -> Expression<Bool> { return infix("OR", lhs, rhs) }
public func || (lhs: Expression<Bool>, rhs: Expression<Bool?>) -> Expression<Bool?> { return infix("OR", lhs, rhs) }
public func || (lhs: Expression<Bool?>, rhs: Expression<Bool>) -> Expression<Bool?> { return infix("OR", lhs, rhs) }
public func || (lhs: Expression<Bool?>, rhs: Expression<Bool?>) -> Expression<Bool?> { return infix("OR", lhs, rhs) }
public func || (lhs: Expression<Bool>, rhs: Bool) -> Expression<Bool> { return lhs || Expression(binding: rhs) }
public func || (lhs: Expression<Bool?>, rhs: Bool) -> Expression<Bool?> { return lhs || Expression(binding: rhs) }
public func || (lhs: Bool, rhs: Expression<Bool>) -> Expression<Bool> { return Expression(binding: lhs) || rhs }
public func || (lhs: Bool, rhs: Expression<Bool?>) -> Expression<Bool?> { return Expression(binding: lhs) || rhs }
// FIXME: rdar://TODO segfaults during archive // ... Expression(value: lhs)
public func || (lhs: Expression<Bool>, rhs: Bool) -> Expression<Bool> { return lhs || Expression(binding: rhs.datatypeValue) }
public func || (lhs: Expression<Bool?>, rhs: Bool) -> Expression<Bool?> { return lhs || Expression(binding: rhs.datatypeValue) }
// FIXME: rdar://TODO segfaults during archive // ... Expression(value: rhs)
public func || (lhs: Bool, rhs: Expression<Bool>) -> Expression<Bool> { return Expression(binding: lhs.datatypeValue) || rhs }
public func || (lhs: Bool, rhs: Expression<Bool?>) -> Expression<Bool?> { return Expression(binding: lhs.datatypeValue) || rhs }
public prefix func ! (rhs: Expression<Bool>) -> Expression<Bool> { return wrap("NOT ", rhs) }
public prefix func ! (rhs: Expression<Bool?>) -> Expression<Bool?> { return wrap("NOT ", rhs) }
@ -655,11 +660,11 @@ public func * (Expression<Binding>?, Expression<Binding>?) -> Expression<()> {
public func contains<V: Value>(values: [V], column: Expression<V>) -> Expression<Bool> {
let templates = join(", ", [String](count: values.count, repeatedValue: "?"))
return infix("IN", column, Expression<V>(literal: "(\(templates))", values.map { $0 }))
return infix("IN", column, Expression<V>(literal: "(\(templates))", values.map { $0.datatypeValue }))
}
public func contains<V: Value>(values: [V?], column: Expression<V?>) -> Expression<Bool> {
let templates = join(", ", [String](count: values.count, repeatedValue: "?"))
return infix("IN", column, Expression<V>(literal: "(\(templates))", values.map { $0 }))
return infix("IN", column, Expression<V>(literal: "(\(templates))", values.map { $0?.datatypeValue }))
}
// MARK: - Modifying
@ -829,7 +834,6 @@ public postfix func -- (column: Expression<Int?>) -> 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"

Просмотреть файл

@ -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:

Просмотреть файл

@ -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
}
}

Просмотреть файл

@ -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)
}