Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 221e3e3

Browse files
authored
Improve row decoding performance (vapor#221)
* Leverage better-performing new APIs from PostgresNIO, also solving (most) deprecation warnings. Also some additional minor improvements to connection handling. * Generating NIOSSLContexts is (very) expensive, do it only once (per connection source, at least) rather than for every connection. * CI cleanup - update Swift versions, use generic names, don't over-test, use new checkout action
1 parent 452b034 commit 221e3e3

File tree

6 files changed

+112
-90
lines changed

6 files changed

+112
-90
lines changed

‎.github/workflows/test.yml‎

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,84 +9,86 @@ jobs:
99
dbimage:
1010
- postgres:14
1111
- postgres:13
12-
- postgres:12
1312
- postgres:11
1413
dbauth:
1514
- trust
1615
- md5
1716
- scram-sha-256
1817
swiftver:
19-
- 'swift:5.2'
20-
- 'swift:5.5'
21-
- 'swiftlang/swift:nightly-main'
18+
- swift:5.2
19+
- swift:5.5
20+
- swift:5.6
21+
- swiftlang/swift:nightly-main
2222
swiftos:
2323
- focal
24+
include:
25+
- swiftver: swift:5.2
26+
test_flag: --enable-test-discovery
2427
container: ${{ format('{0}-{1}', matrix.swiftver, matrix.swiftos) }}
2528
runs-on: ubuntu-latest
2629
env:
2730
LOG_LEVEL: debug
2831
POSTGRES_HOSTNAME: 'psql-a'
2932
POSTGRES_HOSTNAME_A: 'psql-a'
3033
POSTGRES_HOSTNAME_B: 'psql-b'
31-
POSTGRES_DB: 'vapor_database'
32-
POSTGRES_DB_A: 'vapor_database'
33-
POSTGRES_DB_B: 'vapor_database'
34-
POSTGRES_USER: 'vapor_username'
35-
POSTGRES_USER_A: 'vapor_username'
36-
POSTGRES_USER_B: 'vapor_username'
37-
POSTGRES_PASSWORD: 'vapor_password'
38-
POSTGRES_PASSWORD_A: 'vapor_password'
39-
POSTGRES_PASSWORD_B: 'vapor_password'
34+
POSTGRES_DB: 'test_database'
35+
POSTGRES_DB_A: 'test_database'
36+
POSTGRES_DB_B: 'test_database'
37+
POSTGRES_USER: 'test_username'
38+
POSTGRES_USER_A: 'test_username'
39+
POSTGRES_USER_B: 'test_username'
40+
POSTGRES_PASSWORD: 'test_password'
41+
POSTGRES_PASSWORD_A: 'test_password'
42+
POSTGRES_PASSWORD_B: 'test_password'
4043
services:
4144
psql-a:
4245
image: ${{ matrix.dbimage }}
4346
env:
44-
POSTGRES_USER: 'vapor_username'
45-
POSTGRES_DB: 'vapor_database'
46-
POSTGRES_PASSWORD: 'vapor_password'
47+
POSTGRES_USER: 'test_username'
48+
POSTGRES_DB: 'test_database'
49+
POSTGRES_PASSWORD: 'test_password'
4750
POSTGRES_HOST_AUTH_METHOD: ${{ matrix.dbauth }}
4851
POSTGRES_INITDB_ARGS: --auth-host=${{ matrix.dbauth }}
4952
psql-b:
5053
image: ${{ matrix.dbimage }}
5154
env:
52-
POSTGRES_USER: 'vapor_username'
53-
POSTGRES_DB: 'vapor_database'
54-
POSTGRES_PASSWORD: 'vapor_password'
55+
POSTGRES_USER: 'test_username'
56+
POSTGRES_DB: 'test_database'
57+
POSTGRES_PASSWORD: 'test_password'
5558
POSTGRES_HOST_AUTH_METHOD: ${{ matrix.dbauth }}
5659
POSTGRES_INITDB_ARGS: --auth-host=${{ matrix.dbauth }}
5760
steps:
5861
- name: Check out package
59-
uses: actions/checkout@v2
62+
uses: actions/checkout@v3
6063
with: { path: 'postgres-kit' }
6164
- name: Check out fluent-postgres-driver dependent
62-
uses: actions/checkout@v2
65+
uses: actions/checkout@v3
6366
with: { repository: 'vapor/fluent-postgres-driver', path: 'fluent-postgres-driver' }
6467
- name: Run local tests with Thread Sanitizer
65-
run: swift test --package-path postgres-kit --enable-test-discovery --sanitize=thread
68+
run: swift test --package-path postgres-kit ${{ matrix.test_flag }} --sanitize=thread
6669
- name: Use local package
6770
run: swift package --package-path fluent-postgres-driver edit postgres-kit --path postgres-kit
6871
- name: Run fluent-postgres-kit tests with Thread Sanitizer
69-
run: swift test --package-path fluent-postgres-driver --enable-test-discovery --sanitize=thread
72+
run: swift test --package-path fluent-postgres-driver ${{ matrix.test_flag }} --sanitize=thread
7073

7174
macos:
7275
strategy:
7376
fail-fast: false
7477
matrix:
78+
# Only test latest version and one auth method on macOS
7579
dbimage:
76-
# Only test the lastest version on macOS, let Linux do the rest
7780
- postgresql@14
7881
dbauth:
79-
# Only test one auth method on macOS, Linux tests will cover the others
8082
- scram-sha-256
8183
xcode:
8284
- latest-stable
83-
- latest
85+
#- latest
8486
runs-on: macos-11
8587
env:
8688
LOG_LEVEL: debug
8789
POSTGRES_HOSTNAME: 127.0.0.1
88-
POSTGRES_USER: vapor_username
89-
POSTGRES_PASSWORD: vapor_password
90+
POSTGRES_USER: test_username
91+
POSTGRES_PASSWORD: test_password
9092
POSTGRES_DB: postgres
9193
POSTGRES_HOST_AUTH_METHOD: ${{ matrix.dbauth }}
9294
steps:
@@ -102,7 +104,7 @@ jobs:
102104
pg_ctl start --wait
103105
timeout-minutes: 2
104106
- name: Checkout code
105-
uses: actions/checkout@v2
107+
uses: actions/checkout@v3
106108
- name: Run local tests with Thread Sanitizer
107109
run: |
108110
swift test --sanitize=thread -Xlinker -rpath \

‎Package.swift‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ let package = Package(
1010
.library(name: "PostgresKit", targets: ["PostgresKit"]),
1111
],
1212
dependencies: [
13-
.package(url: "https://github.com/vapor/postgres-nio.git", from: "1.7.2"),
13+
.package(url: "https://github.com/vapor/postgres-nio.git", from: "1.9.0"),
1414
.package(url: "https://github.com/vapor/sql-kit.git", from: "3.16.0"),
1515
.package(url: "https://github.com/vapor/async-kit.git", from: "1.0.0"),
1616
],

‎Sources/PostgresKit/PostgresConfiguration.swift‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public struct PostgresConfiguration {
1515
public static var ianaPortNumber: Int { 5432 }
1616

1717
internal var _hostname: String?
18+
internal var _port: Int?
1819

1920
public init?(url: String) {
2021
guard let url = URL(string: url) else {
@@ -67,6 +68,7 @@ public struct PostgresConfiguration {
6768
self.database = database
6869
self.tlsConfiguration = nil
6970
self._hostname = nil
71+
self._port = nil
7072
}
7173

7274
public init(
@@ -85,5 +87,6 @@ public struct PostgresConfiguration {
8587
self.password = password
8688
self.tlsConfiguration = tlsConfiguration
8789
self._hostname = hostname
90+
self._port = port
8891
}
8992
}

‎Sources/PostgresKit/PostgresConnectionSource.swift‎

Lines changed: 63 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,79 @@
1+
import NIOConcurrencyHelpers
2+
import NIOSSL
3+
14
public struct PostgresConnectionSource: ConnectionPoolSource {
25
public let configuration: PostgresConfiguration
6+
public let sslContext: Result<NIOSSLContext?, Error>
7+
private static let idGenerator = NIOAtomic.makeAtomic(value: 0)
38

49
public init(configuration: PostgresConfiguration) {
510
self.configuration = configuration
11+
// TODO: Figure out a way to throw errors from this initializer sensibly, or to lazily init the NIOSSLContext only once in makeConnection()
12+
self.sslContext = .init(catching: { try configuration._hostname.flatMap { _ in try configuration.tlsConfiguration.map { try .init(configuration: 0ドル) } } })
613
}
714

815
public func makeConnection(
916
logger: Logger,
1017
on eventLoop: EventLoop
1118
) -> EventLoopFuture<PostgresConnection> {
12-
let address: SocketAddress
13-
do {
14-
address = try self.configuration.address()
15-
} catch {
16-
return eventLoop.makeFailedFuture(error)
17-
}
18-
return PostgresConnection.connect(
19-
to: address,
20-
tlsConfiguration: self.configuration.tlsConfiguration,
21-
serverHostname: self.configuration._hostname,
22-
logger: logger,
23-
on: eventLoop
24-
).flatMap { conn in
25-
return conn.authenticate(
26-
username: self.configuration.username,
27-
database: self.configuration.database,
28-
password: self.configuration.password,
19+
if let hostname = self.configuration._hostname {
20+
let tlsMode: PostgresConnection.Configuration.TLS
21+
switch self.sslContext {
22+
case let .success(sslContext): tlsMode = sslContext.map { .require(0ドル) } ?? .disable
23+
case let .failure(error): return eventLoop.makeFailedFuture(error)
24+
}
25+
let future = PostgresConnection.connect(
26+
on: eventLoop,
27+
configuration: .init(
28+
connection: .init(host: hostname, port: self.configuration._port ?? PostgresConfiguration.ianaPortNumber),
29+
authentication: .init(username: self.configuration.username, database: self.configuration.database, password: self.configuration.password),
30+
tls: tlsMode
31+
),
32+
id: Self.idGenerator.add(1),
2933
logger: logger
30-
).flatMap {
31-
if let searchPath = self.configuration.searchPath {
32-
let string = searchPath.map { "\"" + 0ドル + "\"" }.joined(separator: ", ")
33-
return conn.simpleQuery("SET search_path = \(string)")
34-
.map { _ in }
35-
} else {
36-
return eventLoop.makeSucceededFuture(())
34+
)
35+
36+
if let searchPath = self.configuration.searchPath {
37+
return future.flatMap { conn in
38+
let string = searchPath.map { #""\#(0ドル)""# }.joined(separator: ", ")
39+
return conn.simpleQuery("SET search_path = \(string)").map { _ in conn }
40+
}
41+
} else {
42+
return future
43+
}
44+
} else {
45+
let address: SocketAddress
46+
do {
47+
address = try self.configuration.address()
48+
} catch {
49+
return eventLoop.makeFailedFuture(error)
50+
}
51+
52+
// Legacy code path until PostgresNIO regains support for connecting directly to a SocketAddress.
53+
return PostgresConnection.connect(
54+
to: address,
55+
tlsConfiguration: self.configuration.tlsConfiguration,
56+
serverHostname: self.configuration._hostname,
57+
logger: logger,
58+
on: eventLoop
59+
).flatMap { conn in
60+
return conn.authenticate(
61+
username: self.configuration.username,
62+
database: self.configuration.database,
63+
password: self.configuration.password,
64+
logger: logger
65+
).flatMap {
66+
if let searchPath = self.configuration.searchPath {
67+
let string = searchPath.map { "\"" + 0ドル + "\"" }.joined(separator: ", ")
68+
return conn.simpleQuery("SET search_path = \(string)").map { _ in conn }
69+
} else {
70+
return eventLoop.makeSucceededFuture(conn)
71+
}
72+
}.flatMapErrorThrowing { error in
73+
_ = conn.close()
74+
throw error
3775
}
38-
}.flatMapErrorThrowing { error in
39-
_ = conn.close()
40-
throw error
41-
}.map { conn }
76+
}
4277
}
4378
}
4479
}
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,40 @@
11
extension PostgresRow {
22
public func sql(decoder: PostgresDataDecoder = .init()) -> SQLRow {
3-
return _PostgreSQLRow(row: self, decoder: decoder)
3+
return _PostgresSQLRow(row: self.makeRandomAccess(), decoder: decoder)
44
}
55
}
66

77
// MARK: Private
88

9-
private struct _PostgreSQLRow: SQLRow {
10-
let row:PostgresRow
9+
private struct _PostgresSQLRow: SQLRow {
10+
let randomAccessView:PostgresRandomAccessRow
1111
let decoder: PostgresDataDecoder
1212

1313
enum _Error: Error {
1414
case missingColumn(String)
1515
}
16+
17+
init(row: PostgresRandomAccessRow, decoder: PostgresDataDecoder) {
18+
self.randomAccessView = row
19+
self.decoder = decoder
20+
}
1621

1722
var allColumns: [String] {
18-
self.row.rowDescription.fields.map { 0ドル.name }
23+
self.randomAccessView.map { 0ドル.columnName }
1924
}
2025

2126
func contains(column: String) -> Bool {
22-
self.row.rowDescription.fields
23-
.contains { 0ドル.name == column }
27+
self.randomAccessView.contains(column)
2428
}
2529

2630
func decodeNil(column: String) throws -> Bool {
27-
self.row.column(column)?.value == nil
31+
!self.randomAccessView.contains(column) || self.randomAccessView[column].bytes == nil
2832
}
2933

3034
func decode<D>(column: String, as type: D.Type) throws -> D where D : Decodable {
31-
guard let data =self.row.column(column) else {
35+
guard self.randomAccessView.contains(column) else {
3236
throw _Error.missingColumn(column)
3337
}
34-
return try self.decoder.decode(D.self, from: data)
38+
return try self.decoder.decode(D.self, from: self.randomAccessView[data: column])
3539
}
3640
}

‎Tests/PostgresKitTests/Utilities.swift‎

Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import XCTest
22
import PostgresKit
3-
import NIOCore
43
import Logging
54
#if canImport(Darwin)
65
import Darwin.C
@@ -10,28 +9,7 @@ import Glibc
109

1110
extension PostgresConnection {
1211
static func test(on eventLoop: EventLoop) -> EventLoopFuture<PostgresConnection> {
13-
let config = PostgresConfiguration.test
14-
15-
return eventLoop.flatSubmit { () -> EventLoopFuture<PostgresConnection> in
16-
do {
17-
let address = try config.address()
18-
return self.connect(to: address, on: eventLoop)
19-
} catch {
20-
return eventLoop.makeFailedFuture(error)
21-
}
22-
}.flatMap { conn in
23-
return conn.authenticate(
24-
username: config.username,
25-
database: config.database,
26-
password: config.password
27-
)
28-
.map { conn }
29-
.flatMapError { error in
30-
conn.close().flatMapThrowing {
31-
throw error
32-
}
33-
}
34-
}
12+
return PostgresConnectionSource(configuration: .test).makeConnection(logger: .init(label: "vapor.codes.postgres-kit.test"), on: eventLoop)
3513
}
3614
}
3715

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /