891 lines
38 KiB
Swift
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)
|
|
}
|
|
}
|
|
}
|
|
}
|