-
-
Notifications
You must be signed in to change notification settings - Fork 80
/
Copy pathPostgresDatabase+PreparedQuery.swift
79 lines (69 loc) · 2.76 KB
/
PostgresDatabase+PreparedQuery.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
import NIOCore
import NIOConcurrencyHelpers
import struct Foundation.UUID
extension PostgresDatabase {
public func prepare(query: String) -> EventLoopFuture<PreparedQuery> {
let name = "nio-postgres-\(UUID().uuidString)"
let request = PrepareQueryRequest(query, as: name)
return self.send(PostgresCommands.prepareQuery(request: request), logger: self.logger).map { _ in
// we can force unwrap the prepared here, since in a success case it must be set
// in the send method of `PostgresDatabase`. We do this dirty trick to work around
// the fact that the send method only returns an `EventLoopFuture<Void>`.
// Eventually we should move away from the `PostgresDatabase.send` API.
request.prepared!
}
}
@preconcurrency
public func prepare(query: String, handler: @Sendable @escaping (PreparedQuery) -> EventLoopFuture<[[PostgresRow]]>) -> EventLoopFuture<[[PostgresRow]]> {
prepare(query: query)
.flatMap { preparedQuery in
handler(preparedQuery)
.flatMap { results in
preparedQuery.deallocate().map { results }
}
}
}
}
public struct PreparedQuery: Sendable {
let underlying: PSQLPreparedStatement
let database: PostgresDatabase
init(underlying: PSQLPreparedStatement, database: PostgresDatabase) {
self.underlying = underlying
self.database = database
}
public func execute(_ binds: [PostgresData] = []) -> EventLoopFuture<[PostgresRow]> {
let rowsBoxed = NIOLockedValueBox([PostgresRow]())
return self.execute(binds) { row in
rowsBoxed.withLockedValue {
$0.append(row)
}
}.map { rowsBoxed.withLockedValue { $0 } }
}
@preconcurrency
public func execute(_ binds: [PostgresData] = [], _ onRow: @Sendable @escaping (PostgresRow) throws -> ()) -> EventLoopFuture<Void> {
let command = PostgresCommands.executePreparedStatement(query: self, binds: binds, onRow: onRow)
return self.database.send(command, logger: self.database.logger)
}
public func deallocate() -> EventLoopFuture<Void> {
self.underlying.connection.close(.preparedStatement(self.underlying.name), logger: self.database.logger)
}
}
final class PrepareQueryRequest: Sendable {
let query: String
let name: String
var prepared: PreparedQuery? {
get {
self._prepared.withLockedValue { $0 }
}
set {
self._prepared.withLockedValue {
$0 = newValue
}
}
}
let _prepared: NIOLockedValueBox<PreparedQuery?> = .init(nil)
init(_ query: String, as name: String) {
self.query = query
self.name = name
}
}