Skip to content

Use PSQLFormat when encoding and decoding #158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ extension PostgresConnection: PostgresDatabase {
dataType: PostgresDataType(UInt32(column.dataType.rawValue)),
dataTypeSize: column.dataTypeSize,
dataTypeModifier: column.dataTypeModifier,
formatCode: .init(psqlFormatCode: column.formatCode)
formatCode: .init(psqlFormatCode: column.format)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public struct PreparedQuery {
dataType: PostgresDataType(UInt32(column.dataType.rawValue)),
dataTypeSize: column.dataTypeSize,
dataTypeModifier: column.dataTypeModifier,
formatCode: .init(psqlFormatCode: column.formatCode)
formatCode: .init(psqlFormatCode: column.format)
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,20 @@ struct ExtendedQueryStateMachine {
}

return self.avoidingStateMachineCoW { state -> Action in
state = .rowDescriptionReceived(queryContext, rowDescription.columns)
// In Postgres extended queries we receive the `rowDescription` before we send the
// `Bind` message. Well actually it's vice versa, but this is only true since we do
// pipelining during a query.
//
// In the actual protocol description we receive a rowDescription before the Bind

// In Postgres extended queries we always request the response rows to be returned in
// `.binary` format.
let columns = rowDescription.columns.map { column -> PSQLBackendMessage.RowDescription.Column in
var column = column
column.format = .binary
return column
}
state = .rowDescriptionReceived(queryContext, columns)
return .wait
}
}
Expand Down Expand Up @@ -157,7 +170,7 @@ struct ExtendedQueryStateMachine {

return self.avoidingStateMachineCoW { state -> Action in
let row = dataRow.columns.enumerated().map { (index, buffer) in
PSQLData(bytes: buffer, dataType: columns[index].dataType)
PSQLData(bytes: buffer, dataType: columns[index].dataType, format: columns[index].format)
}
buffer.append(row)
state = .bufferingRows(columns, buffer, readOnEmpty: readOnEmpty)
Expand All @@ -174,7 +187,7 @@ struct ExtendedQueryStateMachine {
return self.avoidingStateMachineCoW { state -> Action in
precondition(buffer.isEmpty, "Expected the buffer to be empty")
let row = dataRow.columns.enumerated().map { (index, buffer) in
PSQLData(bytes: buffer, dataType: columns[index].dataType)
PSQLData(bytes: buffer, dataType: columns[index].dataType, format: columns[index].format)
}

state = .bufferingRows(columns, buffer, readOnEmpty: false)
Expand Down
13 changes: 11 additions & 2 deletions Sources/PostgresNIO/New/Data/Array+PSQLCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@ extension Array: PSQLEncodable where Element: PSQLArrayElement {
Element.psqlArrayType
}

var psqlFormat: PSQLFormat {
.binary
}

func encode(into buffer: inout ByteBuffer, context: PSQLEncodingContext) throws {
// 0 if empty, 1 if not
buffer.writeInteger(self.isEmpty ? 0 : 1, as: UInt32.self)
Expand All @@ -98,7 +102,12 @@ extension Array: PSQLEncodable where Element: PSQLArrayElement {

extension Array: PSQLDecodable where Element: PSQLArrayElement {

static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Array<Element> {
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Array<Element> {
guard case .binary = format else {
// currently we only support decoding arrays in binary format.
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}

guard let isNotEmpty = buffer.readInteger(as: Int32.self), (0...1).contains(isNotEmpty) else {
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}
Expand Down Expand Up @@ -135,7 +144,7 @@ extension Array: PSQLDecodable where Element: PSQLArrayElement {
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}

let element = try Element.decode(from: &elementBuffer, type: elementType, context: context)
let element = try Element.decode(from: &elementBuffer, type: elementType, format: format, context: context)

result.append(element)
}
Expand Down
42 changes: 33 additions & 9 deletions Sources/PostgresNIO/New/Data/Bool+PSQLCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,42 @@ extension Bool: PSQLCodable {
.bool
}

static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Bool {
guard type == .bool, buffer.readableBytes == 1 else {
var psqlFormat: PSQLFormat {
.binary
}

static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Bool {
guard type == .bool else {
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}

switch buffer.readInteger(as: UInt8.self) {
case .some(0):
return false
case .some(1):
return true
default:
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
switch format {
case .binary:
guard buffer.readableBytes == 1 else {
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}

switch buffer.readInteger(as: UInt8.self) {
case .some(0):
return false
case .some(1):
return true
default:
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}
case .text:
guard buffer.readableBytes == 1 else {
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}

switch buffer.readInteger(as: UInt8.self) {
case .some(UInt8(ascii: "f")):
return false
case .some(UInt8(ascii: "t")):
return true
default:
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}
}
}

Expand Down
20 changes: 16 additions & 4 deletions Sources/PostgresNIO/New/Data/Bytes+PSQLCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ extension PSQLEncodable where Self: Sequence, Self.Element == UInt8 {
.bytea
}

var psqlFormat: PSQLFormat {
.binary
}

func encode(into byteBuffer: inout ByteBuffer, context: PSQLEncodingContext) {
byteBuffer.writeBytes(self)
}
Expand All @@ -16,12 +20,16 @@ extension ByteBuffer: PSQLCodable {
.bytea
}

var psqlFormat: PSQLFormat {
.binary
}

func encode(into byteBuffer: inout ByteBuffer, context: PSQLEncodingContext) {
var copyOfSelf = self // dirty hack
byteBuffer.writeBuffer(&copyOfSelf)
}

static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Self {
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Self {
return buffer
}
}
Expand All @@ -30,12 +38,16 @@ extension Data: PSQLCodable {
var psqlType: PSQLDataType {
.bytea
}


var psqlFormat: PSQLFormat {
.binary
}

func encode(into byteBuffer: inout ByteBuffer, context: PSQLEncodingContext) {
byteBuffer.writeBytes(self)
}
static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Self {

static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Self {
return buffer.readData(length: buffer.readableBytes, byteTransferStrategy: .automatic)!
}
}
6 changes: 5 additions & 1 deletion Sources/PostgresNIO/New/Data/Date+PSQLCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ extension Date: PSQLCodable {
.timestamptz
}

static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Self {
var psqlFormat: PSQLFormat {
.binary
}

static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Self {
switch type {
case .timestamp, .timestamptz:
guard buffer.readableBytes == 8, let microseconds = buffer.readInteger(as: Int64.self) else {
Expand Down
34 changes: 26 additions & 8 deletions Sources/PostgresNIO/New/Data/Float+PSQLCodable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,27 @@ extension Float: PSQLCodable {
.float4
}

static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Float {
switch type {
case .float4:
var psqlFormat: PSQLFormat {
.binary
}

static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Float {
switch (format, type) {
case (.binary, .float4):
guard buffer.readableBytes == 4, let float = buffer.readFloat() else {
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}
return float
case .float8:
case (.binary, .float8):
guard buffer.readableBytes == 8, let double = buffer.readDouble() else {
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}
return Float(double)
Comment on lines +17 to 21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we okay with the float-to-double conversion?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation I inherited did this, so I guess it's okay for now. Maybe change with the next major release?

case (.text, .float4), (.text, .float8):
guard let string = buffer.readString(length: buffer.readableBytes), let value = Float(string) else {
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}
return value
default:
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}
Expand All @@ -30,18 +39,27 @@ extension Double: PSQLCodable {
.float8
}

static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, context: PSQLDecodingContext) throws -> Double {
switch type {
case .float4:
var psqlFormat: PSQLFormat {
.binary
}

static func decode(from buffer: inout ByteBuffer, type: PSQLDataType, format: PSQLFormat, context: PSQLDecodingContext) throws -> Double {
switch (format, type) {
case (.binary, .float4):
guard buffer.readableBytes == 4, let float = buffer.readFloat() else {
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}
return Double(float)
case .float8:
case (.binary, .float8):
guard buffer.readableBytes == 8, let double = buffer.readDouble() else {
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}
return double
case (.text, .float4), (.text, .float8):
guard let string = buffer.readString(length: buffer.readableBytes), let value = Double(string) else {
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}
return value
default:
throw PSQLCastingError.failure(targetType: Self.self, type: type, postgresData: buffer, context: context)
}
Expand Down
Loading