Skip to content

Commit 910c51f

Browse files
author
Shaun Hubbard
committed
Merge branch 'notify-listen' of github.com:pedantix/postgresql into notify-listen
2 parents 86e1d80 + 7a13eb7 commit 910c51f

7 files changed

+117
-36
lines changed

Package.swift

+8-5
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,25 @@ let package = Package(
88
],
99
dependencies: [
1010
// 🌎 Utility package containing tools for byte manipulation, Codable, OS APIs, and debugging.
11-
.package(url: "https://github.com/vapor/core.git", from: "3.0.0-rc.2"),
11+
.package(url: "https://github.com/vapor/core.git", from: "3.0.0"),
1212

1313
// 🔑 Hashing (BCrypt, SHA, HMAC, etc), encryption, and randomness.
14-
.package(url: "https://github.com/vapor/crypto.git", from: "3.0.0-rc.2"),
14+
.package(url: "https://github.com/vapor/crypto.git", from: "3.0.0"),
1515

1616
// 🗄 Core services for creating database integrations.
17-
.package(url: "https://github.com/vapor/database-kit.git", from: "1.0.0-rc.2"),
17+
.package(url: "https://github.com/vapor/database-kit.git", .branch("gm")),
1818

1919
// 📦 Dependency injection / inversion of control framework.
20-
.package(url: "https://github.com/vapor/service.git", from: "1.0.0-rc.2"),
20+
.package(url: "https://github.com/vapor/service.git", from: "1.0.0"),
21+
22+
// *️⃣ Build SQL queries in Swift.
23+
.package(url: "https://github.com/vapor/sql.git", from: "1.0.0"),
2124

2225
// Event-driven network application framework for high performance protocol servers & clients, non-blocking.
2326
.package(url: "https://github.com/apple/swift-nio.git", from: "1.0.0"),
2427
],
2528
targets: [
26-
.target(name: "PostgreSQL", dependencies: ["Async", "Bits", "Core", "Crypto", "DatabaseKit", "NIO", "Service"]),
29+
.target(name: "PostgreSQL", dependencies: ["Async", "Bits", "Core", "Crypto", "DatabaseKit", "NIO", "Service", "SQL"]),
2730
.testTarget(name: "PostgreSQLTests", dependencies: ["Core", "PostgreSQL"]),
2831
]
2932
)

Sources/PostgreSQL/Connection/PostgreSQLConnection+Query.swift

