Files

361 lines
14 KiB
Swift

import XCTest
import GRDB
private struct CustomValueType : DatabaseValueConvertible {
var databaseValue: DatabaseValue { "CustomValueType".databaseValue }
static func fromDatabaseValue(_ dbValue: DatabaseValue) -> CustomValueType? {
guard let string = String.fromDatabaseValue(dbValue), string == "CustomValueType" else {
return nil
}
return CustomValueType()
}
}
class DatabaseFunctionTests: GRDBTestCase {
// MARK: - Default functions
func testDefaultFunctions() throws {
let dbQueue = try makeDatabaseQueue()
try dbQueue.inDatabase { db in
// Those functions are automatically added to all connections.
// See Database.setupDefaultFunctions()
let capitalize = DatabaseFunction.capitalize
XCTAssertEqual(try String.fetchOne(db, sql: "SELECT \(capitalize.name)('jérÔME')"), "Jérôme")
let lowercase = DatabaseFunction.lowercase
XCTAssertEqual(try String.fetchOne(db, sql: "SELECT \(lowercase.name)('jérÔME')"), "jérôme")
let uppercase = DatabaseFunction.uppercase
XCTAssertEqual(try String.fetchOne(db, sql: "SELECT \(uppercase.name)('jérÔME')"), "JÉRÔME")
// Locale-dependent tests. Are they fragile?
let localizedCapitalize = DatabaseFunction.localizedCapitalize
XCTAssertEqual(try String.fetchOne(db, sql: "SELECT \(localizedCapitalize.name)('jérÔME')"), "Jérôme")
let localizedLowercase = DatabaseFunction.localizedLowercase
XCTAssertEqual(try String.fetchOne(db, sql: "SELECT \(localizedLowercase.name)('jérÔME')"), "jérôme")
let localizedUppercase = DatabaseFunction.localizedUppercase
XCTAssertEqual(try String.fetchOne(db, sql: "SELECT \(localizedUppercase.name)('jérÔME')"), "JÉRÔME")
}
}
// MARK: - Return values
func testFunctionReturningNull() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 0) { dbValues in
return nil
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertTrue(try DatabaseValue.fetchOne(db, sql: "SELECT f()")!.isNull)
}
}
func testFunctionReturningInt64() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 0) { dbValues in
return Int64(1)
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertEqual(try Int64.fetchOne(db, sql: "SELECT f()")!, Int64(1))
}
}
func testFunctionReturningDouble() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 0) { dbValues in
return 1e100
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertEqual(try Double.fetchOne(db, sql: "SELECT f()")!, 1e100)
}
}
func testFunctionReturningString() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 0) { dbValues in
return "foo"
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertEqual(try String.fetchOne(db, sql: "SELECT f()")!, "foo")
}
}
func testFunctionReturningData() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 0) { dbValues in
return "foo".data(using: .utf8)
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertEqual(try Data.fetchOne(db, sql: "SELECT f()")!, "foo".data(using: .utf8))
}
}
func testFunctionReturningCustomValueType() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 0) { dbValues in
return CustomValueType()
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertTrue(try CustomValueType.fetchOne(db, sql: "SELECT f()") != nil)
}
}
// MARK: - Argument values
func testFunctionArgumentNil() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 1) { (dbValues: [DatabaseValue]) in
return dbValues[0].isNull
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertTrue(try Bool.fetchOne(db, sql: "SELECT f(NULL)")!)
XCTAssertFalse(try Bool.fetchOne(db, sql: "SELECT f(1)")!)
XCTAssertFalse(try Bool.fetchOne(db, sql: "SELECT f(1.1)")!)
XCTAssertFalse(try Bool.fetchOne(db, sql: "SELECT f('foo')")!)
XCTAssertFalse(try Bool.fetchOne(db, sql: "SELECT f(?)", arguments: ["foo".data(using: .utf8)])!)
XCTAssertFalse(try Bool.fetchOne(db, sql: "SELECT f(?)", arguments: [Data()])!)
}
}
func testFunctionArgumentInt64() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 1) { (dbValues: [DatabaseValue]) in
return Int64.fromDatabaseValue(dbValues[0])
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertTrue(try Int64.fetchOne(db, sql: "SELECT f(NULL)") == nil)
XCTAssertEqual(try Int64.fetchOne(db, sql: "SELECT f(1)")!, 1)
XCTAssertEqual(try Int64.fetchOne(db, sql: "SELECT f(1.1)")!, 1)
}
}
func testFunctionArgumentDouble() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 1) { (dbValues: [DatabaseValue]) in
return Double.fromDatabaseValue(dbValues[0])
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertTrue(try Double.fetchOne(db, sql: "SELECT f(NULL)") == nil)
XCTAssertEqual(try Double.fetchOne(db, sql: "SELECT f(1)")!, 1.0)
XCTAssertEqual(try Double.fetchOne(db, sql: "SELECT f(1.1)")!, 1.1)
}
}
func testFunctionArgumentString() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 1) { (dbValues: [DatabaseValue]) in
return String.fromDatabaseValue(dbValues[0])
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertTrue(try String.fetchOne(db, sql: "SELECT f(NULL)") == nil)
XCTAssertEqual(try String.fetchOne(db, sql: "SELECT f('foo')")!, "foo")
}
}
func testFunctionArgumentBlob() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 1) { (dbValues: [DatabaseValue]) in
return Data.fromDatabaseValue(dbValues[0])
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertTrue(try Data.fetchOne(db, sql: "SELECT f(NULL)") == nil)
XCTAssertEqual(try Data.fetchOne(db, sql: "SELECT f(?)", arguments: ["foo".data(using: .utf8)])!, "foo".data(using: .utf8))
XCTAssertEqual(try Data.fetchOne(db, sql: "SELECT f(?)", arguments: [Data()])!, Data())
}
}
func testFunctionArgumentCustomValueType() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 1) { (dbValues: [DatabaseValue]) in
return CustomValueType.fromDatabaseValue(dbValues[0])
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertTrue(try CustomValueType.fetchOne(db, sql: "SELECT f(NULL)") == nil)
XCTAssertTrue(try CustomValueType.fetchOne(db, sql: "SELECT f('CustomValueType')") != nil)
}
}
// MARK: - Argument count
func testFunctionWithoutArgument() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 0) { dbValues in
return "foo"
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertEqual(try String.fetchOne(db, sql: "SELECT f()")!, "foo")
do {
try db.execute(sql: "SELECT f(1)")
XCTFail("Expected error")
} catch let error as DatabaseError {
XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
XCTAssertEqual(error.message!, "wrong number of arguments to function f()")
XCTAssertEqual(error.sql!, "SELECT f(1)")
XCTAssertEqual(error.description, "SQLite error 1: wrong number of arguments to function f() - while executing `SELECT f(1)`")
}
}
}
func testFunctionOfOneArgument() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 1) { (dbValues: [DatabaseValue]) in
String.fromDatabaseValue(dbValues[0]).map { $0.uppercased() }
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertEqual(try String.fetchOne(db, sql: "SELECT upper(?)", arguments: ["Roué"])!, "ROUé")
XCTAssertEqual(try String.fetchOne(db, sql: "SELECT f(?)", arguments: ["Roué"])!, "ROUÉ")
XCTAssertTrue(try String.fetchOne(db, sql: "SELECT f(NULL)") == nil)
do {
try db.execute(sql: "SELECT f()")
XCTFail("Expected error")
} catch let error as DatabaseError {
XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
XCTAssertEqual(error.message!, "wrong number of arguments to function f()")
XCTAssertEqual(error.sql!, "SELECT f()")
XCTAssertEqual(error.description, "SQLite error 1: wrong number of arguments to function f() - while executing `SELECT f()`")
}
}
}
func testFunctionOfTwoArguments() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f", argumentCount: 2) { dbValues in
let ints = dbValues.compactMap { Int.fromDatabaseValue($0) }
return ints.reduce(0, +)
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT f(1, 2)")!, 3)
do {
try db.execute(sql: "SELECT f()")
XCTFail("Expected error")
} catch let error as DatabaseError {
XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
XCTAssertEqual(error.message!, "wrong number of arguments to function f()")
XCTAssertEqual(error.sql!, "SELECT f()")
XCTAssertEqual(error.description, "SQLite error 1: wrong number of arguments to function f() - while executing `SELECT f()`")
}
}
}
func testVariadicFunction() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f") { dbValues in
return dbValues.count
}
try dbQueue.inDatabase { db in
db.add(function: fn)
XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT f()")!, 0)
XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT f(1)")!, 1)
XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT f(1, 1)")!, 2)
}
}
// MARK: - Errors
func testFunctionThrowingDatabaseErrorWithMessage() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f") { dbValues in
throw DatabaseError(message: "custom error message")
}
try dbQueue.inDatabase { db in
db.add(function: fn)
do {
try db.execute(sql: "SELECT f()")
XCTFail("Expected DatabaseError")
} catch let error as DatabaseError {
XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
XCTAssertEqual(error.message, "custom error message")
}
}
}
func testFunctionThrowingDatabaseErrorWithCode() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f") { dbValues in
throw DatabaseError(resultCode: ResultCode(rawValue: 123))
}
try dbQueue.inDatabase { db in
db.add(function: fn)
do {
try db.execute(sql: "SELECT f()")
XCTFail("Expected DatabaseError")
} catch let error as DatabaseError {
XCTAssertEqual(error.resultCode.rawValue, 123)
XCTAssertEqual(error.message, "unknown error")
}
}
}
func testFunctionThrowingDatabaseErrorWithMessageAndCode() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f") { dbValues in
throw DatabaseError(resultCode: ResultCode(rawValue: 123), message: "custom error message")
}
try dbQueue.inDatabase { db in
db.add(function: fn)
do {
try db.execute(sql: "SELECT f()")
XCTFail("Expected DatabaseError")
} catch let error as DatabaseError {
XCTAssertEqual(error.resultCode.rawValue, 123)
XCTAssertEqual(error.message, "custom error message")
}
}
}
func testFunctionThrowingCustomError() throws {
let dbQueue = try makeDatabaseQueue()
let fn = DatabaseFunction("f") { dbValues in
throw NSError(domain: "CustomErrorDomain", code: 123, userInfo: [NSLocalizedDescriptionKey: "custom error message"])
}
try dbQueue.inDatabase { db in
db.add(function: fn)
do {
try db.execute(sql: "SELECT f()")
XCTFail("Expected DatabaseError")
} catch let error as DatabaseError {
XCTAssertEqual(error.resultCode, .SQLITE_ERROR)
XCTAssertTrue(error.message!.contains("CustomErrorDomain"))
XCTAssertTrue(error.message!.contains("123"))
XCTAssertTrue(error.message!.contains("custom error message"))
}
}
}
// MARK: - Misc
func testFunctionsAreClosures() throws {
let dbQueue = try makeDatabaseQueue()
var x = 123
let fn = DatabaseFunction("f", argumentCount: 0) { dbValues in
return x
}
try dbQueue.inDatabase { db in
db.add(function: fn)
x = 321
XCTAssertEqual(try Int.fetchOne(db, sql: "SELECT f()")!, 321)
}
}
}