Skip to content

Commit e18b7f8

Browse files
fabianfettglbrntt
andauthored
Use PSQLFormat when encoding and decoding (vapor#158)
Postgres supports two encodings on the wire text (0) and binary (1). Up until now the new `PSQLDecodable` only supported binary encoding. - This PR does not change any public API - Rename `PSQLFormatCode` to `PSQLFormat` - Add `PSQLFormat` as parameter to the decode function. - Add `psqlFormat: PSQLFormat` as protocol requirement for `PSQLEncodable` - All existing types encode into and decode from `.binary` format only (we can change this with follow up PRs) Co-authored-by: George Barnett <gbarnett@apple.com>
1 parent 8527fef commit e18b7f8

35 files changed

+438
-179
lines changed

Sources/PostgresNIO/Connection/PostgresConnection+Database.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extension PostgresConnection: PostgresDatabase {
2323
dataType: PostgresDataType(UInt32(column.dataType.rawValue)),
2424
dataTypeSize: column.dataTypeSize,
2525
dataTypeModifier: column.dataTypeModifier,
26-
formatCode: .init(psqlFormatCode: column.formatCode)
26+
formatCode: .init(psqlFormatCode: column.format)
2727
)
2828
}
2929

Sources/PostgresNIO/Connection/PostgresDatabase+PreparedQuery.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public struct PreparedQuery {
4343
dataType: PostgresDataType(UInt32(column.dataType.rawValue)),
4444
dataTypeSize: column.dataTypeSize,
4545
dataTypeModifier: column.dataTypeModifier,
46-
formatCode: .init(psqlFormatCode: column.formatCode)
46+
formatCode: .init(psqlFormatCode: column.format)
4747
)
4848
}
4949

Sources/PostgresNIO/New/Connection State Machine/ExtendedQueryStateMachine.swift

+16-3
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,20 @@ struct ExtendedQueryStateMachine {
114114
}
115115

