From 8239f0a3b156ba168ab97a94ce21cac831c8f1b4 Mon Sep 17 00:00:00 2001 From: Sascha John Hesse Date: Fri, 22 May 2026 09:44:52 +0200 Subject: [PATCH 1/7] feat(cubesql): expose database name from SQL startup message in LoadRequestMeta The PostgreSQL wire protocol startup message includes a `database` parameter, which is already parsed and stored on SessionState. However, it was never included in LoadRequestMeta, making it inaccessible to JS configuration callbacks like extendContext. This is needed for multi-tenant setups where the database name carries the tenant identifier (e.g. a UUID), allowing checkSqlAuth or extendContext to resolve tenant context from the client-supplied dbname. Changes: - Add `database: Option` field to LoadRequestMeta (Rust) - Populate it from SessionState::database() in get_load_request_meta - Add `database?: string` to BaseMeta TypeScript interface Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/cubejs-backend-native/js/index.ts | 2 ++ rust/cubesql/cubesql/src/sql/session.rs | 6 ++++-- rust/cubesql/cubesql/src/transport/service.rs | 7 +++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/cubejs-backend-native/js/index.ts b/packages/cubejs-backend-native/js/index.ts index b5dbf3cdc4ad2..d6f5a919b0566 100644 --- a/packages/cubejs-backend-native/js/index.ts +++ b/packages/cubejs-backend-native/js/index.ts @@ -15,6 +15,8 @@ export interface BaseMeta { apiType: string, // Application name, for example Metabase appName?: string, + // Database name from the client startup message (e.g. psql dbname parameter) + database?: string, } export interface LoadRequestMeta extends BaseMeta { diff --git a/rust/cubesql/cubesql/src/sql/session.rs b/rust/cubesql/cubesql/src/sql/session.rs index 610d4c77c5f19..eb2c3167bc70d 100644 --- a/rust/cubesql/cubesql/src/sql/session.rs +++ b/rust/cubesql/cubesql/src/sql/session.rs @@ -409,11 +409,13 @@ impl SessionState { None }; - LoadRequestMeta::new( + let mut meta = LoadRequestMeta::new( self.protocol.get_name().to_string(), api_type.to_string(), application_name, - ) + ); + meta.set_database(self.database()); + meta } } diff --git a/rust/cubesql/cubesql/src/transport/service.rs b/rust/cubesql/cubesql/src/transport/service.rs index 364306be5298d..9b613ffdca08f 100644 --- a/rust/cubesql/cubesql/src/transport/service.rs +++ b/rust/cubesql/cubesql/src/transport/service.rs @@ -51,6 +51,8 @@ pub struct LoadRequestMeta { // Optional fields #[serde(rename = "changeUser", skip_serializing_if = "Option::is_none")] change_user: Option, + #[serde(skip_serializing_if = "Option::is_none")] + database: Option, } impl LoadRequestMeta { @@ -61,6 +63,7 @@ impl LoadRequestMeta { api_type, app_name, change_user: None, + database: None, } } @@ -71,6 +74,10 @@ impl LoadRequestMeta { pub fn set_change_user(&mut self, change_user: Option) { self.change_user = change_user; } + + pub fn set_database(&mut self, database: Option) { + self.database = database; + } } #[derive(Debug, Deserialize)] From a90ae3c88abb42b3ca6b30ae66183d7b51a2c9b0 Mon Sep 17 00:00:00 2001 From: Sascha John Hesse Date: Fri, 22 May 2026 10:49:15 +0200 Subject: [PATCH 2/7] feat(load-request-meta): add database name handling and tests for LoadRequestMeta --- rust/cubesql/cubesql/src/compile/test/mod.rs | 2 + .../src/compile/test/test_database_meta.rs | 59 +++++++++++++++++++ rust/cubesql/cubesql/src/transport/service.rs | 4 ++ 3 files changed, 65 insertions(+) create mode 100644 rust/cubesql/cubesql/src/compile/test/test_database_meta.rs diff --git a/rust/cubesql/cubesql/src/compile/test/mod.rs b/rust/cubesql/cubesql/src/compile/test/mod.rs index 75bd04812e53f..a2a4f5bf45918 100644 --- a/rust/cubesql/cubesql/src/compile/test/mod.rs +++ b/rust/cubesql/cubesql/src/compile/test/mod.rs @@ -31,6 +31,8 @@ pub mod test_bi_workarounds; #[cfg(test)] pub mod test_cube_join; #[cfg(test)] +pub mod test_database_meta; +#[cfg(test)] pub mod test_cube_join_grouped; #[cfg(test)] pub mod test_cube_scan; diff --git a/rust/cubesql/cubesql/src/compile/test/test_database_meta.rs b/rust/cubesql/cubesql/src/compile/test/test_database_meta.rs new file mode 100644 index 0000000000000..b22a532ec890a --- /dev/null +++ b/rust/cubesql/cubesql/src/compile/test/test_database_meta.rs @@ -0,0 +1,59 @@ +//! Tests that check database name propagation through LoadRequestMeta + +use pretty_assertions::assert_eq; + +use crate::compile::{ + test::{init_testing_logger, TestContext}, + DatabaseProtocol, Rewriter, +}; +use crate::transport::LoadRequestMeta; + +#[tokio::test] +async fn test_database_propagates_through_load_request_meta() { + if !Rewriter::sql_push_down_enabled() { + return; + } + init_testing_logger(); + + let context = TestContext::new(DatabaseProtocol::PostgreSQL).await; + + context + .execute_query( + // language=PostgreSQL + r#" +SELECT + COALESCE(customer_gender, 'N/A'), + AVG(avgPrice) +FROM + KibanaSampleDataEcommerce +WHERE + LOWER(customer_gender) = 'test' +GROUP BY 1 +; + "# + .to_string(), + ) + .await + .expect_err("Test transport does not support load with SQL"); + + let load_calls = context.load_calls().await; + assert_eq!(load_calls.len(), 1); + assert_eq!(load_calls[0].meta.database(), Some("cubedb".to_string())); +} + +#[test] +fn test_load_request_meta_database_serialization() { + let mut meta = LoadRequestMeta::new( + "postgres".to_string(), + "sql".to_string(), + Some("test-app".to_string()), + ); + + let json = serde_json::to_value(&meta).unwrap(); + assert!(json.get("database").is_none()); + + meta.set_database(Some("mydb".to_string())); + let json = serde_json::to_value(&meta).unwrap(); + assert_eq!(json["database"], "mydb"); + assert_eq!(meta.database(), Some("mydb".to_string())); +} diff --git a/rust/cubesql/cubesql/src/transport/service.rs b/rust/cubesql/cubesql/src/transport/service.rs index 9b613ffdca08f..f7e431fdbd682 100644 --- a/rust/cubesql/cubesql/src/transport/service.rs +++ b/rust/cubesql/cubesql/src/transport/service.rs @@ -75,6 +75,10 @@ impl LoadRequestMeta { self.change_user = change_user; } + pub fn database(&self) -> Option { + self.database.clone() + } + pub fn set_database(&mut self, database: Option) { self.database = database; } From 85933927db85baea2a96c178da6f81c833956093 Mon Sep 17 00:00:00 2001 From: Sascha John Hesse Date: Fri, 22 May 2026 10:52:23 +0200 Subject: [PATCH 3/7] fix(tests): reorder test module declarations for consistency after linting --- rust/cubesql/cubesql/src/compile/test/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rust/cubesql/cubesql/src/compile/test/mod.rs b/rust/cubesql/cubesql/src/compile/test/mod.rs index a2a4f5bf45918..7db5fcd79f2dc 100644 --- a/rust/cubesql/cubesql/src/compile/test/mod.rs +++ b/rust/cubesql/cubesql/src/compile/test/mod.rs @@ -31,12 +31,12 @@ pub mod test_bi_workarounds; #[cfg(test)] pub mod test_cube_join; #[cfg(test)] -pub mod test_database_meta; -#[cfg(test)] pub mod test_cube_join_grouped; #[cfg(test)] pub mod test_cube_scan; #[cfg(test)] +pub mod test_database_meta; +#[cfg(test)] pub mod test_df_execution; #[cfg(test)] pub mod test_filters; From 6c379557ebc26ca40437e4e0942540572a68f11b Mon Sep 17 00:00:00 2001 From: Sascha John Hesse Date: Fri, 22 May 2026 10:49:15 +0200 Subject: [PATCH 4/7] feat(load-request-meta): add database name handling and tests for LoadRequestMeta --- rust/cubesql/cubesql/src/compile/test/mod.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/rust/cubesql/cubesql/src/compile/test/mod.rs b/rust/cubesql/cubesql/src/compile/test/mod.rs index ba5d8dc09639e..00fdde8b4201e 100644 --- a/rust/cubesql/cubesql/src/compile/test/mod.rs +++ b/rust/cubesql/cubesql/src/compile/test/mod.rs @@ -31,6 +31,8 @@ pub mod test_bi_workarounds; #[cfg(test)] pub mod test_cube_join; #[cfg(test)] +pub mod test_database_meta; +#[cfg(test)] pub mod test_cube_join_grouped; #[cfg(test)] pub mod test_cube_scan; From b7ec8e2179d5acaf925a17be715bb2289ee152b0 Mon Sep 17 00:00:00 2001 From: Sascha John Hesse Date: Fri, 22 May 2026 10:52:23 +0200 Subject: [PATCH 5/7] fix(tests): reorder test module declarations for consistency after linting --- rust/cubesql/cubesql/src/compile/test/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/rust/cubesql/cubesql/src/compile/test/mod.rs b/rust/cubesql/cubesql/src/compile/test/mod.rs index 00fdde8b4201e..ba5d8dc09639e 100644 --- a/rust/cubesql/cubesql/src/compile/test/mod.rs +++ b/rust/cubesql/cubesql/src/compile/test/mod.rs @@ -31,8 +31,6 @@ pub mod test_bi_workarounds; #[cfg(test)] pub mod test_cube_join; #[cfg(test)] -pub mod test_database_meta; -#[cfg(test)] pub mod test_cube_join_grouped; #[cfg(test)] pub mod test_cube_scan; From 07e9692859eba084a39caf45bd0029b760c9a573 Mon Sep 17 00:00:00 2001 From: Sascha John Hesse Date: Tue, 2 Jun 2026 12:32:40 +0200 Subject: [PATCH 6/7] feat(cubesql): pass database name through SQL auth flow Thread database name from PostgreSQL startup parameters through SqlAuthServiceAuthenticateRequest into CheckSQLAuthPayload, constructing LoadRequestMeta with database context when available. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/cubejs-backend-native/js/index.ts | 2 +- packages/cubejs-backend-native/src/auth.rs | 14 +++++++++++++- rust/cubesql/cubesql/src/compile/router.rs | 2 ++ rust/cubesql/cubesql/src/sql/auth_service.rs | 2 ++ .../cubesql/src/sql/postgres/pg_auth_service.rs | 1 + 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/cubejs-backend-native/js/index.ts b/packages/cubejs-backend-native/js/index.ts index d6f5a919b0566..24e2c26cdd532 100644 --- a/packages/cubejs-backend-native/js/index.ts +++ b/packages/cubejs-backend-native/js/index.ts @@ -54,7 +54,7 @@ export interface CheckAuthPayload { } export interface CheckSQLAuthPayload { - request: Request, + request: Request, user: string | null, password: string | null, } diff --git a/packages/cubejs-backend-native/src/auth.rs b/packages/cubejs-backend-native/src/auth.rs index 73b2131b2ce2d..4b77a2225033a 100644 --- a/packages/cubejs-backend-native/src/auth.rs +++ b/packages/cubejs-backend-native/src/auth.rs @@ -120,10 +120,22 @@ impl SqlAuthService for NodeBridgeAuthService { let request_id = Uuid::new_v4().to_string(); + let meta = if request.database.is_some() { + let mut m = LoadRequestMeta::new( + "postgres".to_string(), + "sql".to_string(), + None, + ); + m.set_database(request.database.clone()); + Some(m) + } else { + None + }; + let extra = serde_json::to_string(&CheckSQLAuthTransportRequest { request: TransportAuthRequest { id: format!("{}-span-1", request_id), - meta: None, + meta, protocol: request.protocol, method: request.method, }, diff --git a/rust/cubesql/cubesql/src/compile/router.rs b/rust/cubesql/cubesql/src/compile/router.rs index 1761a900b3b30..1d841aa7fcb67 100644 --- a/rust/cubesql/cubesql/src/compile/router.rs +++ b/rust/cubesql/cubesql/src/compile/router.rs @@ -503,6 +503,7 @@ impl QueryRouter { let sql_auth_request = SqlAuthServiceAuthenticateRequest { protocol: "postgres".to_string(), method: "password".to_string(), + database: self.state.database(), }; let authenticate_response = self .session_manager @@ -647,6 +648,7 @@ impl QueryRouter { let sql_auth_request = SqlAuthServiceAuthenticateRequest { protocol: "postgres".to_string(), method: "password".to_string(), + database: self.state.database(), }; let authenticate_response = self .session_manager diff --git a/rust/cubesql/cubesql/src/sql/auth_service.rs b/rust/cubesql/cubesql/src/sql/auth_service.rs index 3550716db9759..aa919098ffea6 100644 --- a/rust/cubesql/cubesql/src/sql/auth_service.rs +++ b/rust/cubesql/cubesql/src/sql/auth_service.rs @@ -48,6 +48,8 @@ pub struct AuthenticateResponse { pub struct SqlAuthServiceAuthenticateRequest { pub protocol: String, pub method: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub database: Option, } #[async_trait] diff --git a/rust/cubesql/cubesql/src/sql/postgres/pg_auth_service.rs b/rust/cubesql/cubesql/src/sql/postgres/pg_auth_service.rs index 240046fd50da1..3a955f50a2ff7 100644 --- a/rust/cubesql/cubesql/src/sql/postgres/pg_auth_service.rs +++ b/rust/cubesql/cubesql/src/sql/postgres/pg_auth_service.rs @@ -78,6 +78,7 @@ impl PostgresAuthService for PostgresAuthServiceDefaultImpl { let sql_auth_request = SqlAuthServiceAuthenticateRequest { protocol: "postgres".to_string(), method: "password".to_string(), + database: parameters.get("database").cloned(), }; let authenticate_response = service .authenticate( From 04fb7eff94d2d32eb41b85279d2a3650690096aa Mon Sep 17 00:00:00 2001 From: Sascha John Hesse Date: Tue, 2 Jun 2026 12:46:24 +0200 Subject: [PATCH 7/7] test(cubesql): add coverage for database name propagation through auth flow Update JS test assertions to expect BaseMeta with database field instead of null, and add Rust tests for database propagation through SQL query meta and default absence when not set. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../cubejs-backend-native/test/sql.test.ts | 12 +++- .../src/compile/test/test_database_meta.rs | 56 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/packages/cubejs-backend-native/test/sql.test.ts b/packages/cubejs-backend-native/test/sql.test.ts index 943383ecb5cca..656e6eed0de4e 100644 --- a/packages/cubejs-backend-native/test/sql.test.ts +++ b/packages/cubejs-backend-native/test/sql.test.ts @@ -182,7 +182,11 @@ describe('SQLInterface', () => { expect(checkSqlAuth.mock.calls[0][0]).toEqual({ request: { id: expect.any(String), - meta: null, + meta: { + protocol: 'postgres', + apiType: 'sql', + database: 'test', + }, method: expect.any(String), protocol: expect.any(String), }, @@ -241,7 +245,11 @@ describe('SQLInterface', () => { expect(checkSqlAuth.mock.calls[0][0]).toEqual({ request: { id: expect.any(String), - meta: null, + meta: { + protocol: 'postgres', + apiType: 'sql', + database: 'test', + }, method: expect.any(String), protocol: expect.any(String), }, diff --git a/rust/cubesql/cubesql/src/compile/test/test_database_meta.rs b/rust/cubesql/cubesql/src/compile/test/test_database_meta.rs index b22a532ec890a..d22d79c15eb89 100644 --- a/rust/cubesql/cubesql/src/compile/test/test_database_meta.rs +++ b/rust/cubesql/cubesql/src/compile/test/test_database_meta.rs @@ -57,3 +57,59 @@ fn test_load_request_meta_database_serialization() { assert_eq!(json["database"], "mydb"); assert_eq!(meta.database(), Some("mydb".to_string())); } + +#[test] +fn test_load_request_meta_no_database_by_default() { + let meta = LoadRequestMeta::new( + "postgres".to_string(), + "sql".to_string(), + None, + ); + + assert_eq!(meta.database(), None); + + let json = serde_json::to_value(&meta).unwrap(); + assert!(json.get("database").is_none()); +} + +/// Verifies that database name from session state propagates into the SQL query +/// meta passed to TestConnectionTransport::sql. Follows the same pattern as +/// test_user_change::test_user_change_sql_generation. +#[tokio::test] +async fn test_database_in_sql_query_meta() { + if !Rewriter::sql_push_down_enabled() { + return; + } + init_testing_logger(); + + let context = TestContext::new(DatabaseProtocol::PostgreSQL).await; + + context + .execute_query( + // language=PostgreSQL + r#" +SELECT + COALESCE(customer_gender, 'N/A'), + AVG(avgPrice) +FROM + KibanaSampleDataEcommerce +WHERE + LOWER(customer_gender) = 'test' +GROUP BY 1 +; + "# + .to_string(), + ) + .await + .expect_err("Test transport does not support load with SQL"); + + let load_calls = context.load_calls().await; + assert_eq!(load_calls.len(), 1); + + // Database should appear in the serialized SQL query (set by TestConnectionTransport::sql) + let sql_query = load_calls[0].sql_query.as_ref().unwrap(); + assert!(sql_query.sql.contains(r#""database": "cubedb""#)); + + // And directly on the meta object + assert_eq!(load_calls[0].meta.database(), Some("cubedb".to_string())); +}