import XCTest import GRDB class IndexInfoTests: GRDBTestCase { func testIndexes() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in do { _ = try db.indexes(on: "missing") XCTFail("Expected DatabaseError") } catch let error as DatabaseError { XCTAssertEqual(error.resultCode, .SQLITE_ERROR) XCTAssertEqual(error.message, "no such table: missing") } do { try db.execute(sql: "CREATE TABLE persons (id INTEGER PRIMARY KEY, name TEXT, email TEXT UNIQUE)") let indexes = try db.indexes(on: "persons") XCTAssertEqual(indexes.count, 1) XCTAssertEqual(indexes[0].name, "sqlite_autoindex_persons_1") XCTAssertEqual(indexes[0].origin, .uniqueConstraint) XCTAssertEqual(indexes[0].columns, ["email"]) XCTAssertTrue(indexes[0].isUnique) } do { try db.execute(sql: "CREATE TABLE citizenships (year INTEGER, personId INTEGER NOT NULL, countryIsoCode TEXT NOT NULL, PRIMARY KEY (personId, countryIsoCode))") try db.execute(sql: "CREATE INDEX citizenshipsOnYear ON citizenships(year)") let indexes = try db.indexes(on: "citizenships") XCTAssertEqual(indexes.count, 2) if let i = indexes.firstIndex(where: { $0.columns == ["year"] }) { XCTAssertEqual(indexes[i].name, "citizenshipsOnYear") XCTAssertEqual(indexes[i].origin, .createIndex) XCTAssertEqual(indexes[i].columns, ["year"]) XCTAssertFalse(indexes[i].isUnique) } else { XCTFail() } if let i = indexes.firstIndex(where: { $0.columns == ["personId", "countryIsoCode"] }) { XCTAssertEqual(indexes[i].name, "sqlite_autoindex_citizenships_1") XCTAssertEqual(indexes[i].origin, .primaryKeyConstraint) XCTAssertEqual(indexes[i].columns, ["personId", "countryIsoCode"]) XCTAssertTrue(indexes[i].isUnique) } else { XCTFail() } } } } func testColumnsThatUniquelyIdentityRows() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "CREATE TABLE persons (id INTEGER PRIMARY KEY, name TEXT, email TEXT UNIQUE)") try XCTAssertTrue(db.table("persons", hasUniqueKey: ["rowid"])) try XCTAssertTrue(db.table("persons", hasUniqueKey: ["id"])) try XCTAssertTrue(db.table("persons", hasUniqueKey: ["email"])) try XCTAssertFalse(db.table("persons", hasUniqueKey: [])) try XCTAssertFalse(db.table("persons", hasUniqueKey: ["name"])) try XCTAssertTrue(db.table("persons", hasUniqueKey: ["id", "email"])) try db.execute(sql: "CREATE TABLE citizenships (year INTEGER, personId INTEGER NOT NULL, countryIsoCode TEXT NOT NULL, PRIMARY KEY (personId, countryIsoCode))") try db.execute(sql: "CREATE INDEX citizenshipsOnYear ON citizenships(year)") try XCTAssertTrue(db.table("citizenships", hasUniqueKey: ["rowid"])) try XCTAssertTrue(db.table("citizenships", hasUniqueKey: ["personId", "countryIsoCode"])) try XCTAssertTrue(db.table("citizenships", hasUniqueKey: ["countryIsoCode", "personId"])) try XCTAssertFalse(db.table("citizenships", hasUniqueKey: [])) try XCTAssertFalse(db.table("citizenships", hasUniqueKey: ["year"])) try XCTAssertFalse(db.table("citizenships", hasUniqueKey: ["personId"])) try XCTAssertFalse(db.table("citizenships", hasUniqueKey: ["countryIsoCode"])) } } // Regression test for https://github.com/groue/GRDB.swift/issues/840 func testIndexOnExpressionIsExcluded() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in do { try db.execute(sql: """ CREATE TABLE player (id INTEGER PRIMARY KEY, name TEXT); CREATE INDEX expressionIndex ON player (LENGTH(name)); CREATE INDEX columnIndex ON player (name); """) let indexes = try db.indexes(on: "player") XCTAssertEqual(indexes.count, 1) XCTAssertEqual(indexes[0].name, "columnIndex") XCTAssertEqual(indexes[0].origin, .createIndex) XCTAssertEqual(indexes[0].columns, ["name"]) XCTAssertFalse(indexes[0].isUnique) } } } func testUnknownSchema() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: """ CREATE TABLE player (id INTEGER PRIMARY KEY, name TEXT); CREATE INDEX columnIndex ON player (name); """) do { _ = try db.indexes(on: "player", in: "invalid") XCTFail("Expected Error") } catch let error as DatabaseError { XCTAssertEqual(error.resultCode, .SQLITE_ERROR) XCTAssertEqual(error.message, "no such schema: invalid") XCTAssertEqual(error.description, "SQLite error 1: no such schema: invalid") } } } func testSpecifiedMainSchema() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: """ CREATE TABLE player (id INTEGER PRIMARY KEY, name TEXT); CREATE INDEX columnIndex ON player (name); """) let indexes = try db.indexes(on: "player", in: "main") XCTAssertEqual(indexes.count, 1) XCTAssertEqual(indexes[0].name, "columnIndex") } } func testSpecifiedSchemaWithTableNameCollisions() throws { #if GRDBCIPHER_USE_ENCRYPTION // Avoid error due to key not being provided: // file is not a database - while executing `ATTACH DATABASE...` throw XCTSkip("This test does not support encrypted databases") #endif let attached = try makeDatabaseQueue(filename: "attached1") try attached.inDatabase { db in try db.execute(sql: """ CREATE TABLE player (id INTEGER PRIMARY KEY, name TEXT); CREATE INDEX columnIndexAttached ON player (name); """) } let main = try makeDatabaseQueue(filename: "main") try main.inDatabase { db in try db.execute(sql: """ CREATE TABLE player (id INTEGER PRIMARY KEY, name TEXT); CREATE INDEX columnIndex ON player (name); """) try db.execute(literal: "ATTACH DATABASE \(attached.path) AS attached") let mainIndexes = try db.indexes(on: "player", in: "main") XCTAssertEqual(mainIndexes.count, 1) XCTAssertEqual(mainIndexes[0].name, "columnIndex") let attachedIndexes = try db.indexes(on: "player", in: "attached") XCTAssertEqual(attachedIndexes.count, 1) XCTAssertEqual(attachedIndexes[0].name, "columnIndexAttached") } } // The `player` table in the attached database should never // be found unless explicitly specified as it is after // `main.player` in resolution order. func testUnspecifiedSchemaWithTableNameCollisions() throws { #if GRDBCIPHER_USE_ENCRYPTION // Avoid error due to key not being provided: // file is not a database - while executing `ATTACH DATABASE...` throw XCTSkip("This test does not support encrypted databases") #endif let attached = try makeDatabaseQueue(filename: "attached1") try attached.inDatabase { db in try db.execute(sql: """ CREATE TABLE player (id INTEGER PRIMARY KEY, name TEXT); CREATE INDEX columnIndexAttached ON player (name); """) } let main = try makeDatabaseQueue(filename: "main") try main.inDatabase { db in try db.execute(sql: """ CREATE TABLE player (id INTEGER PRIMARY KEY, name TEXT); CREATE INDEX columnIndex ON player (name); """) try db.execute(literal: "ATTACH DATABASE \(attached.path) AS attached") let mainIndexes = try db.indexes(on: "player") XCTAssertEqual(mainIndexes.count, 1) XCTAssertEqual(mainIndexes[0].name, "columnIndex") } } func testUnspecifiedSchemaFindsAttachedDatabase() throws { #if GRDBCIPHER_USE_ENCRYPTION // Avoid error due to key not being provided: // file is not a database - while executing `ATTACH DATABASE...` throw XCTSkip("This test does not support encrypted databases") #endif let attached = try makeDatabaseQueue(filename: "attached1") try attached.inDatabase { db in try db.execute(sql: """ CREATE TABLE player (id INTEGER PRIMARY KEY, name TEXT); CREATE INDEX columnIndexAttached ON player (name); """) } let main = try makeDatabaseQueue(filename: "main") try main.inDatabase { db in try db.execute(literal: "ATTACH DATABASE \(attached.path) AS attached") let attachedIndexes = try db.indexes(on: "player", in: "attached") XCTAssertEqual(attachedIndexes.count, 1) XCTAssertEqual(attachedIndexes[0].name, "columnIndexAttached") } } }