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..614ba8d9 100644 --- a/Sources/PostgresNIO/Data/PostgresData+Numeric.swift +++ b/Sources/PostgresNIO/Data/PostgresData+Numeric.swift @@ -40,9 +40,11 @@ public struct PostgresNumeric: CustomStringConvertible, CustomDebugStringConvert 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,6 +119,7 @@ public struct PostgresNumeric: CustomStringConvertible, CustomDebugStringConvert self.dscale = numericCast(dscale) self.value = buffer } + public var decimal: Decimal { // force cast should always succeed since we know 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 + } + } +}