-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDatabaseClientPostgres.swift
More file actions
159 lines (147 loc) · 5.21 KB
/
DatabaseClientPostgres.swift
File metadata and controls
159 lines (147 loc) · 5.21 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
//
// DatabaseClientPostgres.swift
// feather-database-postgres
//
// Created by Tibor Bödecs on 2026. 01. 10..
//
import FeatherDatabase
import Logging
import PostgresNIO
/// A Postgres-backed database client.
///
/// Use this client to execute queries and manage transactions on Postgres.
public struct DatabaseClientPostgres: DatabaseClient {
public typealias Connection = DatabaseConnectionPostgres
var client: PostgresNIO.PostgresClient
let logger: Logger
/// Create a Postgres database client.
///
/// Use this initializer to provide an existing Postgres client.
/// - Parameters:
/// - client: The underlying Postgres client.
/// - logger: The logger for database operations.
public init(
client: PostgresNIO.PostgresClient,
logger: Logger
) {
self.client = client
self.logger = logger
}
// MARK: - database api
/// Execute work using a managed Postgres connection.
///
/// The closure receives a Postgres connection for the duration of the call.
/// - Parameter: closure: A closure that receives the connection.
/// - Throws: A `DatabaseError` if connection handling fails.
/// - Returns: The query result produced by the closure.
@discardableResult
public func withConnection<T>(
_ closure: (Connection) async throws -> T,
) async throws(DatabaseError) -> T {
let logger = self.logger
let body: (PostgresConnection) async throws -> T = { connection in
try await closure(
DatabaseConnectionPostgres(
connection: connection,
logger: logger
)
)
}
do {
return try await client.withConnection(body)
}
catch let error as DatabaseError {
throw error
}
catch {
throw .connection(error)
}
}
/// Execute work inside a Postgres transaction.
///
/// The closure is wrapped in a transactional scope.
/// - Parameter: closure: A closure that receives the connection.
/// - Throws: A `DatabaseError` if the transaction fails.
/// - Returns: The query result produced by the closure.
@discardableResult
public func withTransaction<T>(
_ closure: (Connection) async throws -> T,
) async throws(DatabaseError) -> T {
let logger = self.logger
let beginQuery = PostgresQuery(unsafeSQL: "BEGIN", binds: .init())
let commitQuery = PostgresQuery(unsafeSQL: "COMMIT", binds: .init())
let rollbackQuery = PostgresQuery(unsafeSQL: "ROLLBACK", binds: .init())
do {
return try await client.withConnection { connection in
let databaseConnection = DatabaseConnectionPostgres(
connection: connection,
logger: logger
)
do {
_ = try await connection.query(beginQuery, logger: logger)
}
catch {
throw DatabaseError.transaction(
DatabaseTransactionErrorPostgres(
beginError: error
)
)
}
do {
let result = try await closure(databaseConnection)
do {
_ = try await connection.query(
commitQuery,
logger: logger
)
return result
}
catch {
let commitError = error
var rollbackError: (any Error)?
do {
_ = try await connection.query(
rollbackQuery,
logger: logger
)
}
catch {
rollbackError = error
}
throw DatabaseError.transaction(
DatabaseTransactionErrorPostgres(
commitError: commitError,
rollbackError: rollbackError
)
)
}
}
catch {
let closureError = error
var rollbackError: (any Error)?
do {
_ = try await connection.query(
rollbackQuery,
logger: logger
)
}
catch {
rollbackError = error
}
throw DatabaseError.transaction(
DatabaseTransactionErrorPostgres(
closureError: closureError,
rollbackError: rollbackError
)
)
}
}
}
catch let error as DatabaseError {
throw error
}
catch {
throw .connection(error)
}
}
}