1- // Copyright 2026 Stackable GmbH
2- // Licensed under the Open Software License version 3.0 (OSL-3.0).
3- // See LICENSE file in the project root for full license text.
1+ // SPDX-FileCopyrightText: 2026 Stackable GmbH
2+ // SPDX-License-Identifier: OSL-3.0
43use std:: collections:: HashMap ;
54use std:: fmt:: Debug ;
65use std:: sync:: Arc ;
@@ -16,7 +15,6 @@ use pgwire::api::{ClientInfo, PgWireConnectionState};
1615use pgwire:: error:: { ErrorInfo , PgWireError , PgWireResult } ;
1716use pgwire:: messages:: startup:: { Authentication , SecretKey } ;
1817use pgwire:: messages:: { PgWireBackendMessage , PgWireFrontendMessage } ;
19- use rand:: Rng ;
2018use trino_rust_client:: auth:: Auth ;
2119
2220use crate :: config:: Config ;
@@ -32,7 +30,7 @@ static CONNECTION_PID: AtomicI32 = AtomicI32::new(1);
3230/// authorisation token for somebody else's connection.
3331fn new_pid_and_secret_key ( ) -> ( i32 , SecretKey ) {
3432 let pid = CONNECTION_PID . fetch_add ( 1 , Ordering :: Relaxed ) ;
35- let secret = rand:: thread_rng ( ) . r#gen :: < i32 > ( ) ;
33+ let secret = rand:: random :: < i32 > ( ) ;
3634 ( pid, SecretKey :: I32 ( secret) )
3735}
3836
@@ -142,31 +140,9 @@ impl StartupHandler for GatewayStartupHandler {
142140 ) )
143141 . await ?;
144142 } else {
145- // No auth — create Trino client immediately
143+ // No auth — create Trino client immediately and finish.
146144 let trino_client = Arc :: new ( self . build_trino_client ( None , None ) ?) ;
147- let ( pid, secret_key) = new_pid_and_secret_key ( ) ;
148- client. set_pid_and_secret_key ( pid, secret_key. clone ( ) ) ;
149- let active_query_id = session:: register_cancel (
150- pid,
151- secret_key. clone ( ) ,
152- Arc :: clone ( & trino_client) ,
153- ) ;
154- let conn_id = format ! ( "{}_{}" , client. socket_addr( ) , pid) ;
155- client
156- . metadata_mut ( )
157- . insert ( session:: CONNECTION_ID_KEY . to_owned ( ) , conn_id. clone ( ) ) ;
158- session:: register_connection (
159- conn_id,
160- ConnectionState {
161- trino_client,
162- config : self . config . clone ( ) ,
163- portals : Default :: default ( ) ,
164- active_query_id,
165- cancel_key : ( pid, secret_key) ,
166- } ,
167- ) ;
168- finish_authentication ( client, & GatewayParameterProvider ) . await ?;
169- tracing:: info!( addr = %client. socket_addr( ) , "client connected (no auth)" ) ;
145+ self . establish_session ( client, trino_client, None ) . await ?;
170146 }
171147 }
172148 PgWireFrontendMessage :: PasswordMessageFamily ( pwd) => {
@@ -177,50 +153,35 @@ impl StartupHandler for GatewayStartupHandler {
177153 let password = pwd. into_password ( ) ?;
178154 let user = client. metadata ( ) . get ( "user" ) . cloned ( ) . unwrap_or_default ( ) ;
179155
180- // Build Trino client with the PG client's credentials
156+ // Build Trino client with the PG client's credentials.
181157 let trino_client =
182158 self . build_trino_client ( Some ( & user) , Some ( & password. password ) ) ?;
183159
184- // Validate credentials by running a lightweight query against Trino.
185- // If Trino rejects the auth, we reject the PG connection immediately
186- // rather than letting the first real query fail with a confusing error.
160+ // Validate credentials by running a lightweight query against
161+ // Trino. We reject the PG connection here on any failure
162+ // (auth rejection, network error, malformed reply, ...) so
163+ // the client gets a clear failure during startup rather than
164+ // a confusing error on its first real query. The error
165+ // message is logged at the gateway with the underlying
166+ // cause; the client gets the standard `InvalidPassword`
167+ // response — distinguishing auth-failure from other failures
168+ // requires inspecting `trino-rust-client`'s error enum and
169+ // is tracked separately.
187170 if let Err ( e) = trino_client
188171 . get :: < trino_rust_client:: Row > ( "SELECT 1" . to_owned ( ) )
189172 . await
190173 {
191- let msg = e. to_string ( ) ;
192174 tracing:: warn!(
193175 addr = %client. socket_addr( ) ,
194176 user = %user,
195- "Trino authentication failed: {msg}"
177+ error = %e,
178+ "rejecting PG connection — Trino credential check failed (auth rejection, network error, or unreachable Trino)"
196179 ) ;
197180 return Err ( PgWireError :: InvalidPassword ( user) ) ;
198181 }
199182
200- let trino_client = Arc :: new ( trino_client) ;
201- let ( pid, secret_key) = new_pid_and_secret_key ( ) ;
202- client. set_pid_and_secret_key ( pid, secret_key. clone ( ) ) ;
203- let active_query_id = session:: register_cancel (
204- pid,
205- secret_key. clone ( ) ,
206- Arc :: clone ( & trino_client) ,
207- ) ;
208- let conn_id = format ! ( "{}_{}" , client. socket_addr( ) , pid) ;
209- client
210- . metadata_mut ( )
211- . insert ( session:: CONNECTION_ID_KEY . to_owned ( ) , conn_id. clone ( ) ) ;
212- session:: register_connection (
213- conn_id,
214- ConnectionState {
215- trino_client,
216- config : self . config . clone ( ) ,
217- portals : Default :: default ( ) ,
218- active_query_id,
219- cancel_key : ( pid, secret_key) ,
220- } ,
221- ) ;
222- finish_authentication ( client, & GatewayParameterProvider ) . await ?;
223- tracing:: info!( addr = %client. socket_addr( ) , user = %user, "client connected" ) ;
183+ self . establish_session ( client, Arc :: new ( trino_client) , Some ( user. as_str ( ) ) )
184+ . await ?;
224185 }
225186 _ => {
226187 return Err ( PgWireError :: ApiError (
@@ -241,6 +202,51 @@ impl GatewayStartupHandler {
241202 self . config . tls_cert . is_some ( ) && self . config . tls_key . is_some ( )
242203 }
243204
205+ /// Common per-connection setup once the Trino client has been built
206+ /// and (for `--auth=true`) credentials have been verified. Allocates a
207+ /// fresh `(pid, secret_key)`, registers the connection in the cancel
208+ /// registry and the per-connection state map, and finishes the PG
209+ /// startup handshake. `user` is `Some` only on the auth=true path,
210+ /// where it's logged for operator visibility.
211+ async fn establish_session < C > (
212+ & self ,
213+ client : & mut C ,
214+ trino_client : Arc < trino_rust_client:: Client > ,
215+ user : Option < & str > ,
216+ ) -> PgWireResult < ( ) >
217+ where
218+ C : ClientInfo + Sink < PgWireBackendMessage > + Unpin + Send + Sync ,
219+ C :: Error : Debug ,
220+ PgWireError : From < <C as Sink < PgWireBackendMessage > >:: Error > ,
221+ {
222+ let ( pid, secret_key) = new_pid_and_secret_key ( ) ;
223+ client. set_pid_and_secret_key ( pid, secret_key. clone ( ) ) ;
224+ let active_query_id =
225+ session:: register_cancel ( pid, secret_key. clone ( ) , Arc :: clone ( & trino_client) ) ;
226+ let conn_id = format ! ( "{}_{}" , client. socket_addr( ) , pid) ;
227+ client
228+ . metadata_mut ( )
229+ . insert ( session:: CONNECTION_ID_KEY . to_owned ( ) , conn_id. clone ( ) ) ;
230+ session:: register_connection (
231+ conn_id,
232+ ConnectionState {
233+ trino_client,
234+ config : self . config . clone ( ) ,
235+ portals : Default :: default ( ) ,
236+ active_query_id,
237+ cancel_key : ( pid, secret_key) ,
238+ } ,
239+ ) ;
240+ finish_authentication ( client, & GatewayParameterProvider ) . await ?;
241+ match user {
242+ Some ( u) => {
243+ tracing:: info!( addr = %client. socket_addr( ) , user = %u, "client connected" )
244+ }
245+ None => tracing:: info!( addr = %client. socket_addr( ) , "client connected (no auth)" ) ,
246+ }
247+ Ok ( ( ) )
248+ }
249+
244250 /// Build a Trino client, optionally with Basic auth credentials from the PG client.
245251 fn build_trino_client (
246252 & self ,
0 commit comments