@@ -70,7 +70,8 @@ public final class PostgresConnection: SQLDatabase, @unchecked Sendable {
7070
7171 public static func connect(
7272 configuration: Configuration ,
73- eventLoopGroup: any EventLoopGroup = MultiThreadedEventLoopGroup . singleton
73+ eventLoopGroup: any EventLoopGroup = MultiThreadedEventLoopGroup . singleton,
74+ sslContext: NIOSSLContext ? = nil // supply pre-built context from pool to avoid per-connect creation cost
7475 ) async throws -> PostgresConnection {
7576 let bootstrap = ClientBootstrap ( group: eventLoopGroup)
7677 . channelOption ( ChannelOptions . socket ( IPPROTO_TCP, TCP_NODELAY) , value: 1 )
@@ -79,7 +80,7 @@ public final class PostgresConnection: SQLDatabase, @unchecked Sendable {
7980 port: configuration. port) . get ( )
8081 let conn = PostgresConnection ( channel: channel, config: configuration,
8182 logger: configuration. logger)
82- try await conn. handshake ( )
83+ try await conn. handshake ( sslContext : sslContext )
8384 return conn
8485 }
8586
@@ -91,40 +92,47 @@ public final class PostgresConnection: SQLDatabase, @unchecked Sendable {
9192
9293 // MARK: - Handshake
9394
94- private func handshake( ) async throws {
95- // The PostgreSQL SSL negotiation uses a single raw byte response ('S'/'N')
96- // which predates the standard PG framing format. We handle it by using a
97- // temporary raw bridge handler before installing the proper framing handler.
98- let rawBridge = AsyncChannelBridge ( )
99- try await channel. pipeline. addHandler ( rawBridge) . get ( )
95+ private func handshake( sslContext: NIOSSLContext ? = nil ) async throws {
96+ let b = AsyncChannelBridge ( )
97+ bridge = b
10098
101- // 1. Optionally request TLS
10299 if config. tls != . disable {
100+ // Postgres SSL negotiation uses a raw single-byte response ('S'/'N') before
101+ // the normal framing kicks in. Use a temporary raw bridge to read that byte,
102+ // then swap in the proper framing handler.
103+ let rawBridge = AsyncChannelBridge ( )
104+ try await channel. pipeline. addHandler ( rawBridge) . get ( )
105+
103106 let sslReq = PGFrontend . sslRequest ( allocator: channel. allocator)
104107 try await send ( sslReq)
105- // SSL response is a single raw byte, not a framed PG message
108+
106109 var sslResponse = try await rawBridge. waitForMessage ( on: channel. eventLoop)
107110 let sslByte = sslResponse. readInteger ( as: UInt8 . self) ?? UInt8 ( ascii: " N " )
111+
112+ try await channel. pipeline. removeHandler ( rawBridge) . get ( )
113+
114+ // Install framing + bridge
115+ let frameBox = _UnsafeSendable ( ByteToMessageHandler ( PGFramingHandler ( ) ) )
116+ try await channel. eventLoop. submit {
117+ try self . channel. pipeline. syncOperations. addHandlers ( [ frameBox. value, b] )
118+ } . get ( )
119+
108120 if sslByte == UInt8 ( ascii: " S " ) {
109- try await upgradeTLS ( )
121+ try await upgradeTLS ( sslContext : sslContext )
110122 logger. debug ( " PostgreSQL TLS established " )
111123 } else if config. tls == . require {
112124 throw SQLError . tlsError ( " Server does not support TLS " )
113125 }
126+ } else {
127+ // No TLS — install framing + bridge directly, skipping the rawBridge cycle.
128+ // Swift 6: ByteToMessageHandler has Sendable marked unavailable (event-loop-bound).
129+ let frameBox = _UnsafeSendable ( ByteToMessageHandler ( PGFramingHandler ( ) ) )
130+ try await channel. eventLoop. submit {
131+ try self . channel. pipeline. syncOperations. addHandlers ( [ frameBox. value, b] )
132+ } . get ( )
114133 }
115134
116- // 2. Now switch to proper PG message framing
117- try await channel. pipeline. removeHandler ( rawBridge) . get ( )
118- let b = AsyncChannelBridge ( )
119- bridge = b
120- // Swift 6: ByteToMessageHandler has Sendable marked unavailable (event-loop-bound).
121- // Use syncOperations from within the event loop to avoid the Sendable requirement.
122- let frameBox = _UnsafeSendable ( ByteToMessageHandler ( PGFramingHandler ( ) ) )
123- try await channel. eventLoop. submit {
124- try self . channel. pipeline. syncOperations. addHandlers ( [ frameBox. value, b] )
125- } . get ( )
126-
127- // 3. Startup + authentication
135+ // Startup + authentication
128136 let startup = PGFrontend . startup ( user: config. username,
129137 database: config. database,
130138 allocator: channel. allocator)
@@ -133,11 +141,18 @@ public final class PostgresConnection: SQLDatabase, @unchecked Sendable {
133141 logger. debug ( " PostgreSQL connected as \( config. username) " )
134142 }
135143
136- private func upgradeTLS( ) async throws {
137- var tlsConfig = TLSConfiguration . makeClientConfiguration ( )
138- tlsConfig. certificateVerification = . none
139- let sslContext = try NIOSSLContext ( configuration: tlsConfig)
140- let sslHandler = try NIOSSLClientHandler ( context: sslContext,
144+ // sslContext: reuse the pool-level NIOSSLContext instead of constructing one per connection.
145+ // NIOSSLContext wraps OpenSSL's SSL_CTX which is safe to share across connections.
146+ private func upgradeTLS( sslContext: NIOSSLContext ? = nil ) async throws {
147+ let ctx : NIOSSLContext
148+ if let provided = sslContext {
149+ ctx = provided
150+ } else {
151+ var tlsConfig = TLSConfiguration . makeClientConfiguration ( )
152+ tlsConfig. certificateVerification = . none
153+ ctx = try NIOSSLContext ( configuration: tlsConfig)
154+ }
155+ let sslHandler = try NIOSSLClientHandler ( context: ctx,
141156 serverHostname: config. host)
142157 // Swift 6: NIOSSLHandler has Sendable marked unavailable (event-loop-bound).
143158 let sslBox = _UnsafeSendable ( sslHandler)
@@ -466,7 +481,7 @@ public final class PostgresConnection: SQLDatabase, @unchecked Sendable {
466481
467482// Static formatters — DateFormatter/ISO8601DateFormatter are expensive to construct;
468483// allocating one per cell (old behaviour) added measurable overhead on date-heavy result sets.
469- private nonisolated ( unsafe ) let _pgDateFmt: DateFormatter = {
484+ private let _pgDateFmt : DateFormatter = {
470485 let f = DateFormatter ( ) ; f. locale = Locale ( identifier: " en_US_POSIX " )
471486 f. dateFormat = " yyyy-MM-dd " ; return f
472487} ( )
0 commit comments