diff --git a/influxdb3/tests/server/system_tables.rs b/influxdb3/tests/server/system_tables.rs index ecad206823c..faaff52773e 100644 --- a/influxdb3/tests/server/system_tables.rs +++ b/influxdb3/tests/server/system_tables.rs @@ -661,3 +661,154 @@ async fn test_nodes_table_filters_sensitive_params() { ); } } + +#[tokio::test] +async fn system_tables_scoped_to_queried_database() { + let server = TestServer::spawn().await; + let db1 = "db_alpha"; + let db2 = "db_beta"; + + server + .write_lp_to_db( + db1, + "cpu,host=a usage=10\nmem,host=a usage=20", + Precision::Second, + ) + .await + .expect("write to db_alpha"); + + server + .write_lp_to_db( + db2, + "disk,host=b usage=30\nnet,host=b usage=40\nswap,host=b usage=50", + Precision::Second, + ) + .await + .expect("write to db_beta"); + + // Query system.tables from db1 — should only see db1's tables + { + let resp = server + .flight_sql_client(db1) + .await + .query("SELECT database_name, table_name FROM system.tables ORDER BY table_name") + .await + .unwrap(); + let batches = collect_stream(resp).await; + assert_batches_sorted_eq!( + [ + "+---------------+------------+", + "| database_name | table_name |", + "+---------------+------------+", + "| db_alpha | cpu |", + "| db_alpha | mem |", + "+---------------+------------+", + ], + &batches + ); + } + + // Query system.tables from db2 — should only see db2's tables + { + let resp = server + .flight_sql_client(db2) + .await + .query("SELECT database_name, table_name FROM system.tables ORDER BY table_name") + .await + .unwrap(); + let batches = collect_stream(resp).await; + assert_batches_sorted_eq!( + [ + "+---------------+------------+", + "| database_name | table_name |", + "+---------------+------------+", + "| db_beta | disk |", + "| db_beta | net |", + "| db_beta | swap |", + "+---------------+------------+", + ], + &batches + ); + } +} + +#[tokio::test] +async fn system_tables_from_internal_returns_all_databases() { + let server = TestServer::spawn().await; + let db1 = "db_one"; + let db2 = "db_two"; + + server + .write_lp_to_db(db1, "cpu,host=a usage=10", Precision::Second) + .await + .expect("write to db_one"); + + server + .write_lp_to_db(db2, "mem,host=b usage=20", Precision::Second) + .await + .expect("write to db_two"); + + // Query system.tables from _internal — should return tables from all databases + let resp = server + .flight_sql_client("_internal") + .await + .query("SELECT database_name, table_name FROM system.tables ORDER BY database_name, table_name") + .await + .unwrap(); + let batches = collect_stream(resp).await; + + // Collect results into a set of (database_name, table_name) pairs + let mut found_db1_cpu = false; + let mut found_db2_mem = false; + for batch in &batches { + let db_col = batch + .column_by_name("database_name") + .unwrap() + .as_any() + .downcast_ref::() + .unwrap(); + let table_col = batch + .column_by_name("table_name") + .unwrap() + .as_any() + .downcast_ref::() + .unwrap(); + for i in 0..batch.num_rows() { + let db = db_col.value(i); + let table = table_col.value(i); + if db == "db_one" && table == "cpu" { + found_db1_cpu = true; + } + if db == "db_two" && table == "mem" { + found_db2_mem = true; + } + } + } + + assert!( + found_db1_cpu, + "Expected to find db_one.cpu in system.tables when queried from _internal" + ); + assert!( + found_db2_mem, + "Expected to find db_two.mem in system.tables when queried from _internal" + ); +} + +#[tokio::test] +async fn system_tables_nonexistent_database_returns_error() { + let server = TestServer::spawn().await; + + let error = server + .flight_sql_client("nonexistent_db") + .await + .query("SELECT * FROM system.tables") + .await + .unwrap_err(); + + let error_msg = error.to_string(); + assert!( + error_msg.contains("nonexistent_db"), + "Expected error to reference the missing database name, got: {error_msg}" + ); +} diff --git a/influxdb3_system_tables/src/lib.rs b/influxdb3_system_tables/src/lib.rs index ca0b0e5af49..b4a79fbe846 100644 --- a/influxdb3_system_tables/src/lib.rs +++ b/influxdb3_system_tables/src/lib.rs @@ -176,7 +176,17 @@ impl AllSystemSchemaTablesProvider { InfluxdbSchemaTable::new(Arc::clone(&db_schema)), ))), ); - if db_schema.name.as_ref() == INTERNAL_DB_NAME { + let is_internal_database = db_schema.name.as_ref() == INTERNAL_DB_NAME; + // `system.tables` is available for all databases but restricts tables + // to that contained in the mentioned database only. + tables.insert( + TABLES_TABLE_NAME, + Arc::new(SystemTableProvider::new(Arc::new(TablesTable::new( + Arc::clone(&catalog), + (!is_internal_database).then_some(db_schema), + )))), + ); + if is_internal_database { tables.insert( TOKENS_TABLE_NAME, Arc::new(SystemTableProvider::new(Arc::new(TokenSystemTable::new( @@ -202,12 +212,6 @@ impl AllSystemSchemaTablesProvider { Arc::clone(&catalog), )))), ); - tables.insert( - TABLES_TABLE_NAME, - Arc::new(SystemTableProvider::new(Arc::new(TablesTable::new( - Arc::clone(&catalog), - )))), - ); tables.insert( GENERATION_DURATIONS_TABLE_NAME, Arc::new(SystemTableProvider::new(Arc::new( diff --git a/influxdb3_system_tables/src/tables.rs b/influxdb3_system_tables/src/tables.rs index 2c4f45f8813..e34e557cb9f 100644 --- a/influxdb3_system_tables/src/tables.rs +++ b/influxdb3_system_tables/src/tables.rs @@ -5,19 +5,21 @@ use arrow::array::{StringViewBuilder, UInt64Builder}; use arrow_array::{ArrayRef, RecordBatch}; use arrow_schema::{DataType, Field, Schema, SchemaRef, TimeUnit}; use datafusion::{error::DataFusionError, logical_expr::Expr}; -use influxdb3_catalog::catalog::Catalog; +use influxdb3_catalog::catalog::{Catalog, DatabaseSchema}; use iox_system_tables::IoxSystemTable; #[derive(Debug)] pub(crate) struct TablesTable { catalog: Arc, + database: Option>, schema: SchemaRef, } impl TablesTable { - pub(crate) fn new(catalog: Arc) -> Self { + pub(crate) fn new(catalog: Arc, database: Option>) -> Self { Self { catalog, + database, schema: tables_schema(), } } @@ -52,7 +54,11 @@ impl IoxSystemTable for TablesTable { _filters: Option>, _limit: Option, ) -> Result { - let databases = self.catalog.list_db_schema(); + let databases = if let Some(database_schema) = &self.database { + vec![Arc::clone(database_schema)] + } else { + self.catalog.list_db_schema() + }; // Count total tables across all databases let total_tables: usize = databases