Files

891 lines
38 KiB
Swift

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)
}
}
}
}