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:
Родитель
d52d3af9cf
Коммит
fc61295b21
|
@ -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)
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче