Skip to content

Commit c6d8ee8

Browse files
committed
parameterized types passing
1 parent 58746f9 commit c6d8ee8

File tree

7 files changed

+143
-17
lines changed

7 files changed

+143
-17
lines changed

Sources/PostgreSQL/Base/PostgreSQLDataType.swift

+5-1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ enum PostgreSQLDataType: Int32, Codable {
2424
case void = 2278
2525
}
2626

27+
2728
extension PostgreSQLDataType {
2829
/// Converts the supplied `PostgreSQLData` to the best matching `PostgreSQLDataType`
2930
static func type(forData data: PostgreSQLData) -> PostgreSQLDataType {
@@ -33,7 +34,7 @@ extension PostgreSQLDataType {
3334
case .int32: return .int4
3435
case .int: return .int8
3536
case .uint: return .int8
36-
case .uint8: return .char
37+
case .uint8: return .bool
3738
case .uint16: return .int2
3839
case .uint32: return .int4
3940
case .null: return .void
@@ -44,7 +45,10 @@ extension PostgreSQLDataType {
4445
case .date: return .timestamp
4546
}
4647
}
48+
}
4749

50+
51+
extension PostgreSQLDataType {
4852
/// If true, this type supports binary format.
4953
var supportsBinaryFormat: Bool {
5054
switch self {

Sources/PostgreSQL/Base/PostgreSQLMessage.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,6 @@ enum PostgreSQLMessage {
2525
case parseComplete
2626
/// Identifies the message as a Bind-complete indicator.
2727
case bindComplete
28-
29-
28+
/// Identifies the message as a no-data indicator.
29+
case noData
3030
}

Sources/PostgreSQL/Client/PostgreSQLClient.swift

+1
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ final class PostgreSQLClient {
123123
onRow(parsed)
124124
return false
125125
case .close: return false
126+
case .noData: return false
126127
case .readyForQuery: return true
127128
default: fatalError("Unexpected message during PostgreSQLParseRequest: \(message)")
128129
}

Sources/PostgreSQL/Coders/PostgreSQLMessageDecoder.swift

+1
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ final class PostgreSQLMessageDecoder {
3030
case .C: message = try .close(decoder.decode())
3131
case .one: message = .parseComplete
3232
case .two: message = .bindComplete
33+
case .n: message = .noData
3334
default:
3435
let string = String(bytes: [type], encoding: .ascii) ?? "n/a"
3536
fatalError("Unrecognized message type: \(string) (\(type)")

Sources/PostgreSQL/Message/PostgreSQLBindRequest.swift

+32-6
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,39 @@ struct PostgreSQLBindParameter: Encodable {
3535
switch data {
3636
case .string(let string): serialized = Data(string.utf8)
3737
case .null: serialized = nil
38-
case .int32(let int):
39-
var bytes = [UInt8](repeating: 0, count: 4)
40-
var intNetwork = int.bigEndian
41-
memcpy(&bytes, &intNetwork, 4)
42-
serialized = Data(bytes) // FIXME
43-
default: fatalError("Data type not yet supported: \(data)")
38+
case .int8(let int): serialized = Data(int.bytes)
39+
case .int16(let int): serialized = Data(int.bytes)
40+
case .int32(let int): serialized = Data(int.bytes)
41+
case .int(let int): serialized = Data(int.bytes)
42+
case .uint8(let int): serialized = Data(int.bytes)
43+
case .uint16(let int): serialized = Data(int.bytes)
44+
case .uint32(let int): serialized = Data(int.bytes)
45+
case .uint(let int): serialized = Data(int.bytes)
46+
case .double(let double): serialized = Data(double.bytes)
47+
case .float(let float): serialized = Data(float.bytes)
48+
case .data(let data): serialized = data
49+
case .date(let date): serialized = Data(date.description.utf8)
4450
}
4551
return .init(data: serialized)
4652
}
4753
}
54+
55+
extension FixedWidthInteger {
56+
/// Big-endian bytes for this integer.
57+
var bytes: [UInt8] {
58+
var bytes = [UInt8](repeating: 0, count: Self.bitWidth / 8)
59+
var intNetwork = bigEndian
60+
memcpy(&bytes, &intNetwork, bytes.count)
61+
return bytes
62+
}
63+
}
64+
65+
extension FloatingPoint {
66+
/// Big-endian bytes for this floating-point number.
67+
var bytes: [UInt8] {
68+
var bytes = [UInt8](repeating: 0, count: MemoryLayout<Self>.size)
69+
var copy = self
70+
memcpy(&bytes, &copy, bytes.count)
71+
return bytes.reversed()
72+
}
73+
}

Sources/PostgreSQL/Message/PostgreSQLDataRow.swift

+7-3
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ struct PostgreSQLDataRowColumn: Decodable {
6565
case .float4: return try Float(value.makeString()).flatMap { .float($0) } ?? .null
6666
case .numeric, .float8: return try Double(value.makeString()).flatMap { .double($0) } ?? .null
6767
case .bytea: return try value.makeString().hexadecimal().flatMap { .data($0) } ?? .null
68-
case .timestamp: return try .date(value.makeString().parseDate(format: "yyyy-MM-dd HH:mm:ss.SSSSSS"))
68+
case .timestamp: return try .date(value.makeString().parseDate(format: "yyyy-MM-dd HH:mm:ss"))
6969
case .date: return try .date(value.makeString().parseDate(format: "yyyy-MM-dd"))
70-
case .time: return try .date(value.makeString().parseDate(format: "HH:mm:ss.SSSSSS"))
70+
case .time: return try .date(value.makeString().parseDate(format: "HH:mm:ss"))
7171
case .void: return .null
7272
case .pg_node_tree, ._aclitem: return try .string(value.makeString())
7373
}
@@ -100,7 +100,11 @@ extension String {
100100
/// Parses a Date from this string with the supplied date format.
101101
func parseDate(format: String) throws -> Date {
102102
let formatter = DateFormatter()
103-
formatter.dateFormat = format
103+
if contains(".") {
104+
formatter.dateFormat = format + ".SSSSSS"
105+
} else {
106+
formatter.dateFormat = format
107+
}
104108
guard let date = formatter.date(from: self) else {
105109
throw PostgreSQLError(identifier: "date", reason: "Malformed date: \(self)")
106110
}

Tests/PostgreSQLTests/PostgreSQLClientTests.swift

+95-5
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ class PostgreSQLClientTests: XCTestCase {
105105
let queryResult = try client.query("select * from kitchen_sink").await(on: eventLoop)
106106
if queryResult.count == 1 {
107107
let row = queryResult[0]
108-
print(row)
109108
XCTAssertEqual(row["smallint"], .int16(1))
110109
XCTAssertEqual(row["integer"], .int32(2))
111110
XCTAssertEqual(row["bigint"], .int(3))
@@ -120,20 +119,107 @@ class PostgreSQLClientTests: XCTestCase {
120119
} else {
121120
XCTFail("query result count is: \(queryResult.count)")
122121
}
122+
}
123+
124+
func testParameterizedTypes() throws {
125+
let (client, eventLoop) = try PostgreSQLClient.makeTest()
126+
let createQuery = """
127+
create table kitchen_sink (
128+
"smallint" smallint,
129+
"integer" integer,
130+
"bigint" bigint,
131+
"decimal" decimal,
132+
"numeric" numeric,
133+
"real" real,
134+
"double" double precision,
135+
"varchar" varchar(64),
136+
"char" char(4),
137+
"text" text,
138+
"bytea" bytea,
139+
"timestamp" timestamp,
140+
"date" date,
141+
"time" time,
142+
"boolean" boolean-- ,
143+
-- "point" point,
144+
-- "line" line,
145+
-- "lseg" lseg,
146+
-- "box" box,
147+
-- "path" path,
148+
-- "polygon" polygon,
149+
-- "circle" circle,
150+
-- "cidr" cidr,
151+
-- "inet" inet,
152+
-- "macaddr" macaddr,
153+
-- "bit" bit(16),
154+
-- "uuid" uuid
155+
);
156+
"""
157+
_ = try client.query("drop table if exists kitchen_sink;").await(on: eventLoop)
158+
let createResult = try client.query(createQuery).await(on: eventLoop)
159+
XCTAssertEqual(createResult.count, 0)
160+
161+
let insertQuery = """
162+
insert into kitchen_sink values (
163+
$1, -- "smallint" smallint
164+
$2, -- "integer" integer
165+
$3, -- "bigint" bigint
166+
$4, -- "decimal" decimal
167+
$5, -- "numeric" numeric
168+
$6, -- "real" real
169+
$7, -- "double" double precision
170+
$8, -- "varchar" varchar(64)
171+
$9, -- "char" char(4)
172+
$10, -- "text" text
173+
$11, -- "bytea" bytea
174+
$12, -- "timestamp" timestamp
175+
$13, -- "date" date
176+
$14, -- "time" time
177+
$15 -- "boolean" boolean
178+
-- "point" point,
179+
-- "line" line,
180+
-- "lseg" lseg,
181+
-- "box" box,
182+
-- "path" path,
183+
-- "polygon" polygon,
184+
-- "circle" circle,
185+
-- "cidr" cidr,
186+
-- "inet" inet,
187+
-- "macaddr" macaddr,
188+
-- "bit" bit(16),
189+
-- "uuid" uuid
190+
);
191+
"""
192+
let insertResult = try client.parameterizedQuery(insertQuery, [
193+
PostgreSQLData.int16(1), // smallint
194+
PostgreSQLData.int32(2), // integer
195+
PostgreSQLData.int(3), // bigint
196+
PostgreSQLData.double(4), // decimal
197+
PostgreSQLData.double(5), // numeric
198+
PostgreSQLData.float(6), // real
199+
PostgreSQLData.double(7), // double
200+
PostgreSQLData.string("8"), // varchar
201+
PostgreSQLData.string("9"), // char
202+
PostgreSQLData.string("10"), // text
203+
PostgreSQLData.data(Data([0x31, 0x32])), // bytea
204+
PostgreSQLData.date(Date()), // timestamp
205+
PostgreSQLData.date(Date()), // date
206+
PostgreSQLData.date(Date()), // time
207+
PostgreSQLData.uint8(1), // boolean
208+
]).await(on: eventLoop)
209+
XCTAssertEqual(insertResult.count, 0)
123210

124211
let parameterizedResult = try client.parameterizedQuery("select * from kitchen_sink").await(on: eventLoop)
125212
if parameterizedResult.count == 1 {
126213
let row = parameterizedResult[0]
127-
print(row)
128214
XCTAssertEqual(row["smallint"], .int16(1))
129215
XCTAssertEqual(row["integer"], .int32(2))
130216
XCTAssertEqual(row["bigint"], .int(3))
131217
XCTAssertEqual(row["decimal"], .double(4))
132218
XCTAssertEqual(row["real"], .float(6))
133219
XCTAssertEqual(row["double"], .double(7))
134-
XCTAssertEqual(row["varchar"], .string("9"))
135-
XCTAssertEqual(row["char"], .string("10 "))
136-
XCTAssertEqual(row["text"], .string("11"))
220+
XCTAssertEqual(row["varchar"], .string("8"))
221+
XCTAssertEqual(row["char"], .string("9 "))
222+
XCTAssertEqual(row["text"], .string("10"))
137223
XCTAssertEqual(row["bytea"], .data(Data([0x31, 0x32])))
138224
XCTAssertEqual(row["boolean"], .uint8(0x01))
139225
} else {
@@ -143,6 +229,10 @@ class PostgreSQLClientTests: XCTestCase {
143229

144230
static var allTests = [
145231
("testVersion", testVersion),
232+
("testSelectTypes", testSelectTypes),
233+
("testParse", testParse),
234+
("testTypes", testTypes),
235+
("testParameterizedTypes", testParameterizedTypes),
146236
]
147237
}
148238

0 commit comments

Comments
 (0)