+20-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ extension PostgreSQLConnection {
55
public func query(
66
_ string: String,
77
_ parameters: [PostgreSQLDataConvertible] = []
8-
) throws -> Future<[[PostgreSQLColumn: PostgreSQLData]]> {
8+
) -> Future<[[PostgreSQLColumn: PostgreSQLData]]> {
99
var rows: [[PostgreSQLColumn: PostgreSQLData]] = []
10-
return try query(string, parameters) { row in
10+
return query(string, parameters) { row in
1111
rows.append(row)
1212
}.map(to: [[PostgreSQLColumn: PostgreSQLData]].self) {
1313
return rows
@@ -21,8 +21,26 @@ extension PostgreSQLConnection {
2121
_ parameters: [PostgreSQLDataConvertible] = [],
2222
resultFormat: PostgreSQLResultFormat = .binary(),
2323
onRow: @escaping ([PostgreSQLColumn: PostgreSQLData]) throws -> ()
24+
) -> Future<Void> {
25+
return operation {
26+
do {
27+
return try self._query(string, parameters, resultFormat: resultFormat, onRow: onRow)
28+
} catch {
29+
return self.eventLoop.newFailedFuture(error: error)
30+
}
31+
}
32+
}
33+
34+
/// Non-operation bounded query.
35+
private func _query(
36+
_ string: String,
37+
_ parameters: [PostgreSQLDataConvertible] = [],
38+
resultFormat: PostgreSQLResultFormat = .binary(),
39+
onRow: @escaping ([PostgreSQLColumn: PostgreSQLData]) throws -> ()
2440
) throws -> Future<Void> {
2541
let parameters = try parameters.map { try $0.convertToPostgreSQLData() }
42+
logger?.record(query: string, values: parameters.map { $0.description })
43+
2644
let parse = PostgreSQLParseRequest(
2745
statementName: "",
2846
query: string,

Sources/PostgreSQL/Connection/PostgreSQLConnection+SimpleQuery.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ extension PostgreSQLConnection {
1010
return rows
1111
}
1212
}
13-
1413
/// Sends a simple PostgreSQL query command, returning the parsed results to
1514
/// the supplied closure.
1615
public func simpleQuery(_ string: String, onRow: @escaping ([PostgreSQLColumn: PostgreSQLData]) -> ()) -> Future<Void> {
17-
logger?.log(query: string, parameters: [])
16+
return operation { self._simpleQuery(string, onRow: onRow) }
17+
}
18+
19+
/// Non-operation bounded simple query.
20+
private func _simpleQuery(_ string: String, onRow: @escaping ([PostgreSQLColumn: PostgreSQLData]) -> ()) -> Future<Void> {
21+
logger?.record(query: string)
1822
var currentRow: PostgreSQLRowDescription?
1923
let query = PostgreSQLQuery(query: string)
2024
return send([.query(query)]) { message in

Sources/PostgreSQL/Connection/PostgreSQLConnection.swift

+73-15
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import Async
21
import Crypto
32
import NIO
43

54
/// A PostgreSQL frontend client.
6-
public final class PostgreSQLConnection {
5+
public final class PostgreSQLConnection: DatabaseConnection, BasicWorker {
6+
/// See `BasicWorker`.
77
public var eventLoop: EventLoop {
88
return channel.eventLoop
99
}
@@ -15,7 +15,13 @@ public final class PostgreSQLConnection {
1515
private let channel: Channel
1616

1717
/// If non-nil, will log queries.
18-
public var logger: PostgreSQLLogger?
18+
public var logger: DatabaseLogger?
19+
20+
/// See `DatabaseConnection`.
21+
public var isClosed: Bool
22+
23+
/// See `Extendable`.
24+
public var extend: Extend
1925

2026
/// Returns a new unique portal name.
2127
internal var nextPortalName: String {
@@ -32,21 +38,54 @@ public final class PostgreSQLConnection {
3238
/// A unique identifier for this connection, used to generate statment and portal names
3339
private var uniqueNameCounter: UInt8
3440

41+
/// In-flight `send(...)` futures.
42+
private var currentSend: Promise<Void>?
43+
44+
/// The current query running, if one exists.
45+
private var pipeline: Future<Void>
46+
3547
/// Creates a new Redis client on the provided data source and sink.
3648
init(queue: QueueHandler<PostgreSQLMessage, PostgreSQLMessage>, channel: Channel) {
3749
self.queue = queue
3850
self.channel = channel
3951
self.uniqueNameCounter = 0
52+
self.isClosed = false
53+
self.extend = [:]
54+
self.pipeline = channel.eventLoop.newSucceededFuture(result: ())
55+
channel.closeFuture.always {
56+
self.isClosed = true
57+
if let current = self.currentSend {
58+
current.fail(error: closeError)
59+
}
60+
}
4061
}
41-
42-
deinit {
43-
close()
62+
/// Sends `PostgreSQLMessage` to the server.
63+
func send(_ message: [PostgreSQLMessage]) -> Future<[PostgreSQLMessage]> {
64+
var responses: [PostgreSQLMessage] = []
65+
return send(message) { response in
66+
responses.append(response)
67+
}.map(to: [PostgreSQLMessage].self) {
68+
return responses
69+
}
4470
}
4571

4672
/// Sends `PostgreSQLMessage` to the server.
4773
func send(_ messages: [PostgreSQLMessage], onResponse: @escaping (PostgreSQLMessage) throws -> ()) -> Future<Void> {
74+
// if currentSend is not nil, previous send has not completed
75+
assert(currentSend == nil, "Attempting to call `send(...)` again before previous invocation has completed.")
76+
77+
// ensure the connection is not closed
78+
guard !isClosed else {
79+
return eventLoop.newFailedFuture(error: closeError)
80+
}
81+
82+
// create a new promise and store it
83+
let promise = eventLoop.newPromise(Void.self)
84+
currentSend = promise
85+
86+
// cascade this enqueue to the newly created promise
4887
var error: Error?
49-
return queue.enqueue(messages) { message in
88+
queue.enqueue(messages) { message in
5089
switch message {
5190
case .readyForQuery:
5291
if let e = error { throw e }
@@ -56,17 +95,28 @@ public final class PostgreSQLConnection {
5695
default: try onResponse(message)
5796
}
5897
return false // request until ready for query
59-
}
98+
}.cascade(promise: promise)
99+
100+
// when the promise completes, remove the reference to it
101+
promise.futureResult.always { self.currentSend = nil }
102+
103+
// return the promise's future result (same as `queue.enqueue`)
104+
return promise.futureResult
60105
}
61106

62-
/// Sends `PostgreSQLMessage` to the server.
63-
func send(_ message: [PostgreSQLMessage]) -> Future<[PostgreSQLMessage]> {
64-
var responses: [PostgreSQLMessage] = []
65-
return send(message) { response in
66-
responses.append(response)
67-
}.map(to: [PostgreSQLMessage].self) {
68-
return responses
107+
/// Submits an async task to be pipelined.
108+
internal func operation(_ work: @escaping () -> Future<Void>) -> Future<Void> {
109+
/// perform this work when the current pipeline future is completed
110+
let new = pipeline.then(work)
111+
112+
/// append this work to the pipeline, discarding errors as the pipeline
113+
//// does not care about them
114+
pipeline = new.catchMap { err in
115+
return ()
69116
}
117+
118+
/// return the newly enqueued work's future result
119+
return new
70120
}
71121

72122
/// Authenticates the `PostgreSQLClient` using a username with no password.
@@ -146,4 +196,12 @@ public final class PostgreSQLConnection {
146196
channel.close(promise: nil)
147197
}
148198
}
199+
200+
/// Called when this class deinitializes.
201+
deinit {
202+
close()
203+
}
204+
149205
}
206+
207+
private let closeError = PostgreSQLError(identifier: "closed", reason: "Connection is closed.", source: .capture())

Sources/PostgreSQL/Database/PostgreSQLDatabase.swift

+7-9
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
import Async
22

33
/// Creates connections to an identified PostgreSQL database.
4-
public final class PostgreSQLDatabase: Database {
4+
public final class PostgreSQLDatabase: Database, LogSupporting {
5+
/// See `LogSupporting`
6+
public static func enableLogging(_ logger: DatabaseLogger, on conn: PostgreSQLConnection) {
7+
conn.logger = logger
8+
}
9+
510
/// This database's configuration.
611
public let config: PostgreSQLDatabaseConfig
712

8-
/// If non-nil, will log queries.
9-
public var logger: PostgreSQLLogger?
10-
1113
/// Creates a new `PostgreSQLDatabase`.
1214
public init(config: PostgreSQLDatabaseConfig) {
1315
self.config = config
1416
}
1517

1618
/// See `Database.makeConnection()`
17-
public func makeConnection(on worker: Worker) -> Future<PostgreSQLConnection> {
19+
public func newConnection(on worker: Worker) -> Future<PostgreSQLConnection> {
1820
let config = self.config
1921
return Future.flatMap(on: worker) {
2022
return try PostgreSQLConnection.connect(hostname: config.hostname, port: config.port, on: worker) { error in
2123
print("[PostgreSQL] \(error)")
2224
}.flatMap(to: PostgreSQLConnection.self) { client in
23-
client.logger = self.logger
2425
return client.authenticate(
2526
username: config.username,
2627
database: config.database,
@@ -31,9 +32,6 @@ public final class PostgreSQLDatabase: Database {
3132
}
3233
}
3334

34-
/// A connection created by a `PostgreSQLDatabase`.
35-
extension PostgreSQLConnection: DatabaseConnection, BasicWorker { }
36-
3735
extension DatabaseIdentifier {
3836
/// Default identifier for `PostgreSQLDatabase`.
3937
public static var psql: DatabaseIdentifier<PostgreSQLDatabase> {

Sources/PostgreSQL/PostgreSQLProvider.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ public final class PostgreSQLProvider: Provider {
1414
try services.register(DatabaseKitProvider())
1515
services.register(PostgreSQLDatabaseConfig.self)
1616
services.register(PostgreSQLDatabase.self)
17-
var databases = DatabaseConfig()
17+
var databases = DatabasesConfig()
1818
databases.add(database: PostgreSQLDatabase.self, as: .psql)
1919
services.register(databases)
2020
}

Tests/PostgreSQLTests/PostgreSQLConnectionTests.swift

+2-2
Original file line numberDiff line numberDiff line change
@@ -332,10 +332,10 @@ class PostgreSQLConnectionTests: XCTestCase {
332332

333333

334334
/// SELECT
335-
let acronyms = try client.query("""
335+
let acronyms = client.query("""
336336
SELECT "acronyms".* FROM "acronyms" WHERE ("acronyms"."id" = $1) LIMIT 1 OFFSET 0
337337
""", [1])
338-
let categories = try client.query("""
338+
let categories = client.query("""
339339
SELECT "categories".* FROM "categories" WHERE ("categories"."id" = $1) LIMIT 1 OFFSET 0
340340
""", [1])
341341

0 commit comments

Comments
 (0)