Files

584 lines
24 KiB
Swift

import XCTest
import GRDB
class StatementArgumentsTests: GRDBTestCase {
override func setup(_ dbWriter: some DatabaseWriter) throws {
var migrator = DatabaseMigrator()
migrator.registerMigration("createPersons") { db in
try db.execute(sql: """
CREATE TABLE persons (
id INTEGER PRIMARY KEY,
firstName TEXT,
lastName TEXT,
age INT)
""")
}
try migrator.migrate(dbWriter)
}
func testPositionalStatementArgumentsValidation() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let statement = try db.makeStatement(sql: "INSERT INTO persons (firstName, age) VALUES (?, ?)")
do {
// Correct number of arguments
try statement.validateArguments(["foo", 1])
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Missing arguments
try statement.validateArguments([])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Two few arguments
try statement.validateArguments(["foo"])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Two many arguments
try statement.validateArguments(["foo", 1, "bar"])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Missing arguments
try statement.validateArguments([:])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Unmappable arguments
try statement.validateArguments(["firstName": "foo", "age": 1])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
}
}
func testPositionalStatementArguments() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let name = "Arthur"
let age = 42
let arguments = StatementArguments([name, age] as [(any DatabaseValueConvertible)?])
let updateStatement = try db.makeStatement(sql: "INSERT INTO persons (firstName, age) VALUES (?, ?)")
updateStatement.arguments = arguments
try updateStatement.execute()
let selectStatement = try db.makeStatement(sql: "SELECT * FROM persons WHERE firstName = ? AND age = ?")
selectStatement.arguments = arguments
let row = try Row.fetchOne(selectStatement)!
XCTAssertEqual(row["firstName"] as String, name)
XCTAssertEqual(row["age"] as Int, age)
}
}
func testCheckedPositionalStatementArguments() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let name = "Arthur"
let age = 42
let arguments = StatementArguments([name, age] as [(any DatabaseValueConvertible)?])
let updateStatement = try db.makeStatement(sql: "INSERT INTO persons (firstName, age) VALUES (?, ?)")
try updateStatement.setArguments(arguments)
try updateStatement.execute()
let selectStatement = try db.makeStatement(sql: "SELECT * FROM persons WHERE firstName = ? AND age = ?")
try selectStatement.setArguments(arguments)
let row = try Row.fetchOne(selectStatement)!
XCTAssertEqual(row["firstName"] as String, name)
XCTAssertEqual(row["age"] as Int, age)
do {
try updateStatement.setArguments([1])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
XCTAssertEqual(updateStatement.arguments, arguments)
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
try selectStatement.setArguments([1])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
XCTAssertEqual(selectStatement.arguments, arguments)
} catch {
XCTFail("Unexpected error: \(error)")
}
}
}
func testUncheckedPositionalStatementArguments() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let name = "Arthur"
let age = 42
let arguments = StatementArguments([name, age] as [(any DatabaseValueConvertible)?])
let updateStatement = try db.makeStatement(sql: "INSERT INTO persons (firstName, age) VALUES (?, ?)")
updateStatement.setUncheckedArguments(arguments)
try updateStatement.execute()
let selectStatement = try db.makeStatement(sql: "SELECT * FROM persons WHERE firstName = ? AND age = ?")
selectStatement.setUncheckedArguments(arguments)
let row = try Row.fetchOne(selectStatement)!
XCTAssertEqual(row["firstName"] as String, name)
XCTAssertEqual(row["age"] as Int, age)
}
}
func testNamedStatementArgumentsValidation() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let statement = try db.makeStatement(sql: "INSERT INTO persons (firstName, age) VALUES (:firstName, :age)")
do {
// Correct number of arguments
try statement.validateArguments(["foo", 1])
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// All arguments are mapped
try statement.validateArguments(["firstName": "foo", "age": 1])
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// All arguments are mapped
try statement.validateArguments(["firstName": "foo", "age": 1, "bar": "baz"])
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Missing arguments
try statement.validateArguments([])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Missing arguments
try statement.validateArguments(["foo"])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Too many arguments
try statement.validateArguments(["foo", 1, "baz"])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Missing arguments
try statement.validateArguments([:])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Missing arguments
try statement.validateArguments(["firstName": "foo"])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
}
}
func testNamedStatementArguments() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let name = "Arthur"
let age = 42
let arguments = StatementArguments(["name": name, "age": age] as [String: (any DatabaseValueConvertible)?])
let updateStatement = try db.makeStatement(sql: "INSERT INTO persons (firstName, age) VALUES (:name, :age)")
updateStatement.arguments = arguments
try updateStatement.execute()
let selectStatement = try db.makeStatement(sql: "SELECT * FROM persons WHERE firstName = :name AND age = :age")
selectStatement.arguments = arguments
let row = try Row.fetchOne(selectStatement)!
XCTAssertEqual(row["firstName"] as String, name)
XCTAssertEqual(row["age"] as Int, age)
}
}
func testCheckedNamedStatementArguments() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let name = "Arthur"
let age = 42
let arguments = StatementArguments(["name": name, "age": age] as [String: (any DatabaseValueConvertible)?])
let updateStatement = try db.makeStatement(sql: "INSERT INTO persons (firstName, age) VALUES (:name, :age)")
try updateStatement.setArguments(arguments)
try updateStatement.execute()
let selectStatement = try db.makeStatement(sql: "SELECT * FROM persons WHERE firstName = :name AND age = :age")
try selectStatement.setArguments(arguments)
let row = try Row.fetchOne(selectStatement)!
XCTAssertEqual(row["firstName"] as String, name)
XCTAssertEqual(row["age"] as Int, age)
do {
try updateStatement.setArguments(["name": name])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
XCTAssertEqual(updateStatement.arguments, arguments)
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
try selectStatement.setArguments(["name": name])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
XCTAssertEqual(selectStatement.arguments, arguments)
} catch {
XCTFail("Unexpected error: \(error)")
}
}
}
func testUncheckedNamedStatementArguments() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let name = "Arthur"
let age = 42
let arguments = StatementArguments(["name": name, "age": age] as [String: (any DatabaseValueConvertible)?])
let updateStatement = try db.makeStatement(sql: "INSERT INTO persons (firstName, age) VALUES (:name, :age)")
updateStatement.setUncheckedArguments(arguments)
try updateStatement.execute()
let selectStatement = try db.makeStatement(sql: "SELECT * FROM persons WHERE firstName = :name AND age = :age")
selectStatement.setUncheckedArguments(arguments)
let row = try Row.fetchOne(selectStatement)!
XCTAssertEqual(row["firstName"] as String, name)
XCTAssertEqual(row["age"] as Int, age)
}
}
func testReusedNamedStatementArgumentsValidation() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let statement = try db.makeStatement(sql: "INSERT INTO persons (firstName, lastName, age) VALUES (:name, :name, :age)")
do {
try statement.execute(arguments: ["name": "foo", "age": 1])
let row = try Row.fetchOne(db, sql: "SELECT * FROM persons")!
XCTAssertEqual(row["firstName"] as String, "foo")
XCTAssertEqual(row["lastName"] as String, "foo")
XCTAssertEqual(row["age"] as Int, 1)
}
do {
// Correct number of arguments
try statement.validateArguments(["foo", 1])
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// All arguments are mapped
try statement.validateArguments(["name": "foo", "age": 1])
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// All arguments are mapped
try statement.validateArguments(["name": "foo", "age": 1, "bar": "baz"])
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Missing arguments
try statement.validateArguments([])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Missing arguments
try statement.validateArguments(["foo"])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Too many arguments
try statement.validateArguments(["foo", 1, "baz"])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Missing arguments
try statement.validateArguments([:])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
// Missing arguments
try statement.validateArguments(["name": "foo"])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
} catch {
XCTFail("Unexpected error: \(error)")
}
}
}
func testReusedNamedStatementArguments() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let name = "Arthur"
let age = 42
let arguments = StatementArguments(["name": name, "age": age] as [String: (any DatabaseValueConvertible)?])
let updateStatement = try db.makeStatement(sql: "INSERT INTO persons (firstName, lastName, age) VALUES (:name, :name, :age)")
updateStatement.arguments = arguments
try updateStatement.execute()
let selectStatement = try db.makeStatement(sql: "SELECT * FROM persons WHERE firstName = :name AND lastName = :name AND age = :age")
selectStatement.arguments = arguments
let row = try Row.fetchOne(selectStatement)!
XCTAssertEqual(row["firstName"] as String, name)
XCTAssertEqual(row["age"] as Int, age)
}
}
func testCheckedReusedNamedStatementArguments() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let name = "Arthur"
let age = 42
let arguments = StatementArguments(["name": name, "age": age] as [String: (any DatabaseValueConvertible)?])
let updateStatement = try db.makeStatement(sql: "INSERT INTO persons (firstName, lastName, age) VALUES (:name, :name, :age)")
try updateStatement.setArguments(arguments)
try updateStatement.execute()
let selectStatement = try db.makeStatement(sql: "SELECT * FROM persons WHERE firstName = :name AND lastName = :name AND age = :age")
try selectStatement.setArguments(arguments)
let row = try Row.fetchOne(selectStatement)!
XCTAssertEqual(row["firstName"] as String, name)
XCTAssertEqual(row["age"] as Int, age)
do {
try updateStatement.setArguments(["name": name])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
XCTAssertEqual(updateStatement.arguments, arguments)
} catch {
XCTFail("Unexpected error: \(error)")
}
do {
try selectStatement.setArguments(["name": name])
XCTFail("Expected error")
} catch DatabaseError.SQLITE_MISUSE {
XCTAssertEqual(selectStatement.arguments, arguments)
} catch {
XCTFail("Unexpected error: \(error)")
}
}
}
func testUncheckedReusedNamedStatementArguments() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let name = "Arthur"
let age = 42
let arguments = StatementArguments(["name": name, "age": age] as [String: (any DatabaseValueConvertible)?])
let updateStatement = try db.makeStatement(sql: "INSERT INTO persons (firstName, lastName, age) VALUES (:name, :name, :age)")
updateStatement.setUncheckedArguments(arguments)
try updateStatement.execute()
let selectStatement = try db.makeStatement(sql: "SELECT * FROM persons WHERE firstName = :name AND lastName = :name AND age = :age")
selectStatement.setUncheckedArguments(arguments)
let row = try Row.fetchOne(selectStatement)!
XCTAssertEqual(row["firstName"] as String, name)
XCTAssertEqual(row["age"] as Int, age)
}
}
func testMixedArguments() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
let sql = "SELECT ?2 AS two, :foo AS foo, ?1 AS one, :foo AS foo2, :bar AS bar"
let row = try Row.fetchOne(db, sql: sql, arguments: [1, 2, "bar"] + ["foo": "foo"])!
XCTAssertEqual(row, ["two": 2, "foo": "foo", "one": 1, "foo2": "foo", "bar": "bar"])
}
}
func testAppendContentsOf() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
do {
var arguments: StatementArguments = [1, 2]
let replacedValues = arguments.append(contentsOf: [3, 4])
XCTAssert(replacedValues.isEmpty)
let row = try Row.fetchOne(db, sql: "SELECT ?, ?, ?, ?", arguments: arguments)!
XCTAssertEqual(row, ["?": 1, "?": 2, "?": 3, "?": 4])
}
do {
var arguments: StatementArguments = ["foo": "foo", "bar": "bar", "toto": "titi"]
let replacedValues = arguments.append(contentsOf: ["foo": "qux", "bar": "baz", "tata": "tutu"])
XCTAssertEqual(replacedValues, ["foo": "foo".databaseValue, "bar": "bar".databaseValue])
let row = try Row.fetchOne(db, sql: "SELECT :foo, :bar, :toto, :tata", arguments: arguments)!
XCTAssertEqual(row, [":foo": "qux", ":bar": "baz", ":toto": "titi", ":tata": "tutu"])
}
do {
var arguments: StatementArguments = [1, 2]
let replacedValues = arguments.append(contentsOf: ["foo": "qux", "bar": "baz", "tata": "tutu"])
XCTAssert(replacedValues.isEmpty)
let row = try Row.fetchOne(db, sql: "SELECT ?, :foo, :bar, ?, :tata", arguments: arguments)!
XCTAssertEqual(row, ["?": 1, ":foo": "qux", ":bar": "baz", "?": 2, ":tata": "tutu"])
}
}
}
func testPlusOperator() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
do {
let arguments: StatementArguments = [1, 2] + [3, 4]
let row = try Row.fetchOne(db, sql: "SELECT ?, ?, ?, ?", arguments: arguments)!
XCTAssertEqual(row, ["?": 1, "?": 2, "?": 3, "?": 4])
}
do {
// + does not allow overrides
let arguments: StatementArguments = ["foo": "foo", "bar": "bar", "toto": "titi"] + ["tata": "tutu"]
let row = try Row.fetchOne(db, sql: "SELECT :foo, :bar, :toto, :tata", arguments: arguments)!
XCTAssertEqual(row, [":foo": "foo", ":bar": "bar", ":toto": "titi", ":tata": "tutu"])
}
do {
let arguments: StatementArguments = [1, 2] + ["foo": "qux", "bar": "baz", "tata": "tutu"]
let row = try Row.fetchOne(db, sql: "SELECT ?, :foo, :bar, ?, :tata", arguments: arguments)!
XCTAssertEqual(row, ["?": 1, ":foo": "qux", ":bar": "baz", "?": 2, ":tata": "tutu"])
}
}
}
func testOverflowPlusOperator() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
do {
let arguments: StatementArguments = [1, 2] &+ [3, 4]
let row = try Row.fetchOne(db, sql: "SELECT ?, ?, ?, ?", arguments: arguments)!
XCTAssertEqual(row, ["?": 1, "?": 2, "?": 3, "?": 4])
}
do {
// &+ does not allow overrides
let arguments: StatementArguments = ["foo": "foo", "bar": "bar", "toto": "titi"] &+ ["foo": "qux", "bar": "baz", "tata": "tutu"]
let row = try Row.fetchOne(db, sql: "SELECT :foo, :bar, :toto, :tata", arguments: arguments)!
XCTAssertEqual(row, [":foo": "qux", ":bar": "baz", ":toto": "titi", ":tata": "tutu"])
}
do {
let arguments: StatementArguments = [1, 2] &+ ["foo": "qux", "bar": "baz", "tata": "tutu"]
let row = try Row.fetchOne(db, sql: "SELECT ?, :foo, :bar, ?, :tata", arguments: arguments)!
XCTAssertEqual(row, ["?": 1, ":foo": "qux", ":bar": "baz", "?": 2, ":tata": "tutu"])
}
}
}
func testPlusEqualOperator() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
do {
var arguments: StatementArguments = [1, 2]
arguments += [3, 4]
let row = try Row.fetchOne(db, sql: "SELECT ?, ?, ?, ?", arguments: arguments)!
XCTAssertEqual(row, ["?": 1, "?": 2, "?": 3, "?": 4])
}
do {
// += does not allow overrides
var arguments: StatementArguments = ["foo": "foo", "bar": "bar", "toto": "titi"]
arguments += ["tata": "tutu"]
let row = try Row.fetchOne(db, sql: "SELECT :foo, :bar, :toto, :tata", arguments: arguments)!
XCTAssertEqual(row, [":foo": "foo", ":bar": "bar", ":toto": "titi", ":tata": "tutu"])
}
do {
var arguments: StatementArguments = [1, 2]
arguments += ["foo": "qux", "bar": "baz", "tata": "tutu"]
let row = try Row.fetchOne(db, sql: "SELECT ?, :foo, :bar, ?, :tata", arguments: arguments)!
XCTAssertEqual(row, ["?": 1, ":foo": "qux", ":bar": "baz", "?": 2, ":tata": "tutu"])
}
}
}
}