import XCTest @testable import GRDB private struct Col { static let id = Column("id") static let name = Column("name") static let age = Column("age") static let readerId = Column("readerId") } private struct Reader : TableRecord { static let databaseTableName = "readers" } private let tableRequest = Reader.all() class QueryInterfaceExpressionsTests: GRDBTestCase { let collation = DatabaseCollation("localized_case_insensitive") { (lhs, rhs) in return (lhs as NSString).localizedCaseInsensitiveCompare(rhs) } let customFunction = DatabaseFunction("avgOf", pure: true) { dbValues in let sum = dbValues.compactMap { Int.fromDatabaseValue($0) }.reduce(0, +) return Double(sum) / Double(dbValues.count) } override func setUp() { super.setUp() dbConfiguration.prepareDatabase { db in db.add(collation: self.collation) db.add(function: self.customFunction) } } override func setup(_ dbWriter: some DatabaseWriter) throws { var migrator = DatabaseMigrator() migrator.registerMigration("createReaders") { db in try db.execute(sql: """ CREATE TABLE readers ( id INTEGER PRIMARY KEY, name TEXT NOT NULL, age INT) """) } try migrator.migrate(dbWriter) } // MARK: - Boolean expressions func testContains() throws { let dbQueue = try makeDatabaseQueue() // emptyArray.contains(): 0 XCTAssertEqual( sql(dbQueue, tableRequest.filter([Int]().contains(Col.id))), "SELECT * FROM \"readers\" WHERE 0") // !emptyArray.contains(): 0 XCTAssertEqual( sql(dbQueue, tableRequest.filter(![Int]().contains(Col.id))), "SELECT * FROM \"readers\" WHERE 1") // Array.contains(): = operator XCTAssertEqual( sql(dbQueue, tableRequest.filter([1].contains(Col.id))), "SELECT * FROM \"readers\" WHERE \"id\" = 1") // Array.contains(): IN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter([1,2,3].contains(Col.id))), "SELECT * FROM \"readers\" WHERE \"id\" IN (1, 2, 3)") // Array.contains(): IN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id].contains(Col.id))), "SELECT * FROM \"readers\" WHERE \"id\" = \"id\"") // EmptyCollection.contains(): 0 XCTAssertEqual( sql(dbQueue, tableRequest.filter(EmptyCollection().contains(Col.id))), "SELECT * FROM \"readers\" WHERE 0") // !EmptyCollection.contains(): 1 XCTAssertEqual( sql(dbQueue, tableRequest.filter(!EmptyCollection().contains(Col.id))), "SELECT * FROM \"readers\" WHERE 1") // Sequence.contains(): = operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(AnySequence([1]).contains(Col.id))), "SELECT * FROM \"readers\" WHERE \"id\" = 1") // Sequence.contains(): IN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(AnySequence([1,2,3]).contains(Col.id))), "SELECT * FROM \"readers\" WHERE \"id\" IN (1, 2, 3)") // Sequence.contains(): IN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(AnySequence([Col.id]).contains(Col.id))), "SELECT * FROM \"readers\" WHERE \"id\" = \"id\"") // !Sequence.contains(): <> operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(![1].contains(Col.id))), "SELECT * FROM \"readers\" WHERE \"id\" <> 1") // !Sequence.contains(): NOT IN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(![1,2,3].contains(Col.id))), "SELECT * FROM \"readers\" WHERE \"id\" NOT IN (1, 2, 3)") // !!Sequence.contains(): = operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(![1].contains(Col.id)))), "SELECT * FROM \"readers\" WHERE \"id\" = 1") // !!Sequence.contains(): IN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(![1,2,3].contains(Col.id)))), "SELECT * FROM \"readers\" WHERE \"id\" IN (1, 2, 3)") // CountableRange.contains(): min <= x < max XCTAssertEqual( sql(dbQueue, tableRequest.filter((1..<10).contains(Col.id))), "SELECT * FROM \"readers\" WHERE (\"id\" >= 1) AND (\"id\" < 10)") // CountableClosedRange.contains(): BETWEEN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter((1...10).contains(Col.id))), "SELECT * FROM \"readers\" WHERE \"id\" BETWEEN 1 AND 10") // !CountableClosedRange.contains(): BETWEEN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(1...10).contains(Col.id))), "SELECT * FROM \"readers\" WHERE \"id\" NOT BETWEEN 1 AND 10") // Range.contains(): min <= x < max XCTAssertEqual( sql(dbQueue, tableRequest.filter((1.1..<10.9).contains(Col.id))), "SELECT * FROM \"readers\" WHERE (\"id\" >= 1.1) AND (\"id\" < 10.9)") // Range.contains(): min <= x < max XCTAssertEqual( sql(dbQueue, tableRequest.filter(("A"..<"z").contains(Col.name))), "SELECT * FROM \"readers\" WHERE (\"name\" >= 'A') AND (\"name\" < 'z')") // ClosedRange.contains(): BETWEEN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter((1.1...10.9).contains(Col.id))), "SELECT * FROM \"readers\" WHERE \"id\" BETWEEN 1.1 AND 10.9") // ClosedRange.contains(): BETWEEN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(("A"..."z").contains(Col.name))), "SELECT * FROM \"readers\" WHERE \"name\" BETWEEN 'A' AND 'z'") // !ClosedRange.contains(): NOT BETWEEN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(!("A"..."z").contains(Col.name))), "SELECT * FROM \"readers\" WHERE \"name\" NOT BETWEEN 'A' AND 'z'") } func testContainsCollated() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.read { db in // Reminder of the SQLite behavior // https://sqlite.org/datatype3.html#assigning_collating_sequences_from_sql // > If an explicit collating sequence is required on an IN operator // > it should be applied to the left operand, like this: // > "x COLLATE nocase IN (y,z, ...)". try XCTAssertFalse(Bool.fetchOne(db, sql: "SELECT 'arthur' IN ('ARTHUR') COLLATE NOCASE")!) try XCTAssertTrue(Bool.fetchOne(db, sql: "SELECT 'arthur' COLLATE NOCASE IN ('ARTHUR')")!) } // Array.contains(): IN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(["arthur", "barbara"].contains(Col.name.collating(.nocase)))), "SELECT * FROM \"readers\" WHERE (\"name\" COLLATE NOCASE) IN ('arthur', 'barbara')") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!["arthur", "barbara"].contains(Col.name.collating(.nocase)))), "SELECT * FROM \"readers\" WHERE (\"name\" COLLATE NOCASE) NOT IN ('arthur', 'barbara')") XCTAssertEqual( sql(dbQueue, tableRequest.filter((["arthur", "barbara"] as [any SQLExpressible]).contains(Col.name.collating(.nocase)))), "SELECT * FROM \"readers\" WHERE (\"name\" COLLATE NOCASE) IN ('arthur', 'barbara')") // Sequence.contains(): IN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(AnySequence(["arthur", "barbara"]).contains(Col.name.collating(.nocase)))), "SELECT * FROM \"readers\" WHERE (\"name\" COLLATE NOCASE) IN ('arthur', 'barbara')") // Sequence.contains(): = operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(AnySequence([Col.name]).contains(Col.name.collating(.nocase)))), "SELECT * FROM \"readers\" WHERE \"name\" = \"name\" COLLATE NOCASE") // Sequence.contains(): false XCTAssertEqual( sql(dbQueue, tableRequest.filter(EmptyCollection().contains(Col.name.collating(.nocase)))), "SELECT * FROM \"readers\" WHERE 0") // ClosedInterval: BETWEEN operator let closedInterval = "A"..."z" XCTAssertEqual( sql(dbQueue, tableRequest.filter(closedInterval.contains(Col.name.collating(.nocase)))), "SELECT * FROM \"readers\" WHERE \"name\" BETWEEN 'A' AND 'z' COLLATE NOCASE") // HalfOpenInterval: min <= x < max let halfOpenInterval = "A"..<"z" XCTAssertEqual( sql(dbQueue, tableRequest.filter(halfOpenInterval.contains(Col.name.collating(.nocase)))), "SELECT * FROM \"readers\" WHERE (\"name\" >= 'A' COLLATE NOCASE) AND (\"name\" < 'z' COLLATE NOCASE)") } func testCollatedContains() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.read { db in // Reminder of the SQLite behavior // https://sqlite.org/datatype3.html#assigning_collating_sequences_from_sql // > If an explicit collating sequence is required on an IN operator // > it should be applied to the left operand, like this: // > "x COLLATE nocase IN (y,z, ...)". try XCTAssertFalse(Bool.fetchOne(db, sql: "SELECT 'arthur' IN ('ARTHUR') COLLATE NOCASE")!) try XCTAssertTrue(Bool.fetchOne(db, sql: "SELECT 'arthur' COLLATE NOCASE IN ('ARTHUR')")!) } // Array.contains(): IN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(["arthur", "barbara"].contains(Col.name).collating(.nocase))), "SELECT * FROM \"readers\" WHERE (\"name\" COLLATE NOCASE) IN ('arthur', 'barbara')") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!["arthur", "barbara"].contains(Col.name).collating(.nocase))), "SELECT * FROM \"readers\" WHERE (\"name\" COLLATE NOCASE) NOT IN ('arthur', 'barbara')") XCTAssertEqual( sql(dbQueue, tableRequest.filter((["arthur", "barbara"] as [any SQLExpressible]).contains(Col.name).collating(.nocase))), "SELECT * FROM \"readers\" WHERE (\"name\" COLLATE NOCASE) IN ('arthur', 'barbara')") // Sequence.contains(): IN operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(AnySequence(["arthur", "barbara"]).contains(Col.name).collating(.nocase))), "SELECT * FROM \"readers\" WHERE (\"name\" COLLATE NOCASE) IN ('arthur', 'barbara')") // Sequence.contains(): = operator XCTAssertEqual( sql(dbQueue, tableRequest.filter(AnySequence([Col.name]).contains(Col.name).collating(.nocase))), "SELECT * FROM \"readers\" WHERE \"name\" = \"name\" COLLATE NOCASE") // Sequence.contains(): false XCTAssertEqual( sql(dbQueue, tableRequest.filter(EmptyCollection().contains(Col.name).collating(.nocase))), "SELECT * FROM \"readers\" WHERE 0 COLLATE NOCASE") // ClosedInterval: BETWEEN operator let closedInterval = "A"..."z" XCTAssertEqual( sql(dbQueue, tableRequest.filter(closedInterval.contains(Col.name).collating(.nocase))), "SELECT * FROM \"readers\" WHERE \"name\" BETWEEN 'A' AND 'z' COLLATE NOCASE") // HalfOpenInterval: min <= x < max let halfOpenInterval = "A"..<"z" XCTAssertEqual( sql(dbQueue, tableRequest.filter(halfOpenInterval.contains(Col.name).collating(.nocase))), "SELECT * FROM \"readers\" WHERE (\"name\" >= 'A' COLLATE NOCASE) AND (\"name\" < 'z' COLLATE NOCASE)") } func testSubqueryContains() throws { let dbQueue = try makeDatabaseQueue() do { let subquery = tableRequest.select(Col.age).filter(Col.name != nil).distinct() XCTAssertEqual( sql(dbQueue, tableRequest.filter(subquery.contains(Col.age))), """ SELECT * FROM "readers" WHERE "age" IN \ (SELECT DISTINCT "age" FROM "readers" WHERE "name" IS NOT NULL) """) } do { let subquery = SQLRequest(sql: "SELECT ? UNION SELECT ?", arguments: [1, 2]) XCTAssertEqual( sql(dbQueue, tableRequest.filter(subquery.contains(Col.age + 1))), """ SELECT * FROM "readers" WHERE ("age" + 1) IN (SELECT 1 UNION SELECT 2) """) } do { let subquery1 = tableRequest.select(max(Col.age)) let subquery2 = tableRequest.filter(Col.age == subquery1) XCTAssertEqual( sql(dbQueue, tableRequest.filter(subquery2.select(Col.id).contains(Col.id))), """ SELECT * FROM "readers" WHERE "id" IN (\ SELECT "id" FROM "readers" WHERE "age" = (\ SELECT MAX("age") FROM "readers")) """) } } func testSubqueryExists() throws { let dbQueue = try makeDatabaseQueue() do { try dbQueue.write { db in try db.create(table: "team") { t in t.autoIncrementedPrimaryKey("id") } try db.create(table: "player") { t in t.column("teamID", .integer).references("team") } struct Player: TableRecord { } struct Team: TableRecord { } let teamAlias = TableAlias() let player = Player.filter(Column("teamID") == teamAlias[Column("id")]) let teams = Team.aliased(teamAlias).filter(player.exists()) try assertEqualSQL(db, teams, """ SELECT * FROM "team" WHERE EXISTS (SELECT * FROM "player" WHERE "teamID" = "team"."id") """) } } do { let alias = TableAlias(name: "r") let subquery = tableRequest.filter(Col.age > alias[Col.age]) XCTAssertEqual( sql(dbQueue, tableRequest.aliased(alias).filter(subquery.exists())), """ SELECT "r".* FROM "readers" "r" WHERE EXISTS (SELECT * FROM "readers" WHERE "age" > "r"."age") """) } do { let alias = TableAlias(name: "r") let subquery = tableRequest.filter(Col.age > alias[Col.age]) XCTAssertEqual( sql(dbQueue, tableRequest.aliased(alias).filter(!subquery.exists())), """ SELECT "r".* FROM "readers" "r" WHERE NOT EXISTS (SELECT * FROM "readers" WHERE "age" > "r"."age") """) } do { let alias = TableAlias(name: "r") let subquery = SQLRequest("SELECT * FROM readers WHERE age > \(alias[Col.age])") XCTAssertEqual( sql(dbQueue, tableRequest.aliased(alias).filter(subquery.exists())), """ SELECT "r".* FROM "readers" "r" WHERE EXISTS (SELECT * FROM readers WHERE age > "r"."age") """) } } func testGreaterThan() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age > 10)), "SELECT * FROM \"readers\" WHERE \"age\" > 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(10 > Col.age)), "SELECT * FROM \"readers\" WHERE 10 > \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 10 > 10 }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age > Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" > \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name > "B")), "SELECT * FROM \"readers\" WHERE \"name\" > 'B'") XCTAssertEqual( sql(dbQueue, tableRequest.filter("B" > Col.name)), "SELECT * FROM \"readers\" WHERE 'B' > \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in "B" > "B" }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name > Col.name)), "SELECT * FROM \"readers\" WHERE \"name\" > \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.group(Col.name).having(average(Col.age) + 10 > 20)), "SELECT * FROM \"readers\" GROUP BY \"name\" HAVING (AVG(\"age\") + 10) > 20") } func testGreaterThanWithCollation() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) > "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" > 'fOo' COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(collation) > "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" > 'fOo' COLLATE localized_case_insensitive") } func testGreaterThanOrEqual() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age >= 10)), "SELECT * FROM \"readers\" WHERE \"age\" >= 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(10 >= Col.age)), "SELECT * FROM \"readers\" WHERE 10 >= \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 10 >= 10 }), "SELECT * FROM \"readers\" WHERE 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age >= Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" >= \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name >= "B")), "SELECT * FROM \"readers\" WHERE \"name\" >= 'B'") XCTAssertEqual( sql(dbQueue, tableRequest.filter("B" >= Col.name)), "SELECT * FROM \"readers\" WHERE 'B' >= \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in "B" >= "B" }), "SELECT * FROM \"readers\" WHERE 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name >= Col.name)), "SELECT * FROM \"readers\" WHERE \"name\" >= \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.group(Col.name).having(average(Col.age) + 10 >= 20)), "SELECT * FROM \"readers\" GROUP BY \"name\" HAVING (AVG(\"age\") + 10) >= 20") } func testGreaterThanOrEqualWithCollation() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) >= "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" >= 'fOo' COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(collation) >= "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" >= 'fOo' COLLATE localized_case_insensitive") } func testLessThan() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age < 10)), "SELECT * FROM \"readers\" WHERE \"age\" < 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(10 < Col.age)), "SELECT * FROM \"readers\" WHERE 10 < \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 10 < 10 }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age < Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" < \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name < "B")), "SELECT * FROM \"readers\" WHERE \"name\" < 'B'") XCTAssertEqual( sql(dbQueue, tableRequest.filter("B" < Col.name)), "SELECT * FROM \"readers\" WHERE 'B' < \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in "B" < "B" }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name < Col.name)), "SELECT * FROM \"readers\" WHERE \"name\" < \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.group(Col.name).having(average(Col.age) + 10 < 20)), "SELECT * FROM \"readers\" GROUP BY \"name\" HAVING (AVG(\"age\") + 10) < 20") } func testLessThanWithCollation() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) < "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" < 'fOo' COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(collation) < "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" < 'fOo' COLLATE localized_case_insensitive") } func testLessThanOrEqual() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age <= 10)), "SELECT * FROM \"readers\" WHERE \"age\" <= 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(10 <= Col.age)), "SELECT * FROM \"readers\" WHERE 10 <= \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 10 <= 10 }), "SELECT * FROM \"readers\" WHERE 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age <= Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" <= \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name <= "B")), "SELECT * FROM \"readers\" WHERE \"name\" <= 'B'") XCTAssertEqual( sql(dbQueue, tableRequest.filter("B" <= Col.name)), "SELECT * FROM \"readers\" WHERE 'B' <= \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in "B" <= "B" }), "SELECT * FROM \"readers\" WHERE 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name <= Col.name)), "SELECT * FROM \"readers\" WHERE \"name\" <= \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.group(Col.name).having(average(Col.age) + 10 <= 20)), "SELECT * FROM \"readers\" GROUP BY \"name\" HAVING (AVG(\"age\") + 10) <= 20") } func testLessThanOrEqualWithCollation() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) <= "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" <= 'fOo' COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(collation) <= "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" <= 'fOo' COLLATE localized_case_insensitive") } func testEqual() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age == 10)), "SELECT * FROM \"readers\" WHERE \"age\" = 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age == (10 as Int?))), "SELECT * FROM \"readers\" WHERE \"age\" = 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(10 == Col.age)), "SELECT * FROM \"readers\" WHERE 10 = \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter((10 as Int?) == Col.age)), "SELECT * FROM \"readers\" WHERE 10 = \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 10 == 10 }), "SELECT * FROM \"readers\" WHERE 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age == Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" = \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age == nil)), "SELECT * FROM \"readers\" WHERE \"age\" IS NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age == DatabaseValue.null)), "SELECT * FROM \"readers\" WHERE \"age\" IS NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(nil == Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" IS NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(DatabaseValue.null == Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" IS NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age == Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" = \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name == "B")), "SELECT * FROM \"readers\" WHERE \"name\" = 'B'") XCTAssertEqual( sql(dbQueue, tableRequest.filter("B" == Col.name)), "SELECT * FROM \"readers\" WHERE 'B' = \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in "B" == "B" }), "SELECT * FROM \"readers\" WHERE 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name == Col.name)), "SELECT * FROM \"readers\" WHERE \"name\" = \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age == true)), "SELECT * FROM \"readers\" WHERE \"age\" = 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(true == Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" = 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age == false)), "SELECT * FROM \"readers\" WHERE \"age\" = 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(false == Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" = 0") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in true == true }), "SELECT * FROM \"readers\" WHERE 1") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in false == false }), "SELECT * FROM \"readers\" WHERE 1") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in true == false }), "SELECT * FROM \"readers\" WHERE 0") } func testEqualWithCollation() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) == "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" = 'fOo' COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) == ("fOo" as String?))), "SELECT * FROM \"readers\" WHERE \"name\" = 'fOo' COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) == nil)), "SELECT * FROM \"readers\" WHERE \"name\" IS NULL COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) == DatabaseValue.null)), "SELECT * FROM \"readers\" WHERE \"name\" IS NULL COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(collation) == "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" = 'fOo' COLLATE localized_case_insensitive") } func testSubqueryEqual() throws { let dbQueue = try makeDatabaseQueue() do { let subquery = tableRequest.select(max(Col.age)) XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age == subquery)), """ SELECT * FROM "readers" WHERE "age" = (SELECT MAX("age") FROM "readers") """) } do { let subquery = SQLRequest(sql: "SELECT MAX(age + ?) FROM readers", arguments: [1]) XCTAssertEqual( sql(dbQueue, tableRequest.filter((Col.age + 2) == subquery)), """ SELECT * FROM "readers" WHERE ("age" + 2) = (SELECT MAX(age + 1) FROM readers) """) } } func testSubqueryWithOuterAlias() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.write { db in try db.create(table: "parent") { t in t.autoIncrementedPrimaryKey("id") t.belongsTo("parent") } try db.create(table: "child") { t in t.column("childParentId", .integer).references("parent") } } struct Parent: TableRecord { static let parent = belongsTo(Parent.self) } struct Child: TableRecord { } do { let parentAlias = TableAlias() // Some ugly subquery whose only purpose is to use a table alias // which requires disambiguation in the parent query. let subquery = Child.select(sql: "COUNT(*)").filter(Column("childParentId") == parentAlias[Column("id")]) let request = Parent .joining(optional: Parent.parent.aliased(parentAlias)) .filter(subquery > 1) XCTAssertEqual( sql(dbQueue, request), """ SELECT "parent1".* FROM "parent" "parent1" \ LEFT JOIN "parent" "parent2" ON "parent2"."id" = "parent1"."parentId" \ WHERE (SELECT COUNT(*) FROM "child" WHERE "childParentId" = "parent2"."id") > 1 """) } } func testNotEqual() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age != 10)), "SELECT * FROM \"readers\" WHERE \"age\" <> 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age != (10 as Int?))), "SELECT * FROM \"readers\" WHERE \"age\" <> 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(10 != Col.age)), "SELECT * FROM \"readers\" WHERE 10 <> \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter((10 as Int?) != Col.age)), "SELECT * FROM \"readers\" WHERE 10 <> \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 10 != 10 }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age != Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" <> \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == 10))), "SELECT * FROM \"readers\" WHERE \"age\" <> 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == (10 as Int?)))), "SELECT * FROM \"readers\" WHERE \"age\" <> 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(10 == Col.age))), "SELECT * FROM \"readers\" WHERE 10 <> \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!((10 as Int?) == Col.age))), "SELECT * FROM \"readers\" WHERE 10 <> \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in !(10 == 10) }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" <> \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age != nil)), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age != DatabaseValue.null)), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(nil != Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(DatabaseValue.null != Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age != Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" <> \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == nil))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == DatabaseValue.null))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(nil == Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(DatabaseValue.null == Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" <> \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name != "B")), "SELECT * FROM \"readers\" WHERE \"name\" <> 'B'") XCTAssertEqual( sql(dbQueue, tableRequest.filter("B" != Col.name)), "SELECT * FROM \"readers\" WHERE 'B' <> \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in "B" != "B" }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name != Col.name)), "SELECT * FROM \"readers\" WHERE \"name\" <> \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.name == "B"))), "SELECT * FROM \"readers\" WHERE \"name\" <> 'B'") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!("B" == Col.name))), "SELECT * FROM \"readers\" WHERE 'B' <> \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in !("B" == "B") }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.name == Col.name))), "SELECT * FROM \"readers\" WHERE \"name\" <> \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age != true)), "SELECT * FROM \"readers\" WHERE \"age\" <> 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(true != Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" <> 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age != false)), "SELECT * FROM \"readers\" WHERE \"age\" <> 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(false != Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" <> 0") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in true != true }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in false != false }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in true != false }), "SELECT * FROM \"readers\" WHERE 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == true))), "SELECT * FROM \"readers\" WHERE \"age\" <> 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(true == Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" <> 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == false))), "SELECT * FROM \"readers\" WHERE \"age\" <> 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(false == Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" <> 0") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in !(true == true) }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in !(false == false) }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in !(true == false) }), "SELECT * FROM \"readers\" WHERE 1") } func testNotEqualWithCollation() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) != "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" <> 'fOo' COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) != ("fOo" as String?))), "SELECT * FROM \"readers\" WHERE \"name\" <> 'fOo' COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) != nil)), "SELECT * FROM \"readers\" WHERE \"name\" IS NOT NULL COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) != DatabaseValue.null)), "SELECT * FROM \"readers\" WHERE \"name\" IS NOT NULL COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(collation) != "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" <> 'fOo' COLLATE localized_case_insensitive") } func testNotEqualWithSwiftNotOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == 10))), "SELECT * FROM \"readers\" WHERE \"age\" <> 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(10 == Col.age))), "SELECT * FROM \"readers\" WHERE 10 <> \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in !(10 == 10) }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" <> \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == nil))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == DatabaseValue.null))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(nil == Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(DatabaseValue.null == Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age == Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" <> \"age\"") } func testIs() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age === 10)), "SELECT * FROM \"readers\" WHERE \"age\" IS 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(10 === Col.age)), "SELECT * FROM \"readers\" WHERE 10 IS \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age === Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" IS \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age === nil)), "SELECT * FROM \"readers\" WHERE \"age\" IS NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(nil === Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" IS NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age === Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" IS \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name === "B")), "SELECT * FROM \"readers\" WHERE \"name\" IS 'B'") XCTAssertEqual( sql(dbQueue, tableRequest.filter("B" === Col.name)), "SELECT * FROM \"readers\" WHERE 'B' IS \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name === Col.name)), "SELECT * FROM \"readers\" WHERE \"name\" IS \"name\"") } func testIsWithCollation() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) === "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" IS 'fOo' COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(collation) === "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" IS 'fOo' COLLATE localized_case_insensitive") } func testIsNot() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age !== 10)), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(10 !== Col.age)), "SELECT * FROM \"readers\" WHERE 10 IS NOT \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age !== Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age !== nil)), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(nil !== Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age !== Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name !== "B")), "SELECT * FROM \"readers\" WHERE \"name\" IS NOT 'B'") XCTAssertEqual( sql(dbQueue, tableRequest.filter("B" !== Col.name)), "SELECT * FROM \"readers\" WHERE 'B' IS NOT \"name\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name !== Col.name)), "SELECT * FROM \"readers\" WHERE \"name\" IS NOT \"name\"") } func testIsNotWithCollation() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(.nocase) !== "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" IS NOT 'fOo' COLLATE NOCASE") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.collating(collation) !== "fOo")), "SELECT * FROM \"readers\" WHERE \"name\" IS NOT 'fOo' COLLATE localized_case_insensitive") } func testIsNotWithSwiftNotOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age === 10))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(10 === Col.age))), "SELECT * FROM \"readers\" WHERE 10 IS NOT \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age === Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age === nil))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(nil === Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT NULL") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age === Col.age))), "SELECT * FROM \"readers\" WHERE \"age\" IS NOT \"age\"") } func testLogicalOperators() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(!Col.age)), "SELECT * FROM \"readers\" WHERE NOT \"age\"") // Make sure NOT NOT "hack" is available in order to produce 0 or 1 XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(!Col.age))), "SELECT * FROM \"readers\" WHERE NOT (NOT \"age\")") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age && true)), "SELECT * FROM \"readers\" WHERE \"age\" AND 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(true && Col.age)), "SELECT * FROM \"readers\" WHERE 1 AND \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age || false)), "SELECT * FROM \"readers\" WHERE \"age\" OR 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(false || Col.age)), "SELECT * FROM \"readers\" WHERE 0 OR \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age != nil || Col.name == nil)), "SELECT * FROM \"readers\" WHERE (\"age\" IS NOT NULL) OR (\"name\" IS NULL)") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age != nil || Col.name != nil && Col.id != nil)), "SELECT * FROM \"readers\" WHERE (\"age\" IS NOT NULL) OR ((\"name\" IS NOT NULL) AND (\"id\" IS NOT NULL))") XCTAssertEqual( sql(dbQueue, tableRequest.filter((Col.age != nil || Col.name != nil) && Col.id != nil)), "SELECT * FROM \"readers\" WHERE ((\"age\" IS NOT NULL) OR (\"name\" IS NOT NULL)) AND (\"id\" IS NOT NULL)") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!(Col.age > 18) && !(Col.name > "foo"))), "SELECT * FROM \"readers\" WHERE (NOT (\"age\" > 18)) AND (NOT (\"name\" > 'foo'))") } func testJoinedOperatorAnd() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter([].joined(operator: .and))), "SELECT * FROM \"readers\" WHERE 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter([].joined(operator: .and) || false)), "SELECT * FROM \"readers\" WHERE 1 OR 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter([false.databaseValue].joined(operator: .and))), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter([false.databaseValue].joined(operator: .and) || false)), "SELECT * FROM \"readers\" WHERE 0 OR 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id == 1].joined(operator: .and))), "SELECT * FROM \"readers\" WHERE \"id\" = 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id == 1].joined(operator: .and) || false)), "SELECT * FROM \"readers\" WHERE (\"id\" = 1) OR 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id == 1, Col.name != nil].joined(operator: .and))), "SELECT * FROM \"readers\" WHERE (\"id\" = 1) AND (\"name\" IS NOT NULL)") XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id == 1, Col.name != nil].joined(operator: .and) || false)), "SELECT * FROM \"readers\" WHERE ((\"id\" = 1) AND (\"name\" IS NOT NULL)) OR 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id == 1, Col.name != nil, Col.age == nil].joined(operator: .and))), "SELECT * FROM \"readers\" WHERE (\"id\" = 1) AND (\"name\" IS NOT NULL) AND (\"age\" IS NULL)") XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id == 1, Col.name != nil, Col.age == nil].joined(operator: .and) || false)), "SELECT * FROM \"readers\" WHERE ((\"id\" = 1) AND (\"name\" IS NOT NULL) AND (\"age\" IS NULL)) OR 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(![Col.id == 1, Col.name != nil, Col.age == nil].joined(operator: .and))), "SELECT * FROM \"readers\" WHERE NOT ((\"id\" = 1) AND (\"name\" IS NOT NULL) AND (\"age\" IS NULL))") } func testJoinedOperatorOr() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter([].joined(operator: .or))), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter([].joined(operator: .or) && true)), "SELECT * FROM \"readers\" WHERE 0 AND 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter([true.databaseValue].joined(operator: .or))), "SELECT * FROM \"readers\" WHERE 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter([true.databaseValue].joined(operator: .or) && true)), "SELECT * FROM \"readers\" WHERE 1 AND 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id == 1].joined(operator: .or))), "SELECT * FROM \"readers\" WHERE \"id\" = 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id == 1].joined(operator: .or) && true)), "SELECT * FROM \"readers\" WHERE (\"id\" = 1) AND 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id == 1, Col.name != nil].joined(operator: .or))), "SELECT * FROM \"readers\" WHERE (\"id\" = 1) OR (\"name\" IS NOT NULL)") XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id == 1, Col.name != nil].joined(operator: .or) && true)), "SELECT * FROM \"readers\" WHERE ((\"id\" = 1) OR (\"name\" IS NOT NULL)) AND 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id == 1, Col.name != nil, Col.age == nil].joined(operator: .or))), "SELECT * FROM \"readers\" WHERE (\"id\" = 1) OR (\"name\" IS NOT NULL) OR (\"age\" IS NULL)") XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.id == 1, Col.name != nil, Col.age == nil].joined(operator: .or) && true)), "SELECT * FROM \"readers\" WHERE ((\"id\" = 1) OR (\"name\" IS NOT NULL) OR (\"age\" IS NULL)) AND 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(![Col.id == 1, Col.name != nil, Col.age == nil].joined(operator: .or))), "SELECT * FROM \"readers\" WHERE NOT ((\"id\" = 1) OR (\"name\" IS NOT NULL) OR (\"age\" IS NULL))") } // MARK: - String functions func testStringFunctions() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(Col.name.capitalized)), "SELECT swiftCapitalizedString(\"name\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(Col.name.lowercased)), "SELECT swiftLowercaseString(\"name\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(Col.name.uppercased)), "SELECT swiftUppercaseString(\"name\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(Col.name.localizedCapitalized)), "SELECT swiftLocalizedCapitalizedString(\"name\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(Col.name.localizedLowercased)), "SELECT swiftLocalizedLowercaseString(\"name\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(Col.name.localizedUppercased)), "SELECT swiftLocalizedUppercaseString(\"name\") FROM \"readers\"") } // MARK: - Arithmetic expressions func testPrefixMinusOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(-Col.age)), "SELECT * FROM \"readers\" WHERE -\"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(-(Col.age + 1))), "SELECT * FROM \"readers\" WHERE -(\"age\" + 1)") XCTAssertEqual( sql(dbQueue, tableRequest.filter(-(-Col.age + 1))), "SELECT * FROM \"readers\" WHERE -((-\"age\") + 1)") } func testInfixSubtractOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age - 2)), "SELECT * FROM \"readers\" WHERE \"age\" - 2") XCTAssertEqual( sql(dbQueue, tableRequest.filter(2 - Col.age)), "SELECT * FROM \"readers\" WHERE 2 - \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 2 - 2 }), "SELECT * FROM \"readers\" WHERE 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age - Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" - \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter((Col.age - Col.age) - 1)), "SELECT * FROM \"readers\" WHERE (\"age\" - \"age\") - 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(1 - [Col.age > 1, Col.age == nil].joined(operator: .and))), "SELECT * FROM \"readers\" WHERE 1 - ((\"age\" > 1) AND (\"age\" IS NULL))") } func testInfixAddOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age + 2)), "SELECT * FROM \"readers\" WHERE \"age\" + 2") XCTAssertEqual( sql(dbQueue, tableRequest.filter(2 + Col.age)), "SELECT * FROM \"readers\" WHERE 2 + \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 2 + 2 }), "SELECT * FROM \"readers\" WHERE 4") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age + Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" + \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(1 + (Col.age + Col.age))), "SELECT * FROM \"readers\" WHERE 1 + (\"age\" + \"age\")") XCTAssertEqual( sql(dbQueue, tableRequest.filter(1 + [Col.age > 1, Col.age == nil].joined(operator: .and))), "SELECT * FROM \"readers\" WHERE 1 + ((\"age\" > 1) AND (\"age\" IS NULL))") } func testJoinedAddOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select([].joined(operator: .add))), "SELECT 0 FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select([Col.age, Col.age].joined(operator: .add))), "SELECT \"age\" + \"age\" FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select([Col.age, 2.databaseValue, Col.age].joined(operator: .add))), "SELECT \"age\" + 2 + \"age\" FROM \"readers\"") // Not flattened XCTAssertEqual( sql(dbQueue, tableRequest.select([ [Col.age, 1.databaseValue].joined(operator: .add), [2.databaseValue, Col.age].joined(operator: .add), ].joined(operator: .add))), "SELECT (\"age\" + 1) + (2 + \"age\") FROM \"readers\"") } func testInfixMultiplyOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age * 2)), "SELECT * FROM \"readers\" WHERE \"age\" * 2") XCTAssertEqual( sql(dbQueue, tableRequest.filter(2 * Col.age)), "SELECT * FROM \"readers\" WHERE 2 * \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 2 * 2 }), "SELECT * FROM \"readers\" WHERE 4") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age * Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" * \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(2 * (Col.age * Col.age))), "SELECT * FROM \"readers\" WHERE 2 * (\"age\" * \"age\")") } func testJoinedMultiplyOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select([].joined(operator: .multiply))), "SELECT 1 FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select([Col.age, Col.age].joined(operator: .multiply))), "SELECT \"age\" * \"age\" FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select([Col.age, 2.databaseValue, Col.age].joined(operator: .multiply))), "SELECT \"age\" * 2 * \"age\" FROM \"readers\"") // Not flattened XCTAssertEqual( sql(dbQueue, tableRequest.select([ [Col.age, 1.databaseValue].joined(operator: .multiply), [2.databaseValue, Col.age].joined(operator: .multiply), ].joined(operator: .multiply))), "SELECT (\"age\" * 1) * (2 * \"age\") FROM \"readers\"") } func testInfixDivideOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age / 2)), "SELECT * FROM \"readers\" WHERE \"age\" / 2") XCTAssertEqual( sql(dbQueue, tableRequest.filter(2 / Col.age)), "SELECT * FROM \"readers\" WHERE 2 / \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 2 / 2 }), "SELECT * FROM \"readers\" WHERE 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age / Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" / \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(1 / (Col.age / Col.age))), "SELECT * FROM \"readers\" WHERE 1 / (\"age\" / \"age\")") } func testCompoundArithmeticExpression() throws { let dbQueue = try makeDatabaseQueue() // Int / Double XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age / 2.0)), "SELECT * FROM \"readers\" WHERE \"age\" / 2.0") // Double / Int XCTAssertEqual( sql(dbQueue, tableRequest.filter(2.0 / Col.age)), "SELECT * FROM \"readers\" WHERE 2.0 / \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age + 2 * 5)), "SELECT * FROM \"readers\" WHERE \"age\" + 10") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age * 2 + 5)), "SELECT * FROM \"readers\" WHERE (\"age\" * 2) + 5") } // MARK: - Bitwise expressions func testPrefixBitwiseNotOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(~Col.age)), "SELECT * FROM \"readers\" WHERE ~\"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(~(Col.age + 1))), "SELECT * FROM \"readers\" WHERE ~(\"age\" + 1)") XCTAssertEqual( sql(dbQueue, tableRequest.filter(~(~Col.age + 1))), "SELECT * FROM \"readers\" WHERE ~((~\"age\") + 1)") } func testInfixLeftShiftOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age << 2)), "SELECT * FROM \"readers\" WHERE \"age\" << 2") XCTAssertEqual( sql(dbQueue, tableRequest.filter(2 << Col.age)), "SELECT * FROM \"readers\" WHERE 2 << \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 2 << 2 }), "SELECT * FROM \"readers\" WHERE 8") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age << Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" << \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter((Col.age << Col.age) << 1)), "SELECT * FROM \"readers\" WHERE (\"age\" << \"age\") << 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(1 << [Col.age > 1, Col.age == nil].joined(operator: .and))), "SELECT * FROM \"readers\" WHERE 1 << ((\"age\" > 1) AND (\"age\" IS NULL))") } func testInfixRightShiftOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age >> 2)), "SELECT * FROM \"readers\" WHERE \"age\" >> 2") XCTAssertEqual( sql(dbQueue, tableRequest.filter(2 >> Col.age)), "SELECT * FROM \"readers\" WHERE 2 >> \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 8 >> 2 }), "SELECT * FROM \"readers\" WHERE 2") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age >> Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" >> \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter((Col.age >> Col.age) >> 1)), "SELECT * FROM \"readers\" WHERE (\"age\" >> \"age\") >> 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(1 >> [Col.age > 1, Col.age == nil].joined(operator: .and))), "SELECT * FROM \"readers\" WHERE 1 >> ((\"age\" > 1) AND (\"age\" IS NULL))") } func testInfixBitwiseAndOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age & 2)), "SELECT * FROM \"readers\" WHERE \"age\" & 2") XCTAssertEqual( sql(dbQueue, tableRequest.filter(2 & Col.age)), "SELECT * FROM \"readers\" WHERE 2 & \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 2 & 2 }), "SELECT * FROM \"readers\" WHERE 2") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age & Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" & \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(2 & (Col.age & Col.age))), "SELECT * FROM \"readers\" WHERE 2 & \"age\" & \"age\"") } func testJoinedBitwiseAndOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select([].joined(operator: .bitwiseAnd))), "SELECT -1 FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select([Col.age, Col.age].joined(operator: .bitwiseAnd))), "SELECT \"age\" & \"age\" FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select([Col.age, 2.databaseValue, Col.age].joined(operator: .bitwiseAnd))), "SELECT \"age\" & 2 & \"age\" FROM \"readers\"") // Flattened XCTAssertEqual( sql(dbQueue, tableRequest.select([ [Col.age, 1.databaseValue].joined(operator: .bitwiseAnd), [2.databaseValue, Col.age].joined(operator: .bitwiseAnd), ].joined(operator: .bitwiseAnd))), "SELECT \"age\" & 1 & 2 & \"age\" FROM \"readers\"") } func testInfixBitwiseOrOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age | 2)), "SELECT * FROM \"readers\" WHERE \"age\" | 2") XCTAssertEqual( sql(dbQueue, tableRequest.filter(2 | Col.age)), "SELECT * FROM \"readers\" WHERE 2 | \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filterWhenConnected { _ in 2 | 2 }), "SELECT * FROM \"readers\" WHERE 2") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age | Col.age)), "SELECT * FROM \"readers\" WHERE \"age\" | \"age\"") XCTAssertEqual( sql(dbQueue, tableRequest.filter(2 | (Col.age | Col.age))), "SELECT * FROM \"readers\" WHERE 2 | \"age\" | \"age\"") } func testJoinedBitwiseOrOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select([].joined(operator: .bitwiseOr))), "SELECT 0 FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select([Col.age, Col.age].joined(operator: .bitwiseOr))), "SELECT \"age\" | \"age\" FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select([Col.age, 2.databaseValue, Col.age].joined(operator: .bitwiseOr))), "SELECT \"age\" | 2 | \"age\" FROM \"readers\"") // Flattened XCTAssertEqual( sql(dbQueue, tableRequest.select([ [Col.age, 1.databaseValue].joined(operator: .bitwiseOr), [2.databaseValue, Col.age].joined(operator: .bitwiseOr), ].joined(operator: .bitwiseOr))), "SELECT \"age\" | 1 | 2 | \"age\" FROM \"readers\"") } // MARK: - IFNULL expression func testIfNull() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age ?? 2)), "SELECT * FROM \"readers\" WHERE IFNULL(\"age\", 2)") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.age ?? Col.age)), "SELECT * FROM \"readers\" WHERE IFNULL(\"age\", \"age\")") } // MARK: - Aggregated expressions func testCountExpression() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(count(Col.age))), "SELECT COUNT(\"age\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(count(Col.age ?? 0))), "SELECT COUNT(IFNULL(\"age\", 0)) FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(count(distinct: Col.age))), "SELECT COUNT(DISTINCT \"age\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(count(distinct: Col.age / Col.age))), "SELECT COUNT(DISTINCT \"age\" / \"age\") FROM \"readers\"") } func testAvgExpression() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(average(Col.age))), "SELECT AVG(\"age\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(average(Col.age / 2))), "SELECT AVG(\"age\" / 2) FROM \"readers\"") } func testAvgExpression_filter() throws { #if GRDBCUSTOMSQLITE || GRDBCIPHER // Prevent SQLCipher failures guard sqlite3_libversion_number() >= 3030000 else { throw XCTSkip("FILTER clause on aggregate functions is not available") } #else guard #available(iOS 14, macOS 10.16, tvOS 14, watchOS 7, *) else { throw XCTSkip("FILTER clause on aggregate functions is not available") } #endif let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(average(Col.age, filter: Col.age > 0))), "SELECT AVG(\"age\") FILTER (WHERE \"age\" > 0) FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(average(Col.age / 2, filter: Col.age > 0))), "SELECT AVG(\"age\" / 2) FILTER (WHERE \"age\" > 0) FROM \"readers\"") } func testCastExpression() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(cast(Col.name, as: .blob))), "SELECT CAST(\"name\" AS BLOB) FROM \"readers\"") } func testLengthExpression() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(length(Col.name))), "SELECT LENGTH(\"name\") FROM \"readers\"") } func testMinExpression() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(min(Col.age))), "SELECT MIN(\"age\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(min(Col.age / 2))), "SELECT MIN(\"age\" / 2) FROM \"readers\"") } func testMinExpression_filter() throws { #if GRDBCUSTOMSQLITE || GRDBCIPHER // Prevent SQLCipher failures guard sqlite3_libversion_number() >= 3030000 else { throw XCTSkip("FILTER clause on aggregate functions is not available") } #else guard #available(iOS 14, macOS 10.16, tvOS 14, watchOS 7, *) else { throw XCTSkip("FILTER clause on aggregate functions is not available") } #endif let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(min(Col.age, filter: Col.age > 0))), "SELECT MIN(\"age\") FILTER (WHERE \"age\" > 0) FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(min(Col.age / 2, filter: Col.age > 0))), "SELECT MIN(\"age\" / 2) FILTER (WHERE \"age\" > 0) FROM \"readers\"") } func testMaxExpression() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(max(Col.age))), "SELECT MAX(\"age\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(max(Col.age / 2))), "SELECT MAX(\"age\" / 2) FROM \"readers\"") } func testMaxExpression_filter() throws { #if GRDBCUSTOMSQLITE || GRDBCIPHER // Prevent SQLCipher failures guard sqlite3_libversion_number() >= 3030000 else { throw XCTSkip("FILTER clause on aggregate functions is not available") } #else guard #available(iOS 14, macOS 10.16, tvOS 14, watchOS 7, *) else { throw XCTSkip("FILTER clause on aggregate functions is not available") } #endif let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(max(Col.age, filter: Col.age < 0))), "SELECT MAX(\"age\") FILTER (WHERE \"age\" < 0) FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(max(Col.age / 2, filter: Col.age < 0))), "SELECT MAX(\"age\" / 2) FILTER (WHERE \"age\" < 0) FROM \"readers\"") } func testSumExpression() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(sum(Col.age))), "SELECT SUM(\"age\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(sum(Col.age / 2))), "SELECT SUM(\"age\" / 2) FROM \"readers\"") } func testSumExpression_filter() throws { #if GRDBCUSTOMSQLITE || GRDBCIPHER // Prevent SQLCipher failures guard sqlite3_libversion_number() >= 3030000 else { throw XCTSkip("FILTER clause on aggregate functions is not available") } #else guard #available(iOS 14, macOS 10.16, tvOS 14, watchOS 7, *) else { throw XCTSkip("FILTER clause on aggregate functions is not available") } #endif let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(sum(Col.age, filter: Col.age > 0))), "SELECT SUM(\"age\") FILTER (WHERE \"age\" > 0) FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(sum(Col.age / 2, filter: Col.age > 0))), "SELECT SUM(\"age\" / 2) FILTER (WHERE \"age\" > 0) FROM \"readers\"") } #if GRDBCUSTOMSQLITE || GRDBCIPHER func testSumExpression_order() throws { // Prevent SQLCipher failures guard sqlite3_libversion_number() >= 3044000 else { throw XCTSkip("ORDER BY clause on aggregate functions is not available") } let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(sum(Col.age, orderBy: Col.age))), "SELECT SUM(\"age\" ORDER BY \"age\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(sum(Col.age / 2, orderBy: Col.age.desc))), "SELECT SUM(\"age\" / 2 ORDER BY \"age\" DESC) FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(sum(Col.age, orderBy: Col.age, filter: Col.age > 0))), "SELECT SUM(\"age\" ORDER BY \"age\") FILTER (WHERE \"age\" > 0) FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(sum(Col.age / 2, orderBy: Col.age.desc, filter: Col.age > 0))), "SELECT SUM(\"age\" / 2 ORDER BY \"age\" DESC) FILTER (WHERE \"age\" > 0) FROM \"readers\"") } #endif func testTotalExpression() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(total(Col.age))), "SELECT TOTAL(\"age\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(total(Col.age / 2))), "SELECT TOTAL(\"age\" / 2) FROM \"readers\"") } func testTotalExpression_filter() throws { #if GRDBCUSTOMSQLITE || GRDBCIPHER // Prevent SQLCipher failures guard sqlite3_libversion_number() >= 3030000 else { throw XCTSkip("FILTER clause on aggregate functions is not available") } #else guard #available(iOS 14, macOS 10.16, tvOS 14, watchOS 7, *) else { throw XCTSkip("FILTER clause on aggregate functions is not available") } #endif let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(total(Col.age, filter: Col.age > 0))), "SELECT TOTAL(\"age\") FILTER (WHERE \"age\" > 0) FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(total(Col.age / 2, filter: Col.age > 0))), "SELECT TOTAL(\"age\" / 2) FILTER (WHERE \"age\" > 0) FROM \"readers\"") } #if GRDBCUSTOMSQLITE || GRDBCIPHER func testTotalExpression_order() throws { // Prevent SQLCipher failures guard sqlite3_libversion_number() >= 3044000 else { throw XCTSkip("ORDER BY clause on aggregate functions is not available") } let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(total(Col.age, orderBy: Col.age))), "SELECT TOTAL(\"age\" ORDER BY \"age\") FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(total(Col.age / 2, orderBy: Col.age.desc))), "SELECT TOTAL(\"age\" / 2 ORDER BY \"age\" DESC) FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(total(Col.age, orderBy: Col.age, filter: Col.age > 0))), "SELECT TOTAL(\"age\" ORDER BY \"age\") FILTER (WHERE \"age\" > 0) FROM \"readers\"") XCTAssertEqual( sql(dbQueue, tableRequest.select(total(Col.age / 2, orderBy: Col.age.desc, filter: Col.age > 0))), "SELECT TOTAL(\"age\" / 2 ORDER BY \"age\" DESC) FILTER (WHERE \"age\" > 0) FROM \"readers\"") } #endif // MARK: - LIKE operator func testLikeOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.like("%foo"))), "SELECT * FROM \"readers\" WHERE \"name\" LIKE '%foo'") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!Col.name.like("%foo"))), "SELECT * FROM \"readers\" WHERE \"name\" NOT LIKE '%foo'") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.like("%foo") == true)), "SELECT * FROM \"readers\" WHERE (\"name\" LIKE '%foo') = 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.like("%foo") == false)), "SELECT * FROM \"readers\" WHERE (\"name\" LIKE '%foo') = 0") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.like("%foo", escape: "\\"))), "SELECT * FROM \"readers\" WHERE \"name\" LIKE '%foo' ESCAPE '\\'") XCTAssertEqual( sql(dbQueue, tableRequest.filter(!Col.name.like("%foo", escape: "\\"))), "SELECT * FROM \"readers\" WHERE \"name\" NOT LIKE '%foo' ESCAPE '\\'") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.like("%foo", escape: "\\") == true)), "SELECT * FROM \"readers\" WHERE (\"name\" LIKE '%foo' ESCAPE '\\') = 1") XCTAssertEqual( sql(dbQueue, tableRequest.filter(Col.name.like("%foo", escape: "\\") == false)), "SELECT * FROM \"readers\" WHERE (\"name\" LIKE '%foo' ESCAPE '\\') = 0") } // MARK: - || concat operator func testConcatOperator() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select([Col.name, Col.name].joined(operator: .concat))), """ SELECT "name" || "name" FROM "readers" """) XCTAssertEqual( sql(dbQueue, tableRequest.filter([Col.name, Col.name].joined(operator: .concat) == "foo")), """ SELECT * FROM "readers" WHERE ("name" || "name") = 'foo' """) XCTAssertEqual( sql(dbQueue, tableRequest.select([Col.name, " ".databaseValue, Col.name].joined(operator: .concat))), """ SELECT "name" || ' ' || "name" FROM "readers" """) // Flattened XCTAssertEqual( sql(dbQueue, tableRequest.select([ [Col.name, "a".databaseValue].joined(operator: .concat), ["b".databaseValue, Col.name].joined(operator: .concat), ].joined(operator: .concat))), """ SELECT "name" || 'a' || 'b' || "name" FROM "readers" """) } // MARK: - Function func testCustomFunction() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, tableRequest.select(customFunction(Col.age, 1, 2))), "SELECT avgOf(\"age\", 1, 2) FROM \"readers\"") } // MARK: - SQLExpressionFastPrimaryKey func testFastPrimaryKeyExpression() throws { struct IntegerPrimaryKeyRecord: TableRecord { } struct UUIDRecord: TableRecord { } struct UUIDRecordWithoutRowID: TableRecord { } struct RowIDRecord: TableRecord { } struct CompoundPrimaryKeyRecord: TableRecord { } let dbQueue = try makeDatabaseQueue() try dbQueue.write { db in try db.execute(sql: """ CREATE TABLE integerPrimaryKeyRecord (id INTEGER PRIMARY KEY); CREATE TABLE uuidRecord (uuid TEXT PRIMARY KEY); CREATE TABLE uuidRecordWithoutRowID (uuid TEXT PRIMARY KEY) WITHOUT ROWID; CREATE TABLE rowIDRecord (name TEXT); CREATE TABLE compoundPrimaryKeyRecord (a INTEGER, b INTEGER, PRIMARY KEY (a, b)); """) try assertEqualSQL(db, IntegerPrimaryKeyRecord.select(SQLExpression.fastPrimaryKey), """ SELECT "id" FROM "integerPrimaryKeyRecord" """) try assertEqualSQL(db, UUIDRecord.select(SQLExpression.fastPrimaryKey), """ SELECT "rowid" FROM "uuidRecord" """) try assertEqualSQL(db, UUIDRecordWithoutRowID.select(SQLExpression.fastPrimaryKey), """ SELECT "uuid" FROM "uuidRecordWithoutRowID" """) try assertEqualSQL(db, RowIDRecord.select(SQLExpression.fastPrimaryKey), """ SELECT "rowid" FROM "rowIDRecord" """) try assertEqualSQL(db, CompoundPrimaryKeyRecord.select(SQLExpression.fastPrimaryKey), """ SELECT "rowid" FROM "compoundPrimaryKeyRecord" """) } } }