add
This commit is contained in:
@@ -1,357 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
extension DatabaseReader {
|
||||
/// Prints the results of all statements in the provided SQL.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // Prints
|
||||
/// // 1|Arthur|500
|
||||
/// // 2|Barbara|1000
|
||||
/// try dbQueue.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
|
||||
{
|
||||
try unsafeReentrantRead { db in
|
||||
try db.dumpSQL(sql, format: format, to: stream)
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the results of a request.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // Prints
|
||||
/// // 1|Arthur|500
|
||||
/// // 2|Barbara|1000
|
||||
/// try dbQueue.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
|
||||
{
|
||||
try unsafeReentrantRead { db in
|
||||
try db.dumpRequest(request, format: format, to: stream)
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the contents of the provided tables and views.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // player
|
||||
/// // 1|Arthur|500
|
||||
/// // 2|Barbara|1000
|
||||
/// //
|
||||
/// // team
|
||||
/// // 1|Red
|
||||
/// // 2|Blue
|
||||
/// try dbQueue.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
|
||||
{
|
||||
try unsafeReentrantRead { db in
|
||||
try db.dumpTables(
|
||||
tables,
|
||||
format: format,
|
||||
tableHeader: tableHeader,
|
||||
stableOrder: stableOrder,
|
||||
to: stream)
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the contents of the database.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// try dbQueue.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
|
||||
{
|
||||
try unsafeReentrantRead { db in
|
||||
try db.dumpContent(format: format, to: stream)
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints the schema of the database.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// try dbQueue.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
|
||||
{
|
||||
try unsafeReentrantRead { db in
|
||||
try db.dumpSchema(to: stream)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/// A type that prints database rows.
|
||||
///
|
||||
/// Types that conform to `DumpFormat` feed the printing methods such as
|
||||
/// ``DatabaseReader/dumpContent(format:to:)`` and
|
||||
/// ``Database/dumpSQL(_:format:to:)``.
|
||||
///
|
||||
/// Most built-in formats are inspired from the
|
||||
/// [output formats of the SQLite command line tool](https://sqlite.org/cli.html#changing_output_formats).
|
||||
///
|
||||
/// ## Topics
|
||||
///
|
||||
/// ### Built-in Formats
|
||||
///
|
||||
/// - ``debug(header:separator:nullValue:)``
|
||||
/// - ``json(encoder:)``
|
||||
/// - ``line(nullValue:)``
|
||||
/// - ``list(header:separator:nullValue:)``
|
||||
/// - ``quote(header:separator:)``
|
||||
///
|
||||
/// ### Supporting Types
|
||||
///
|
||||
/// - ``DebugDumpFormat``
|
||||
/// - ``JSONDumpFormat``
|
||||
/// - ``LineDumpFormat``
|
||||
/// - ``ListDumpFormat``
|
||||
/// - ``QuoteDumpFormat``
|
||||
/// - ``DumpStream``
|
||||
///
|
||||
/// ### Implementing a custom format
|
||||
///
|
||||
/// [**🔥 EXPERIMENTAL**](https://github.com/groue/GRDB.swift/blob/master/README.md#what-are-experimental-features)
|
||||
///
|
||||
/// - ``writeRow(_:statement:to:)``
|
||||
/// - ``finalize(_:statement:to:)``
|
||||
public protocol DumpFormat {
|
||||
/// Writes a row from the given statement.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - db: A connection to the database
|
||||
/// - statement: The iterated statement
|
||||
/// - stream: A stream for text output.
|
||||
mutating func writeRow(
|
||||
_ db: Database,
|
||||
statement: Statement,
|
||||
to stream: inout DumpStream) throws
|
||||
|
||||
/// All rows from the statement have been printed.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - db: A connection to the database
|
||||
/// - statement: The statement that was iterated.
|
||||
/// - stream: A stream for text output.
|
||||
mutating func finalize(
|
||||
_ db: Database,
|
||||
statement: Statement,
|
||||
to stream: inout DumpStream)
|
||||
}
|
||||
|
||||
/// A TextOutputStream that prints to standard output
|
||||
struct StandardOutputStream: TextOutputStream {
|
||||
func write(_ string: String) {
|
||||
print(string, terminator: "")
|
||||
}
|
||||
}
|
||||
|
||||
/// A text output stream suited for printing database content.
|
||||
///
|
||||
/// [**🔥 EXPERIMENTAL**](https://github.com/groue/GRDB.swift/blob/master/README.md#what-are-experimental-features)
|
||||
public struct DumpStream {
|
||||
var base: any TextOutputStream
|
||||
var needsMarginLine = false
|
||||
|
||||
init(_ base: (any TextOutputStream)?) {
|
||||
self.base = base ?? StandardOutputStream()
|
||||
}
|
||||
|
||||
/// Will write `"\n"` before the next non-empty string.
|
||||
public mutating func margin() {
|
||||
needsMarginLine = true
|
||||
}
|
||||
}
|
||||
|
||||
extension DumpStream: TextOutputStream {
|
||||
public mutating func write(_ string: String) {
|
||||
if needsMarginLine && !string.isEmpty {
|
||||
needsMarginLine = false
|
||||
if string.first != "\n" {
|
||||
base.write("\n")
|
||||
}
|
||||
}
|
||||
base.write(string)
|
||||
}
|
||||
}
|
||||
|
||||
extension TextOutputStream {
|
||||
mutating func writeln(_ string: String) {
|
||||
write(string)
|
||||
write("\n")
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
func leftPadding(toLength newLength: Int, withPad padString: String) -> String {
|
||||
precondition(padString.count == 1)
|
||||
if count < newLength {
|
||||
return String(repeating: padString, count: newLength - count) + self
|
||||
} else {
|
||||
let startIndex = index(startIndex, offsetBy: count - newLength)
|
||||
return String(self[startIndex...])
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,153 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// A format that prints one line per database row, suitable
|
||||
/// for debugging.
|
||||
///
|
||||
/// This format may change in future releases. It is not suitable for
|
||||
/// processing by other programs, or testing.
|
||||
///
|
||||
/// On each line, database values are separated by a separator (`|`
|
||||
/// by default).
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // Arthur|500
|
||||
/// // Barbara|1000
|
||||
/// // Craig|200
|
||||
/// try db.dumpRequest(Player.all(), format: .debug())
|
||||
/// ```
|
||||
public struct DebugDumpFormat: Sendable {
|
||||
/// A boolean value indicating if column labels are printed as the first
|
||||
/// line of output.
|
||||
public var header: Bool
|
||||
|
||||
/// The separator between values.
|
||||
public var separator: String
|
||||
|
||||
/// The string to print for NULL values.
|
||||
public var nullValue: String
|
||||
|
||||
private var firstRow = true
|
||||
|
||||
/// Creates a `DebugDumpFormat`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - header: A boolean value indicating if column labels are printed
|
||||
/// as the first line of output.
|
||||
/// - separator: The separator between values.
|
||||
/// - nullValue: The string to print for NULL values.
|
||||
public init(
|
||||
header: Bool = false,
|
||||
separator: String = "|",
|
||||
nullValue: String = "")
|
||||
{
|
||||
self.header = header
|
||||
self.separator = separator
|
||||
self.nullValue = nullValue
|
||||
}
|
||||
}
|
||||
|
||||
extension DebugDumpFormat: DumpFormat {
|
||||
public mutating func writeRow(
|
||||
_ db: Database,
|
||||
statement: Statement,
|
||||
to stream: inout DumpStream)
|
||||
{
|
||||
if firstRow {
|
||||
firstRow = false
|
||||
if header {
|
||||
stream.writeln(statement.columnNames.joined(separator: separator))
|
||||
}
|
||||
}
|
||||
|
||||
let sqliteStatement = statement.sqliteStatement
|
||||
var first = true
|
||||
for index in 0..<sqlite3_column_count(sqliteStatement) {
|
||||
// Don't log GRDB columns
|
||||
let column = String(cString: sqlite3_column_name(sqliteStatement, index))
|
||||
if column.starts(with: "grdb_") { continue }
|
||||
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
stream.write(separator)
|
||||
}
|
||||
|
||||
stream.write(formattedValue(db, in: sqliteStatement, at: index))
|
||||
}
|
||||
stream.write("\n")
|
||||
}
|
||||
|
||||
public mutating func finalize(
|
||||
_ db: Database,
|
||||
statement: Statement,
|
||||
to stream: inout DumpStream)
|
||||
{
|
||||
firstRow = true
|
||||
}
|
||||
|
||||
private func formattedValue(_ db: Database, in sqliteStatement: SQLiteStatement, at index: CInt) -> String {
|
||||
switch sqlite3_column_type(sqliteStatement, index) {
|
||||
case SQLITE_NULL:
|
||||
return nullValue
|
||||
|
||||
case SQLITE_INTEGER:
|
||||
return Int64(sqliteStatement: sqliteStatement, index: index).description
|
||||
|
||||
case SQLITE_FLOAT:
|
||||
return Double(sqliteStatement: sqliteStatement, index: index).description
|
||||
|
||||
case SQLITE_BLOB:
|
||||
let data = Data(sqliteStatement: sqliteStatement, index: index)
|
||||
if let string = String(data: data, encoding: .utf8) {
|
||||
return string
|
||||
} else if data.count == 16, let blob = sqlite3_column_blob(sqliteStatement, index) {
|
||||
let uuid = UUID(uuid: blob.assumingMemoryBound(to: uuid_t.self).pointee)
|
||||
return uuid.uuidString
|
||||
} else {
|
||||
return try! data.sqlExpression.quotedSQL(db)
|
||||
}
|
||||
|
||||
case SQLITE_TEXT:
|
||||
return String(sqliteStatement: sqliteStatement, index: index)
|
||||
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DumpFormat where Self == DebugDumpFormat {
|
||||
/// A format that prints one line per database row, suitable
|
||||
/// for debugging.
|
||||
///
|
||||
/// This format may change in future releases. It is not suitable for
|
||||
/// processing by other programs, or testing.
|
||||
///
|
||||
/// On each line, database values are separated by a separator (`|`
|
||||
/// by default).
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // Arthur|500
|
||||
/// // Barbara|1000
|
||||
/// // Craig|200
|
||||
/// try db.dumpRequest(Player.all(), format: .debug())
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - header: A boolean value indicating if column labels are printed
|
||||
/// as the first line of output.
|
||||
/// - separator: The separator between values.
|
||||
/// - nullValue: The string to print for NULL values.
|
||||
public static func debug(
|
||||
header: Bool = false,
|
||||
separator: String = "|",
|
||||
nullValue: String = "")
|
||||
-> Self
|
||||
{
|
||||
DebugDumpFormat(header: header, separator: separator, nullValue: nullValue)
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// A format that prints database rows as a JSON array.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // [{"name":"Arthur","score":500},
|
||||
/// // {"name":"Barbara","score":1000}]
|
||||
/// try db.dumpRequest(Player.all(), format: .json())
|
||||
/// ```
|
||||
///
|
||||
/// For a pretty-printed output, customize the JSON encoder:
|
||||
///
|
||||
/// ```swift
|
||||
/// // [
|
||||
/// // {
|
||||
/// // "name": "Arthur",
|
||||
/// // "score": 500
|
||||
/// // },
|
||||
/// // {
|
||||
/// // "name": "Barbara",
|
||||
/// // "score": 1000
|
||||
/// // }
|
||||
/// // ]
|
||||
/// let encoder = JSONDumpFormat.defaultEncoder
|
||||
/// encoder.outputFormatting = .prettyPrinted
|
||||
/// try db.dumpRequest(Player.all(), format: .json(encoder))
|
||||
/// ```
|
||||
public struct JSONDumpFormat: Sendable {
|
||||
/// The default `JSONEncoder` for database values.
|
||||
///
|
||||
/// It is configured so that blob values (`Data`) are encoded in the
|
||||
/// base64 format, and Non-conforming floats are encoded as "inf",
|
||||
/// "-inf" and "nan".
|
||||
///
|
||||
/// It uses the output formatting option
|
||||
/// `JSONEncoder.OutputFormatting.withoutEscapingSlashes` when available.
|
||||
///
|
||||
/// Modifying the returned encoder does not affect any encoder returned
|
||||
/// by future calls to this method. It is always safe to use the
|
||||
/// returned encoder as a starting point for additional customization.
|
||||
public static var defaultEncoder: JSONEncoder {
|
||||
// This encoder MUST NOT CHANGE, because some people rely on this format.
|
||||
let encoder = JSONEncoder()
|
||||
if #available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) {
|
||||
encoder.outputFormatting = .withoutEscapingSlashes
|
||||
}
|
||||
encoder.nonConformingFloatEncodingStrategy = .convertToString(
|
||||
positiveInfinity: "inf",
|
||||
negativeInfinity: "-inf",
|
||||
nan: "nan")
|
||||
encoder.dataEncodingStrategy = .base64
|
||||
return encoder
|
||||
}
|
||||
|
||||
/// The JSONEncoder that formats individual database values.
|
||||
public var encoder: JSONEncoder
|
||||
|
||||
var firstRow = true
|
||||
|
||||
/// Creates a `JSONDumpFormat`.
|
||||
///
|
||||
/// - Parameter encoder: The JSONEncoder that formats individual
|
||||
/// database values. If the outputFormatting` options contain
|
||||
/// `.prettyPrinted`, the printed array has one value per line.
|
||||
public init(encoder: JSONEncoder = JSONDumpFormat.defaultEncoder) {
|
||||
self.encoder = encoder
|
||||
}
|
||||
}
|
||||
|
||||
extension JSONDumpFormat: DumpFormat {
|
||||
public mutating func writeRow(
|
||||
_ db: Database,
|
||||
statement: Statement,
|
||||
to stream: inout DumpStream)
|
||||
throws {
|
||||
if firstRow {
|
||||
firstRow = false
|
||||
stream.write("[")
|
||||
if encoder.outputFormatting.contains(.prettyPrinted) {
|
||||
stream.write("\n")
|
||||
}
|
||||
} else {
|
||||
stream.write(",\n")
|
||||
}
|
||||
|
||||
if encoder.outputFormatting.contains(.prettyPrinted) {
|
||||
stream.write(" ")
|
||||
}
|
||||
stream.write("{")
|
||||
let sqliteStatement = statement.sqliteStatement
|
||||
var first = true
|
||||
for index in 0..<sqlite3_column_count(sqliteStatement) {
|
||||
// Don't log GRDB columns
|
||||
let column = String(cString: sqlite3_column_name(sqliteStatement, index))
|
||||
if column.starts(with: "grdb_") {
|
||||
continue
|
||||
}
|
||||
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
stream.write(",")
|
||||
}
|
||||
|
||||
if encoder.outputFormatting.contains(.prettyPrinted) {
|
||||
stream.write("\n ")
|
||||
}
|
||||
try stream.write(formattedValue(column))
|
||||
stream.write(":")
|
||||
try stream.write(formattedValue(db, in: sqliteStatement, at: index))
|
||||
}
|
||||
if encoder.outputFormatting.contains(.prettyPrinted) {
|
||||
stream.write("\n ")
|
||||
}
|
||||
stream.write("}")
|
||||
}
|
||||
|
||||
public mutating func finalize(
|
||||
_ db: Database,
|
||||
statement: Statement,
|
||||
to stream: inout DumpStream)
|
||||
{
|
||||
if firstRow {
|
||||
if !statement.columnNames.isEmpty {
|
||||
stream.writeln("[]")
|
||||
}
|
||||
} else {
|
||||
if encoder.outputFormatting.contains(.prettyPrinted) {
|
||||
stream.write("\n")
|
||||
}
|
||||
stream.writeln("]")
|
||||
}
|
||||
firstRow = true
|
||||
}
|
||||
|
||||
private func formattedValue(_ db: Database, in sqliteStatement: SQLiteStatement, at index: CInt) throws -> String {
|
||||
switch sqlite3_column_type(sqliteStatement, index) {
|
||||
case SQLITE_NULL:
|
||||
return "null"
|
||||
|
||||
case SQLITE_INTEGER:
|
||||
return try formattedValue(Int64(sqliteStatement: sqliteStatement, index: index))
|
||||
|
||||
case SQLITE_FLOAT:
|
||||
return try formattedValue(Double(sqliteStatement: sqliteStatement, index: index))
|
||||
|
||||
case SQLITE_BLOB:
|
||||
return try formattedValue(Data(sqliteStatement: sqliteStatement, index: index))
|
||||
|
||||
case SQLITE_TEXT:
|
||||
return try formattedValue(String(sqliteStatement: sqliteStatement, index: index))
|
||||
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
private func formattedValue(_ value: some Encodable) throws -> String {
|
||||
let data = try encoder.encode(value)
|
||||
guard let string = String(data: data, encoding: .utf8) else {
|
||||
throw EncodingError.invalidValue(data, .init(codingPath: [], debugDescription: "Invalid JSON data"))
|
||||
}
|
||||
return string
|
||||
}
|
||||
}
|
||||
|
||||
extension DumpFormat where Self == JSONDumpFormat {
|
||||
/// A format that prints database rows as a JSON array.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // [{"name":"Arthur","score":500},
|
||||
/// // {"name":"Barbara","score":1000}]
|
||||
/// try db.dumpRequest(Player.all(), format: .json())
|
||||
/// ```
|
||||
///
|
||||
/// For a pretty-printed output, customize the JSON encoder:
|
||||
///
|
||||
/// ```swift
|
||||
/// // [
|
||||
/// // {
|
||||
/// // "name": "Arthur",
|
||||
/// // "score": 500
|
||||
/// // },
|
||||
/// // {
|
||||
/// // "name": "Barbara",
|
||||
/// // "score": 1000
|
||||
/// // }
|
||||
/// // ]
|
||||
/// let encoder = JSONDumpFormat.defaultEncoder
|
||||
/// encoder.outputFormatting = .prettyPrinted
|
||||
/// try db.dumpRequest(Player.all(), format: .json(encoder))
|
||||
/// ```
|
||||
///
|
||||
/// - Parameter encoder: The JSONEncoder that formats individual
|
||||
/// database values. If the outputFormatting` options contain
|
||||
/// `.prettyPrinted`, the printed array has one value per line.
|
||||
public static func json(encoder: JSONEncoder = JSONDumpFormat.defaultEncoder) -> Self {
|
||||
JSONDumpFormat(encoder: encoder)
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// A format that prints one line per database value. All blob values
|
||||
/// are interpreted as strings.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // name = Arthur
|
||||
/// // score = 500
|
||||
/// //
|
||||
/// // name = Barbara
|
||||
/// // score = 1000
|
||||
/// try db.dumpRequest(Player.all(), format: .line())
|
||||
/// ```
|
||||
public struct LineDumpFormat: Sendable {
|
||||
/// The string to print for NULL values.
|
||||
public var nullValue: String
|
||||
|
||||
var firstRow = true
|
||||
|
||||
/// Creates a `LineDumpFormat`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - nullValue: The string to print for NULL values.
|
||||
public init(
|
||||
nullValue: String = "")
|
||||
{
|
||||
self.nullValue = nullValue
|
||||
}
|
||||
}
|
||||
|
||||
extension LineDumpFormat: DumpFormat {
|
||||
public mutating func writeRow(
|
||||
_ db: Database,
|
||||
statement: Statement,
|
||||
to stream: inout DumpStream)
|
||||
{
|
||||
var lines: [(column: String, value: String)] = []
|
||||
let sqliteStatement = statement.sqliteStatement
|
||||
for index in 0..<sqlite3_column_count(sqliteStatement) {
|
||||
// Don't log GRDB columns
|
||||
let column = String(cString: sqlite3_column_name(sqliteStatement, index))
|
||||
if column.starts(with: "grdb_") { continue }
|
||||
|
||||
lines.append((
|
||||
column: column,
|
||||
value: formattedValue(db, in: sqliteStatement, at: index)))
|
||||
}
|
||||
|
||||
if lines.isEmpty { return }
|
||||
|
||||
if firstRow {
|
||||
firstRow = false
|
||||
} else {
|
||||
stream.write("\n")
|
||||
}
|
||||
|
||||
let columnWidth = lines.map(\.column.count).max()!
|
||||
for line in lines {
|
||||
stream.write(line.column.leftPadding(toLength: columnWidth, withPad: " "))
|
||||
stream.write(" = ")
|
||||
stream.writeln(line.value)
|
||||
}
|
||||
}
|
||||
|
||||
public mutating func finalize(
|
||||
_ db: Database,
|
||||
statement: Statement,
|
||||
to stream: inout DumpStream)
|
||||
{
|
||||
if firstRow == false {
|
||||
stream.margin()
|
||||
}
|
||||
firstRow = true
|
||||
}
|
||||
|
||||
func formattedValue(
|
||||
_ db: Database,
|
||||
in sqliteStatement: SQLiteStatement,
|
||||
at index: CInt)
|
||||
-> String
|
||||
{
|
||||
switch sqlite3_column_type(sqliteStatement, index) {
|
||||
case SQLITE_NULL:
|
||||
return nullValue
|
||||
|
||||
case SQLITE_INTEGER:
|
||||
return Int64(sqliteStatement: sqliteStatement, index: index).description
|
||||
|
||||
case SQLITE_FLOAT:
|
||||
return Double(sqliteStatement: sqliteStatement, index: index).description
|
||||
|
||||
case SQLITE_BLOB, SQLITE_TEXT:
|
||||
return String(sqliteStatement: sqliteStatement, index: index)
|
||||
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DumpFormat where Self == LineDumpFormat {
|
||||
/// A format that prints one line per database value. All blob values
|
||||
/// are interpreted as strings.
|
||||
///
|
||||
/// On each line, database values are separated by a separator (`|`
|
||||
/// by default). Blob values are interpreted as UTF8 strings.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // name = Arthur
|
||||
/// // score = 500
|
||||
/// //
|
||||
/// // name = Barbara
|
||||
/// // score = 1000
|
||||
/// try db.dumpRequest(Player.all(), format: .line())
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - nullValue: The string to print for NULL values.
|
||||
public static func line(
|
||||
nullValue: String = "")
|
||||
-> Self
|
||||
{
|
||||
LineDumpFormat(nullValue: nullValue)
|
||||
}
|
||||
}
|
||||
@@ -1,141 +0,0 @@
|
||||
import Foundation
|
||||
|
||||
/// A format that prints one line per database row. All blob values
|
||||
/// are interpreted as strings.
|
||||
///
|
||||
/// On each line, database values are separated by a separator (`|`
|
||||
/// by default). Blob values are interpreted as UTF8 strings.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // Arthur|500
|
||||
/// // Barbara|1000
|
||||
/// // Craig|200
|
||||
/// try db.dumpRequest(Player.all(), format: .list())
|
||||
/// ```
|
||||
public struct ListDumpFormat: Sendable {
|
||||
/// A boolean value indicating if column labels are printed as the first
|
||||
/// line of output.
|
||||
public var header: Bool
|
||||
|
||||
/// The separator between values.
|
||||
public var separator: String
|
||||
|
||||
/// The string to print for NULL values.
|
||||
public var nullValue: String
|
||||
|
||||
private var firstRow = true
|
||||
|
||||
/// Creates a `ListDumpFormat`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - header: A boolean value indicating if column labels are printed
|
||||
/// as the first line of output.
|
||||
/// - separator: The separator between values.
|
||||
/// - nullValue: The string to print for NULL values.
|
||||
public init(
|
||||
header: Bool = false,
|
||||
separator: String = "|",
|
||||
nullValue: String = "")
|
||||
{
|
||||
self.header = header
|
||||
self.separator = separator
|
||||
self.nullValue = nullValue
|
||||
}
|
||||
}
|
||||
|
||||
extension ListDumpFormat: DumpFormat {
|
||||
public mutating func writeRow(
|
||||
_ db: Database,
|
||||
statement: Statement,
|
||||
to stream: inout DumpStream)
|
||||
{
|
||||
if firstRow {
|
||||
firstRow = false
|
||||
if header {
|
||||
stream.writeln(statement.columnNames.joined(separator: separator))
|
||||
}
|
||||
}
|
||||
|
||||
let sqliteStatement = statement.sqliteStatement
|
||||
var first = true
|
||||
for index in 0..<sqlite3_column_count(sqliteStatement) {
|
||||
// Don't log GRDB columns
|
||||
let column = String(cString: sqlite3_column_name(sqliteStatement, index))
|
||||
if column.starts(with: "grdb_") { continue }
|
||||
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
stream.write(separator)
|
||||
}
|
||||
|
||||
stream.write(formattedValue(db, in: sqliteStatement, at: index))
|
||||
}
|
||||
stream.write("\n")
|
||||
}
|
||||
|
||||
public mutating func finalize(
|
||||
_ db: Database,
|
||||
statement: Statement,
|
||||
to stream: inout DumpStream)
|
||||
{
|
||||
firstRow = true
|
||||
}
|
||||
|
||||
func formattedValue(
|
||||
_ db: Database,
|
||||
in sqliteStatement: SQLiteStatement,
|
||||
at index: CInt)
|
||||
-> String
|
||||
{
|
||||
switch sqlite3_column_type(sqliteStatement, index) {
|
||||
case SQLITE_NULL:
|
||||
return nullValue
|
||||
|
||||
case SQLITE_INTEGER:
|
||||
return Int64(sqliteStatement: sqliteStatement, index: index).description
|
||||
|
||||
case SQLITE_FLOAT:
|
||||
return Double(sqliteStatement: sqliteStatement, index: index).description
|
||||
|
||||
case SQLITE_BLOB, SQLITE_TEXT:
|
||||
return String(sqliteStatement: sqliteStatement, index: index)
|
||||
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension DumpFormat where Self == ListDumpFormat {
|
||||
/// A format that prints one line per database row. All blob values
|
||||
/// are interpreted as strings.
|
||||
///
|
||||
/// On each line, database values are separated by a separator (`|`
|
||||
/// by default). Blob values are interpreted as UTF8 strings.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // Arthur|500
|
||||
/// // Barbara|1000
|
||||
/// // Craig|200
|
||||
/// try db.dumpRequest(Player.all(), format: .list())
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - header: A boolean value indicating if column labels are printed
|
||||
/// as the first line of output.
|
||||
/// - separator: The separator between values.
|
||||
/// - nullValue: The string to print for NULL values.
|
||||
public static func list(
|
||||
header: Bool = false,
|
||||
separator: String = "|",
|
||||
nullValue: String = "")
|
||||
-> Self
|
||||
{
|
||||
ListDumpFormat(header: header, separator: separator, nullValue: nullValue)
|
||||
}
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
/// A format that prints one line per database row, formatting values
|
||||
/// as SQL literals.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // 'Arthur',500
|
||||
/// // 'Barbara',1000
|
||||
/// // 'Craig',200
|
||||
/// try db.dumpRequest(Player.all(), format: .quote())
|
||||
/// ```
|
||||
public struct QuoteDumpFormat: Sendable {
|
||||
/// A boolean value indicating if column labels are printed as the first
|
||||
/// line of output.
|
||||
public var header: Bool
|
||||
|
||||
/// The separator between values.
|
||||
public var separator: String
|
||||
|
||||
var firstRow = true
|
||||
|
||||
/// Creates a `QuoteDumpFormat`.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - header: A boolean value indicating if column labels are printed
|
||||
/// as the first line of output.
|
||||
/// - separator: The separator between values.
|
||||
public init(
|
||||
header: Bool = false,
|
||||
separator: String = ",")
|
||||
{
|
||||
self.header = header
|
||||
self.separator = separator
|
||||
}
|
||||
}
|
||||
|
||||
extension QuoteDumpFormat: DumpFormat {
|
||||
public mutating func writeRow(
|
||||
_ db: Database,
|
||||
statement: Statement,
|
||||
to stream: inout DumpStream)
|
||||
{
|
||||
if firstRow {
|
||||
firstRow = false
|
||||
if header {
|
||||
stream.writeln(statement.columnNames
|
||||
.map { try! $0.sqlExpression.quotedSQL(db) }
|
||||
.joined(separator: separator))
|
||||
}
|
||||
}
|
||||
|
||||
let sqliteStatement = statement.sqliteStatement
|
||||
var first = true
|
||||
for index in 0..<sqlite3_column_count(sqliteStatement) {
|
||||
// Don't log GRDB columns
|
||||
let column = String(cString: sqlite3_column_name(sqliteStatement, index))
|
||||
if column.starts(with: "grdb_") { continue }
|
||||
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
stream.write(separator)
|
||||
}
|
||||
|
||||
let dbValue = DatabaseValue(sqliteStatement: sqliteStatement, index: index)
|
||||
try! stream.write(dbValue.sqlExpression.quotedSQL(db))
|
||||
}
|
||||
|
||||
stream.write("\n")
|
||||
}
|
||||
|
||||
public mutating func finalize(
|
||||
_ db: Database,
|
||||
statement: Statement,
|
||||
to stream: inout DumpStream)
|
||||
{
|
||||
firstRow = true
|
||||
}
|
||||
}
|
||||
|
||||
extension DumpFormat where Self == QuoteDumpFormat {
|
||||
/// A format that prints one line per database row, formatting values
|
||||
/// as SQL literals.
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// ```swift
|
||||
/// // 'Arthur',500
|
||||
/// // 'Barbara',1000
|
||||
/// // 'Craig',200
|
||||
/// try db.dumpRequest(Player.all(), format: .quote())
|
||||
/// ```
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - header: A boolean value indicating if column labels are printed
|
||||
/// as the first line of output.
|
||||
/// - separator: The separator between values.
|
||||
public static func quote(header: Bool = false, separator: String = ",") -> Self {
|
||||
QuoteDumpFormat(header: header, separator: separator)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user