Skip to content

Commit 265fba3

Browse files
committed
fixes vapor#24. combines parameterized query phases into one
1 parent 42fbe05 commit 265fba3

File tree

5 files changed

+121
-47
lines changed

5 files changed

+121
-47
lines changed

Sources/PostgreSQL/Connection/PostgreSQLConnection+Query.swift

Lines changed: 31 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,52 +28,44 @@ extension PostgreSQLConnection {
2828
query: string,
2929
parameterTypes: parameters.map { $0.type }
3030
)
31-
let describe = PostgreSQLDescribeRequest(type: .statement, name: "")
31+
let describe = PostgreSQLDescribeRequest(
32+
type: .statement,
33+
name: ""
34+
)
35+
let bind = PostgreSQLBindRequest(
36+
portalName: "",
37+
statementName: "",
38+
parameterFormatCodes: parameters.map { $0.format },
39+
parameters: parameters.map { .init(data: $0.data) },
40+
resultFormatCodes: resultFormat.formatCodes
41+
)
42+
let execute = PostgreSQLExecuteRequest(
43+
portalName: "",
44+
maxRows: 0
45+
)
46+
3247
var currentRow: PostgreSQLRowDescription?
33-
34-
return send([
35-
.parse(parse), .describe(describe), .sync
48+
return self.send([
49+
.parse(parse), .describe(describe), .bind(bind), .execute(execute), .sync
3650
]) { message in
3751
switch message {
3852
case .parseComplete: break
39-
case .rowDescription(let row): currentRow = row
4053
case .parameterDescription: break
4154
case .noData: break
42-
default: throw PostgreSQLError(identifier: "query", reason: "Unexpected message during PostgreSQLParseRequest: \(message)", source: .capture())
43-
}
44-
}.flatMap(to: Void.self) {
45-
let resultFormats = resultFormat.formatCodeFactory(currentRow?.fields.map { $0.dataType } ?? [])
46-
// cache so we don't compute twice
47-
let bind = PostgreSQLBindRequest(
48-
portalName: "",
49-
statementName: "",
50-
parameterFormatCodes: parameters.map { $0.format },
51-
parameters: parameters.map { .init(data: $0.data) },
52-
resultFormatCodes: resultFormats
53-
)
54-
let execute = PostgreSQLExecuteRequest(
55-
portalName: "",
56-
maxRows: 0
57-
)
58-
return self.send([
59-
.bind(bind), .execute(execute), .sync
60-
]) { message in
61-
switch message {
62-
case .bindComplete: break
63-
case .dataRow(let data):
64-
guard let row = currentRow else {
65-
throw PostgreSQLError(identifier: "query", reason: "Unexpected PostgreSQLDataRow without preceding PostgreSQLRowDescription.", source: .capture())
66-
}
67-
let parsed = try row.parse(
68-
data: data,
69-
formatCodes: resultFormats,
70-
tableNameCache: self.tableNameCache
71-
)
72-
try onRow(parsed)
73-
case .close: break
74-
case .noData: break
75-
default: throw PostgreSQLError(identifier: "query", reason: "Unexpected message during PostgreSQLParseRequest: \(message)", source: .capture())
55+
case .bindComplete: break
56+
case .rowDescription(let row): currentRow = row
57+
case .dataRow(let data):
58+
guard let row = currentRow else {
59+
throw PostgreSQLError(identifier: "query", reason: "Unexpected PostgreSQLDataRow without preceding PostgreSQLRowDescription.", source: .capture())
7660
}
61+
let parsed = try row.parse(
62+
data: data,
63+
formatCodes: resultFormat.formatCodes,
64+
tableNameCache: self.tableNameCache
65+
)
66+
try onRow(parsed)
67+
case .close: break
68+
default: throw PostgreSQLError(identifier: "query", reason: "Unexpected message during PostgreSQLParseRequest: \(message)", source: .capture())
7769
}
7870
}
7971
}

Sources/PostgreSQL/Connection/PostgreSQLConnection.swift

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,26 @@ public final class PostgreSQLConnection {
2020
/// Caches oid -> table name data.
2121
internal var tableNameCache: PostgreSQLTableNameCache?
2222

23+
/// Returns a new unique portal name.
24+
internal var nextPortalName: String {
25+
defer { uniqueNameCounter = uniqueNameCounter &+ 1 }
26+
return "p_\(uniqueNameCounter)"
27+
}
28+
29+
/// Returns a new unique statement name.
30+
internal var nextStatementName: String {
31+
defer { uniqueNameCounter = uniqueNameCounter &+ 1 }
32+
return "s_\(uniqueNameCounter)"
33+
}
34+
35+
/// A unique identifier for this connection, used to generate statment and portal names
36+
private var uniqueNameCounter: UInt8
37+
2338
/// Creates a new Redis client on the provided data source and sink.
2439
init(queue: QueueHandler<PostgreSQLMessage, PostgreSQLMessage>, channel: Channel) {
2540
self.queue = queue
2641
self.channel = channel
42+
self.uniqueNameCounter = 0
2743
}
2844

2945
/// Sends `PostgreSQLMessage` to the server.

Sources/PostgreSQL/DataType/PostgreSQLFormatCode.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,11 @@ public enum PostgreSQLFormatCode: Int16, Codable {
99

1010
public struct PostgreSQLResultFormat {
1111
/// The format codes
12-
internal let formatCodeFactory: ([PostgreSQLDataType]) -> [PostgreSQLFormatCode]
13-
14-
/// Dynamically choose result format based on data type.
15-
public static func dynamic(_ callback: @escaping (PostgreSQLDataType) -> PostgreSQLFormatCode) -> PostgreSQLResultFormat {
16-
return .init { return $0.map { callback($0) } }
17-
}
12+
internal let formatCodes: [PostgreSQLFormatCode]
1813

1914
/// Request all of the results in a specific format.
2015
public static func all(_ code: PostgreSQLFormatCode) -> PostgreSQLResultFormat {
21-
return .init { _ in return [code] }
16+
return .init(formatCodes: [code])
2217
}
2318

2419
/// Request all of the results in a specific format.
@@ -33,6 +28,11 @@ public struct PostgreSQLResultFormat {
3328

3429
/// Let the server decide the formatting options.
3530
public static func notSpecified() -> PostgreSQLResultFormat {
36-
return .init { _ in return [] }
31+
return .init(formatCodes: [])
32+
}
33+
34+
/// Request all of the results in a specific format.
35+
public static func specific(_ codes: [PostgreSQLFormatCode]) -> PostgreSQLResultFormat {
36+
return .init(formatCodes: codes)
3737
}
3838
}

Sources/PostgreSQL/Message/PostgreSQLDataRow.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,13 @@ struct PostgreSQLDataRowColumn: Decodable {
3333
return PostgreSQLData(type: dataType, format: format, data: value)
3434
}
3535
}
36+
37+
extension PostgreSQLDataRowColumn: CustomStringConvertible {
38+
var description: String {
39+
if let value = value {
40+
return String(data: value, encoding: .ascii) ?? value.hexDebug
41+
} else {
42+
return "<null>"
43+
}
44+
}
45+
}

Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,61 @@ class PostgreSQLConnectionTests: XCTestCase {
286286
XCTAssertEqual(parameterizedResult.count, 1)
287287
}
288288

289+
func testGH24() throws {
290+
/// PREPARE
291+
let client = try PostgreSQLConnection.makeTest()
292+
_ = try client.query("""
293+
DROP TABLE IF EXISTS "acronym+category"
294+
""").wait()
295+
_ = try client.query("""
296+
DROP TABLE IF EXISTS "categories"
297+
""").wait()
298+
_ = try client.query("""
299+
DROP TABLE IF EXISTS "acronyms"
300+
""").wait()
301+
_ = try client.query("""
302+
DROP TABLE IF EXISTS "users"
303+
""").wait()
304+
305+
/// CREATE
306+
let _ = try client.query("""
307+
CREATE TABLE "users" ("id" UUID PRIMARY KEY, "name" TEXT NOT NULL, "username" TEXT NOT NULL)
308+
""").wait()
309+
let _ = try client.query("""
310+
CREATE TABLE "acronyms" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "short" TEXT NOT NULL, "long" TEXT NOT NULL, "userID" UUID NOT NULL, FOREIGN KEY ("userID") REFERENCES "users" ("id"), FOREIGN KEY ("userID") REFERENCES "users" ("id"))
311+
""").wait()
312+
let _ = try client.query("""
313+
CREATE TABLE "categories" ("id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, "name" TEXT NOT NULL)
314+
""").wait()
315+
let _ = try client.query("""
316+
CREATE TABLE "acronym+category" ("id" UUID PRIMARY KEY, "acronymID" BIGINT NOT NULL, "categoryID" BIGINT NOT NULL, FOREIGN KEY ("acronymID") REFERENCES "acronyms" ("id"), FOREIGN KEY ("categoryID") REFERENCES "categories" ("id"), FOREIGN KEY ("acronymID") REFERENCES "acronyms" ("id"), FOREIGN KEY ("categoryID") REFERENCES "categories" ("id"))
317+
""").wait()
318+
319+
/// INSERT
320+
let userUUID = UUID()
321+
let _ = try client.query("""
322+
INSERT INTO "users" ("id", "name", "username") VALUES ($1, $2, $3)
323+
""", [userUUID, "Vapor Test", "vapor"]).wait()
324+
let _ = try client.query("""
325+
INSERT INTO "acronyms" ("id", "userID", "short", "long") VALUES ($1, $2, $3, $4)
326+
""", [1, userUUID, "ilv", "i love vapor"]).wait()
327+
let _ = try client.query("""
328+
INSERT INTO "categories" ("id", "name") VALUES ($1, $2);
329+
""", [1, "all"]).wait()
330+
331+
332+
/// SELECT
333+
let acronyms = try client.query("""
334+
SELECT "acronyms".* FROM "acronyms" WHERE ("acronyms"."id" = $1) LIMIT 1 OFFSET 0
335+
""", [1])
336+
let categories = try client.query("""
337+
SELECT "categories".* FROM "categories" WHERE ("categories"."id" = $1) LIMIT 1 OFFSET 0
338+
""", [1])
339+
340+
try print(acronyms.wait())
341+
try print(categories.wait())
342+
}
343+
289344
static var allTests = [
290345
("testVersion", testVersion),
291346
("testSelectTypes", testSelectTypes),
@@ -294,6 +349,7 @@ class PostgreSQLConnectionTests: XCTestCase {
294349
("testParameterizedTypes", testParameterizedTypes),
295350
("testStruct", testStruct),
296351
("testNull", testNull),
352+
("testGH24", testGH24),
297353
]
298354
}
299355

0 commit comments

Comments
 (0)