116116
return self.avoidingStateMachineCoW { state -> Action in
117-
state = .rowDescriptionReceived(queryContext, rowDescription.columns)
117+
// In Postgres extended queries we receive the `rowDescription` before we send the
118+
// `Bind` message. Well actually it's vice versa, but this is only true since we do
119+
// pipelining during a query.
120+
//
121+
// In the actual protocol description we receive a rowDescription before the Bind
122+
123+
// In Postgres extended queries we always request the response rows to be returned in
124+
// `.binary` format.
125+
let columns = rowDescription.columns.map { column -> PSQLBackendMessage.RowDescription.Column in
126+
var column = column
127+
column.format = .binary
128+
return column
129+
}
130+
state = .rowDescriptionReceived(queryContext, columns)
118131
return .wait
119132
}
120133
}
@@ -157,7 +170,7 @@ struct ExtendedQueryStateMachine {
157170

158171
return self.avoidingStateMachineCoW { state -> Action in
159172
let row = dataRow.columns.enumerated().map { (index, buffer) in
160-
PSQLData(bytes: buffer, dataType: columns[index].dataType)
173+
PSQLData(bytes: buffer, dataType: columns[index].dataType, format: columns[index].format)
161174
}
162175
buffer.append(row)
163176
state = .bufferingRows(columns, buffer, readOnEmpty: readOnEmpty)
@@ -174,7 +187,7 @@ struct ExtendedQueryStateMachine {
174187
return self.avoidingStateMachineCoW { state -> Action in
175188
precondition(buffer.isEmpty, "Expected the buffer to be empty")
176189
let row = dataRow.columns.enumerated().map { (index, buffer) in
177-
PSQLData(bytes: buffer, dataType: columns[index].dataType)
190+
PSQLData(bytes: buffer, dataType: columns[index].dataType, format: columns[index].format)
178191
}
179192

180193
state = .bufferingRows(columns, buffer, readOnEmpty: false)

Sources/PostgresNIO/New/Data/Array+PSQLCodable.swift

+11-2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ extension Array: PSQLEncodable where Element: PSQLArrayElement {
7272
Element.psqlArrayType
7373
}
7474

75+
var psqlFormat: PSQLFormat {
76+
.binary
77+
}
78+
7579
func encode(into buffer: inout ByteBuffer, context: PSQLEncodingContext) throws {
7680
// 0 if empty, 1 if not
7781
buffer.writeInteger(self.isEmpty ? 0 : 1, as: UInt32.self)
@@ -98,7 +102,12 @@ extension Array: PSQLEncodable where Element: PSQLArrayElement {
98102

99103
extension Array: PSQLDecodable where Element: PSQLArrayElement {
100104

101-
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Array<Element> {
105+
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Array<Element> {
106+
guard case .binary = format else {
107+
// currently we only support decoding arrays in binary format.
108+
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
109+
}
110+
102111
guard let isNotEmpty = buffer.readInteger(as: Int32.self), (0...1).contains(isNotEmpty) else {
103112
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
104113
}
@@ -135,7 +144,7 @@ extension Array: PSQLDecodable where Element: PSQLArrayElement {
135144
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
136145
}
137146

138-
let element = try Element.decode(from: &elementBuffer, type: elementType, context: context)
147+
let element = try Element.decode(from: &elementBuffer, type: elementType, format: format, context: context)
139148

140149
result.append(element)
141150
}

Sources/PostgresNIO/New/Data/Bool+PSQLCodable.swift

+33-9
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,42 @@ extension Bool: PSQLCodable {
33
.bool
44
}
55

6-
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Bool {
7-
guard type == .bool, buffer.readableBytes == 1 else {
6+
var psqlFormat: PSQLFormat {
7+
.binary
8+
}
9+
10+
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Bool {
11+
guard type == .bool else {
812
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
913
}
1014

11-
switch buffer.readInteger(as: UInt8.self) {
12-
case .some(0):
13-
return false
14-
case .some(1):
15-
return true
16-
default:
17-
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
15+
switch format {
16+
case .binary:
17+
guard buffer.readableBytes == 1 else {
18+
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
19+
}
20+
21+
switch buffer.readInteger(as: UInt8.self) {
22+
case .some(0):
23+
return false
24+
case .some(1):
25+
return true
26+
default:
27+
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
28+
}
29+
case .text:
30+
guard buffer.readableBytes == 1 else {
31+
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
32+
}
33+
34+
switch buffer.readInteger(as: UInt8.self) {
35+
case .some(UInt8(ascii: "f")):
36+
return false
37+
case .some(UInt8(ascii: "t")):
38+
return true
39+
default:
40+
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
41+
}
1842
}
1943
}
2044

Sources/PostgresNIO/New/Data/Bytes+PSQLCodable.swift

+16-4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ extension PSQLEncodable where Self: Sequence, Self.Element == UInt8 {
66
.bytea
77
}
88

9+
var psqlFormat: PSQLFormat {
10+
.binary
11+
}
12+
913
func encode(into byteBuffer: inout ByteBuffer, context: PSQLEncodingContext) {
1014
byteBuffer.writeBytes(self)
1115
}
@@ -16,12 +20,16 @@ extension ByteBuffer: PSQLCodable {
1620
.bytea
1721
}
1822

23+
var psqlFormat: PSQLFormat {
24+
.binary
25+
}
26+
1927
func encode(into byteBuffer: inout ByteBuffer, context: PSQLEncodingContext) {
2028
var copyOfSelf = self // dirty hack
2129
byteBuffer.writeBuffer(&copyOfSelf)
2230
}
2331

24-
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Self {
32+
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Self {
2533
return buffer
2634
}
2735
}
@@ -30,12 +38,16 @@ extension Data: PSQLCodable {
3038
var psqlType: PSQLDataType {
3139
.bytea
3240
}
33-
41+
42+
var psqlFormat: PSQLFormat {
43+
.binary
44+
}
45+
3446
func encode(into byteBuffer: inout ByteBuffer, context: PSQLEncodingContext) {
3547
byteBuffer.writeBytes(self)
3648
}
37-
38-
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Self {
49+
50+
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Self {
3951
return buffer.readData(length: buffer.readableBytes, byteTransferStrategy: .automatic)!
4052
}
4153
}

Sources/PostgresNIO/New/Data/Date+PSQLCodable.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,11 @@ extension Date: PSQLCodable {
55
.timestamptz
66
}
77

8-
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Self {
8+
var psqlFormat: PSQLFormat {
9+
.binary
10+
}
11+
12+
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Self {
913
switch type {
1014
case .timestamp, .timestamptz:
1115
guard buffer.readableBytes == 8, let microseconds = buffer.readInteger(as: Int64.self) else {

Sources/PostgresNIO/New/Data/Float+PSQLCodable.swift

+26-8
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,27 @@ extension Float: PSQLCodable {
33
.float4
44
}
55

6-
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Float {
7-
switch type {
8-
case .float4:
6+
var psqlFormat: PSQLFormat {
7+
.binary
8+
}
9+
10+
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Float {
11+
switch (format, type) {
12+
case (.binary, .float4):
913
guard buffer.readableBytes == 4, let float = buffer.readFloat() else {
1014
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
1115
}
1216
return float
13-
case .float8:
17+
case (.binary, .float8):
1418
guard buffer.readableBytes == 8, let double = buffer.readDouble() else {
1519
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
1620
}
1721
return Float(double)
22+
case (.text, .float4), (.text, .float8):
23+
guard let string = buffer.readString(length: buffer.readableBytes), let value = Float(string) else {
24+
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
25+
}
26+
return value
1827
default:
1928
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
2029
}
@@ -30,18 +39,27 @@ extension Double: PSQLCodable {
3039
.float8
3140
}
3241

33-
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Double {
34-
switch type {
35-
case .float4:
42+
var psqlFormat: PSQLFormat {
43+
.binary
44+
}
45+
46+
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Double {
47+
switch (format, type) {
48+
case (.binary, .float4):
3649
guard buffer.readableBytes == 4, let float = buffer.readFloat() else {
3750
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
3851
}
3952
return Double(float)
40-
case .float8:
53+
case (.binary, .float8):
4154
guard buffer.readableBytes == 8, let double = buffer.readDouble() else {
4255
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
4356
}
4457
return double
58+
case (.text, .float4), (.text, .float8):
59+
guard let string = buffer.readString(length: buffer.readableBytes), let value = Double(string) else {
60+
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
61+
}
62+
return value
4563
default:
4664
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
4765
}

0 commit comments

Comments
 (0)