add swiftUI code
This commit is contained in:
@@ -0,0 +1,357 @@
|
||||
import Foundation
|
||||
|
||||
// MARK: - Dump
|
||||
|
||||
extension Database {
|
||||
/// Prints the results of all statements in the provided SQL.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// try dbQueue.read { db in
|
||||
/// // Prints
|
||||
/// // 1|Arthur|500
|
||||
/// // 2|Barbara|1000
|
||||
/// try db.dumpSQL("SELECT * FROM player ORDER BY id")
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - sql: The executed SQL.
|
||||
/// - format: The output format.
|
||||
/// - stream: A stream for text output, which directs output to the
|
||||
/// console by default.
|
||||
public func dumpSQL(
|
||||
_ sql: SQL,
|
||||
format: some DumpFormat = .debug(),
|
||||
to stream: (any TextOutputStream)? = nil)
|
||||
throws
|
||||
{
|
||||
var dumpStream = DumpStream(stream)
|
||||
try _dumpSQL(sql, format: format, to: &dumpStream)
|
||||
}
|
||||
|
||||
/// Prints the results of a request.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// try dbQueue.read { db in
|
||||
/// // Prints
|
||||
/// // 1|Arthur|500
|
||||
/// // 2|Barbara|1000
|
||||
/// try db.dumpRequest(Player.orderByPrimaryKey())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - request : The executed request.
|
||||
/// - format: The output format.
|
||||
/// - stream: A stream for text output, which directs output to the
|
||||
/// console by default.
|
||||
public func dumpRequest(
|
||||
_ request: some FetchRequest,
|
||||
format: some DumpFormat = .debug(),
|
||||
to stream: (any TextOutputStream)? = nil)
|
||||
throws
|
||||
{
|
||||
var dumpStream = DumpStream(stream)
|
||||
try _dumpRequest(request, format: format, to: &dumpStream)
|
||||
}
|
||||
|
||||
/// Prints the contents of the provided tables and views.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// try dbQueue.read { db in
|
||||
/// // player
|
||||
/// // 1|Arthur|500
|
||||
/// // 2|Barbara|1000
|
||||
/// //
|
||||
/// // team
|
||||
/// // 1|Red
|
||||
/// // 2|Blue
|
||||
/// try db.dumpTables(["player", "team"])
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - tables: The table names.
|
||||
/// - format: The output format.
|
||||
/// - tableHeader: Options for printing table names.
|
||||
/// - stableOrder: A boolean value that controls the ordering of
|
||||
/// rows fetched from views. If false (the default), rows are
|
||||
/// printed in the order specified by the view (which may be
|
||||
/// undefined). It true, outputted rows are always printed in the
|
||||
/// same stable order. The purpose of this stable order is to make
|
||||
/// the output suitable for testing.
|
||||
/// - stream: A stream for text output, which directs output to the
|
||||
/// console by default.
|
||||
public func dumpTables(
|
||||
_ tables: [String],
|
||||
format: some DumpFormat = .debug(),
|
||||
tableHeader: DumpTableHeaderOptions = .automatic,
|
||||
stableOrder: Bool = false,
|
||||
to stream: (any TextOutputStream)? = nil)
|
||||
throws
|
||||
{
|
||||
var dumpStream = DumpStream(stream)
|
||||
try _dumpTables(
|
||||
tables,
|
||||
format: format,
|
||||
tableHeader: tableHeader,
|
||||
stableOrder: stableOrder,
|
||||
to: &dumpStream)
|
||||
}
|
||||
|
||||
/// Prints the contents of the database.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// try dbQueue.read { db in
|
||||
/// try db.dumpContent()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This prints the database schema as well as the content of all
|
||||
/// tables. For example:
|
||||
///
|
||||
/// ```
|
||||
/// sqlite_master
|
||||
/// CREATE TABLE player (id INTEGER PRIMARY KEY, name TEXT, score INTEGER)
|
||||
///
|
||||
/// player
|
||||
/// 1,'Arthur',500
|
||||
/// 2,'Barbara',1000
|
||||
/// ```
|
||||
///
|
||||
/// > Note: Internal SQLite and GRDB schema objects are not recorded
|
||||
/// > (those with a name that starts with "sqlite_" or "grdb_").
|
||||
/// >
|
||||
/// > [Shadow tables](https://www.sqlite.org/vtab.html#xshadowname) are
|
||||
/// > not recorded, starting SQLite 3.37+.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - format: The output format.
|
||||
/// - stream: A stream for text output, which directs output to the
|
||||
/// console by default.
|
||||
public func dumpContent(
|
||||
format: some DumpFormat = .debug(),
|
||||
to stream: (any TextOutputStream)? = nil)
|
||||
throws
|
||||
{
|
||||
var dumpStream = DumpStream(stream)
|
||||
try _dumpContent(format: format, to: &dumpStream)
|
||||
}
|
||||
|
||||
/// Prints the schema of the database.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// try dbQueue.read { db in
|
||||
/// try db.dumpSchema()
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This prints the database schema. For example:
|
||||
///
|
||||
/// ```
|
||||
/// sqlite_master
|
||||
/// CREATE TABLE player (id INTEGER PRIMARY KEY, name TEXT, score INTEGER)
|
||||
/// ```
|
||||
///
|
||||
/// > Note: Internal SQLite and GRDB schema objects are not recorded
|
||||
/// > (those with a name that starts with "sqlite_" or "grdb_").
|
||||
/// >
|
||||
/// > [Shadow tables](https://www.sqlite.org/vtab.html#xshadowname) are
|
||||
/// > not recorded, starting SQLite 3.37+.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - stream: A stream for text output, which directs output to the
|
||||
/// console by default.
|
||||
public func dumpSchema(
|
||||
to stream: (any TextOutputStream)? = nil)
|
||||
throws
|
||||
{
|
||||
var dumpStream = DumpStream(stream)
|
||||
try _dumpSchema(to: &dumpStream)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
extension Database {
|
||||
func _dumpStatements(
|
||||
_ statements: some Cursor<Statement>,
|
||||
format: some DumpFormat,
|
||||
to stream: inout DumpStream)
|
||||
throws
|
||||
{
|
||||
while let statement = try statements.next() {
|
||||
var stepFormat = format
|
||||
let cursor = try statement.makeCursor()
|
||||
while try cursor.next() != nil {
|
||||
try stepFormat.writeRow(self, statement: statement, to: &stream)
|
||||
}
|
||||
stepFormat.finalize(self, statement: statement, to: &stream)
|
||||
}
|
||||
}
|
||||
|
||||
func _dumpSQL(
|
||||
_ sql: SQL,
|
||||
format: some DumpFormat,
|
||||
to stream: inout DumpStream)
|
||||
throws
|
||||
{
|
||||
try _dumpStatements(allStatements(literal: sql), format: format, to: &stream)
|
||||
}
|
||||
|
||||
func _dumpRequest(
|
||||
_ request: some FetchRequest,
|
||||
format: some DumpFormat,
|
||||
to stream: inout DumpStream)
|
||||
throws
|
||||
{
|
||||
let preparedRequest = try request.makePreparedRequest(self, forSingleResult: false)
|
||||
try _dumpStatements(AnyCursor([preparedRequest.statement]), format: format, to: &stream)
|
||||
|
||||
if let supplementaryFetch = preparedRequest.supplementaryFetch {
|
||||
let rows = try Row.fetchAll(self, request)
|
||||
try withoutActuallyEscaping(
|
||||
{ request, keyPath in
|
||||
stream.write("\n")
|
||||
stream.writeln(keyPath.joined(separator: "."))
|
||||
try self._dumpRequest(request, format: format, to: &stream)
|
||||
},
|
||||
do: { willExecuteSupplementaryRequest in
|
||||
try supplementaryFetch(self, rows, willExecuteSupplementaryRequest)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func _dumpTables(
|
||||
_ tables: [String],
|
||||
format: some DumpFormat,
|
||||
tableHeader: DumpTableHeaderOptions,
|
||||
stableOrder: Bool,
|
||||
to stream: inout DumpStream)
|
||||
throws
|
||||
{
|
||||
let header: Bool
|
||||
switch tableHeader {
|
||||
case .always: header = true
|
||||
case .automatic: header = tables.count > 1
|
||||
}
|
||||
|
||||
var first = true
|
||||
for table in tables {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
stream.write("\n")
|
||||
}
|
||||
|
||||
if header {
|
||||
stream.writeln(table)
|
||||
}
|
||||
|
||||
if try tableExists(table) {
|
||||
// Always sort tables by primary key
|
||||
try _dumpRequest(Table(table).orderByPrimaryKey(), format: format, to: &stream)
|
||||
} else if stableOrder {
|
||||
// View with stable order
|
||||
try _dumpRequest(Table(table).all().withStableOrder(), format: format, to: &stream)
|
||||
} else {
|
||||
// Use view ordering, if any (no guarantee of stable order).
|
||||
try _dumpRequest(Table(table).all(), format: format, to: &stream)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func _dumpContent(
|
||||
format: some DumpFormat,
|
||||
to stream: inout DumpStream)
|
||||
throws
|
||||
{
|
||||
try _dumpSchema(to: &stream)
|
||||
stream.margin()
|
||||
|
||||
let tables = try String
|
||||
.fetchAll(self, sql: """
|
||||
SELECT name
|
||||
FROM sqlite_master
|
||||
WHERE type = 'table'
|
||||
ORDER BY name COLLATE NOCASE
|
||||
""")
|
||||
.filter {
|
||||
try !ignoresObject(named: $0)
|
||||
}
|
||||
try _dumpTables(tables, format: format, tableHeader: .always, stableOrder: true, to: &stream)
|
||||
}
|
||||
|
||||
func _dumpSchema(
|
||||
to stream: inout DumpStream)
|
||||
throws
|
||||
{
|
||||
stream.writeln("sqlite_master")
|
||||
let sqlRows = try Row.fetchAll(self, sql: """
|
||||
SELECT sql || ';', name
|
||||
FROM sqlite_master
|
||||
WHERE sql IS NOT NULL
|
||||
ORDER BY
|
||||
tbl_name COLLATE NOCASE,
|
||||
CASE type WHEN 'table' THEN 'a' WHEN 'index' THEN 'aa' ELSE type END,
|
||||
name COLLATE NOCASE,
|
||||
sql
|
||||
""")
|
||||
for row in sqlRows {
|
||||
let name: String = row[1]
|
||||
if try ignoresObject(named: name) {
|
||||
continue
|
||||
}
|
||||
stream.writeln(row[0])
|
||||
}
|
||||
}
|
||||
|
||||
private func ignoresObject(named name: String) throws -> Bool {
|
||||
if Database.isSQLiteInternalTable(name) { return true }
|
||||
if Database.isGRDBInternalTable(name) { return true }
|
||||
if try isShadowTable(name) { return true }
|
||||
return false
|
||||
}
|
||||
|
||||
private func isShadowTable(_ tableName: String) throws -> Bool {
|
||||
#if GRDBCUSTOMSQLITE || GRDBCIPHER
|
||||
// Maybe SQLCipher is too old: check actual version
|
||||
if sqlite3_libversion_number() >= 3037000 {
|
||||
guard let table = try table(tableName) else {
|
||||
// Not a table
|
||||
return false
|
||||
}
|
||||
return table.kind == .shadow
|
||||
}
|
||||
#else
|
||||
if #available(iOS 15.4, macOS 12.4, tvOS 15.4, watchOS 8.5, *) { // SQLite 3.37+
|
||||
guard let table = try table(tableName) else {
|
||||
// Not a table
|
||||
return false
|
||||
}
|
||||
return table.kind == .shadow
|
||||
}
|
||||
#endif
|
||||
// Don't know
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/// Options for printing table names.
|
||||
public enum DumpTableHeaderOptions: Sendable {
|
||||
/// Table names are only printed when several tables are printed.
|
||||
case automatic
|
||||
|
||||
/// Table names are always printed.
|
||||
case always
|
||||
}
|
||||
Reference in New Issue
Block a user