add iOS
This commit is contained in:
@@ -0,0 +1,952 @@
|
||||
import XCTest
|
||||
@testable import GRDB
|
||||
|
||||
class SQLLiteralTests: GRDBTestCase {
|
||||
func testSQLInitializer() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
let query = SQL(sql: """
|
||||
SELECT * FROM player
|
||||
WHERE id = \("?")
|
||||
""", arguments: [1])
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player
|
||||
WHERE id = ?
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1])
|
||||
}
|
||||
}
|
||||
|
||||
func testPlusOperator() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
var query = SQL(sql: "SELECT * ")
|
||||
query = query + SQL(sql: "FROM player ")
|
||||
query = query + SQL(sql: "WHERE id = ? ", arguments: [1])
|
||||
query = query + SQL(sql: "AND name = ?", arguments: ["Arthur"])
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player WHERE id = ? AND name = ?
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1, "Arthur"])
|
||||
}
|
||||
}
|
||||
|
||||
func testPlusEqualOperator() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
var query = SQL(sql: "SELECT * ")
|
||||
query += SQL(sql: "FROM player ")
|
||||
query += SQL(sql: "WHERE id = ? ", arguments: [1])
|
||||
query += SQL(sql: "AND name = ?", arguments: ["Arthur"])
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player WHERE id = ? AND name = ?
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1, "Arthur"])
|
||||
}
|
||||
}
|
||||
|
||||
func testAppendLiteral() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
var query = SQL(sql: "SELECT * ")
|
||||
query.append(literal: SQL(sql: "FROM player "))
|
||||
query.append(literal: SQL(sql: "WHERE id = ? ", arguments: [1]))
|
||||
query.append(literal: SQL(sql: "AND name = ?", arguments: ["Arthur"]))
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player WHERE id = ? AND name = ?
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1, "Arthur"])
|
||||
}
|
||||
}
|
||||
|
||||
func testAppendRawSQL() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
var query = SQL(sql: "SELECT * ")
|
||||
query.append(sql: "FROM player ")
|
||||
query.append(sql: "WHERE score > \(1000) ")
|
||||
query.append(sql: "AND \("name") = :name", arguments: ["name": "Arthur"])
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player WHERE score > 1000 AND name = :name
|
||||
""")
|
||||
XCTAssertEqual(arguments, ["name": "Arthur"])
|
||||
}
|
||||
}
|
||||
|
||||
func testSequenceJoined() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
// A sequence that can't be consumed twice
|
||||
var i = 0
|
||||
let sequence = AnySequence<SQL> {
|
||||
return AnyIterator {
|
||||
guard i < 3 else { return nil }
|
||||
i += 1
|
||||
return SQL(sql: "(\(i) = ?)", arguments: [i])
|
||||
}
|
||||
}
|
||||
do {
|
||||
i = 0
|
||||
let joined = sequence.joined()
|
||||
let (sql, arguments) = try joined.build(db)
|
||||
XCTAssertEqual(sql, "(1 = ?)(2 = ?)(3 = ?)")
|
||||
XCTAssertEqual(arguments, [1, 2, 3])
|
||||
}
|
||||
do {
|
||||
i = 0
|
||||
let joined = sequence.joined(separator: " AND ")
|
||||
let (sql, arguments) = try joined.build(db)
|
||||
XCTAssertEqual(sql, "(1 = ?) AND (2 = ?) AND (3 = ?)")
|
||||
XCTAssertEqual(arguments, [1, 2, 3])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testCollectionJoined() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
let collection = AnyCollection([
|
||||
SQL(sql: "SELECT * "),
|
||||
SQL(sql: "FROM player "),
|
||||
SQL(sql: "WHERE score > ? ", arguments: [1000]),
|
||||
SQL(sql: "AND name = :name", arguments: ["name": "Arthur"]),
|
||||
])
|
||||
do {
|
||||
let joined = collection.joined()
|
||||
let (sql, arguments) = try joined.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player WHERE score > ? AND name = :name
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1000] + ["name": "Arthur"])
|
||||
}
|
||||
do {
|
||||
let joined = collection.joined(separator: " ")
|
||||
let (sql, arguments) = try joined.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player WHERE score > ? AND name = :name
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1000] + ["name": "Arthur"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testQualifiedSQLLiteral() throws {
|
||||
struct Player: TableRecord { }
|
||||
try makeDatabaseQueue().write { db in
|
||||
try db.create(table: "player") { t in
|
||||
t.column("name", .text)
|
||||
t.column("createdAt", .datetime)
|
||||
}
|
||||
|
||||
do {
|
||||
// Test of SQL.init(_:) documentation (plus qualification)
|
||||
let columnLiteral = SQL(Column("name"))
|
||||
let suffixLiteral = SQL("O'Brien".databaseValue)
|
||||
let literal = [columnLiteral, suffixLiteral].joined(separator: " || ")
|
||||
let request = Player.aliased(TableAlias(name: "p")).select(literal)
|
||||
try assertEqualSQL(db, request, """
|
||||
SELECT "p"."name" || 'O''Brien' FROM "player" "p"
|
||||
""")
|
||||
}
|
||||
|
||||
do {
|
||||
// Test qualification of interpolated literal
|
||||
let literal: SQL = "\(Column("name")) || 'foo'"
|
||||
let request = Player.aliased(TableAlias(name: "p")).select(literal)
|
||||
try assertEqualSQL(db, request, """
|
||||
SELECT "p"."name" || 'foo' FROM "player" "p"
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension SQLLiteralTests {
|
||||
func testLiteralInitializer() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
let query = SQL("""
|
||||
SELECT * FROM player
|
||||
WHERE id = \(1)
|
||||
""")
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player
|
||||
WHERE id = ?
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1])
|
||||
}
|
||||
}
|
||||
|
||||
func testRawSQLInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
let query: SQL = """
|
||||
SELECT *
|
||||
\(sql: "FROM player")
|
||||
\(sql: "WHERE score > \(1000)")
|
||||
\(sql: "AND \("name") = :name", arguments: ["name": "Arthur"])
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT *
|
||||
FROM player
|
||||
WHERE score > 1000
|
||||
AND name = :name
|
||||
""")
|
||||
XCTAssertEqual(arguments, ["name": "Arthur"])
|
||||
}
|
||||
}
|
||||
|
||||
func testSelectableInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
do {
|
||||
// Non-existential
|
||||
let query: SQL = """
|
||||
SELECT \(AllColumns())
|
||||
FROM player
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT *
|
||||
FROM player
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
do {
|
||||
// Existential
|
||||
let query: SQL = """
|
||||
SELECT \(AllColumns() as any SQLSelectable)
|
||||
FROM player
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT *
|
||||
FROM player
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
do {
|
||||
// Existential
|
||||
let query: SQL = """
|
||||
SELECT \(nil as (any SQLSelectable)?)
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT NULL
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testTableInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
struct Player: TableRecord { }
|
||||
do {
|
||||
// Table
|
||||
let table = Table("player")
|
||||
let query: SQL = """
|
||||
SELECT *
|
||||
FROM \(table)
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT *
|
||||
FROM "player"
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
do {
|
||||
// Non-existential
|
||||
let query: SQL = """
|
||||
SELECT *
|
||||
FROM \(Player.self)
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT *
|
||||
FROM "player"
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
do {
|
||||
// Non-existential
|
||||
let query: SQL = """
|
||||
INSERT INTO \(tableOf: Player()) DEFAULT VALUES
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
INSERT INTO "player" DEFAULT VALUES
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
do {
|
||||
// Existential
|
||||
let query: SQL = """
|
||||
INSERT INTO \(tableOf: Player() as any TableRecord) DEFAULT VALUES
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
INSERT INTO "player" DEFAULT VALUES
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testTableSelectionInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
struct Player: TableRecord { }
|
||||
struct AltPlayer: TableRecord {
|
||||
static let databaseSelection: [any SQLSelectable] = [Column("id"), Column("name")]
|
||||
}
|
||||
do {
|
||||
let query: SQL = """
|
||||
SELECT \(columnsOf: Player.self)
|
||||
FROM player
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT "player".*
|
||||
FROM player
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
do {
|
||||
let query: SQL = """
|
||||
SELECT \(columnsOf: Player.self, tableAlias: "p")
|
||||
FROM player p
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT "p".*
|
||||
FROM player p
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
do {
|
||||
let query: SQL = """
|
||||
SELECT \(columnsOf: AltPlayer.self)
|
||||
FROM player
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT "altPlayer"."id", "altPlayer"."name"
|
||||
FROM player
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
do {
|
||||
let query: SQL = """
|
||||
SELECT \(columnsOf: AltPlayer.self, tableAlias: "p")
|
||||
FROM player p
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT "p"."id", "p"."name"
|
||||
FROM player p
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testExpressibleInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
let a = Column("a")
|
||||
let b = Column("b")
|
||||
let integer: Int = 1
|
||||
let optionalInteger: Int? = 2
|
||||
let nilInteger: Int? = nil
|
||||
let query: SQL = """
|
||||
SELECT
|
||||
\(a),
|
||||
\(a + 1),
|
||||
\(2 * (a + 1)),
|
||||
\(a < b),
|
||||
\(integer),
|
||||
\(optionalInteger),
|
||||
\(nilInteger),
|
||||
\(a == nilInteger)
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT
|
||||
"a",
|
||||
"a" + ?,
|
||||
? * ("a" + ?),
|
||||
"a" < "b",
|
||||
?,
|
||||
?,
|
||||
NULL,
|
||||
"a" IS NULL
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1, 2, 1, 1, 2])
|
||||
}
|
||||
}
|
||||
|
||||
func testDatabaseValueConvertibleInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
func test<V: DatabaseValueConvertible>(value: V, isInterpolatedAs dbValue: DatabaseValue) throws {
|
||||
let query: SQL = "SELECT \(value)"
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, "SELECT ?")
|
||||
XCTAssertEqual(arguments, [dbValue])
|
||||
}
|
||||
|
||||
struct V: DatabaseValueConvertible {
|
||||
var databaseValue: DatabaseValue {
|
||||
"V".databaseValue
|
||||
}
|
||||
|
||||
static func fromDatabaseValue(_ dbValue: DatabaseValue) -> V? {
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
try test(value: 42, isInterpolatedAs: 42.databaseValue)
|
||||
try test(value: 1.23, isInterpolatedAs: 1.23.databaseValue)
|
||||
try test(value: "foo", isInterpolatedAs: "foo".databaseValue)
|
||||
try test(value: "foo".data(using: .utf8)!, isInterpolatedAs: "foo".data(using: .utf8)!.databaseValue)
|
||||
try test(value: V(), isInterpolatedAs: "V".databaseValue)
|
||||
}
|
||||
}
|
||||
|
||||
func testDataInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
// This test makes sure the Sequence conformance of Data does not
|
||||
// kick in.
|
||||
let data = "SQLite".data(using: .utf8)!
|
||||
let query: SQL = "SELECT \(data)"
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, "SELECT ?")
|
||||
XCTAssertEqual(arguments, [data])
|
||||
}
|
||||
}
|
||||
|
||||
func testAliasedExpressionInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
let query: SQL = """
|
||||
SELECT \(Column("name").forKey("foo")), \(1.databaseValue.forKey("bar"))
|
||||
FROM player
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT "name" AS "foo", ? AS "bar"
|
||||
FROM player
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1])
|
||||
}
|
||||
}
|
||||
|
||||
func testCodingKeyInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name
|
||||
}
|
||||
let query: SQL = """
|
||||
SELECT \(CodingKeys.name)
|
||||
FROM player
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT "name"
|
||||
FROM player
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
func testCodingKeyColumnInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
enum CodingKeys: String, CodingKey, ColumnExpression {
|
||||
case name
|
||||
}
|
||||
let query: SQL = """
|
||||
SELECT \(CodingKeys.name)
|
||||
FROM player
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT "name"
|
||||
FROM player
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
func testExpressibleSequenceInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
let set: Set = [1]
|
||||
let array = ["foo", "bar", "baz"]
|
||||
let expressions: [any SQLExpressible] = [Column("a"), Column("b") + 2]
|
||||
let query: SQL = """
|
||||
SELECT * FROM player
|
||||
WHERE teamId IN \(set)
|
||||
AND name IN \(array)
|
||||
AND c IN \(expressions)
|
||||
AND d IN \([])
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player
|
||||
WHERE teamId IN (?)
|
||||
AND name IN (?,?,?)
|
||||
AND c IN ("a","b" + ?)
|
||||
AND d IN (SELECT NULL WHERE NULL)
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1, "foo", "bar", "baz", 2])
|
||||
}
|
||||
}
|
||||
|
||||
func testOrderingTermInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
let query: SQL = """
|
||||
SELECT * FROM player
|
||||
ORDER BY \(Column("name").desc)
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player
|
||||
ORDER BY "name" DESC
|
||||
""")
|
||||
XCTAssert(arguments.isEmpty)
|
||||
}
|
||||
}
|
||||
|
||||
func testSQLLiteralInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
let condition: SQL = "name = \("Arthur")"
|
||||
let query: SQL = """
|
||||
SELECT *, \(true) FROM player
|
||||
WHERE \(literal: condition) AND score > \(1000)
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT *, ? FROM player
|
||||
WHERE name = ? AND score > ?
|
||||
""")
|
||||
XCTAssertEqual(arguments, [true, "Arthur", 1000])
|
||||
}
|
||||
}
|
||||
|
||||
func testSQLLiteralInterpolation2() throws {
|
||||
// Since SQL conforms to SQLExpressible, make sure it is NOT
|
||||
// interpreted as an expression when embedded in another literal.
|
||||
let literal: SQL = "\("foo") \(SQL("bar \("baz".dropFirst())"))"
|
||||
XCTAssertEqual(literal.elements.count, 4)
|
||||
switch literal.elements[0] { case .expression: break; default: XCTFail("Expected expression") }
|
||||
switch literal.elements[1] { case .sql(" ", []): break; default: XCTFail("Expected sql") }
|
||||
switch literal.elements[2] { case .sql("bar ", []): break; default: XCTFail("Expected sql") }
|
||||
switch literal.elements[3] { case .expression: break; default: XCTFail("Expected expression") }
|
||||
|
||||
let (sql, arguments) = try makeDatabaseQueue().read(literal.build)
|
||||
XCTAssertEqual(sql, "? bar ?")
|
||||
XCTAssertEqual(arguments, ["foo", "az"])
|
||||
}
|
||||
|
||||
func testSQLRequestInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
let subquery: SQLRequest<Int> = "SELECT MAX(score) - \(10) FROM player"
|
||||
let query: SQL = """
|
||||
SELECT * FROM player
|
||||
WHERE score = (\(subquery))
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player
|
||||
WHERE score = (SELECT MAX(score) - ? FROM player)
|
||||
""")
|
||||
XCTAssertEqual(arguments, [10])
|
||||
}
|
||||
}
|
||||
|
||||
func testQueryInterfaceRequestInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
try db.create(table: "player") { t in
|
||||
t.autoIncrementedPrimaryKey("id")
|
||||
t.column("score", .integer)
|
||||
}
|
||||
struct Player: TableRecord { }
|
||||
let subquery = Player.select(max(Column("score")) - 10)
|
||||
let query: SQL = """
|
||||
SELECT * FROM player
|
||||
WHERE score = (\(subquery))
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player
|
||||
WHERE score = (SELECT MAX("score") - ? FROM "player")
|
||||
""")
|
||||
XCTAssertEqual(arguments, [10])
|
||||
}
|
||||
}
|
||||
|
||||
func testJoinedQueryInterfaceRequestInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
try db.create(table: "team") { t in
|
||||
t.autoIncrementedPrimaryKey("id")
|
||||
}
|
||||
try db.create(table: "player") { t in
|
||||
t.autoIncrementedPrimaryKey("id")
|
||||
t.belongsTo("team")
|
||||
t.column("score", .integer)
|
||||
}
|
||||
struct Player: TableRecord { }
|
||||
struct Team: TableRecord { }
|
||||
let subquery = Player
|
||||
.select(max(Column("score")) - 10)
|
||||
.joining(required: Player.belongsTo(Team.self))
|
||||
let query: SQL = """
|
||||
SELECT * FROM player
|
||||
WHERE score = (\(subquery))
|
||||
"""
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player
|
||||
WHERE score = (SELECT MAX("player"."score") - ? FROM "player" JOIN "team" ON "team"."id" = "player"."teamId")
|
||||
""")
|
||||
XCTAssertEqual(arguments, [10])
|
||||
}
|
||||
}
|
||||
|
||||
func testPlusOperatorWithInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
var query: SQL = "SELECT \(AllColumns()) "
|
||||
query = query + "FROM player "
|
||||
query = query + "WHERE id = \(1)"
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player WHERE id = ?
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1])
|
||||
}
|
||||
}
|
||||
|
||||
func testPlusEqualOperatorWithInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
var query: SQL = "SELECT \(AllColumns()) "
|
||||
query += "FROM player "
|
||||
query += "WHERE id = \(1)"
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player WHERE id = ?
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1])
|
||||
}
|
||||
}
|
||||
|
||||
func testAppendLiteralWithInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
var query: SQL = "SELECT \(AllColumns()) "
|
||||
query.append(literal: "FROM player ")
|
||||
query.append(literal: "WHERE id = \(1)")
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player WHERE id = ?
|
||||
""")
|
||||
XCTAssertEqual(arguments, [1])
|
||||
}
|
||||
}
|
||||
|
||||
func testAppendRawSQLWithInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
var query: SQL = "SELECT \(AllColumns()) "
|
||||
query.append(sql: "FROM player ")
|
||||
query.append(sql: "WHERE score > \(1000) ")
|
||||
query.append(sql: "AND \("name") = :name", arguments: ["name": "Arthur"])
|
||||
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player WHERE score > 1000 AND name = :name
|
||||
""")
|
||||
XCTAssertEqual(arguments, ["name": "Arthur"])
|
||||
}
|
||||
}
|
||||
|
||||
func testQualifiedSQLInterpolation() throws {
|
||||
struct Player: TableRecord { }
|
||||
try makeDatabaseQueue().write { db in
|
||||
try db.create(table: "player") { t in
|
||||
t.column("name", .text)
|
||||
t.column("createdAt", .datetime)
|
||||
}
|
||||
let nameColumn = Column("name")
|
||||
let baseRequest = Player.aliased(TableAlias(name: "p"))
|
||||
|
||||
do {
|
||||
let alteredNameLiteral = SQL("\(nameColumn) || \("O'Brien")")
|
||||
let request = baseRequest.select(literal: alteredNameLiteral)
|
||||
try assertEqualSQL(db, request, """
|
||||
SELECT "p"."name" || 'O''Brien' FROM "player" "p"
|
||||
""")
|
||||
}
|
||||
|
||||
do {
|
||||
let alteredNameLiteral = SQL("\(nameColumn) || \("O'Brien")")
|
||||
let alteredNameColumn = alteredNameLiteral.forKey("alteredName")
|
||||
let request = baseRequest.select(alteredNameColumn)
|
||||
try assertEqualSQL(db, request, """
|
||||
SELECT "p"."name" || 'O''Brien' AS "alteredName" FROM "player" "p"
|
||||
""")
|
||||
}
|
||||
|
||||
do {
|
||||
let subquery: SQLRequest<String> = "SELECT MAX(\(nameColumn)) FROM \(Player.self)"
|
||||
let conditionLiteral = SQL("\(nameColumn) = (\(subquery))")
|
||||
let request = baseRequest.filter(literal: conditionLiteral)
|
||||
try assertEqualSQL(db, request, """
|
||||
SELECT "p".* FROM "player" "p" WHERE "p"."name" = (SELECT MAX("name") FROM "player")
|
||||
""")
|
||||
}
|
||||
|
||||
do {
|
||||
// Test of documentation
|
||||
let date = "2020-01-23"
|
||||
let createdAt = Column("createdAt")
|
||||
let creationDate = SQL("DATE(\(createdAt))")
|
||||
let request = Player.filter(creationDate == date)
|
||||
try assertEqualSQL(db, request, """
|
||||
SELECT * FROM "player" WHERE (DATE("createdAt")) = '2020-01-23'
|
||||
""")
|
||||
}
|
||||
|
||||
do {
|
||||
// Here we test that users can define functions that return
|
||||
// literal expressions (existential variant).
|
||||
func date(_ value: any SQLExpressible) -> SQLExpression {
|
||||
SQL("DATE(\(value))").sqlExpression
|
||||
}
|
||||
let createdAt = Column("createdAt")
|
||||
let request = Player.filter(date(createdAt) == "2020-01-23")
|
||||
try assertEqualSQL(db, request, """
|
||||
SELECT * FROM "player" WHERE (DATE("createdAt")) = '2020-01-23'
|
||||
""")
|
||||
}
|
||||
|
||||
do {
|
||||
// Here we test that users can define functions that return
|
||||
// literal expressions (generic variant).
|
||||
func date(_ value: some SQLExpressible) -> SQLExpression {
|
||||
SQL("DATE(\(value))").sqlExpression
|
||||
}
|
||||
let createdAt = Column("createdAt")
|
||||
let request = Player.filter(date(createdAt) == "2020-01-23")
|
||||
try assertEqualSQL(db, request, """
|
||||
SELECT * FROM "player" WHERE (DATE("createdAt")) = '2020-01-23'
|
||||
""")
|
||||
}
|
||||
|
||||
do {
|
||||
// Here we test that users can still define functions that
|
||||
// return literal expressions with the previously
|
||||
// supported technique (existential variant).
|
||||
func date(_ value: any SQLExpressible) -> SQLExpression {
|
||||
SQL("DATE(\(value.sqlExpression))").sqlExpression
|
||||
}
|
||||
let createdAt = Column("createdAt")
|
||||
let request = Player.filter(date(createdAt) == "2020-01-23")
|
||||
try assertEqualSQL(db, request, """
|
||||
SELECT * FROM "player" WHERE (DATE("createdAt")) = '2020-01-23'
|
||||
""")
|
||||
}
|
||||
|
||||
do {
|
||||
// Here we test that users can still define functions that
|
||||
// return literal expressions with the previously
|
||||
// supported technique (generic variant).
|
||||
func date(_ value: some SQLExpressible) -> SQLExpression {
|
||||
SQL("DATE(\(value.sqlExpression))").sqlExpression
|
||||
}
|
||||
let createdAt = Column("createdAt")
|
||||
let request = Player.filter(date(createdAt) == "2020-01-23")
|
||||
try assertEqualSQL(db, request, """
|
||||
SELECT * FROM "player" WHERE (DATE("createdAt")) = '2020-01-23'
|
||||
""")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testCollationInterpolation() throws {
|
||||
try makeDatabaseQueue().inDatabase { db in
|
||||
do {
|
||||
// Database.CollationName
|
||||
let query: SQL = "SELECT * FROM player ORDER BY email COLLATION \(.nocase)"
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player ORDER BY email COLLATION NOCASE
|
||||
""")
|
||||
XCTAssertEqual(arguments, [])
|
||||
}
|
||||
do {
|
||||
// DatabaseCollation
|
||||
let query: SQL = "SELECT * FROM player ORDER BY name COLLATION \(.localizedCompare)"
|
||||
let (sql, arguments) = try query.build(db)
|
||||
XCTAssertEqual(sql, """
|
||||
SELECT * FROM player ORDER BY name COLLATION swiftLocalizedCompare
|
||||
""")
|
||||
XCTAssertEqual(arguments, [])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testIsEmpty() {
|
||||
XCTAssertTrue(SQL(elements: []).isEmpty)
|
||||
XCTAssertTrue(SQL(sql: "").isEmpty)
|
||||
XCTAssertTrue(SQL("").isEmpty)
|
||||
}
|
||||
|
||||
func testProtocolResolution() throws {
|
||||
// SQL can feed ordering, selection, and expressions.
|
||||
acceptOrderingTerm_generic(SQL(""))
|
||||
acceptSelectable_generic(SQL(""))
|
||||
acceptSpecificExpressible_generic(SQL(""))
|
||||
acceptExpressible_generic(SQL(""))
|
||||
|
||||
acceptOrderingTerm_existential(SQL(""))
|
||||
acceptSelectable_existential(SQL(""))
|
||||
acceptSpecificExpressible_existential(SQL(""))
|
||||
acceptExpressible_existential(SQL(""))
|
||||
|
||||
// SQL can build complex expressions and orderings
|
||||
_ = SQL("") + 1
|
||||
_ = SQL("").desc
|
||||
|
||||
// Swift String literals are interpreted as String, even when SQL
|
||||
// is an accepted type.
|
||||
//
|
||||
// should not compile: XCTAssertEqual(acceptOrderingTerm_generic(""), String(describing: String.self))
|
||||
// should not compile: XCTAssertEqual(acceptSelectable_generic(""), String(describing: String.self))
|
||||
// should not compile: XCTAssertEqual(acceptSpecificExpressible_generic(""), String(describing: String.self))
|
||||
XCTAssertEqual(acceptExpressible_generic(""), String(describing: String.self))
|
||||
// should not compile: XCTAssertEqual(acceptOrderingTerm_existential(""), String(describing: String.self))
|
||||
// should not compile: XCTAssertEqual(acceptSelectable_existential(""), String(describing: String.self))
|
||||
// should not compile: XCTAssertEqual(acceptSpecificExpressible_existential(""), String(describing: String.self))
|
||||
XCTAssertEqual(acceptExpressible_existential(""), String(describing: String.self))
|
||||
|
||||
// When a literal can be interpreted as an ordering, a selection, or an
|
||||
// expression, then the expression interpretation is favored.
|
||||
// This test targets TableAlias subscript.
|
||||
//
|
||||
// should not compile: XCTAssertEqual(overloaded_generic(""), "a")
|
||||
XCTAssertEqual(overloaded_generic(SQL("")), "SQLSpecificExpressible")
|
||||
// should not compile: XCTAssertEqual(overloaded_existential(""), "a")
|
||||
XCTAssertEqual(overloaded_existential(SQL("")), "SQLSpecificExpressible")
|
||||
|
||||
// In practice:
|
||||
try makeDatabaseQueue().write { db in
|
||||
struct Player: TableRecord { }
|
||||
try db.create(table: "player") { t in
|
||||
t.autoIncrementedPrimaryKey("id")
|
||||
t.column("name")
|
||||
t.column("score")
|
||||
}
|
||||
let statement = try Player
|
||||
.select(SQL("id"), SQL("score").forKey("theScore"))
|
||||
.filter(SQL("name = \("O'Brien")") && SQL("score > 1000"))
|
||||
.order(SQL("score ASC"), SQL("name").desc)
|
||||
.makePreparedRequest(db)
|
||||
.statement
|
||||
XCTAssertEqual(statement.sql, """
|
||||
SELECT id, score AS "theScore" \
|
||||
FROM "player" \
|
||||
WHERE (name = ?) AND (score > 1000) ORDER BY score ASC, name DESC
|
||||
""")
|
||||
XCTAssertEqual(statement.arguments, ["O'Brien"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Support for testProtocolResolution()
|
||||
@discardableResult
|
||||
private func acceptOrderingTerm_generic(_ x: some SQLOrderingTerm) -> String {
|
||||
String(describing: type(of: x))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func acceptSelectable_generic(_ x: some SQLSelectable) -> String {
|
||||
String(describing: type(of: x))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func acceptSpecificExpressible_generic(_ x: some SQLSpecificExpressible) -> String {
|
||||
String(describing: type(of: x))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func acceptExpressible_generic(_ x: some SQLExpressible) -> String {
|
||||
String(describing: type(of: x))
|
||||
}
|
||||
|
||||
private func overloaded_generic(_ x: some SQLOrderingTerm) -> String {
|
||||
"SQLOrderingTerm"
|
||||
}
|
||||
|
||||
private func overloaded_generic(_ x: some SQLSelectable) -> String {
|
||||
"SQLSelectable"
|
||||
}
|
||||
|
||||
private func overloaded_generic(_ x: some SQLSpecificExpressible & SQLSelectable & SQLOrderingTerm) -> String {
|
||||
"SQLSpecificExpressible"
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func acceptOrderingTerm_existential(_ x: any SQLOrderingTerm) -> String {
|
||||
String(describing: type(of: x))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func acceptSelectable_existential(_ x: any SQLSelectable) -> String {
|
||||
String(describing: type(of: x))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func acceptSpecificExpressible_existential(_ x: any SQLSpecificExpressible) -> String {
|
||||
String(describing: type(of: x))
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
private func acceptExpressible_existential(_ x: any SQLExpressible) -> String {
|
||||
String(describing: type(of: x))
|
||||
}
|
||||
|
||||
private func overloaded_existential(_ x: any SQLOrderingTerm) -> String {
|
||||
"SQLOrderingTerm"
|
||||
}
|
||||
|
||||
private func overloaded_existential(_ x: any SQLSelectable) -> String {
|
||||
"SQLSelectable"
|
||||
}
|
||||
|
||||
private func overloaded_existential(_ x: any SQLSpecificExpressible & SQLSelectable & SQLOrderingTerm) -> String {
|
||||
"SQLSpecificExpressible"
|
||||
}
|
||||
Reference in New Issue
Block a user