@@ -135,6 +135,62 @@ extension Database {
135
135
return schemaIdentifiers
136
136
}
137
137
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
+
138
194
/// Returns whether a table exists, in the main or temp schema, or in an
139
195
/// attached database.
140
196
public func tableExists( _ name: String ) throws -> Bool {
@@ -310,7 +366,19 @@ extension Database {
310
366
private func tableHasRowID( _ table: TableIdentifier ) throws -> Bool {
311
367
// No need to cache the result, because this information feeds
312
368
// `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
+
314
382
// To check if the table has a rowid, we compile a statement that
315
383
// selects the `rowid` column. If compilation fails, we assume that the
316
384
// table is WITHOUT ROWID. This is not a very robust test (users may
@@ -1093,6 +1161,42 @@ public struct ForeignKeyInfo {
1093
1161
}
1094
1162
}
1095
1163
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
+
1096
1200
enum SchemaObjectType : String {
1097
1201
case index
1098
1202
case table
0 commit comments