From cf842df8c7fe2cb079d6f3c5905add4ff437c487 Mon Sep 17 00:00:00 2001 From: Phillip LeBlanc Date: Mon, 14 Jul 2025 14:16:08 +0900 Subject: [PATCH 1/4] use index ordering for columns --- src/duckdb/creator.rs | 473 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 464 insertions(+), 9 deletions(-) diff --git a/src/duckdb/creator.rs b/src/duckdb/creator.rs index dc1daffc..c562368f 100644 --- a/src/duckdb/creator.rs +++ b/src/duckdb/creator.rs @@ -468,15 +468,20 @@ impl TableManager { return Ok(()); } - tx.execute( - &format!( - "CREATE OR REPLACE VIEW {base_table} AS SELECT * FROM {internal_table}", - base_table = quote_identifier(&self.definition_name().to_string()), - internal_table = quote_identifier(&self.table_name().to_string()) - ), - [], - ) - .context(super::UnableToCreateDuckDBTableSnafu)?; + let table_columns = self.get_table_columns(tx)?; + let ordered_columns = self.order_columns_by_index(table_columns); + + let view_creation_sql = format!( + "CREATE OR REPLACE VIEW {base_table} AS SELECT {columns} FROM {internal_table}", + base_table = quote_identifier(&self.definition_name().to_string()), + columns = ordered_columns.join(", "), + internal_table = quote_identifier(&self.table_name().to_string()) + ); + + tracing::debug!("{view_creation_sql}"); + + tx.execute(&view_creation_sql, []) + .context(super::UnableToCreateDuckDBTableSnafu)?; Ok(()) } @@ -678,6 +683,61 @@ impl TableManager { Ok(count) } + + fn get_table_columns(&self, tx: &Transaction<'_>) -> super::Result> { + let sql = "SELECT name FROM pragma_table_info(?)".to_string(); + tracing::debug!("{sql}"); + + let mut stmt = tx.prepare(&sql).context(super::UnableToQueryDataSnafu)?; + let columns_iter = stmt + .query_map([&self.table_name().to_string()], |row| { + row.get::(0) + }) + .context(super::UnableToQueryDataSnafu)?; + + let mut columns = Vec::new(); + for column in columns_iter { + columns.push(column.context(super::UnableToQueryDataSnafu)?); + } + + Ok(columns) + } + + /// Orders the given columns such that indexed single columns are first. + /// If there is an index defined on a single column, that column should come first in the list. + /// Multi-column indexes are not considered for ordering. + pub(crate) fn order_columns_by_index(&self, columns: Vec) -> Vec { + let mut indexed_columns = Vec::new(); + let mut non_indexed_columns = Vec::new(); + + // Get single-column indexes + let single_column_indexes: HashSet = self + .table_definition + .indexes + .iter() + .filter_map(|(column_ref, _)| { + let cols: Vec<&str> = column_ref.iter().collect(); + if cols.len() == 1 { + Some(cols[0].to_string()) + } else { + None + } + }) + .collect(); + + // Separate columns into indexed and non-indexed + for column in columns { + if single_column_indexes.contains(&column) { + indexed_columns.push(column); + } else { + non_indexed_columns.push(column); + } + } + + // Return indexed columns first, then non-indexed columns + indexed_columns.extend(non_indexed_columns); + indexed_columns + } } fn create_empty_record_batch_reader(schema: SchemaRef) -> impl RecordBatchReader { @@ -1641,4 +1701,399 @@ pub(crate) mod tests { second_tables.first().expect("should have a table").0 ); } + + #[tokio::test] + async fn test_get_table_columns() { + let _guard = init_tracing(None); + let pool = get_mem_duckdb(); + + let table_definition = get_basic_table_definition(); + + let mut pool_conn = Arc::clone(&pool).connect_sync().expect("to get connection"); + let conn = pool_conn + .as_any_mut() + .downcast_mut::() + .expect("to downcast to duckdb connection"); + let tx = conn + .get_underlying_conn_mut() + .transaction() + .expect("should begin transaction"); + + let table_creator = TableManager::new(Arc::clone(&table_definition)) + .with_internal(false) + .expect("to create table creator"); + + table_creator + .create_table(Arc::clone(&pool), &tx) + .expect("to create table"); + + let columns = table_creator + .get_table_columns(&tx) + .expect("to get table columns"); + + assert_eq!(columns.len(), 2); + assert_eq!(columns[0], "id"); + assert_eq!(columns[1], "name"); + + tx.rollback().expect("should rollback transaction"); + } + + #[tokio::test] + async fn test_get_table_columns_with_internal_table() { + let _guard = init_tracing(None); + let pool = get_mem_duckdb(); + + let table_definition = get_basic_table_definition(); + + let mut pool_conn = Arc::clone(&pool).connect_sync().expect("to get connection"); + let conn = pool_conn + .as_any_mut() + .downcast_mut::() + .expect("to downcast to duckdb connection"); + let tx = conn + .get_underlying_conn_mut() + .transaction() + .expect("should begin transaction"); + + let table_creator = TableManager::new(Arc::clone(&table_definition)) + .with_internal(true) + .expect("to create table creator"); + + table_creator + .create_table(Arc::clone(&pool), &tx) + .expect("to create table"); + + let columns = table_creator + .get_table_columns(&tx) + .expect("to get table columns"); + + assert_eq!(columns.len(), 2); + assert_eq!(columns[0], "id"); + assert_eq!(columns[1], "name"); + + tx.rollback().expect("should rollback transaction"); + } + + #[tokio::test] + async fn test_get_table_columns_with_complex_schema() { + let _guard = init_tracing(None); + let pool = get_mem_duckdb(); + + let schema = Arc::new(arrow::datatypes::Schema::new(vec![ + arrow::datatypes::Field::new("id", arrow::datatypes::DataType::Int64, false), + arrow::datatypes::Field::new("name", arrow::datatypes::DataType::Utf8, false), + arrow::datatypes::Field::new("age", arrow::datatypes::DataType::Int32, true), + arrow::datatypes::Field::new( + "created_at", + arrow::datatypes::DataType::Timestamp( + arrow::datatypes::TimeUnit::Millisecond, + None, + ), + false, + ), + arrow::datatypes::Field::new("metadata", arrow::datatypes::DataType::Utf8, true), + ])); + + let table_definition = Arc::new(TableDefinition::new( + RelationName::new("complex_table"), + schema, + )); + + let mut pool_conn = Arc::clone(&pool).connect_sync().expect("to get connection"); + let conn = pool_conn + .as_any_mut() + .downcast_mut::() + .expect("to downcast to duckdb connection"); + let tx = conn + .get_underlying_conn_mut() + .transaction() + .expect("should begin transaction"); + + let table_creator = TableManager::new(Arc::clone(&table_definition)) + .with_internal(false) + .expect("to create table creator"); + + table_creator + .create_table(Arc::clone(&pool), &tx) + .expect("to create table"); + + let columns = table_creator + .get_table_columns(&tx) + .expect("to get table columns"); + + assert_eq!(columns.len(), 5); + assert_eq!(columns[0], "id"); + assert_eq!(columns[1], "name"); + assert_eq!(columns[2], "age"); + assert_eq!(columns[3], "created_at"); + assert_eq!(columns[4], "metadata"); + + tx.rollback().expect("should rollback transaction"); + } + + #[tokio::test] + async fn test_get_table_columns_nonexistent_table() { + let _guard = init_tracing(None); + let pool = get_mem_duckdb(); + + let table_definition = get_basic_table_definition(); + + let mut pool_conn = Arc::clone(&pool).connect_sync().expect("to get connection"); + let conn = pool_conn + .as_any_mut() + .downcast_mut::() + .expect("to downcast to duckdb connection"); + let tx = conn + .get_underlying_conn_mut() + .transaction() + .expect("should begin transaction"); + + let table_creator = TableManager::new(Arc::clone(&table_definition)) + .with_internal(false) + .expect("to create table creator"); + + let result = table_creator.get_table_columns(&tx); + + assert!(result.is_err()); + + tx.rollback().expect("should rollback transaction"); + } + + #[tokio::test] + async fn test_get_table_columns_with_special_characters_in_table_name() { + let _guard = init_tracing(None); + let pool = get_mem_duckdb(); + + let schema = Arc::new(arrow::datatypes::Schema::new(vec![ + arrow::datatypes::Field::new("id", arrow::datatypes::DataType::Int64, false), + arrow::datatypes::Field::new("name", arrow::datatypes::DataType::Utf8, false), + ])); + + let table_definition = Arc::new(TableDefinition::new( + RelationName::new("table_with_spaces and dots"), + schema, + )); + + let mut pool_conn = Arc::clone(&pool).connect_sync().expect("to get connection"); + let conn = pool_conn + .as_any_mut() + .downcast_mut::() + .expect("to downcast to duckdb connection"); + let tx = conn + .get_underlying_conn_mut() + .transaction() + .expect("should begin transaction"); + + let table_creator = TableManager::new(Arc::clone(&table_definition)) + .with_internal(false) + .expect("to create table creator"); + + table_creator + .create_table(Arc::clone(&pool), &tx) + .expect("to create table"); + + let columns = table_creator + .get_table_columns(&tx) + .expect("to get table columns"); + + assert_eq!(columns.len(), 2); + assert_eq!(columns[0], "id"); + assert_eq!(columns[1], "name"); + + tx.rollback().expect("should rollback transaction"); + } + + #[tokio::test] + async fn test_order_columns_by_index_no_indexes() { + let table_definition = get_basic_table_definition(); + let table_manager = TableManager::new(Arc::clone(&table_definition)) + .with_internal(false) + .expect("to create table manager"); + + let columns = vec!["id".to_string(), "name".to_string()]; + let ordered_columns = table_manager.order_columns_by_index(columns.clone()); + + // Should return columns in original order when no indexes + assert_eq!(ordered_columns, columns); + } + + #[tokio::test] + async fn test_order_columns_by_index_single_column_index() { + let schema = Arc::new(arrow::datatypes::Schema::new(vec![ + arrow::datatypes::Field::new("id", arrow::datatypes::DataType::Int64, false), + arrow::datatypes::Field::new("name", arrow::datatypes::DataType::Utf8, false), + arrow::datatypes::Field::new("age", arrow::datatypes::DataType::Int32, true), + arrow::datatypes::Field::new("email", arrow::datatypes::DataType::Utf8, false), + ])); + + let table_definition = Arc::new( + TableDefinition::new(RelationName::new("test_table"), schema).with_indexes(vec![( + ColumnReference::try_from("age").expect("valid column ref"), + IndexType::Enabled, + )]), + ); + + let table_manager = TableManager::new(Arc::clone(&table_definition)) + .with_internal(false) + .expect("to create table manager"); + + let columns = vec![ + "id".to_string(), + "name".to_string(), + "age".to_string(), + "email".to_string(), + ]; + let ordered_columns = table_manager.order_columns_by_index(columns); + + // 'age' should be first since it has an index + assert_eq!(ordered_columns[0], "age"); + assert_eq!(ordered_columns.len(), 4); + assert!(ordered_columns.contains(&"id".to_string())); + assert!(ordered_columns.contains(&"name".to_string())); + assert!(ordered_columns.contains(&"email".to_string())); + } + + #[tokio::test] + async fn test_order_columns_by_index_multiple_single_column_indexes() { + let schema = Arc::new(arrow::datatypes::Schema::new(vec![ + arrow::datatypes::Field::new("id", arrow::datatypes::DataType::Int64, false), + arrow::datatypes::Field::new("name", arrow::datatypes::DataType::Utf8, false), + arrow::datatypes::Field::new("age", arrow::datatypes::DataType::Int32, true), + arrow::datatypes::Field::new("email", arrow::datatypes::DataType::Utf8, false), + arrow::datatypes::Field::new("status", arrow::datatypes::DataType::Utf8, false), + ])); + + let table_definition = Arc::new( + TableDefinition::new(RelationName::new("test_table"), schema).with_indexes(vec![ + ( + ColumnReference::try_from("age").expect("valid column ref"), + IndexType::Enabled, + ), + ( + ColumnReference::try_from("email").expect("valid column ref"), + IndexType::Unique, + ), + ]), + ); + + let table_manager = TableManager::new(Arc::clone(&table_definition)) + .with_internal(false) + .expect("to create table manager"); + + let columns = vec![ + "id".to_string(), + "name".to_string(), + "age".to_string(), + "email".to_string(), + "status".to_string(), + ]; + let ordered_columns = table_manager.order_columns_by_index(columns); + + // Both 'age' and 'email' should be first (indexed columns) + assert_eq!(ordered_columns.len(), 5); + assert!(ordered_columns[0] == "age" || ordered_columns[0] == "email"); + assert!(ordered_columns[1] == "age" || ordered_columns[1] == "email"); + assert_ne!(ordered_columns[0], ordered_columns[1]); // Should be different indexed columns + + // Non-indexed columns should come after + let non_indexed_start = 2; + let remaining_columns: Vec = ordered_columns[non_indexed_start..].to_vec(); + assert!(remaining_columns.contains(&"id".to_string())); + assert!(remaining_columns.contains(&"name".to_string())); + assert!(remaining_columns.contains(&"status".to_string())); + } + + #[tokio::test] + async fn test_order_columns_by_index_with_multi_column_index() { + let schema = Arc::new(arrow::datatypes::Schema::new(vec![ + arrow::datatypes::Field::new("id", arrow::datatypes::DataType::Int64, false), + arrow::datatypes::Field::new("name", arrow::datatypes::DataType::Utf8, false), + arrow::datatypes::Field::new("age", arrow::datatypes::DataType::Int32, true), + arrow::datatypes::Field::new("email", arrow::datatypes::DataType::Utf8, false), + ])); + + let table_definition = Arc::new( + TableDefinition::new(RelationName::new("test_table"), schema).with_indexes(vec![ + ( + ColumnReference::try_from("(name, age)").expect("valid column ref"), + IndexType::Enabled, + ), + ( + ColumnReference::try_from("email").expect("valid column ref"), + IndexType::Unique, + ), + ]), + ); + + let table_manager = TableManager::new(Arc::clone(&table_definition)) + .with_internal(false) + .expect("to create table manager"); + + let columns = vec![ + "id".to_string(), + "name".to_string(), + "age".to_string(), + "email".to_string(), + ]; + let ordered_columns = table_manager.order_columns_by_index(columns); + + // Only 'email' should be first (single-column index), multi-column index (name, age) should be ignored + assert_eq!(ordered_columns[0], "email"); + assert_eq!(ordered_columns.len(), 4); + + // Other columns should be in remaining positions + let remaining_columns: Vec = ordered_columns[1..].to_vec(); + assert!(remaining_columns.contains(&"id".to_string())); + assert!(remaining_columns.contains(&"name".to_string())); + assert!(remaining_columns.contains(&"age".to_string())); + } + + #[tokio::test] + async fn test_order_columns_by_index_with_missing_indexed_column() { + let schema = Arc::new(arrow::datatypes::Schema::new(vec![ + arrow::datatypes::Field::new("id", arrow::datatypes::DataType::Int64, false), + arrow::datatypes::Field::new("name", arrow::datatypes::DataType::Utf8, false), + arrow::datatypes::Field::new("age", arrow::datatypes::DataType::Int32, true), + ])); + + let table_definition = Arc::new( + TableDefinition::new(RelationName::new("test_table"), schema).with_indexes(vec![ + ( + ColumnReference::try_from("age").expect("valid column ref"), + IndexType::Enabled, + ), + ( + ColumnReference::try_from("status").expect("valid column ref"), // Column not in schema + IndexType::Enabled, + ), + ]), + ); + + let table_manager = TableManager::new(Arc::clone(&table_definition)) + .with_internal(false) + .expect("to create table manager"); + + // Only provide columns that exist in schema + let columns = vec!["id".to_string(), "name".to_string(), "age".to_string()]; + let ordered_columns = table_manager.order_columns_by_index(columns); + + // 'age' should be first since it has an index and exists in the column list + assert_eq!(ordered_columns[0], "age"); + assert_eq!(ordered_columns.len(), 3); + assert!(ordered_columns.contains(&"id".to_string())); + assert!(ordered_columns.contains(&"name".to_string())); + } + + #[tokio::test] + async fn test_order_columns_by_index_empty_columns() { + let table_definition = get_basic_table_definition(); + let table_manager = TableManager::new(Arc::clone(&table_definition)) + .with_internal(false) + .expect("to create table manager"); + + let columns = vec![]; + let ordered_columns = table_manager.order_columns_by_index(columns); + + assert_eq!(ordered_columns.len(), 0); + } } From 29e9125f022ed5b0b2799379b14385045a60b902 Mon Sep 17 00:00:00 2001 From: Phillip LeBlanc Date: Mon, 14 Jul 2025 15:30:46 +0900 Subject: [PATCH 2/4] Re-order the columns to have the indexed column first --- src/duckdb/creator.rs | 85 +++++++++++++++++-- ...plain_analyze_with_index_and_view.snap.new | 26 ++++++ 2 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_with_index_and_view.snap.new diff --git a/src/duckdb/creator.rs b/src/duckdb/creator.rs index c562368f..c386a0ba 100644 --- a/src/duckdb/creator.rs +++ b/src/duckdb/creator.rs @@ -686,13 +686,15 @@ impl TableManager { fn get_table_columns(&self, tx: &Transaction<'_>) -> super::Result> { let sql = "SELECT name FROM pragma_table_info(?)".to_string(); - tracing::debug!("{sql}"); + + let owned_table_name = self.table_name().to_string(); + let table_name = quote_identifier(&owned_table_name); + + tracing::debug!("{sql}; ?={table_name}"); let mut stmt = tx.prepare(&sql).context(super::UnableToQueryDataSnafu)?; let columns_iter = stmt - .query_map([&self.table_name().to_string()], |row| { - row.get::(0) - }) + .query_map([table_name], |row| row.get::(0)) .context(super::UnableToQueryDataSnafu)?; let mut columns = Vec::new(); @@ -881,7 +883,7 @@ pub(crate) mod tests { } #[tokio::test] - async fn test_table_creator() { + async fn test_table_creator_indexes() { let _guard = init_tracing(None); let batches = get_logs_batches().await; @@ -2096,4 +2098,77 @@ pub(crate) mod tests { assert_eq!(ordered_columns.len(), 0); } + + #[tokio::test] + async fn test_explain_analyze_with_index_and_view() { + let _guard = init_tracing(None); + let pool = get_mem_duckdb(); + + let schema = Arc::new(arrow::datatypes::Schema::new(vec![ + arrow::datatypes::Field::new("id", arrow::datatypes::DataType::Int64, false), + arrow::datatypes::Field::new("name", arrow::datatypes::DataType::Utf8, false), + arrow::datatypes::Field::new("age", arrow::datatypes::DataType::Int32, true), + arrow::datatypes::Field::new("status", arrow::datatypes::DataType::Utf8, false), + ])); + + let table_definition = Arc::new( + TableDefinition::new(RelationName::new("test_table"), Arc::clone(&schema)) + .with_indexes(vec![( + ColumnReference::try_from("id").expect("valid column ref"), + IndexType::Enabled, + )]), + ); + + let mut pool_conn = Arc::clone(&pool).connect_sync().expect("to get connection"); + let conn = pool_conn + .as_any_mut() + .downcast_mut::() + .expect("to downcast to duckdb connection"); + let tx = conn + .get_underlying_conn_mut() + .transaction() + .expect("should begin transaction"); + + let table_manager = TableManager::new(Arc::clone(&table_definition)) + .with_internal(true) + .expect("to create table manager"); + + table_manager + .create_table(Arc::clone(&pool), &tx) + .expect("to create table"); + + table_manager + .create_indexes(&tx) + .expect("to create indexes"); + + tx.execute( + &format!( + r#"INSERT INTO "{table_name}" VALUES (1, 'Alice', 30, 'active'), (2, 'Bob', 25, 'inactive'), (3, 'Charlie', 35, 'active')"#, + table_name = table_manager.table_name() + ), + [], + ) + .expect("to insert test data"); + + table_manager.create_view(&tx).expect("to create view"); + + let explain_query = format!( + "EXPLAIN SELECT * FROM {} WHERE id = 1", + table_definition.name() + ); + + let mut stmt = tx.prepare(&explain_query).expect("to prepare statement"); + let mut rows = stmt.query([]).expect("to execute query"); + + let mut explain_output = Vec::new(); + while let Some(row) = rows.next().expect("to get next row") { + let line: String = row.get(1).expect("to get explain line"); + explain_output.push(line); + } + + let explain_result = explain_output.join("\n"); + insta::assert_snapshot!(explain_result); + + tx.rollback().expect("should rollback transaction"); + } } diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_with_index_and_view.snap.new b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_with_index_and_view.snap.new new file mode 100644 index 00000000..8a82447c --- /dev/null +++ b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_with_index_and_view.snap.new @@ -0,0 +1,26 @@ +--- +source: src/duckdb/creator.rs +assertion_line: 2170 +expression: explain_result +--- +┌───────────────────────────┐ +│ FILTER │ +│ ──────────────────── │ +│ (id = 1) │ +│ │ +│ ~1 Rows │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ INDEX_SCAN │ +│ ──────────────────── │ +│__data_test_table_175247454│ +│ 7434 │ +│ │ +│ Projections: │ +│ id │ +│ name │ +│ age │ +│ status │ +│ │ +│ ~0 Rows │ +└───────────────────────────┘ From 03bbfc480aa464f687a6e26c011fc51868a7099e Mon Sep 17 00:00:00 2001 From: Phillip LeBlanc Date: Mon, 14 Jul 2025 16:59:47 +0900 Subject: [PATCH 3/4] Upgrade DuckDB to 1.3.2 --- Cargo.toml | 4 +- src/duckdb/creator.rs | 55 ++++++++++++------- ...db__creator__tests__explain_analyze_0.snap | 44 +++++++++++++++ ...db__creator__tests__explain_analyze_1.snap | 38 +++++++++++++ ...plain_analyze_with_index_and_view.snap.new | 26 --------- 5 files changed, 120 insertions(+), 47 deletions(-) create mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_0.snap create mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_1.snap delete mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_with_index_and_view.snap.new diff --git a/Cargo.toml b/Cargo.toml index df6cd1c9..88a86c9d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ datafusion-expr = { version = "47", optional = true } datafusion-physical-expr = { version = "47", optional = true } datafusion-physical-plan = { version = "47", optional = true } datafusion-proto = { version = "47", optional = true } -duckdb = { version = "1.1.3", features = [ +duckdb = { version = "1.3.2", features = [ "bundled", "r2d2", "vtab", @@ -103,7 +103,7 @@ postgres-federation = ["postgres"] [patch.crates-io] datafusion-federation = { git = "https://github.com/spiceai/datafusion-federation.git", rev = "9db74a4b360df6be1bb554c59a474a2fd4bfb7e9" } # spiceai-47 -duckdb = { git = "https://github.com/spiceai/duckdb-rs.git", rev = "69ae7518ee093a1b070e9e4e6f011ef353431086" } # spiceai-1.1.3-backported-arrow-55 +duckdb = { git = "https://github.com/spiceai/duckdb-rs.git", rev = "b3547f0c1b37030b623b1e03fcaea0e4e2bb753e" } # spiceai-1.3.2 datafusion = { git = "https://github.com/spiceai/datafusion.git", rev = "b5c62f29d2c70c5331ff50015b67b5e1cafcd578" } # spiceai-47 datafusion-expr = { git = "https://github.com/spiceai/datafusion.git", rev = "b5c62f29d2c70c5331ff50015b67b5e1cafcd578" } # spiceai-47 diff --git a/src/duckdb/creator.rs b/src/duckdb/creator.rs index c386a0ba..ed4651b7 100644 --- a/src/duckdb/creator.rs +++ b/src/duckdb/creator.rs @@ -709,7 +709,7 @@ impl TableManager { /// If there is an index defined on a single column, that column should come first in the list. /// Multi-column indexes are not considered for ordering. pub(crate) fn order_columns_by_index(&self, columns: Vec) -> Vec { - let mut indexed_columns = Vec::new(); + let mut ordered_columns = Vec::new(); let mut non_indexed_columns = Vec::new(); // Get single-column indexes @@ -730,15 +730,15 @@ impl TableManager { // Separate columns into indexed and non-indexed for column in columns { if single_column_indexes.contains(&column) { - indexed_columns.push(column); + ordered_columns.push(column); } else { non_indexed_columns.push(column); } } // Return indexed columns first, then non-indexed columns - indexed_columns.extend(non_indexed_columns); - indexed_columns + ordered_columns.extend(non_indexed_columns); + ordered_columns } } @@ -2105,8 +2105,8 @@ pub(crate) mod tests { let pool = get_mem_duckdb(); let schema = Arc::new(arrow::datatypes::Schema::new(vec![ - arrow::datatypes::Field::new("id", arrow::datatypes::DataType::Int64, false), arrow::datatypes::Field::new("name", arrow::datatypes::DataType::Utf8, false), + arrow::datatypes::Field::new("id", arrow::datatypes::DataType::Int64, false), arrow::datatypes::Field::new("age", arrow::datatypes::DataType::Int32, true), arrow::datatypes::Field::new("status", arrow::datatypes::DataType::Utf8, false), ])); @@ -2143,7 +2143,7 @@ pub(crate) mod tests { tx.execute( &format!( - r#"INSERT INTO "{table_name}" VALUES (1, 'Alice', 30, 'active'), (2, 'Bob', 25, 'inactive'), (3, 'Charlie', 35, 'active')"#, + r#"INSERT INTO "{table_name}" VALUES ('Alice', 1, 30, 'active'), ('Bob', 2, 25, 'inactive'), ('Charlie', 3, 35, 'active')"#, table_name = table_manager.table_name() ), [], @@ -2152,22 +2152,39 @@ pub(crate) mod tests { table_manager.create_view(&tx).expect("to create view"); - let explain_query = format!( - "EXPLAIN SELECT * FROM {} WHERE id = 1", - table_definition.name() - ); + let queries = [ + format!( + "EXPLAIN ANALYZE SELECT * FROM {} WHERE id = 1", + table_definition.name() + ), + format!( + "EXPLAIN ANALYZE SELECT name FROM {} WHERE id = 1", + table_definition.name() + ), + ]; - let mut stmt = tx.prepare(&explain_query).expect("to prepare statement"); - let mut rows = stmt.query([]).expect("to execute query"); + for (idx, query) in queries.iter().enumerate() { + let mut stmt = tx.prepare(query).expect("to prepare statement"); + let mut rows = stmt.query([]).expect("to execute query"); - let mut explain_output = Vec::new(); - while let Some(row) = rows.next().expect("to get next row") { - let line: String = row.get(1).expect("to get explain line"); - explain_output.push(line); - } + let mut explain_output = Vec::new(); + while let Some(row) = rows.next().expect("to get next row") { + let line: String = row.get(1).expect("to get explain line"); + explain_output.push(line); + } - let explain_result = explain_output.join("\n"); - insta::assert_snapshot!(explain_result); + let explain_result = explain_output.join("\n"); + + insta::with_settings!({ + filters => vec![ + (r"Total Time: \d+\.\d+s", "Total Time: replaced"), + (r"\(\d+\.\d+s\)", "(0.00s)"), + (r"│__data_test_table_\d+│\n│\s+\d+\s+│", "│__data_test_table_redacted│\n│ redacted │"), + ], + }, { + insta::assert_snapshot!(format!("explain_analyze_{idx}"), explain_result); + }); + } tx.rollback().expect("should rollback transaction"); } diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_0.snap b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_0.snap new file mode 100644 index 00000000..aa26c4cf --- /dev/null +++ b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_0.snap @@ -0,0 +1,44 @@ +--- +source: src/duckdb/creator.rs +expression: explain_result +--- +┌─────────────────────────────────────┐ +│┌───────────────────────────────────┐│ +││ Query Profiling Information ││ +│└───────────────────────────────────┘│ +└─────────────────────────────────────┘ +EXPLAIN ANALYZE SELECT * FROM test_table WHERE id = 1 +┌────────────────────────────────────────────────┐ +│┌──────────────────────────────────────────────┐│ +││ Total Time: replaced ││ +│└──────────────────────────────────────────────┘│ +└────────────────────────────────────────────────┘ +┌───────────────────────────┐ +│ QUERY │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ EXPLAIN_ANALYZE │ +│ ──────────────────── │ +│ 0 Rows │ +│ (0.00s) │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ TABLE_SCAN │ +│ ──────────────────── │ +│ Table: │ +│__data_test_table_redacted│ +│ redacted │ +│ │ +│ Type: Index Scan │ +│ │ +│ Projections: │ +│ id │ +│ name │ +│ age │ +│ status │ +│ │ +│ Filters: id=1 │ +│ │ +│ 1 Rows │ +│ (0.00s) │ +└───────────────────────────┘ diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_1.snap b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_1.snap new file mode 100644 index 00000000..59d93396 --- /dev/null +++ b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_1.snap @@ -0,0 +1,38 @@ +--- +source: src/duckdb/creator.rs +expression: explain_result +--- +┌─────────────────────────────────────┐ +│┌───────────────────────────────────┐│ +││ Query Profiling Information ││ +│└───────────────────────────────────┘│ +└─────────────────────────────────────┘ +EXPLAIN ANALYZE SELECT name FROM test_table WHERE id = 1 +┌────────────────────────────────────────────────┐ +│┌──────────────────────────────────────────────┐│ +││ Total Time: replaced ││ +│└──────────────────────────────────────────────┘│ +└────────────────────────────────────────────────┘ +┌───────────────────────────┐ +│ QUERY │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ EXPLAIN_ANALYZE │ +│ ──────────────────── │ +│ 0 Rows │ +│ (0.00s) │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ TABLE_SCAN │ +│ ──────────────────── │ +│ Table: │ +│__data_test_table_redacted│ +│ redacted │ +│ │ +│ Type: Index Scan │ +│ Projections: name │ +│ Filters: id=1 │ +│ │ +│ 1 Rows │ +│ (0.00s) │ +└───────────────────────────┘ diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_with_index_and_view.snap.new b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_with_index_and_view.snap.new deleted file mode 100644 index 8a82447c..00000000 --- a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_with_index_and_view.snap.new +++ /dev/null @@ -1,26 +0,0 @@ ---- -source: src/duckdb/creator.rs -assertion_line: 2170 -expression: explain_result ---- -┌───────────────────────────┐ -│ FILTER │ -│ ──────────────────── │ -│ (id = 1) │ -│ │ -│ ~1 Rows │ -└─────────────┬─────────────┘ -┌─────────────┴─────────────┐ -│ INDEX_SCAN │ -│ ──────────────────── │ -│__data_test_table_175247454│ -│ 7434 │ -│ │ -│ Projections: │ -│ id │ -│ name │ -│ age │ -│ status │ -│ │ -│ ~0 Rows │ -└───────────────────────────┘ From 4e062b28924a3eb9aba65dfb235ac657d6374482 Mon Sep 17 00:00:00 2001 From: Phillip LeBlanc Date: Mon, 14 Jul 2025 23:10:10 +0900 Subject: [PATCH 4/4] Add test for multiple indexes --- src/duckdb/creator.rs | 136 ++++++++++++++++++ ...s__explain_analyze_multiple_indexes_0.snap | 44 ++++++ ...s__explain_analyze_multiple_indexes_1.snap | 38 +++++ ...s__explain_analyze_multiple_indexes_2.snap | 51 +++++++ ...s__explain_analyze_multiple_indexes_3.snap | 44 ++++++ ...s__explain_analyze_multiple_indexes_4.snap | 38 +++++ ...s__explain_analyze_multiple_indexes_5.snap | 42 ++++++ ...s__explain_analyze_multiple_indexes_6.snap | 45 ++++++ ...s__explain_analyze_multiple_indexes_7.snap | 40 ++++++ ...s__explain_analyze_multiple_indexes_8.snap | 43 ++++++ 10 files changed, 521 insertions(+) create mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_0.snap create mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_1.snap create mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_2.snap create mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_3.snap create mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_4.snap create mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_5.snap create mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_6.snap create mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_7.snap create mode 100644 src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_8.snap diff --git a/src/duckdb/creator.rs b/src/duckdb/creator.rs index ed4651b7..bf6f5215 100644 --- a/src/duckdb/creator.rs +++ b/src/duckdb/creator.rs @@ -2188,4 +2188,140 @@ pub(crate) mod tests { tx.rollback().expect("should rollback transaction"); } + + #[tokio::test] + async fn test_explain_analyze_with_multiple_indexes_and_view() { + let _guard = init_tracing(None); + let pool = get_mem_duckdb(); + + let schema = Arc::new(arrow::datatypes::Schema::new(vec![ + arrow::datatypes::Field::new("name", arrow::datatypes::DataType::Utf8, false), + arrow::datatypes::Field::new("id", arrow::datatypes::DataType::Int64, false), + arrow::datatypes::Field::new("age", arrow::datatypes::DataType::Int32, true), + arrow::datatypes::Field::new("status", arrow::datatypes::DataType::Utf8, false), + ])); + + let table_definition = Arc::new( + TableDefinition::new(RelationName::new("test_table"), Arc::clone(&schema)) + .with_indexes(vec![ + ( + ColumnReference::try_from("id").expect("valid column ref"), + IndexType::Enabled, + ), + ( + ColumnReference::try_from("age").expect("valid column ref"), + IndexType::Enabled, + ), + ( + ColumnReference::try_from("status").expect("valid column ref"), + IndexType::Enabled, + ), + ]), + ); + + let mut pool_conn = Arc::clone(&pool).connect_sync().expect("to get connection"); + let conn = pool_conn + .as_any_mut() + .downcast_mut::() + .expect("to downcast to duckdb connection"); + let tx = conn + .get_underlying_conn_mut() + .transaction() + .expect("should begin transaction"); + + let table_manager = TableManager::new(Arc::clone(&table_definition)) + .with_internal(true) + .expect("to create table manager"); + + table_manager + .create_table(Arc::clone(&pool), &tx) + .expect("to create table"); + + table_manager + .create_indexes(&tx) + .expect("to create indexes"); + + tx.execute( + &format!( + r#"INSERT INTO "{table_name}" VALUES + ('Alice', 1, 30, 'active'), + ('Bob', 2, 25, 'inactive'), + ('Charlie', 3, 35, 'active'), + ('David', 4, 30, 'pending'), + ('Eve', 5, 40, 'active')"#, + table_name = table_manager.table_name() + ), + [], + ) + .expect("to insert test data"); + + table_manager.create_view(&tx).expect("to create view"); + + let queries = [ + // Test index on id column + format!( + "EXPLAIN ANALYZE SELECT * FROM {} WHERE id = 1", + table_definition.name() + ), + format!( + "EXPLAIN ANALYZE SELECT name FROM {} WHERE id = 1", + table_definition.name() + ), + format!( + "EXPLAIN ANALYZE SELECT name, status FROM {} WHERE id = 1", + table_definition.name() + ), + // Test index on age column + format!( + "EXPLAIN ANALYZE SELECT * FROM {} WHERE age = 30", + table_definition.name() + ), + format!( + "EXPLAIN ANALYZE SELECT name FROM {} WHERE age = 30", + table_definition.name() + ), + format!( + "EXPLAIN ANALYZE SELECT id, name FROM {} WHERE age = 30", + table_definition.name() + ), + // Test index on status column + format!( + "EXPLAIN ANALYZE SELECT * FROM {} WHERE status = 'active'", + table_definition.name() + ), + format!( + "EXPLAIN ANALYZE SELECT name FROM {} WHERE status = 'active'", + table_definition.name() + ), + format!( + "EXPLAIN ANALYZE SELECT id, age FROM {} WHERE status = 'active'", + table_definition.name() + ), + ]; + + for (idx, query) in queries.iter().enumerate() { + let mut stmt = tx.prepare(query).expect("to prepare statement"); + let mut rows = stmt.query([]).expect("to execute query"); + + let mut explain_output = Vec::new(); + while let Some(row) = rows.next().expect("to get next row") { + let line: String = row.get(1).expect("to get explain line"); + explain_output.push(line); + } + + let explain_result = explain_output.join("\n"); + + insta::with_settings!({ + filters => vec![ + (r"Total Time: \d+\.\d+s", "Total Time: replaced"), + (r"\(\d+\.\d+s\)", "(0.00s)"), + (r"│__data_test_table_\d+│\n│\s+\d+\s+│", "│__data_test_table_redacted│\n│ redacted │"), + ], + }, { + insta::assert_snapshot!(format!("explain_analyze_multiple_indexes_{idx}"), explain_result); + }); + } + + tx.rollback().expect("should rollback transaction"); + } } diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_0.snap b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_0.snap new file mode 100644 index 00000000..8075574f --- /dev/null +++ b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_0.snap @@ -0,0 +1,44 @@ +--- +source: src/duckdb/creator.rs +expression: explain_result +--- +┌─────────────────────────────────────┐ +│┌───────────────────────────────────┐│ +││ Query Profiling Information ││ +│└───────────────────────────────────┘│ +└─────────────────────────────────────┘ +EXPLAIN ANALYZE SELECT * FROM test_table WHERE id = 1 +┌────────────────────────────────────────────────┐ +│┌──────────────────────────────────────────────┐│ +││ Total Time: replaced ││ +│└──────────────────────────────────────────────┘│ +└────────────────────────────────────────────────┘ +┌───────────────────────────┐ +│ QUERY │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ EXPLAIN_ANALYZE │ +│ ──────────────────── │ +│ 0 Rows │ +│ (0.00s) │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ TABLE_SCAN │ +│ ──────────────────── │ +│ Table: │ +│__data_test_table_redacted│ +│ redacted │ +│ │ +│ Type: Index Scan │ +│ │ +│ Projections: │ +│ id │ +│ age │ +│ status │ +│ name │ +│ │ +│ Filters: id=1 │ +│ │ +│ 1 Rows │ +│ (0.00s) │ +└───────────────────────────┘ diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_1.snap b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_1.snap new file mode 100644 index 00000000..59d93396 --- /dev/null +++ b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_1.snap @@ -0,0 +1,38 @@ +--- +source: src/duckdb/creator.rs +expression: explain_result +--- +┌─────────────────────────────────────┐ +│┌───────────────────────────────────┐│ +││ Query Profiling Information ││ +│└───────────────────────────────────┘│ +└─────────────────────────────────────┘ +EXPLAIN ANALYZE SELECT name FROM test_table WHERE id = 1 +┌────────────────────────────────────────────────┐ +│┌──────────────────────────────────────────────┐│ +││ Total Time: replaced ││ +│└──────────────────────────────────────────────┘│ +└────────────────────────────────────────────────┘ +┌───────────────────────────┐ +│ QUERY │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ EXPLAIN_ANALYZE │ +│ ──────────────────── │ +│ 0 Rows │ +│ (0.00s) │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ TABLE_SCAN │ +│ ──────────────────── │ +│ Table: │ +│__data_test_table_redacted│ +│ redacted │ +│ │ +│ Type: Index Scan │ +│ Projections: name │ +│ Filters: id=1 │ +│ │ +│ 1 Rows │ +│ (0.00s) │ +└───────────────────────────┘ diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_2.snap b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_2.snap new file mode 100644 index 00000000..5e3a0494 --- /dev/null +++ b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_2.snap @@ -0,0 +1,51 @@ +--- +source: src/duckdb/creator.rs +expression: explain_result +--- +┌─────────────────────────────────────┐ +│┌───────────────────────────────────┐│ +││ Query Profiling Information ││ +│└───────────────────────────────────┘│ +└─────────────────────────────────────┘ +EXPLAIN ANALYZE SELECT name, status FROM test_table WHERE id = 1 +┌────────────────────────────────────────────────┐ +│┌──────────────────────────────────────────────┐│ +││ Total Time: replaced ││ +│└──────────────────────────────────────────────┘│ +└────────────────────────────────────────────────┘ +┌───────────────────────────┐ +│ QUERY │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ EXPLAIN_ANALYZE │ +│ ──────────────────── │ +│ 0 Rows │ +│ (0.00s) │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ PROJECTION │ +│ ──────────────────── │ +│ name │ +│ status │ +│ │ +│ 1 Rows │ +│ (0.00s) │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ TABLE_SCAN │ +│ ──────────────────── │ +│ Table: │ +│__data_test_table_redacted│ +│ redacted │ +│ │ +│ Type: Index Scan │ +│ │ +│ Projections: │ +│ status │ +│ name │ +│ │ +│ Filters: id=1 │ +│ │ +│ 1 Rows │ +│ (0.00s) │ +└───────────────────────────┘ diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_3.snap b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_3.snap new file mode 100644 index 00000000..57e56ab4 --- /dev/null +++ b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_3.snap @@ -0,0 +1,44 @@ +--- +source: src/duckdb/creator.rs +expression: explain_result +--- +┌─────────────────────────────────────┐ +│┌───────────────────────────────────┐│ +││ Query Profiling Information ││ +│└───────────────────────────────────┘│ +└─────────────────────────────────────┘ +EXPLAIN ANALYZE SELECT * FROM test_table WHERE age = 30 +┌────────────────────────────────────────────────┐ +│┌──────────────────────────────────────────────┐│ +││ Total Time: replaced ││ +│└──────────────────────────────────────────────┘│ +└────────────────────────────────────────────────┘ +┌───────────────────────────┐ +│ QUERY │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ EXPLAIN_ANALYZE │ +│ ──────────────────── │ +│ 0 Rows │ +│ (0.00s) │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ TABLE_SCAN │ +│ ──────────────────── │ +│ Table: │ +│__data_test_table_redacted│ +│ redacted │ +│ │ +│ Type: Sequential Scan │ +│ │ +│ Projections: │ +│ id │ +│ age │ +│ status │ +│ name │ +│ │ +│ Filters: age=30 │ +│ │ +│ 2 Rows │ +│ (0.00s) │ +└───────────────────────────┘ diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_4.snap b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_4.snap new file mode 100644 index 00000000..22700bf5 --- /dev/null +++ b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_4.snap @@ -0,0 +1,38 @@ +--- +source: src/duckdb/creator.rs +expression: explain_result +--- +┌─────────────────────────────────────┐ +│┌───────────────────────────────────┐│ +││ Query Profiling Information ││ +│└───────────────────────────────────┘│ +└─────────────────────────────────────┘ +EXPLAIN ANALYZE SELECT name FROM test_table WHERE age = 30 +┌────────────────────────────────────────────────┐ +│┌──────────────────────────────────────────────┐│ +││ Total Time: replaced ││ +│└──────────────────────────────────────────────┘│ +└────────────────────────────────────────────────┘ +┌───────────────────────────┐ +│ QUERY │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ EXPLAIN_ANALYZE │ +│ ──────────────────── │ +│ 0 Rows │ +│ (0.00s) │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ TABLE_SCAN │ +│ ──────────────────── │ +│ Table: │ +│__data_test_table_redacted│ +│ redacted │ +│ │ +│ Type: Index Scan │ +│ Projections: name │ +│ Filters: age=30 │ +│ │ +│ 2 Rows │ +│ (0.00s) │ +└───────────────────────────┘ diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_5.snap b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_5.snap new file mode 100644 index 00000000..09e6349c --- /dev/null +++ b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_5.snap @@ -0,0 +1,42 @@ +--- +source: src/duckdb/creator.rs +expression: explain_result +--- +┌─────────────────────────────────────┐ +│┌───────────────────────────────────┐│ +││ Query Profiling Information ││ +│└───────────────────────────────────┘│ +└─────────────────────────────────────┘ +EXPLAIN ANALYZE SELECT id, name FROM test_table WHERE age = 30 +┌────────────────────────────────────────────────┐ +│┌──────────────────────────────────────────────┐│ +││ Total Time: replaced ││ +│└──────────────────────────────────────────────┘│ +└────────────────────────────────────────────────┘ +┌───────────────────────────┐ +│ QUERY │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ EXPLAIN_ANALYZE │ +│ ──────────────────── │ +│ 0 Rows │ +│ (0.00s) │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ TABLE_SCAN │ +│ ──────────────────── │ +│ Table: │ +│__data_test_table_redacted│ +│ redacted │ +│ │ +│ Type: Sequential Scan │ +│ │ +│ Projections: │ +│ id │ +│ name │ +│ │ +│ Filters: age=30 │ +│ │ +│ 2 Rows │ +│ (0.00s) │ +└───────────────────────────┘ diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_6.snap b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_6.snap new file mode 100644 index 00000000..68b636e8 --- /dev/null +++ b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_6.snap @@ -0,0 +1,45 @@ +--- +source: src/duckdb/creator.rs +expression: explain_result +--- +┌─────────────────────────────────────┐ +│┌───────────────────────────────────┐│ +││ Query Profiling Information ││ +│└───────────────────────────────────┘│ +└─────────────────────────────────────┘ +EXPLAIN ANALYZE SELECT * FROM test_table WHERE status = 'active' +┌────────────────────────────────────────────────┐ +│┌──────────────────────────────────────────────┐│ +││ Total Time: replaced ││ +│└──────────────────────────────────────────────┘│ +└────────────────────────────────────────────────┘ +┌───────────────────────────┐ +│ QUERY │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ EXPLAIN_ANALYZE │ +│ ──────────────────── │ +│ 0 Rows │ +│ (0.00s) │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ TABLE_SCAN │ +│ ──────────────────── │ +│ Table: │ +│__data_test_table_redacted│ +│ redacted │ +│ │ +│ Type: Sequential Scan │ +│ │ +│ Projections: │ +│ id │ +│ age │ +│ status │ +│ name │ +│ │ +│ Filters: │ +│ status='active' │ +│ │ +│ 3 Rows │ +│ (0.00s) │ +└───────────────────────────┘ diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_7.snap b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_7.snap new file mode 100644 index 00000000..2533ea0a --- /dev/null +++ b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_7.snap @@ -0,0 +1,40 @@ +--- +source: src/duckdb/creator.rs +expression: explain_result +--- +┌─────────────────────────────────────┐ +│┌───────────────────────────────────┐│ +││ Query Profiling Information ││ +│└───────────────────────────────────┘│ +└─────────────────────────────────────┘ +EXPLAIN ANALYZE SELECT name FROM test_table WHERE status = 'active' +┌────────────────────────────────────────────────┐ +│┌──────────────────────────────────────────────┐│ +││ Total Time: replaced ││ +│└──────────────────────────────────────────────┘│ +└────────────────────────────────────────────────┘ +┌───────────────────────────┐ +│ QUERY │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ EXPLAIN_ANALYZE │ +│ ──────────────────── │ +│ 0 Rows │ +│ (0.00s) │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ TABLE_SCAN │ +│ ──────────────────── │ +│ Table: │ +│__data_test_table_redacted│ +│ redacted │ +│ │ +│ Type: Index Scan │ +│ Projections: name │ +│ │ +│ Filters: │ +│ status='active' │ +│ │ +│ 3 Rows │ +│ (0.00s) │ +└───────────────────────────┘ diff --git a/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_8.snap b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_8.snap new file mode 100644 index 00000000..d6217175 --- /dev/null +++ b/src/duckdb/snapshots/datafusion_table_providers__duckdb__creator__tests__explain_analyze_multiple_indexes_8.snap @@ -0,0 +1,43 @@ +--- +source: src/duckdb/creator.rs +expression: explain_result +--- +┌─────────────────────────────────────┐ +│┌───────────────────────────────────┐│ +││ Query Profiling Information ││ +│└───────────────────────────────────┘│ +└─────────────────────────────────────┘ +EXPLAIN ANALYZE SELECT id, age FROM test_table WHERE status = 'active' +┌────────────────────────────────────────────────┐ +│┌──────────────────────────────────────────────┐│ +││ Total Time: replaced ││ +│└──────────────────────────────────────────────┘│ +└────────────────────────────────────────────────┘ +┌───────────────────────────┐ +│ QUERY │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ EXPLAIN_ANALYZE │ +│ ──────────────────── │ +│ 0 Rows │ +│ (0.00s) │ +└─────────────┬─────────────┘ +┌─────────────┴─────────────┐ +│ TABLE_SCAN │ +│ ──────────────────── │ +│ Table: │ +│__data_test_table_redacted│ +│ redacted │ +│ │ +│ Type: Sequential Scan │ +│ │ +│ Projections: │ +│ id │ +│ age │ +│ │ +│ Filters: │ +│ status='active' │ +│ │ +│ 3 Rows │ +│ (0.00s) │ +└───────────────────────────┘