395 lines
18 KiB
Swift
395 lines
18 KiB
Swift
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")
|
|
}
|
|
}
|
|
}
|