import XCTest @testable import GRDB class DatabaseRegionTests : GRDBTestCase { func testRegionEquatable() { // An array of distinct selection infos let regions = [ DatabaseRegion.fullDatabase, DatabaseRegion(), DatabaseRegion(table: "foo"), DatabaseRegion(table: "foo", columns: ["a", "b"]), DatabaseRegion(table: "foo", columns: ["b", "c"]), DatabaseRegion(table: "foo", rowIds: [1, 2]), DatabaseRegion(table: "foo", rowIds: [2, 3]), DatabaseRegion(table: "bar")] for (i1, s1) in regions.enumerated() { for (i2, s2) in regions.enumerated() { if i1 == i2 { XCTAssertEqual(s1, s2) } else { XCTAssertNotEqual(s1, s2) } } } // Case insensitivity XCTAssertEqual( DatabaseRegion(table: "foo"), DatabaseRegion(table: "FOO")) XCTAssertEqual( DatabaseRegion(table: "foo", columns: ["a", "b"]), DatabaseRegion(table: "FOO", columns: ["A", "B"])) } func testRegionUnion() { let regions = [ DatabaseRegion.fullDatabase, DatabaseRegion(), DatabaseRegion(table: "foo"), DatabaseRegion(table: "foo", columns: ["a", "b"]), DatabaseRegion(table: "foo", columns: ["b", "c"]), DatabaseRegion(table: "foo", rowIds: [1, 2]), DatabaseRegion(table: "foo", rowIds: [2, 3]), DatabaseRegion(table: "bar")] var unions: [DatabaseRegion] = [] for s1 in regions { for s2 in regions { unions.append(s1.union(s2)) } } XCTAssertEqual(unions.map(\.description), [ "full database", "full database", "full database", "full database", "full database", "full database", "full database", "full database", "full database", "empty", "foo(*)", "foo(a,b)", "foo(b,c)", "foo(*)[1,2]", "foo(*)[2,3]", "bar(*)", "full database", "foo(*)", "foo(*)", "foo(*)", "foo(*)", "foo(*)", "foo(*)", "bar(*),foo(*)", "full database", "foo(a,b)", "foo(*)", "foo(a,b)", "foo(a,b,c)", "foo(*)", "foo(*)", "bar(*),foo(a,b)", "full database", "foo(b,c)", "foo(*)", "foo(a,b,c)", "foo(b,c)", "foo(*)", "foo(*)", "bar(*),foo(b,c)", "full database", "foo(*)[1,2]", "foo(*)", "foo(*)", "foo(*)", "foo(*)[1,2]", "foo(*)[1,2,3]", "bar(*),foo(*)[1,2]", "full database", "foo(*)[2,3]", "foo(*)", "foo(*)", "foo(*)", "foo(*)[1,2,3]", "foo(*)[2,3]", "bar(*),foo(*)[2,3]", "full database", "bar(*)", "bar(*),foo(*)", "bar(*),foo(a,b)", "bar(*),foo(b,c)", "bar(*),foo(*)[1,2]", "bar(*),foo(*)[2,3]", "bar(*)"]) } func testRegionUnionOfColumnsAndRows() { let regions = [ DatabaseRegion(table: "foo", columns: ["a"]).intersection(DatabaseRegion(table: "foo", rowIds: [1])), DatabaseRegion(table: "foo", columns: ["b"]).intersection(DatabaseRegion(table: "foo", rowIds: [2])), ] var unions: [DatabaseRegion] = [] for s1 in regions { for s2 in regions { unions.append(s1.union(s2)) } } XCTAssertEqual(unions.map(\.description), ["foo(a)[1]", "foo(a,b)[1,2]", "foo(a,b)[1,2]", "foo(b)[2]"]) } func testRegionIntersection() { let regions = [ DatabaseRegion.fullDatabase, DatabaseRegion(), DatabaseRegion(table: "foo"), DatabaseRegion(table: "foo", columns: ["a", "b"]), DatabaseRegion(table: "foo", columns: ["b", "c"]), DatabaseRegion(table: "foo", rowIds: [1, 2]), DatabaseRegion(table: "foo", rowIds: [2, 3]), DatabaseRegion(table: "bar")] var intersection: [DatabaseRegion] = [] for s1 in regions { for s2 in regions { intersection.append(s1.intersection(s2)) } } XCTAssertEqual(intersection.map(\.description), [ "full database", "empty", "foo(*)", "foo(a,b)", "foo(b,c)", "foo(*)[1,2]", "foo(*)[2,3]", "bar(*)", "empty", "empty", "empty", "empty", "empty", "empty", "empty", "empty", "foo(*)", "empty", "foo(*)", "foo(a,b)", "foo(b,c)", "foo(*)[1,2]", "foo(*)[2,3]", "empty", "foo(a,b)", "empty", "foo(a,b)", "foo(a,b)", "foo(b)", "foo(a,b)[1,2]", "foo(a,b)[2,3]", "empty", "foo(b,c)", "empty", "foo(b,c)", "foo(b)", "foo(b,c)", "foo(b,c)[1,2]", "foo(b,c)[2,3]", "empty", "foo(*)[1,2]", "empty", "foo(*)[1,2]", "foo(a,b)[1,2]", "foo(b,c)[1,2]", "foo(*)[1,2]", "foo(*)[2]", "empty", "foo(*)[2,3]", "empty", "foo(*)[2,3]", "foo(a,b)[2,3]", "foo(b,c)[2,3]", "foo(*)[2]", "foo(*)[2,3]", "empty", "bar(*)", "empty", "empty", "empty", "empty", "empty", "empty", "bar(*)"]) } func testRegionIntersectionOfColumnsAndRows() { let regions = [ DatabaseRegion(table: "foo", columns: ["a"]).intersection(DatabaseRegion(table: "foo", rowIds: [1])), DatabaseRegion(table: "foo", columns: ["b"]).intersection(DatabaseRegion(table: "foo", rowIds: [2])), ] var intersection: [DatabaseRegion] = [] for s1 in regions { for s2 in regions { intersection.append(s1.intersection(s2)) } } XCTAssertEqual(intersection.map(\.description), ["foo(a)[1]", "empty", "empty", "foo(b)[2]"]) } func testSelectStatement_rowid() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "CREATE TABLE foo (id INTEGER PRIMARY KEY, name TEXT)") do { // Select the rowid let statement = try db.makeStatement(sql: "SELECT id FROM foo") let expectedRegion = DatabaseRegion(table: "foo") XCTAssertEqual(statement.databaseRegion, expectedRegion) XCTAssertEqual(statement.databaseRegion.description, "foo(*)") } do { let statement = try db.makeStatement(sql: "SELECT ID FROM FOO") let expectedRegion = DatabaseRegion(table: "foo") XCTAssertEqual(statement.databaseRegion, expectedRegion) XCTAssertEqual(statement.databaseRegion.description, "foo(*)") } } } func testSelectStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "CREATE TABLE foo (id INTEGER PRIMARY KEY, name TEXT)") try db.execute(sql: "CREATE TABLE bar (id INTEGER PRIMARY KEY, fooId INTEGER)") do { let statement = try db.makeStatement(sql: "SELECT name FROM foo") let expectedRegion = DatabaseRegion(table: "foo", columns: ["name"]) XCTAssertEqual(statement.databaseRegion, expectedRegion) XCTAssertEqual(statement.databaseRegion.description, "foo(name)") } do { let statement = try db.makeStatement(sql: "SELECT NAME FROM FOO") let expectedRegion = DatabaseRegion(table: "foo", columns: ["name"]) XCTAssertEqual(statement.databaseRegion, expectedRegion) XCTAssertEqual(statement.databaseRegion.description, "foo(name)") } do { let statement = try db.makeStatement(sql: "SELECT foo.name FROM foo JOIN bar ON fooId = foo.id") let expectedRegion = DatabaseRegion(table: "foo", columns: ["name", "id"]) .union(DatabaseRegion(table: "bar", columns: ["fooId"])) XCTAssertEqual(statement.databaseRegion, expectedRegion) XCTAssertEqual(statement.databaseRegion.description, "bar(fooId),foo(id,name)") } do { let statement = try db.makeStatement(sql: "SELECT FOO.NAME FROM FOO JOIN BAR ON FOOID = FOO.ID") let expectedRegion = DatabaseRegion(table: "foo", columns: ["name", "id"]) .union(DatabaseRegion(table: "bar", columns: ["fooId"])) XCTAssertEqual(statement.databaseRegion, expectedRegion) XCTAssertEqual(statement.databaseRegion.description, "bar(fooId),foo(id,name)") } do { let statement = try db.makeStatement(sql: "SELECT COUNT(*) FROM foo") let expectedRegion = DatabaseRegion(table: "foo") XCTAssertEqual(statement.databaseRegion, expectedRegion) XCTAssertEqual(statement.databaseRegion.description, "foo(*)") } do { let statement = try db.makeStatement(sql: "SELECT COUNT(*) FROM FOO") let expectedRegion = DatabaseRegion(table: "foo") XCTAssertEqual(statement.databaseRegion, expectedRegion) XCTAssertEqual(statement.databaseRegion.description, "FOO(*)") } } } func testRegionRowIds() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "CREATE TABLE foo (id INTEGER PRIMARY KEY, a TEXT)") struct Record: TableRecord { static let databaseTableName = "foo" } // Undefined rowIds do { let request = Record.all() try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)") } do { let request = Record.filter(Column("a") == 1) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)") } do { let request = Record.filter(Column("id") >= 1) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)") } do { let request = Record.filter((Column("id") == 1) || (Column("a") == "foo")) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)") } // No rowId do { let request = Record.none() try XCTAssertEqual(request.databaseRegion(db).description, "empty") } do { let request = Record.filter([Int]().contains(Column("id"))) try XCTAssertEqual(request.databaseRegion(db).description, "empty") } do { let request = Record.filter([String]().contains(Column("a"))) try XCTAssertEqual(request.databaseRegion(db).description, "empty") } do { let request = Record.filter(Column("id") == nil) try XCTAssertEqual(request.databaseRegion(db).description, "empty") } do { let request = Record.filter(Column("id") === nil) try XCTAssertEqual(request.databaseRegion(db).description, "empty") } do { let request = Record.filter(nil == Column("id")) try XCTAssertEqual(request.databaseRegion(db).description, "empty") } do { let request = Record.filter(nil === Column("id")) try XCTAssertEqual(request.databaseRegion(db).description, "empty") } do { let request = Record.filter((Column("id") == 1) && (Column("id") == 2)) try XCTAssertEqual(request.databaseRegion(db).description, "empty") } do { let request = Record.filter(key: 1).filter(key: 2) try XCTAssertEqual(request.databaseRegion(db).description, "empty") } // Single rowId do { let request = Record.filter(Column("id") == 1) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1]") } do { let request = Record.filter(Column("id") === 1) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1]") } do { let request = Record.filter(Column("id") == 1 && Column("a") == "foo") try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1]") } do { let request = Record.filter(Column.rowID == 1) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1]") } do { let request = Record.filter(1 == Column("id")) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1]") } do { let request = Record.filter(1 === Column("id")) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1]") } do { let request = Record.filter(1 === Column.rowID) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1]") } do { let request = Record.filter(key: 1) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1]") } do { let request = Record.filter(key: 1).filter(key: 1) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1]") } do { let request = Record.filter(key: 1).filter(Column("a") == "foo") try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1]") } // Multiple rowIds do { let request = Record.filter(Column("id") == 1 || Column.rowID == 2) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1,2]") } do { let request = Record.filter((Column("id") == 1 && Column("a") == "foo") || Column.rowID == 2) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1,2]") } do { let request = Record.filter([1, 2, 3].contains(Column("id"))) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1,2,3]") } do { let request = Record.filter([1, 2, 3].contains(Column.rowID)) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1,2,3]") } do { let request = Record.filter([1, 2, 3].contains(Column("id")) || [2, 3, 4].contains(Column.rowID)) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1,2,3,4]") } do { let request = Record.filter([1, 2, 3].contains(Column("id")) && [2, 3, 4].contains(Column.rowID)) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[2,3]") } do { let request = Record.filter(keys: [1, 2, 3]) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1,2,3]") } } } func testDatabaseRegionOfJoinedRequests() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "CREATE TABLE a (id INTEGER PRIMARY KEY, name TEXT)") try db.execute(sql: "CREATE TABLE b (id INTEGER PRIMARY KEY, name TEXT, aid INTEGER REFERENCES a(id))") try db.execute(sql: "CREATE TABLE c (id INTEGER PRIMARY KEY, name TEXT, aid INTEGER REFERENCES a(id))") struct A: TableRecord { static let databaseTableName = "a" static let b = hasOne(B.self) static let c = hasMany(C.self) } struct B: TableRecord { static let databaseTableName = "b" static let a = belongsTo(A.self) } struct C: TableRecord { static let databaseTableName = "c" } do { let request = A.filter(key: 1) .including(optional: A.b.filter(key: 2)) .including(optional: A.c.filter(keys: [1, 2, 3])) // This test will fail when we are able to improve regions of joined requestt try XCTAssertEqual(request.databaseRegion(db).description, "a(id,name)[1],b(aid,id,name),c(aid,id,name)") } do { let request = B.filter(key: 1) .including(optional: B.a.filter(key: 2) .including(optional: A.c.filter(keys: [1, 2, 3]))) // This test will fail when we are able to improve regions of joined requestt try XCTAssertEqual(request.databaseRegion(db).description, "a(id,name),b(aid,id,name)[1],c(aid,id,name)") } } } func testDatabaseRegionOfDerivedRequests() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "CREATE TABLE foo (id INTEGER PRIMARY KEY, a TEXT)") struct Record: TableRecord { static let databaseTableName = "foo" } let request = Record.filter(keys: [1, 2, 3]) try XCTAssertEqual(request.databaseRegion(db).description, "foo(a,id)[1,2,3]") do { let derivedRequest = AnyFetchRequest(request) try XCTAssertEqual(derivedRequest.databaseRegion(db).description, "foo(a,id)[1,2,3]") } do { let derivedRequest: AdaptedFetchRequest = request.adapted { db in SuffixRowAdapter(fromIndex: 1) } try XCTAssertEqual(derivedRequest.databaseRegion(db).description, "foo(a,id)[1,2,3]") } } } func testUpdateStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "CREATE TABLE foo (id INTEGER PRIMARY KEY, bar TEXT, baz TEXT, qux TEXT)") do { let statement = try db.makeStatement(sql: "UPDATE foo SET bar = 'bar', baz = 'baz' WHERE id = 1") XCTAssertFalse(statement.invalidatesDatabaseSchemaCache) XCTAssertEqual(statement.authorizerEventKinds.count, 1) guard case .update(let tableName, let columnNames) = statement.authorizerEventKinds[0] else { XCTFail() return } XCTAssertEqual(tableName, "foo") XCTAssertEqual(columnNames, Set(["bar", "baz"])) } do { let statement = try db.makeStatement(sql: "UPDATE FOO SET BAR = 'bar', BAZ = 'baz' WHERE ID = 1") XCTAssertFalse(statement.invalidatesDatabaseSchemaCache) XCTAssertEqual(statement.authorizerEventKinds.count, 1) guard case .update(let tableName, let columnNames) = statement.authorizerEventKinds[0] else { XCTFail() return } XCTAssertEqual(tableName, "foo") XCTAssertEqual(columnNames, Set(["bar", "baz"])) } do { let statement = try db.makeStatement(sql: "UPDATE foo SET bar = 'bar' WHERE baz = 'baz'") XCTAssertEqual(statement.databaseRegion.description, "foo(baz)") } } } func testRowIdNameInSelectStatement() throws { // Here we test that sqlite authorizer gives the "ROWID" name to // the rowid column, regardless of its name in the request (rowid, oid, _rowid_) // // See also testRowIdNameInUpdateStatement guard sqlite3_libversion_number() < 3019003 else { // This test fails on SQLite 3.19.3 (iOS 11.2) and SQLite 3.21.0 (custom build), // but succeeds on SQLite 3.16.0 (iOS 10.3.1). // TODO: evaluate the consequences return } let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "CREATE TABLE foo (name TEXT)") do { let statement = try db.makeStatement(sql: "SELECT rowid FROM FOO") let expectedRegion = DatabaseRegion(table: "foo", columns: ["ROWID"]) XCTAssertEqual(statement.databaseRegion, expectedRegion) XCTAssertEqual(statement.databaseRegion.description, "foo(ROWID)") } do { let statement = try db.makeStatement(sql: "SELECT _ROWID_ FROM FOO") let expectedRegion = DatabaseRegion(table: "foo", columns: ["ROWID"]) XCTAssertEqual(statement.databaseRegion, expectedRegion) XCTAssertEqual(statement.databaseRegion.description, "foo(ROWID)") } do { let statement = try db.makeStatement(sql: "SELECT oID FROM FOO") let expectedRegion = DatabaseRegion(table: "foo", columns: ["ROWID"]) XCTAssertEqual(statement.databaseRegion, expectedRegion) XCTAssertEqual(statement.databaseRegion.description, "foo(ROWID)") } } } func testRowIdNameInUpdateStatement() throws { // Here we test that sqlite authorizer gives the "ROWID" name to // the rowid column, regardless of its name in the request (rowid, oid, _rowid_) // // See also testRowIdNameInSelectStatement let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "CREATE TABLE foo (name TEXT)") do { let statement = try db.makeStatement(sql: "UPDATE foo SET rowid = 1") XCTAssertEqual(statement.authorizerEventKinds.count, 1) guard case .update(let tableName, let columnNames) = statement.authorizerEventKinds[0] else { XCTFail() return } XCTAssertEqual(tableName, "foo") XCTAssertEqual(columnNames, ["ROWID"]) } do { let statement = try db.makeStatement(sql: "UPDATE foo SET _ROWID_ = 1") XCTAssertEqual(statement.authorizerEventKinds.count, 1) guard case .update(let tableName, let columnNames) = statement.authorizerEventKinds[0] else { XCTFail() return } XCTAssertEqual(tableName, "foo") XCTAssertEqual(columnNames, ["ROWID"]) } do { let statement = try db.makeStatement(sql: "UPDATE foo SET oID = 1") XCTAssertEqual(statement.authorizerEventKinds.count, 1) guard case .update(let tableName, let columnNames) = statement.authorizerEventKinds[0] else { XCTFail() return } XCTAssertEqual(tableName, "foo") XCTAssertEqual(columnNames, ["ROWID"]) } } } func testInsertStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "CREATE TABLE foo (id INTEGER, bar TEXT, baz TEXT, qux TEXT)") let statement = try db.makeStatement(sql: "INSERT INTO foo (id, bar) VALUES (1, 'bar')") XCTAssertFalse(statement.invalidatesDatabaseSchemaCache) XCTAssertEqual(statement.authorizerEventKinds.count, 1) guard case .insert(let tableName) = statement.authorizerEventKinds[0] else { XCTFail() return } XCTAssertEqual(tableName, "foo") } } func testDeleteStatement() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "CREATE TABLE foo (id INTEGER, bar TEXT, baz TEXT, qux TEXT)") let statement = try db.makeStatement(sql: "DELETE FROM foo") XCTAssertFalse(statement.invalidatesDatabaseSchemaCache) XCTAssertEqual(statement.authorizerEventKinds.count, 1) guard case .delete(let tableName) = statement.authorizerEventKinds[0] else { XCTFail() return } XCTAssertEqual(tableName, "foo") } } func testUpdateStatementInvalidatesDatabaseSchemaCache() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in do { let statement = try db.makeStatement(sql: "CREATE TABLE foo (id INTEGER)") XCTAssertTrue(statement.invalidatesDatabaseSchemaCache) try statement.execute() } do { let statement = try db.makeStatement(sql: "ALTER TABLE foo ADD COLUMN name TEXT") XCTAssertTrue(statement.invalidatesDatabaseSchemaCache) } do { let statement = try db.makeStatement(sql: "DROP TABLE foo") XCTAssertTrue(statement.invalidatesDatabaseSchemaCache) } } } func testRegionIsModifiedByDatabaseEvent() { do { // Empty selection let region = DatabaseRegion() XCTAssertEqual(region.description, "empty") do { let eventKind = DatabaseEventKind.insert(tableName: "foo") XCTAssertFalse(region.isModified(byEventsOfKind: eventKind)) // Can't test for individual events due to DatabaseRegion.isModified(by:) precondition } do { let eventKind = DatabaseEventKind.delete(tableName: "foo") XCTAssertFalse(region.isModified(byEventsOfKind: eventKind)) // Can't test for individual events due to DatabaseRegion.isModified(by:) precondition } do { let eventKind = DatabaseEventKind.update(tableName: "foo", columnNames: ["a", "b"]) XCTAssertFalse(region.isModified(byEventsOfKind: eventKind)) // Can't test for individual events due to DatabaseRegion.isModified(by:) precondition } } do { // Full database selection let region = DatabaseRegion.fullDatabase XCTAssertEqual(region.description, "full database") do { let tableName = "foo" tableName.withCString { tableNameCString in let eventKind = DatabaseEventKind.insert(tableName: tableName) let event = DatabaseEvent(kind: .insert, rowID: 1, databaseNameCString: nil, tableNameCString: tableNameCString) XCTAssertTrue(region.isModified(byEventsOfKind: eventKind)) XCTAssertTrue(region.isModified(by: event)) } } do { let tableName = "foo" tableName.withCString { tableNameCString in let eventKind = DatabaseEventKind.delete(tableName: tableName) let event = DatabaseEvent(kind: .delete, rowID: 1, databaseNameCString: nil, tableNameCString: tableNameCString) XCTAssertTrue(region.isModified(byEventsOfKind: eventKind)) XCTAssertTrue(region.isModified(by: event)) } } do { let tableName = "foo" tableName.withCString { tableNameCString in let eventKind = DatabaseEventKind.update(tableName: tableName, columnNames: ["a", "b"]) let event = DatabaseEvent(kind: .update, rowID: 1, databaseNameCString: nil, tableNameCString: tableNameCString) XCTAssertTrue(region.isModified(byEventsOfKind: eventKind)) XCTAssertTrue(region.isModified(by: event)) } } } do { // Complex selection let region = DatabaseRegion(table: "foo") .union(DatabaseRegion(table: "bar", columns: ["a"]) .intersection(DatabaseRegion(table: "bar", rowIds: [1]))) XCTAssertEqual(region.description, "bar(a)[1],foo(*)") do { let tableName = "foo" tableName.withCString { tableNameCString in let eventKind = DatabaseEventKind.insert(tableName: tableName) let event1 = DatabaseEvent(kind: .insert, rowID: 1, databaseNameCString: nil, tableNameCString: tableNameCString) let event2 = DatabaseEvent(kind: .insert, rowID: 2, databaseNameCString: nil, tableNameCString: tableNameCString) XCTAssertTrue(region.isModified(byEventsOfKind: eventKind)) XCTAssertTrue(region.isModified(by: event1)) XCTAssertTrue(region.isModified(by: event2)) } } do { let tableName = "foo" tableName.withCString { tableNameCString in let eventKind = DatabaseEventKind.delete(tableName: tableName) let event1 = DatabaseEvent(kind: .delete, rowID: 1, databaseNameCString: nil, tableNameCString: tableNameCString) let event2 = DatabaseEvent(kind: .delete, rowID: 2, databaseNameCString: nil, tableNameCString: tableNameCString) XCTAssertTrue(region.isModified(byEventsOfKind: eventKind)) XCTAssertTrue(region.isModified(by: event1)) XCTAssertTrue(region.isModified(by: event2)) } } do { let tableName = "foo" tableName.withCString { tableNameCString in let eventKind = DatabaseEventKind.update(tableName: tableName, columnNames: ["a", "b"]) let event1 = DatabaseEvent(kind: .update, rowID: 1, databaseNameCString: nil, tableNameCString: tableNameCString) let event2 = DatabaseEvent(kind: .update, rowID: 2, databaseNameCString: nil, tableNameCString: tableNameCString) XCTAssertTrue(region.isModified(byEventsOfKind: eventKind)) XCTAssertTrue(region.isModified(by: event1)) XCTAssertTrue(region.isModified(by: event2)) } } do { let tableName = "bar" tableName.withCString { tableNameCString in let eventKind = DatabaseEventKind.insert(tableName: tableName) let event1 = DatabaseEvent(kind: .insert, rowID: 1, databaseNameCString: nil, tableNameCString: tableNameCString) let event2 = DatabaseEvent(kind: .insert, rowID: 2, databaseNameCString: nil, tableNameCString: tableNameCString) XCTAssertTrue(region.isModified(byEventsOfKind: eventKind)) XCTAssertTrue(region.isModified(by: event1)) XCTAssertFalse(region.isModified(by: event2)) } } do { let tableName = "bar" tableName.withCString { tableNameCString in let eventKind = DatabaseEventKind.delete(tableName: tableName) let event1 = DatabaseEvent(kind: .delete, rowID: 1, databaseNameCString: nil, tableNameCString: tableNameCString) let event2 = DatabaseEvent(kind: .delete, rowID: 2, databaseNameCString: nil, tableNameCString: tableNameCString) XCTAssertTrue(region.isModified(byEventsOfKind: eventKind)) XCTAssertTrue(region.isModified(by: event1)) XCTAssertFalse(region.isModified(by: event2)) } } do { let tableName = "bar" tableName.withCString { tableNameCString in let eventKind = DatabaseEventKind.update(tableName: tableName, columnNames: ["a", "b"]) let event1 = DatabaseEvent(kind: .update, rowID: 1, databaseNameCString: nil, tableNameCString: tableNameCString) let event2 = DatabaseEvent(kind: .update, rowID: 2, databaseNameCString: nil, tableNameCString: tableNameCString) XCTAssertTrue(region.isModified(byEventsOfKind: eventKind)) XCTAssertTrue(region.isModified(by: event1)) XCTAssertFalse(region.isModified(by: event2)) } } do { let eventKind = DatabaseEventKind.update(tableName: "bar", columnNames: ["b", "c"]) XCTAssertFalse(region.isModified(byEventsOfKind: eventKind)) // Can't test for individual events due to DatabaseRegion.isModified(by:) precondition } do { let eventKind = DatabaseEventKind.insert(tableName: "qux") XCTAssertFalse(region.isModified(byEventsOfKind: eventKind)) // Can't test for individual events due to DatabaseRegion.isModified(by:) precondition } do { let eventKind = DatabaseEventKind.delete(tableName: "qux") XCTAssertFalse(region.isModified(byEventsOfKind: eventKind)) // Can't test for individual events due to DatabaseRegion.isModified(by:) precondition } do { let eventKind = DatabaseEventKind.update(tableName: "qux", columnNames: ["a", "b"]) XCTAssertFalse(region.isModified(byEventsOfKind: eventKind)) // Can't test for individual events due to DatabaseRegion.isModified(by:) precondition } } } // Regression test for https://github.com/groue/GRDB.swift/issues/514 func testIssue514() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.write { db in try db.execute(sql: """ CREATE TABLE a (id INTEGER PRIMARY KEY, name TEXT); CREATE TABLE b (id TEXT, name TEXT); """) // INTEGER PRIMARY KEY do { // TODO: contact SQLite and ask if this test is expected to fail // let statement = try db.makeStatement(sql: "SELECT id FROM a") // let expectedRegion = DatabaseRegion(table: "a", columns: ["id"]) // XCTAssertEqual(statement.databaseRegion, expectedRegion) } do { let statement = try db.makeStatement(sql: "SELECT name FROM a") let expectedRegion = DatabaseRegion(table: "a", columns: ["name"]) XCTAssertEqual(statement.databaseRegion, expectedRegion) } do { let statement = try db.makeStatement(sql: "SELECT id, name FROM a") let expectedRegion = DatabaseRegion(table: "a", columns: ["id", "name"]) XCTAssertEqual(statement.databaseRegion, expectedRegion) } // TEXT primary key do { let statement = try db.makeStatement(sql: "SELECT id FROM b") let expectedRegion = DatabaseRegion(table: "b", columns: ["id"]) XCTAssertEqual(statement.databaseRegion, expectedRegion) } do { let statement = try db.makeStatement(sql: "SELECT name FROM b") let expectedRegion = DatabaseRegion(table: "b", columns: ["name"]) XCTAssertEqual(statement.databaseRegion, expectedRegion) } do { let statement = try db.makeStatement(sql: "SELECT id, name FROM b") let expectedRegion = DatabaseRegion(table: "b", columns: ["id", "name"]) XCTAssertEqual(statement.databaseRegion, expectedRegion) } } } }