-
Notifications
You must be signed in to change notification settings - Fork 114
Expand file tree
/
Copy pathQueryCursor.swift
More file actions
170 lines (150 loc) · 4.71 KB
/
QueryCursor.swift
File metadata and controls
170 lines (150 loc) · 4.71 KB
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
163
164
165
166
167
168
169
170
import Foundation
import GRDB
import GRDBSQLite
import StructuredQueriesCore
/// A cursor of a structured query.
///
/// Iterates over and decodes all of the rows of a structured query.
public class QueryCursor<Element>: DatabaseCursor {
public var _isDone = false
public let _statement: GRDB.Statement
@usableFromInline
var decoder: SQLiteQueryDecoder
@usableFromInline
init(db: Database, query: QueryFragment) throws {
(_statement, decoder) = try db.prepare(query: query)
}
deinit {
sqlite3_reset(_statement.sqliteStatement)
}
public func _element(sqliteStatement _: SQLiteStatement) throws -> Element {
fatalError("Abstract method should be overridden in subclass")
}
@usableFromInline
struct DecodingError: Error, CustomStringConvertible {
let columnIndex: Int
let columnName: String
let sql: String
@usableFromInline
init(columnIndex: Int, columnName: String, sql: String) {
self.columnIndex = columnIndex
self.columnName = columnName
self.sql = sql
}
@usableFromInline
var description: String {
"""
Expected column \(columnIndex) (\(columnName.debugDescription)) to not be NULL: …
\(sql)
"""
}
}
}
@usableFromInline
final class QueryValueCursor<QueryValue: QueryRepresentable>: QueryCursor<QueryValue.QueryOutput> {
public typealias Element = QueryValue.QueryOutput
// NB: Required to workaround a "Legacy previews execution" bug
// https://github.com/pointfreeco/sharing-grdb/pull/60
@usableFromInline
override init(db: Database, query: QueryFragment) throws {
try super.init(db: db, query: query)
}
@inlinable
public override func _element(sqliteStatement _: SQLiteStatement) throws -> Element {
do {
let element = try QueryValue(decoder: &decoder).queryOutput
decoder.next()
return element
} catch QueryDecodingError.missingRequiredColumn {
let columnIndex = Int(decoder.currentIndex) - 1
throw DecodingError(
columnIndex: columnIndex,
columnName: _statement.columnNames[columnIndex],
sql: _statement.sql
)
}
}
}
@available(iOS 17, macOS 14, tvOS 17, watchOS 10, *)
@usableFromInline
final class QueryPackCursor<
each QueryValue: QueryRepresentable
>: QueryCursor<(repeat (each QueryValue).QueryOutput)> {
public typealias Element = (repeat (each QueryValue).QueryOutput)
// NB: Required to workaround a "Legacy previews execution" bug
// https://github.com/pointfreeco/sharing-grdb/pull/60
@usableFromInline
override init(db: Database, query: QueryFragment) throws {
try super.init(db: db, query: query)
}
@inlinable
public override func _element(sqliteStatement _: SQLiteStatement) throws -> Element {
do {
let element = try decoder.decodeColumns((repeat each QueryValue).self)
decoder.next()
return element
} catch QueryDecodingError.missingRequiredColumn {
let columnIndex = Int(decoder.currentIndex) - 1
throw DecodingError(
columnIndex: columnIndex,
columnName: _statement.columnNames[columnIndex],
sql: _statement.sql
)
}
}
}
@usableFromInline
final class QueryVoidCursor: QueryCursor<Void> {
typealias Element = ()
// NB: Required to workaround a "Legacy previews execution" bug
// https://github.com/pointfreeco/sharing-grdb/pull/60
@usableFromInline
override init(db: Database, query: QueryFragment) throws {
try super.init(db: db, query: query)
}
@inlinable
override func _element(sqliteStatement _: SQLiteStatement) throws {
try decoder.decodeColumns(Void.self)
decoder.next()
}
}
extension Database {
@inlinable
func prepare(query: QueryFragment) throws -> (GRDB.Statement, SQLiteQueryDecoder) {
let queryString =
query.isEmpty
? "SELECT 1 WHERE 0 -- Empty query generated by StructuredQueries"
: query.string
let statement = try makeStatement(sql: queryString)
statement.arguments = try StatementArguments(query.bindings.map { try $0.databaseValue })
return (
statement,
SQLiteQueryDecoder(statement: statement.sqliteStatement)
)
}
}
extension QueryBinding {
@inlinable
var databaseValue: DatabaseValue {
get throws {
switch self {
case let .blob(blob):
return Data(blob).databaseValue
case let .date(date):
return date.iso8601String.databaseValue
case let .double(double):
return double.databaseValue
case let .int(int):
return int.databaseValue
case .null:
return .null
case let .text(text):
return text.databaseValue
case let .uuid(uuid):
return uuid.uuidString.lowercased().databaseValue
case let .invalid(error):
throw error
}
}
}
}