import XCTest 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" } class TableRecordQueryInterfaceRequestTests: GRDBTestCase { 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: - Count func testFetchCount() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in XCTAssertEqual(try Reader.fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") XCTAssertEqual(try Reader.all().reversed().fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") XCTAssertEqual(try Reader.order(Col.name).fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") XCTAssertEqual(try Reader.limit(10).fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT * FROM \"readers\" LIMIT 10)") XCTAssertEqual(try Reader.filter(Col.age == 42).fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\" WHERE \"age\" = 42") XCTAssertEqual(try Reader.all().distinct().fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT DISTINCT * FROM \"readers\")") XCTAssertEqual(try Reader.select(Col.name).fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") XCTAssertEqual(try Reader.select(Col.name).distinct().fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT \"name\") FROM \"readers\"") XCTAssertEqual(try Reader.select(Col.age * 2).distinct().fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT \"age\" * 2) FROM \"readers\"") XCTAssertEqual(try Reader.select((Col.age * 2).forKey("ignored")).distinct().fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(DISTINCT \"age\" * 2) FROM \"readers\"") XCTAssertEqual(try Reader.select(Col.name, Col.age).fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM \"readers\"") XCTAssertEqual(try Reader.select(Col.name, Col.age).distinct().fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT DISTINCT \"name\", \"age\" FROM \"readers\")") XCTAssertEqual(try Reader.select(max(Col.age)).group(Col.name).fetchCount(db), 0) XCTAssertEqual(lastSQLQuery, "SELECT COUNT(*) FROM (SELECT MAX(\"age\") FROM \"readers\" GROUP BY \"name\")") } } // MARK: - Select func testSelectLiteral() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Arthur", 42]) try db.execute(sql: "INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) let request = Reader.select(sql: "name, id - 1") let rows = try Row.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT name, id - 1 FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0][0] as String, "Arthur") XCTAssertEqual(rows[0][1] as Int64, 0) XCTAssertEqual(rows[1][0] as String, "Barbara") XCTAssertEqual(rows[1][1] as Int64, 1) } } func testSelectLiteralWithPositionalArguments() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Arthur", 42]) try db.execute(sql: "INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) let request = Reader.select(sql: "name, id - ?", arguments: [1]) let rows = try Row.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT name, id - 1 FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0][0] as String, "Arthur") XCTAssertEqual(rows[0][1] as Int64, 0) XCTAssertEqual(rows[1][0] as String, "Barbara") XCTAssertEqual(rows[1][1] as Int64, 1) } } func testSelectLiteralWithNamedArguments() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Arthur", 42]) try db.execute(sql: "INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) let request = Reader.select(sql: "name, id - :n", arguments: ["n": 1]) let rows = try Row.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT name, id - 1 FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0][0] as String, "Arthur") XCTAssertEqual(rows[0][1] as Int64, 0) XCTAssertEqual(rows[1][0] as String, "Barbara") XCTAssertEqual(rows[1][1] as Int64, 1) } } func testSelectSQLLiteral() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Arthur", 42]) try db.execute(sql: "INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) func test(_ request: QueryInterfaceRequest) throws { let rows = try Row.fetchAll(db, request) XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0][0] as String, "O'Brien") XCTAssertEqual(rows[0][1] as Int64, 0) XCTAssertEqual(rows[1][0] as String, "O'Brien") XCTAssertEqual(rows[1][1] as Int64, 1) } try test(Reader.select(literal: SQL(sql: ":name, id - :value", arguments: ["name": "O'Brien", "value": 1]))) // Interpolation try test(Reader.select(literal: "\("O'Brien"), id - \(1)")) } } func testSelect() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Arthur", 42]) try db.execute(sql: "INSERT INTO readers (name, age) VALUES (?, ?)", arguments: ["Barbara", 36]) let request = Reader.select(Col.name, Col.id - 1) let rows = try Row.fetchAll(db, request) XCTAssertEqual(lastSQLQuery, "SELECT \"name\", \"id\" - 1 FROM \"readers\"") XCTAssertEqual(rows.count, 2) XCTAssertEqual(rows[0][0] as String, "Arthur") XCTAssertEqual(rows[0][1] as Int64, 0) XCTAssertEqual(rows[1][0] as String, "Barbara") XCTAssertEqual(rows[1][1] as Int64, 1) } } func testMultipleSelect() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.select(Col.age).select(Col.name)), "SELECT \"name\" FROM \"readers\"") } // MARK: - Filter func testFilterLiteral() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.filter(sql: "id <> 1")), "SELECT * FROM \"readers\" WHERE id <> 1") } func testFilterLiteralWithPositionalArguments() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.filter(sql: "id <> ?", arguments: [1])), "SELECT * FROM \"readers\" WHERE id <> 1") } func testFilterLiteralWithNamedArguments() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.filter(sql: "id <> :id", arguments: ["id": 1])), "SELECT * FROM \"readers\" WHERE id <> 1") } func testFilter() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.filter(true.databaseValue)), "SELECT * FROM \"readers\" WHERE 1") } func testMultipleFilter() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.filter(true.databaseValue).filter(false.databaseValue)), "SELECT * FROM \"readers\" WHERE 1 AND 0") } // MARK: - Sort func testSortLiteral() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.order(sql: "lower(name) desc")), "SELECT * FROM \"readers\" ORDER BY lower(name) desc") } func testSortLiteralWithPositionalArguments() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.order(sql: "age + ?", arguments: [1])), "SELECT * FROM \"readers\" ORDER BY age + 1") } func testSortLiteralWithNamedArguments() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.order(sql: "age + :age", arguments: ["age": 1])), "SELECT * FROM \"readers\" ORDER BY age + 1") } func testSort() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.order(Col.age)), "SELECT * FROM \"readers\" ORDER BY \"age\"") XCTAssertEqual( sql(dbQueue, Reader.order(Col.age.asc)), "SELECT * FROM \"readers\" ORDER BY \"age\" ASC") XCTAssertEqual( sql(dbQueue, Reader.order(Col.age.desc)), "SELECT * FROM \"readers\" ORDER BY \"age\" DESC") XCTAssertEqual( sql(dbQueue, Reader.order(Col.age, Col.name.desc)), "SELECT * FROM \"readers\" ORDER BY \"age\", \"name\" DESC") XCTAssertEqual( sql(dbQueue, Reader.order(abs(Col.age))), "SELECT * FROM \"readers\" ORDER BY ABS(\"age\")") #if GRDBCUSTOMSQLITE XCTAssertEqual( sql(dbQueue, Reader.order(Col.age.ascNullsLast)), "SELECT * FROM \"readers\" ORDER BY \"age\" ASC NULLS LAST") XCTAssertEqual( sql(dbQueue, Reader.order(Col.age.descNullsFirst)), "SELECT * FROM \"readers\" ORDER BY \"age\" DESC NULLS FIRST") #elseif !GRDBCIPHER if #available(iOS 14, macOS 10.16, tvOS 14, watchOS 7, *) { XCTAssertEqual( sql(dbQueue, Reader.order(Col.age.ascNullsLast)), "SELECT * FROM \"readers\" ORDER BY \"age\" ASC NULLS LAST") XCTAssertEqual( sql(dbQueue, Reader.order(Col.age.descNullsFirst)), "SELECT * FROM \"readers\" ORDER BY \"age\" DESC NULLS FIRST") } #endif } func testMultipleSort() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.order(Col.age).order(Col.name)), "SELECT * FROM \"readers\" ORDER BY \"name\"") } // MARK: - Limit func testLimit() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.limit(1)), "SELECT * FROM \"readers\" LIMIT 1") XCTAssertEqual( sql(dbQueue, Reader.limit(1, offset: 2)), "SELECT * FROM \"readers\" LIMIT 1 OFFSET 2") } func testMultipleLimit() throws { let dbQueue = try makeDatabaseQueue() XCTAssertEqual( sql(dbQueue, Reader.limit(1, offset: 2).limit(3)), "SELECT * FROM \"readers\" LIMIT 3") } // MARK: - Exists func testExists() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inTransaction { db in struct Player: TableRecord { } try db.create(table: "player") { t in t.column("a").unique() t.column("b") t.column("c") t.uniqueKey(["b", "c"]) } try XCTAssertFalse(Player.exists(db, key: 1)) XCTAssertEqual(lastSQLQuery, "SELECT EXISTS (SELECT * FROM \"player\" WHERE \"rowid\" = 1)") try XCTAssertFalse(Player.exists(db, key: ["a": 1])) XCTAssertEqual(lastSQLQuery, "SELECT EXISTS (SELECT * FROM \"player\" WHERE \"a\" = 1)") try XCTAssertFalse(Player.exists(db, key: ["a": 1, "b": 2])) XCTAssertEqual(lastSQLQuery, "SELECT EXISTS (SELECT * FROM \"player\" WHERE (\"a\" = 1) AND (\"b\" = 2))") try XCTAssertFalse(Player.exists(db, key: ["b": 1, "c": 2])) XCTAssertEqual(lastSQLQuery, "SELECT EXISTS (SELECT * FROM \"player\" WHERE (\"b\" = 1) AND (\"c\" = 2))") return .rollback } try dbQueue.inTransaction { db in struct Player: TableRecord { } try db.create(table: "player") { t in t.primaryKey("a", .integer) } try XCTAssertFalse(Player.exists(db, key: 1)) XCTAssertEqual(lastSQLQuery, "SELECT EXISTS (SELECT * FROM \"player\" WHERE \"a\" = 1)") try XCTAssertFalse(Player.exists(db, key: ["a": 2])) XCTAssertEqual(lastSQLQuery, "SELECT EXISTS (SELECT * FROM \"player\" WHERE \"a\" = 2)") return .rollback } try dbQueue.inTransaction { db in struct Player: TableRecord { } try db.create(table: "player") { t in t.primaryKey("id", .text) } try XCTAssertFalse(Player.exists(db, key: "foo")) XCTAssertEqual(lastSQLQuery, "SELECT EXISTS (SELECT * FROM \"player\" WHERE \"id\" = 'foo')") try XCTAssertFalse(Player.exists(db, key: ["id": "bar"])) XCTAssertEqual(lastSQLQuery, "SELECT EXISTS (SELECT * FROM \"player\" WHERE \"id\" = 'bar')") return .rollback } } func testExistsIdentifiable() throws { guard #available(iOS 13, macOS 10.15, tvOS 13, watchOS 6, *) else { throw XCTSkip("Identifiable is not available") } let dbQueue = try makeDatabaseQueue() try dbQueue.inTransaction { db in struct Player: TableRecord, Identifiable { var id: Int64 } try db.create(table: "player") { t in t.autoIncrementedPrimaryKey("id") } try XCTAssertFalse(Player.exists(db, id: 1)) XCTAssertEqual(lastSQLQuery, "SELECT EXISTS (SELECT * FROM \"player\" WHERE \"id\" = 1)") return .rollback } try dbQueue.inTransaction { db in struct Player: TableRecord, Identifiable { var id: Int64? } try db.create(table: "player") { t in t.autoIncrementedPrimaryKey("id") } try XCTAssertFalse(Player.exists(db, id: 1)) XCTAssertEqual(lastSQLQuery, "SELECT EXISTS (SELECT * FROM \"player\" WHERE \"id\" = 1)") sqlQueries.removeAll() try XCTAssertFalse(Player.exists(db, id: nil)) XCTAssertNil(lastSQLQuery) // Database not hit return .rollback } try dbQueue.inTransaction { db in struct Player: TableRecord, Identifiable { var id: String } try db.create(table: "player") { t in t.primaryKey("id", .text) } try XCTAssertFalse(Player.exists(db, id: "foo")) XCTAssertEqual(lastSQLQuery, "SELECT EXISTS (SELECT * FROM \"player\" WHERE \"id\" = 'foo')") return .rollback } } }