Skip to content

Commit e2b412a

Browse files
committed
Merge branch 'dev/TableInfo' into development
2 parents 3d77a8d + 040e5d9 commit e2b412a

File tree

2 files changed

+115
-1
lines changed

2 files changed

+115
-1
lines changed

GRDB/Core/Database+Schema.swift

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,62 @@ extension Database {
135135
return schemaIdentifiers
136136
}
137137

138+
#if GRDBCUSTOMSQLITE || GRDBCIPHER
139+
/// Returns information about a table or a view
140+
func table(_ tableName: String) throws -> TableInfo? {
141+
for schemaIdentifier in try schemaIdentifiers() {
142+
if let result = try table(for: TableIdentifier(schemaID: schemaIdentifier, name: tableName)) {
143+
return result
144+
}
145+
}
146+
return nil
147+
}
148+
149+
/// Returns information about a table or a view
150+
func table(for table: TableIdentifier) throws -> TableInfo? {
151+
// Maybe SQLCipher is too old: check actual version
152+
GRDBPrecondition(sqlite3_libversion_number() >= 3037000, "SQLite 3.37+ required")
153+
return try _table(for: table)
154+
}
155+
#else
156+
/// Returns information about a table or a view
157+
@available(iOS 15.4, macOS 12.4, tvOS 15.4, watchOS 8.5, *) // SQLite 3.37+
158+
func table(_ tableName: String) throws -> TableInfo? {
159+
for schemaIdentifier in try schemaIdentifiers() {
160+
if let result = try table(for: TableIdentifier(schemaID: schemaIdentifier, name: tableName)) {
161+
return result
162+
}
163+
}
164+
return nil
165+
}
166+
167+
/// Returns information about a table or a view
168+
@available(iOS 15.4, macOS 12.4, tvOS 15.4, watchOS 8.5, *) // SQLite 3.37+
169+
func table(for table: TableIdentifier) throws -> TableInfo? {
170+
try _table(for: table)
171+
}
172+
#endif
173+
/// Returns information about a table or a view
174+
private func _table(for table: TableIdentifier) throws -> TableInfo? {
175+
assert(sqlite3_libversion_number() >= 3037000, "SQLite 3.37+ required")
176+
SchedulingWatchdog.preconditionValidQueue(self)
177+
178+
if let tableInfo = schemaCache[table.schemaID].table(table.name) {
179+
return tableInfo.value
180+
}
181+
182+
guard let tableInfo = try TableInfo
183+
.fetchOne(self, sql: "PRAGMA \(table.schemaID.sql).table_list(\(table.name.quotedDatabaseIdentifier))")
184+
else {
185+
// table does not exist
186+
schemaCache[table.schemaID].set(tableInfo: .missing, forTable: table.name)
187+
return nil
188+
}
189+
190+
schemaCache[table.schemaID].set(tableInfo: .value(tableInfo), forTable: table.name)
191+
return tableInfo
192+
}
193+
138194
/// Returns whether a table exists, in the main or temp schema, or in an
139195
/// attached database.
140196
public func tableExists(_ name: String) throws -> Bool {
@@ -310,7 +366,19 @@ extension Database {
310366
private func tableHasRowID(_ table: TableIdentifier) throws -> Bool {
311367
// No need to cache the result, because this information feeds
312368
// `PrimaryKeyInfo`, which is cached.
313-
//
369+
370+
// Prefer PRAGMA table_list if available
371+
#if GRDBCUSTOMSQLITE || GRDBCIPHER
372+
// Maybe SQLCipher is too old: check actual version
373+
if sqlite3_libversion_number() >= 3037000 {
374+
return try self.table(for: table)!.hasRowID
375+
}
376+
#else
377+
if #available(iOS 15.4, macOS 12.4, tvOS 15.4, watchOS 8.5, *) {
378+
return try self.table(for: table)!.hasRowID
379+
}
380+
#endif
381+
314382
// To check if the table has a rowid, we compile a statement that
315383
// selects the `rowid` column. If compilation fails, we assume that the
316384
// table is WITHOUT ROWID. This is not a very robust test (users may
@@ -1093,6 +1161,42 @@ public struct ForeignKeyInfo {
10931161
}
10941162
}
10951163

1164+
/// See <https://www.sqlite.org/pragma.html#pragma_table_list>
1165+
struct TableInfo: FetchableRecord {
1166+
struct Kind: RawRepresentable {
1167+
var rawValue: String
1168+
1169+
static let table = Kind(rawValue: "table")
1170+
static let view = Kind(rawValue: "view")
1171+
static let shadow = Kind(rawValue: "shadow")
1172+
static let virtual = Kind(rawValue: "virtual")
1173+
}
1174+
1175+
var schemaID: Database.SchemaIdentifier
1176+
var name: String
1177+
var kind: Kind
1178+
var columnCount: Int
1179+
var hasRowID: Bool
1180+
var strict: Bool
1181+
1182+
init(row: Row) throws {
1183+
switch row[0] as String {
1184+
case "main":
1185+
schemaID = .main
1186+
case "temp":
1187+
schemaID = .temp
1188+
case let name:
1189+
schemaID = .attached(name)
1190+
}
1191+
1192+
name = row[1]
1193+
kind = Kind(rawValue: row[2])
1194+
columnCount = row[3]
1195+
hasRowID = !row[4]
1196+
strict = row[5]
1197+
}
1198+
}
1199+
10961200
enum SchemaObjectType: String {
10971201
case index
10981202
case table

GRDB/Core/DatabaseSchemaCache.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,29 @@ struct DatabaseSchemaCache {
2121
}
2222

2323
var schemaInfo: SchemaInfo?
24+
private var tables: [String: Presence<TableInfo>] = [:]
2425
private var primaryKeys: [String: Presence<PrimaryKeyInfo>] = [:]
2526
private var columns: [String: Presence<[ColumnInfo]>] = [:]
2627
private var indexes: [String: Presence<[IndexInfo]>] = [:]
2728
private var foreignKeys: [String: Presence<[ForeignKeyInfo]>] = [:]
2829

2930
mutating func clear() {
31+
tables = [:]
3032
primaryKeys = [:]
3133
columns = [:]
3234
indexes = [:]
3335
foreignKeys = [:]
3436
schemaInfo = nil
3537
}
3638

39+
func table(_ table: String) -> Presence<TableInfo>? {
40+
tables[table]
41+
}
42+
43+
mutating func set(tableInfo: Presence<TableInfo>, forTable table: String) {
44+
tables[table] = tableInfo
45+
}
46+
3747
func primaryKey(_ table: String) -> Presence<PrimaryKeyInfo>? {
3848
primaryKeys[table]
3949
}

0 commit comments

Comments
 (0)