-
-
Notifications
You must be signed in to change notification settings - Fork 25
/
Copy pathFluentSQLiteDatabase.swift
157 lines (131 loc) · 6.61 KB
/
FluentSQLiteDatabase.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
import FluentSQL
import SQLiteKit
import NIOCore
import SQLiteNIO
import SQLKit
import FluentKit
struct FluentSQLiteDatabase: Database, SQLDatabase, SQLiteDatabase {
let database: any SQLiteDatabase
let context: DatabaseContext
let dataEncoder: SQLiteDataEncoder
let dataDecoder: SQLiteDataDecoder
let queryLogLevel: Logger.Level?
let inTransaction: Bool
private func adjustFluentQuery(_ original: DatabaseQuery, _ converted: any SQLExpression) -> any SQLExpression {
/// For `.create` query actions, we want to return the generated IDs, unless the `customIDKey` is the
/// empty string, which we use as a very hacky signal for "we don't implement this for composite IDs yet".
if case .create = original.action, original.customIDKey != .some(.string("")) {
return SQLKit.SQLList([converted, SQLReturning(.init((original.customIDKey ?? .id).description))], separator: SQLRaw(" "))
} else {
return converted
}
}
// Database
func execute(query: DatabaseQuery, onOutput: @escaping @Sendable (any DatabaseOutput) -> ()) -> EventLoopFuture<Void> {
/// SQLiteKit will handle applying the configured data decoder to each row when providing `SQLRow`s.
return self.execute(
sql: self.adjustFluentQuery(query, SQLQueryConverter(delegate: SQLiteConverterDelegate()).convert(query)),
{ onOutput($0.databaseOutput()) }
)
}
func execute(query: DatabaseQuery, onOutput: @escaping @Sendable (any DatabaseOutput) -> ()) async throws {
try await self.execute(
sql: self.adjustFluentQuery(query, SQLQueryConverter(delegate: SQLiteConverterDelegate()).convert(query)),
{ onOutput($0.databaseOutput()) }
)
}
func execute(schema: DatabaseSchema) -> EventLoopFuture<Void> {
var schema = schema
if schema.action == .update {
schema.updateFields = schema.updateFields.filter { switch $0 { // Filter out enum updates.
case .dataType(_, .enum(_)): return false
default: return true
} }
guard schema.createConstraints.isEmpty, schema.updateFields.isEmpty, schema.deleteConstraints.isEmpty else {
return self.eventLoop.makeFailedFuture(FluentSQLiteUnsupportedAlter())
}
if schema.createFields.isEmpty, schema.deleteFields.isEmpty { // If there were only enum updates, bail out.
return self.eventLoop.makeSucceededFuture(())
}
}
return self.execute(
sql: SQLSchemaConverter(delegate: SQLiteConverterDelegate()).convert(schema),
{ self.logger.debug("Unexpected row returned from schema query: \($0)") }
)
}
func execute(enum: DatabaseEnum) -> EventLoopFuture<Void> {
self.eventLoop.makeSucceededFuture(())
}
func withConnection<T>(_ closure: @escaping @Sendable (any Database) -> EventLoopFuture<T>) -> EventLoopFuture<T> {
self.eventLoop.makeFutureWithTask { try await self.withConnection { try await closure($0).get() } }
}
func withConnection<T>(_ closure: @escaping @Sendable (any Database) async throws -> T) async throws -> T {
try await self.withConnection {
try await closure(FluentSQLiteDatabase(
database: $0,
context: self.context,
dataEncoder: self.dataEncoder,
dataDecoder: self.dataDecoder,
queryLogLevel: self.queryLogLevel,
inTransaction: self.inTransaction
))
}
}
func transaction<T>(_ closure: @escaping @Sendable (any Database) -> EventLoopFuture<T>) -> EventLoopFuture<T> {
self.inTransaction ?
closure(self) :
self.eventLoop.makeFutureWithTask { try await self.transaction { try await closure($0).get() } }
}
func transaction<T>(_ closure: @escaping @Sendable (any Database) async throws -> T) async throws -> T {
guard !self.inTransaction else {
return try await closure(self)
}
return try await self.withConnection { conn in
let db = FluentSQLiteDatabase(
database: conn,
context: self.context,
dataEncoder: self.dataEncoder,
dataDecoder: self.dataDecoder,
queryLogLevel: self.queryLogLevel,
inTransaction: true
)
try await db.raw("BEGIN TRANSACTION").run()
do {
let result = try await closure(db)
try await db.raw("COMMIT TRANSACTION").run()
return result
} catch {
try? await db.raw("ROLLBACK TRANSACTION").run()
throw error
}
}
}
// SQLDatabase
var dialect: any SQLDialect {
self.database.sql(encoder: self.dataEncoder, decoder: self.dataDecoder, queryLogLevel: self.queryLogLevel).dialect
}
var version: (any SQLDatabaseReportedVersion)? {
self.database.sql(encoder: self.dataEncoder, decoder: self.dataDecoder, queryLogLevel: self.queryLogLevel).version
}
func execute(sql query: any SQLExpression, _ onRow: @escaping @Sendable (any SQLRow) -> ()) -> EventLoopFuture<Void> {
self.database.sql(encoder: self.dataEncoder, decoder: self.dataDecoder, queryLogLevel: self.queryLogLevel).execute(sql: query, onRow)
}
func execute(sql query: any SQLExpression, _ onRow: @escaping @Sendable (any SQLRow) -> ()) async throws {
try await self.database.sql(encoder: self.dataEncoder, decoder: self.dataDecoder, queryLogLevel: self.queryLogLevel).execute(sql: query, onRow)
}
func withSession<R>(_ closure: @escaping @Sendable (any SQLDatabase) async throws -> R) async throws -> R {
try await self.database.sql(encoder: self.dataEncoder, decoder: self.dataDecoder, queryLogLevel: self.queryLogLevel).withSession(closure)
}
// SQLiteDatabase
func query(_ query: String, _ binds: [SQLiteData], logger: Logger, _ onRow: @escaping @Sendable (SQLiteRow) -> Void) -> EventLoopFuture<Void> {
self.withConnection { $0.query(query, binds, logger: logger, onRow) }
}
func withConnection<T>(_ closure: @escaping @Sendable (SQLiteConnection) -> EventLoopFuture<T>) -> EventLoopFuture<T> {
self.database.withConnection(closure)
}
}
private struct FluentSQLiteUnsupportedAlter: Error, CustomStringConvertible {
var description: String {
"SQLite only supports adding columns in ALTER TABLE statements."
}
}