-
-
Notifications
You must be signed in to change notification settings - Fork 744
/
Copy pathDebugDumpFormat.swift
162 lines (144 loc) · 4.8 KB
/
DebugDumpFormat.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
// Import C SQLite functions
#if GRDBCIPHER
import SQLCipher
#elseif SWIFT_PACKAGE
import GRDBSQLite
#elseif !GRDBCUSTOMSQLITE && !GRDBCIPHER
import SQLite3
#endif
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)
}
}