import XCTest @testable import GRDB class ColumnInfoTests: GRDBTestCase { func testColumnInfo() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: """ CREATE TABLE t ( a INT, b TEXT, c VARCHAR(10), d int DEFAULT NULL, e Text NOT NULL DEFAULT 'foo', "fooéı👨👨🏿🇫🇷🇨🇮" INT DEFAULT 0, g INT DEFAULT 1e6, h REAL DEFAULT 1.0, i DATETIME DEFAULT CURRENT_TIMESTAMP, j DATE DEFAULT (DATETIME('now', 'localtime')), "" fooéı👨👨🏿🇫🇷🇨🇮, untyped, PRIMARY KEY(c, a) ) """) let columns = try db.columns(in: "t") XCTAssertEqual(columns.count, 12) XCTAssertEqual(columns[0].name, "a") XCTAssertEqual(columns[0].isNotNull, false) XCTAssertEqual(columns[0].type, "INT") XCTAssertEqual(columns[0].columnType?.rawValue, "INT") XCTAssertEqual(columns[0].primaryKeyIndex, 2) XCTAssertNil(columns[0].defaultValueSQL) XCTAssertEqual(columns[1].name, "b") XCTAssertEqual(columns[1].isNotNull, false) XCTAssertEqual(columns[1].type, "TEXT") XCTAssertEqual(columns[1].columnType?.rawValue, "TEXT") XCTAssertEqual(columns[1].primaryKeyIndex, 0) XCTAssertNil(columns[1].defaultValueSQL) XCTAssertEqual(columns[2].name, "c") XCTAssertEqual(columns[2].isNotNull, false) XCTAssertEqual(columns[2].type, "VARCHAR(10)") XCTAssertEqual(columns[2].columnType?.rawValue, "VARCHAR(10)") XCTAssertEqual(columns[2].primaryKeyIndex, 1) XCTAssertNil(columns[2].defaultValueSQL) XCTAssertEqual(columns[3].name, "d") XCTAssertEqual(columns[3].isNotNull, false) XCTAssertEqual(columns[3].type.uppercased(), "INT") // "int" or "INT" depending of SQLite version XCTAssertEqual(columns[3].columnType?.rawValue.uppercased(), "INT") // "int" or "INT" depending of SQLite version XCTAssertEqual(columns[3].primaryKeyIndex, 0) XCTAssertEqual(columns[3].defaultValueSQL, "NULL") XCTAssertEqual(columns[4].name, "e") XCTAssertEqual(columns[4].isNotNull, true) XCTAssertEqual(columns[4].type.uppercased(), "TEXT") // "Text" or "TEXT" depending of SQLite version XCTAssertEqual(columns[4].columnType?.rawValue.uppercased(), "TEXT") // "Text" or "TEXT" depending of SQLite version XCTAssertEqual(columns[4].primaryKeyIndex, 0) XCTAssertEqual(columns[4].defaultValueSQL, "'foo'") XCTAssertEqual(columns[5].name, "fooéı👨👨🏿🇫🇷🇨🇮") XCTAssertEqual(columns[5].isNotNull, false) XCTAssertEqual(columns[5].type, "INT") XCTAssertEqual(columns[5].columnType?.rawValue, "INT") XCTAssertEqual(columns[5].primaryKeyIndex, 0) XCTAssertEqual(columns[5].defaultValueSQL, "0") XCTAssertEqual(columns[6].name, "g") XCTAssertEqual(columns[6].isNotNull, false) XCTAssertEqual(columns[6].type, "INT") XCTAssertEqual(columns[6].columnType?.rawValue, "INT") XCTAssertEqual(columns[6].primaryKeyIndex, 0) XCTAssertEqual(columns[6].defaultValueSQL, "1e6") XCTAssertEqual(columns[7].name, "h") XCTAssertEqual(columns[7].isNotNull, false) XCTAssertEqual(columns[7].type, "REAL") XCTAssertEqual(columns[7].columnType?.rawValue, "REAL") XCTAssertEqual(columns[7].primaryKeyIndex, 0) XCTAssertEqual(columns[7].defaultValueSQL, "1.0") XCTAssertEqual(columns[8].name, "i") XCTAssertEqual(columns[8].isNotNull, false) XCTAssertEqual(columns[8].type, "DATETIME") XCTAssertEqual(columns[8].columnType?.rawValue, "DATETIME") XCTAssertEqual(columns[8].primaryKeyIndex, 0) XCTAssertEqual(columns[8].defaultValueSQL, "CURRENT_TIMESTAMP") XCTAssertEqual(columns[9].name, "j") XCTAssertEqual(columns[9].isNotNull, false) XCTAssertEqual(columns[9].type, "DATE") XCTAssertEqual(columns[9].columnType?.rawValue, "DATE") XCTAssertEqual(columns[9].primaryKeyIndex, 0) XCTAssertEqual(columns[9].defaultValueSQL, "DATETIME('now', 'localtime')") XCTAssertEqual(columns[10].name, "") XCTAssertEqual(columns[10].isNotNull, false) XCTAssertEqual(columns[10].type, "fooéı👨👨🏿🇫🇷🇨🇮") XCTAssertEqual(columns[10].columnType?.rawValue, "fooéı👨👨🏿🇫🇷🇨🇮") XCTAssertEqual(columns[10].primaryKeyIndex, 0) XCTAssertNil(columns[10].defaultValueSQL) XCTAssertEqual(columns[11].name, "untyped") XCTAssertEqual(columns[11].isNotNull, false) XCTAssertEqual(columns[11].type, "") XCTAssertNil(columns[11].columnType) XCTAssertEqual(columns[11].primaryKeyIndex, 0) XCTAssertNil(columns[11].defaultValueSQL) } } func testGeneratedColumnInfo() throws { #if !GRDBCUSTOMSQLITE throw XCTSkip("Generated columns are not available") #else let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: """ CREATE TABLE t ( a INT, b ALWAYS GENERATED AS (a), c ALWAYS GENERATED AS (a) VIRTUAL, d ALWAYS GENERATED AS (a) STORED, e INT ALWAYS GENERATED AS (a), f TEXT ALWAYS GENERATED AS (a), g ALWAYS GENERATED AS (a) NOT NULL ) """) let columns = try db.columns(in: "t") XCTAssertEqual(columns.count, 7) XCTAssertEqual(columns[0].name, "a") XCTAssertEqual(columns[0].isNotNull, false) XCTAssertEqual(columns[0].type, "INT") XCTAssertEqual(columns[0].primaryKeyIndex, 0) XCTAssertNil(columns[0].defaultValueSQL) XCTAssertEqual(columns[1].name, "b") XCTAssertEqual(columns[1].isNotNull, false) XCTAssertEqual(columns[1].type, "ALWAYS GENERATED") XCTAssertEqual(columns[1].primaryKeyIndex, 0) XCTAssertNil(columns[1].defaultValueSQL) XCTAssertEqual(columns[2].name, "c") XCTAssertEqual(columns[2].isNotNull, false) XCTAssertEqual(columns[2].type, "ALWAYS GENERATED") XCTAssertEqual(columns[2].primaryKeyIndex, 0) XCTAssertNil(columns[2].defaultValueSQL) XCTAssertEqual(columns[3].name, "d") XCTAssertEqual(columns[3].isNotNull, false) XCTAssertEqual(columns[3].type, "ALWAYS GENERATED") XCTAssertEqual(columns[3].primaryKeyIndex, 0) XCTAssertNil(columns[3].defaultValueSQL) XCTAssertEqual(columns[4].name, "e") XCTAssertEqual(columns[4].isNotNull, false) XCTAssertEqual(columns[4].type, "INT ALWAYS GENERATED") XCTAssertEqual(columns[4].primaryKeyIndex, 0) XCTAssertNil(columns[4].defaultValueSQL) XCTAssertEqual(columns[5].name, "f") XCTAssertEqual(columns[5].isNotNull, false) XCTAssertEqual(columns[5].type, "TEXT ALWAYS GENERATED") XCTAssertEqual(columns[5].primaryKeyIndex, 0) XCTAssertNil(columns[5].defaultValueSQL) XCTAssertEqual(columns[6].name, "g") XCTAssertEqual(columns[6].isNotNull, true) XCTAssertEqual(columns[6].type, "ALWAYS GENERATED") XCTAssertEqual(columns[6].primaryKeyIndex, 0) XCTAssertNil(columns[6].defaultValueSQL) } #endif } func testViews() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.write { db in try db.execute(sql: """ CREATE TABLE t ( a INT, b TEXT, c VARCHAR(10), d int DEFAULT NULL, e Text NOT NULL DEFAULT 'foo', "fooéı👨👨🏿🇫🇷🇨🇮" INT DEFAULT 0, g INT DEFAULT 1e6, h REAL DEFAULT 1.0, i DATETIME DEFAULT CURRENT_TIMESTAMP, j DATE DEFAULT (DATETIME('now', 'localtime')), "" fooéı👨👨🏿🇫🇷🇨🇮, PRIMARY KEY(c, a) ); CREATE VIEW v AS SELECT * FROM t; """) let columns = try db.columns(in: "v") XCTAssertEqual(columns.count, 11) XCTAssertEqual(columns[0].name, "a") XCTAssertEqual(columns[0].isNotNull, false) XCTAssertEqual(columns[0].type, "INT") XCTAssertEqual(columns[0].primaryKeyIndex, 0) XCTAssertNil(columns[0].defaultValueSQL) XCTAssertEqual(columns[1].name, "b") XCTAssertEqual(columns[1].isNotNull, false) XCTAssertEqual(columns[1].type, "TEXT") XCTAssertEqual(columns[1].primaryKeyIndex, 0) XCTAssertNil(columns[1].defaultValueSQL) XCTAssertEqual(columns[2].name, "c") XCTAssertEqual(columns[2].isNotNull, false) XCTAssertEqual(columns[2].type, "VARCHAR(10)") XCTAssertEqual(columns[2].primaryKeyIndex, 0) XCTAssertNil(columns[2].defaultValueSQL) XCTAssertEqual(columns[3].name, "d") XCTAssertEqual(columns[3].isNotNull, false) XCTAssertEqual(columns[3].type.uppercased(), "INT") // "int" or "INT" depending of SQLite version XCTAssertEqual(columns[3].primaryKeyIndex, 0) XCTAssertNil(columns[3].defaultValueSQL) XCTAssertEqual(columns[4].name, "e") XCTAssertEqual(columns[3].isNotNull, false) XCTAssertEqual(columns[4].type.uppercased(), "TEXT") // "Text" or "TEXT" depending of SQLite version XCTAssertEqual(columns[4].primaryKeyIndex, 0) XCTAssertNil(columns[4].defaultValueSQL) XCTAssertEqual(columns[5].name, "fooéı👨👨🏿🇫🇷🇨🇮") XCTAssertEqual(columns[5].isNotNull, false) XCTAssertEqual(columns[5].type, "INT") XCTAssertEqual(columns[5].primaryKeyIndex, 0) XCTAssertNil(columns[5].defaultValueSQL) XCTAssertEqual(columns[6].name, "g") XCTAssertEqual(columns[6].isNotNull, false) XCTAssertEqual(columns[6].type, "INT") XCTAssertEqual(columns[6].primaryKeyIndex, 0) XCTAssertNil(columns[6].defaultValueSQL) XCTAssertEqual(columns[7].name, "h") XCTAssertEqual(columns[7].isNotNull, false) XCTAssertEqual(columns[7].type, "REAL") XCTAssertEqual(columns[7].primaryKeyIndex, 0) XCTAssertNil(columns[7].defaultValueSQL) XCTAssertEqual(columns[8].name, "i") XCTAssertEqual(columns[8].isNotNull, false) XCTAssertEqual(columns[8].type, "DATETIME") XCTAssertEqual(columns[8].primaryKeyIndex, 0) XCTAssertNil(columns[8].defaultValueSQL) XCTAssertEqual(columns[9].name, "j") XCTAssertEqual(columns[9].isNotNull, false) XCTAssertEqual(columns[9].type, "DATE") XCTAssertEqual(columns[9].primaryKeyIndex, 0) XCTAssertNil(columns[9].defaultValueSQL) XCTAssertEqual(columns[10].name, "") XCTAssertEqual(columns[10].isNotNull, false) XCTAssertEqual(columns[10].type, "fooéı👨👨🏿🇫🇷🇨🇮") XCTAssertEqual(columns[10].primaryKeyIndex, 0) XCTAssertNil(columns[10].defaultValueSQL) } } // Regression test for https://github.com/groue/GRDB.swift/issues/1124 // See also https://sqlite.org/forum/forumpost/721da02ba2 func testIssue1124() throws { class Observer: TransactionObserver { func observes(eventsOfKind eventKind: DatabaseEventKind) -> Bool { true } func databaseDidChange(with event: DatabaseEvent) { } func databaseDidCommit(_ db: Database) { } func databaseDidRollback(_ db: Database) { } } let dbQueue = try makeDatabaseQueue() dbQueue.add(transactionObserver: Observer(), extent: .databaseLifetime) try dbQueue.write { db in try db.execute(sql: """ CREATE VIRTUAL TABLE t1 USING rtree(id,minX,maxX,minY,maxY); CREATE TABLE t2 (a); ALTER TABLE t2 RENAME TO t3; """) _ = try db.columns(in: "t1") } } func testUnknownSchema() throws { let dbQueue = try makeDatabaseQueue() try dbQueue.inDatabase { db in try db.execute(sql: "CREATE TABLE t (id INTEGER)") do { _ = try db.columns(in: "t", 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 t (id INTEGER)") let columns = try db.columns(in: "t") XCTAssertEqual(columns.count, 1) XCTAssertEqual(columns[0].name, "id") XCTAssertEqual(columns[0].type, "INTEGER") } } 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 t (id2 TEXT)") } let main = try makeDatabaseQueue(filename: "main") try main.inDatabase { db in try db.execute(sql: "CREATE TABLE t (id1 INTEGER)") try db.execute(literal: "ATTACH DATABASE \(attached.path) AS attached") let columnsMain = try db.columns(in: "t", in: "main") XCTAssertEqual(columnsMain.count, 1) XCTAssertEqual(columnsMain[0].name, "id1") XCTAssertEqual(columnsMain[0].type, "INTEGER") let columnsAttached = try db.columns(in: "t", in: "attached") XCTAssertEqual(columnsAttached.count, 1) XCTAssertEqual(columnsAttached[0].name, "id2") XCTAssertEqual(columnsAttached[0].type, "TEXT") } } // The `t` table in the attached database should never // be found unless explicitly specified as it is after // `main.t` 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 t (id2 TEXT)") } let main = try makeDatabaseQueue(filename: "main") try main.inDatabase { db in try db.execute(sql: "CREATE TABLE t (id1 INTEGER)") try db.execute(literal: "ATTACH DATABASE \(attached.path) AS attached") let columnsMain = try db.columns(in: "t") XCTAssertEqual(columnsMain.count, 1) XCTAssertEqual(columnsMain[0].name, "id1") XCTAssertEqual(columnsMain[0].type, "INTEGER") } } 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 t (id2 TEXT)") } let main = try makeDatabaseQueue(filename: "main") try main.inDatabase { db in try db.execute(literal: "ATTACH DATABASE \(attached.path) AS attached") let columnsMain = try db.columns(in: "t") XCTAssertEqual(columnsMain.count, 1) XCTAssertEqual(columnsMain[0].name, "id2") XCTAssertEqual(columnsMain[0].type, "TEXT") } } }