From a16dbccf8a47055ba7e8490603e8ee89993c9443 Mon Sep 17 00:00:00 2001 From: Alex Tran Qui Date: Thu, 27 Jul 2023 12:36:12 +0200 Subject: [PATCH 1/4] - add support for decimal types other than Foundation.Decimal --- .../Data/PostgresData+Decimal.swift | 2 +- .../Data/PostgresData+Numeric.swift | 13 +--- .../New/Data/Decimal+PostgresCodable.swift | 48 +-------------- ...essibleByPostgresFloatingPointString.swift | 59 +++++++++++++++++++ 4 files changed, 65 insertions(+), 57 deletions(-) create mode 100644 Sources/PostgresNIO/New/Data/ExpressibleByPostgresFloatingPointString.swift diff --git a/Sources/PostgresNIO/Data/PostgresData+Decimal.swift b/Sources/PostgresNIO/Data/PostgresData+Decimal.swift index 3af709e5..25d91850 100644 --- a/Sources/PostgresNIO/Data/PostgresData+Decimal.swift +++ b/Sources/PostgresNIO/Data/PostgresData+Decimal.swift @@ -30,6 +30,6 @@ extension Decimal: PostgresDataConvertible { } public var postgresData: PostgresData? { - return .init(numeric: PostgresNumeric(decimal: self)) + return .init(numeric: PostgresNumeric(decimalString: self.description)) } } diff --git a/Sources/PostgresNIO/Data/PostgresData+Numeric.swift b/Sources/PostgresNIO/Data/PostgresData+Numeric.swift index 5e564d6d..fe284282 100644 --- a/Sources/PostgresNIO/Data/PostgresData+Numeric.swift +++ b/Sources/PostgresNIO/Data/PostgresData+Numeric.swift @@ -1,5 +1,4 @@ import NIOCore -import struct Foundation.Decimal public struct PostgresNumeric: CustomStringConvertible, CustomDebugStringConvertible, ExpressibleByStringLiteral { /// The number of digits after this metadata @@ -37,12 +36,10 @@ public struct PostgresNumeric: CustomStringConvertible, CustomDebugStringConvert return Double(self.string) } - public init(decimal: Decimal) { - self.init(decimalString: decimal.description) - } - public init?(string: String) { // validate string contents are decimal + // TODO: this won't work for all Big decimals + // TODO: how does this handle Nan and Infinity guard Double(string) != nil else { return nil } @@ -117,12 +114,6 @@ public struct PostgresNumeric: CustomStringConvertible, CustomDebugStringConvert self.dscale = numericCast(dscale) self.value = buffer } - - public var decimal: Decimal { - // force cast should always succeed since we know - // string returns a valid decimal - return Decimal(string: self.string)! - } public var string: String { guard self.ndigits > 0 else { diff --git a/Sources/PostgresNIO/New/Data/Decimal+PostgresCodable.swift b/Sources/PostgresNIO/New/Data/Decimal+PostgresCodable.swift index f634d4ae..61692cd7 100644 --- a/Sources/PostgresNIO/New/Data/Decimal+PostgresCodable.swift +++ b/Sources/PostgresNIO/New/Data/Decimal+PostgresCodable.swift @@ -1,49 +1,7 @@ -import NIOCore import struct Foundation.Decimal -extension Decimal: PostgresEncodable { - public static var psqlType: PostgresDataType { - .numeric - } - - public static var psqlFormat: PostgresFormat { - .binary - } - - public func encode( - into byteBuffer: inout ByteBuffer, - context: PostgresEncodingContext - ) { - let numeric = PostgresNumeric(decimal: self) - byteBuffer.writeInteger(numeric.ndigits) - byteBuffer.writeInteger(numeric.weight) - byteBuffer.writeInteger(numeric.sign) - byteBuffer.writeInteger(numeric.dscale) - var value = numeric.value - byteBuffer.writeBuffer(&value) - } -} - -extension Decimal: PostgresDecodable { - public init( - from buffer: inout ByteBuffer, - type: PostgresDataType, - format: PostgresFormat, - context: PostgresDecodingContext - ) throws { - switch (format, type) { - case (.binary, .numeric): - guard let numeric = PostgresNumeric(buffer: &buffer) else { - throw PostgresDecodingError.Code.failure - } - self = numeric.decimal - case (.text, .numeric): - guard let string = buffer.readString(length: buffer.readableBytes), let value = Decimal(string: string) else { - throw PostgresDecodingError.Code.failure - } - self = value - default: - throw PostgresDecodingError.Code.typeMismatch - } +extension Decimal: ExpressibleByPostgresFloatingPointString { + public init?(floatingPointString: String) { + self.init(string: floatingPointString) } } diff --git a/Sources/PostgresNIO/New/Data/ExpressibleByPostgresFloatingPointString.swift b/Sources/PostgresNIO/New/Data/ExpressibleByPostgresFloatingPointString.swift new file mode 100644 index 00000000..8b243c1a --- /dev/null +++ b/Sources/PostgresNIO/New/Data/ExpressibleByPostgresFloatingPointString.swift @@ -0,0 +1,59 @@ +import NIOCore + + +/// This protocol allows using various implementations of a Decimal type for Postgres NUMERIC type +public protocol ExpressibleByPostgresFloatingPointString: PostgresEncodable, PostgresDecodable { + static var psqlType: PostgresDataType { get } + static var psqlFormat: PostgresFormat { get } + + init?(floatingPointString: String) + var description: String { get } +} + +extension ExpressibleByPostgresFloatingPointString { + public static var psqlType: PostgresDataType { + .numeric + } + + public static var psqlFormat: PostgresFormat { + .binary + } + + // PostgresEncodable conformance + public func encode( + into byteBuffer: inout ByteBuffer, + context: PostgresEncodingContext + ) { + let numeric = PostgresNumeric(decimalString: self.description) + byteBuffer.writeInteger(numeric.ndigits) + byteBuffer.writeInteger(numeric.weight) + byteBuffer.writeInteger(numeric.sign) + byteBuffer.writeInteger(numeric.dscale) + var value = numeric.value + byteBuffer.writeBuffer(&value) + } + + // PostgresDecodable conformance + public init( + from buffer: inout ByteBuffer, + type: PostgresDataType, + format: PostgresFormat, + context: PostgresDecodingContext + ) throws { + switch (format, type) { + case (.binary, .numeric): + guard let numeric = PostgresNumeric(buffer: &buffer) else { + throw PostgresDecodingError.Code.failure + } + // numeric.string is valid decimal representation + self = Self(floatingPointString: numeric.string)! + case (.text, .numeric): + guard let string = buffer.readString(length: buffer.readableBytes), let value = Self(floatingPointString: string) else { + throw PostgresDecodingError.Code.failure + } + self = value + default: + throw PostgresDecodingError.Code.typeMismatch + } + } +} From fdc197112612fff42b461210a22b31f0a09b96fc Mon Sep 17 00:00:00 2001 From: Alex Tran Qui Date: Thu, 27 Jul 2023 12:45:35 +0200 Subject: [PATCH 2/4] - revert some Decimal related removal to retain compatibility --- Sources/PostgresNIO/Data/PostgresData+Numeric.swift | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Sources/PostgresNIO/Data/PostgresData+Numeric.swift b/Sources/PostgresNIO/Data/PostgresData+Numeric.swift index fe284282..174c4bf6 100644 --- a/Sources/PostgresNIO/Data/PostgresData+Numeric.swift +++ b/Sources/PostgresNIO/Data/PostgresData+Numeric.swift @@ -1,4 +1,5 @@ import NIOCore +import Foundation.Decimal public struct PostgresNumeric: CustomStringConvertible, CustomDebugStringConvertible, ExpressibleByStringLiteral { /// The number of digits after this metadata @@ -36,6 +37,11 @@ public struct PostgresNumeric: CustomStringConvertible, CustomDebugStringConvert return Double(self.string) } + + public init(decimal: Decimal) { + self.init(decimalString: decimal.description) + } + public init?(string: String) { // validate string contents are decimal // TODO: this won't work for all Big decimals @@ -115,6 +121,13 @@ public struct PostgresNumeric: CustomStringConvertible, CustomDebugStringConvert self.value = buffer } + + public var decimal: Decimal { + // force cast should always succeed since we know + // string returns a valid decimal + return Decimal(string: self.string)! + } + public var string: String { guard self.ndigits > 0 else { return "0" From ec58f3c9123066f119b4c47997fa2143d53d98b0 Mon Sep 17 00:00:00 2001 From: Alex Tran Qui Date: Thu, 27 Jul 2023 12:48:09 +0200 Subject: [PATCH 3/4] - remove extra line --- Sources/PostgresNIO/Data/PostgresData+Numeric.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/Sources/PostgresNIO/Data/PostgresData+Numeric.swift b/Sources/PostgresNIO/Data/PostgresData+Numeric.swift index 174c4bf6..1c9834b4 100644 --- a/Sources/PostgresNIO/Data/PostgresData+Numeric.swift +++ b/Sources/PostgresNIO/Data/PostgresData+Numeric.swift @@ -37,7 +37,6 @@ public struct PostgresNumeric: CustomStringConvertible, CustomDebugStringConvert return Double(self.string) } - public init(decimal: Decimal) { self.init(decimalString: decimal.description) } From 3eab477bf2f43e740058644171b0e85b3e1bbcb2 Mon Sep 17 00:00:00 2001 From: Alex Tran Qui Date: Thu, 27 Jul 2023 12:50:09 +0200 Subject: [PATCH 4/4] - fix struct omission --- Sources/PostgresNIO/Data/PostgresData+Numeric.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/PostgresNIO/Data/PostgresData+Numeric.swift b/Sources/PostgresNIO/Data/PostgresData+Numeric.swift index 1c9834b4..614ba8d9 100644 --- a/Sources/PostgresNIO/Data/PostgresData+Numeric.swift +++ b/Sources/PostgresNIO/Data/PostgresData+Numeric.swift @@ -1,5 +1,5 @@ import NIOCore -import Foundation.Decimal +import struct Foundation.Decimal public struct PostgresNumeric: CustomStringConvertible, CustomDebugStringConvertible, ExpressibleByStringLiteral { /// The number of digits after this metadata