Skip to content

Commit d4c2f38

Browse files
authored
Allow bindings with optional values in PostgresBindings (#520)
1 parent 225c5c4 commit d4c2f38

File tree

2 files changed

+127
-0
lines changed

2 files changed

+127
-0
lines changed

Diff for: Sources/PostgresNIO/New/PostgresQuery.swift

+46
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,16 @@ public struct PostgresBindings: Sendable, Hashable {
172172
try self.append(value, context: .default)
173173
}
174174

175+
@inlinable
176+
public mutating func append<Value: PostgresThrowingDynamicTypeEncodable>(_ value: Optional<Value>) throws {
177+
switch value {
178+
case .none:
179+
self.appendNull()
180+
case let .some(value):
181+
try self.append(value)
182+
}
183+
}
184+
175185
@inlinable
176186
public mutating func append<Value: PostgresThrowingDynamicTypeEncodable, JSONEncoder: PostgresJSONEncoder>(
177187
_ value: Value,
@@ -181,11 +191,34 @@ public struct PostgresBindings: Sendable, Hashable {
181191
self.metadata.append(.init(value: value, protected: true))
182192
}
183193

194+
@inlinable
195+
public mutating func append<Value: PostgresThrowingDynamicTypeEncodable, JSONEncoder: PostgresJSONEncoder>(
196+
_ value: Optional<Value>,
197+
context: PostgresEncodingContext<JSONEncoder>
198+
) throws {
199+
switch value {
200+
case .none:
201+
self.appendNull()
202+
case let .some(value):
203+
try self.append(value, context: context)
204+
}
205+
}
206+
184207
@inlinable
185208
public mutating func append<Value: PostgresDynamicTypeEncodable>(_ value: Value) {
186209
self.append(value, context: .default)
187210
}
188211

212+
@inlinable
213+
public mutating func append<Value: PostgresDynamicTypeEncodable>(_ value: Optional<Value>) {
214+
switch value {
215+
case .none:
216+
self.appendNull()
217+
case let .some(value):
218+
self.append(value)
219+
}
220+
}
221+
189222
@inlinable
190223
public mutating func append<Value: PostgresDynamicTypeEncodable, JSONEncoder: PostgresJSONEncoder>(
191224
_ value: Value,
@@ -195,6 +228,19 @@ public struct PostgresBindings: Sendable, Hashable {
195228
self.metadata.append(.init(value: value, protected: true))
196229
}
197230

231+
@inlinable
232+
public mutating func append<Value: PostgresDynamicTypeEncodable, JSONEncoder: PostgresJSONEncoder>(
233+
_ value: Optional<Value>,
234+
context: PostgresEncodingContext<JSONEncoder>
235+
) {
236+
switch value {
237+
case .none:
238+
self.appendNull()
239+
case let .some(value):
240+
self.append(value, context: context)
241+
}
242+
}
243+
198244
@inlinable
199245
mutating func appendUnprotected<Value: PostgresEncodable, JSONEncoder: PostgresJSONEncoder>(
200246
_ value: Value,

Diff for: Tests/IntegrationTests/AsyncTests.swift

+81
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,87 @@ final class AsyncPostgresConnectionTests: XCTestCase {
476476
XCTFail("Unexpected error: \(String(describing: error))")
477477
}
478478
}
479+
480+
static let preparedStatementWithOptionalTestTable = "AsyncTestPreparedStatementWithOptionalTestTable"
481+
func testPreparedStatementWithOptionalBinding() async throws {
482+
let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1)
483+
defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) }
484+
let eventLoop = eventLoopGroup.next()
485+
486+
struct InsertPreparedStatement: PostgresPreparedStatement {
487+
static let name = "INSERT-AsyncTestPreparedStatementWithOptionalTestTable"
488+
489+
static let sql = #"INSERT INTO "\#(AsyncPostgresConnectionTests.preparedStatementWithOptionalTestTable)" (uuid) VALUES ($1);"#
490+
typealias Row = ()
491+
492+
var uuid: UUID?
493+
494+
func makeBindings() -> PostgresBindings {
495+
var bindings = PostgresBindings()
496+
bindings.append(self.uuid)
497+
return bindings
498+
}
499+
500+
func decodeRow(_ row: PostgresNIO.PostgresRow) throws -> Row {
501+
()
502+
}
503+
}
504+
505+
struct SelectPreparedStatement: PostgresPreparedStatement {
506+
static let name = "SELECT-AsyncTestPreparedStatementWithOptionalTestTable"
507+
508+
static let sql = #"SELECT id, uuid FROM "\#(AsyncPostgresConnectionTests.preparedStatementWithOptionalTestTable)" WHERE id <= $1;"#
509+
typealias Row = (Int, UUID?)
510+
511+
var id: Int
512+
513+
func makeBindings() -> PostgresBindings {
514+
var bindings = PostgresBindings()
515+
bindings.append(self.id)
516+
return bindings
517+
}
518+
519+
func decodeRow(_ row: PostgresNIO.PostgresRow) throws -> Row {
520+
try row.decode((Int, UUID?).self)
521+
}
522+
}
523+
524+
do {
525+
try await withTestConnection(on: eventLoop) { connection in
526+
try await connection.query("""
527+
CREATE TABLE IF NOT EXISTS "\(unescaped: Self.preparedStatementWithOptionalTestTable)" (
528+
id SERIAL PRIMARY KEY,
529+
uuid UUID
530+
)
531+
""",
532+
logger: .psqlTest
533+
)
534+
535+
_ = try await connection.execute(InsertPreparedStatement(uuid: nil), logger: .psqlTest)
536+
_ = try await connection.execute(InsertPreparedStatement(uuid: .init()), logger: .psqlTest)
537+
_ = try await connection.execute(InsertPreparedStatement(uuid: nil), logger: .psqlTest)
538+
_ = try await connection.execute(InsertPreparedStatement(uuid: .init()), logger: .psqlTest)
539+
_ = try await connection.execute(InsertPreparedStatement(uuid: nil), logger: .psqlTest)
540+
541+
let rows = try await connection.execute(SelectPreparedStatement(id: 3), logger: .psqlTest)
542+
var counter = 0
543+
for try await (id, uuid) in rows {
544+
Logger.psqlTest.info("Received row", metadata: [
545+
"id": "\(id)", "uuid": "\(String(describing: uuid))"
546+
])
547+
counter += 1
548+
}
549+
550+
try await connection.query("""
551+
DROP TABLE "\(unescaped: Self.preparedStatementWithOptionalTestTable)";
552+
""",
553+
logger: .psqlTest
554+
)
555+
}
556+
} catch {
557+
XCTFail("Unexpected error: \(String(describing: error))")
558+
}
559+
}
479560
}
480561

481562
extension XCTestCase {

0 commit comments

Comments
 (0)