Skip to content

Commit 054fa99

Browse files
TimelordUKclaude
andcommitted
feat: Enable CTEs to reference temp tables
Temp tables (#tmp) are now first-class citizens that can be referenced within CTEs, just like regular tables or other CTEs. This enables: - Filtering temp tables in CTEs with WHERE clauses - Window functions (PARTITION BY, ROW_NUMBER) over temp tables - Nested CTEs that reference temp tables - Complex multi-stage transformations using temp tables Implementation: - Added execute_with_temp_tables() to QueryEngine - Temp tables are merged into CTE context at query start - Threaded TempTableRegistry through QueryExecutionService - Updated non_interactive.rs to pass temp registry to service 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 901ddfa commit 054fa99

4 files changed

Lines changed: 165 additions & 2 deletions

File tree

src/data/query_engine.rs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use crate::data::hash_join::HashJoinExecutor;
1717
use crate::data::recursive_where_evaluator::RecursiveWhereEvaluator;
1818
use crate::data::row_expanders::RowExpanderRegistry;
1919
use crate::data::subquery_executor::SubqueryExecutor;
20+
use crate::data::temp_table_registry::TempTableRegistry;
2021
use crate::execution_plan::{ExecutionPlan, ExecutionPlanBuilder, StepType};
2122
use crate::sql::aggregates::{contains_aggregate, is_aggregate_compatible};
2223
use crate::sql::parser::ast::ColumnRef;
@@ -216,14 +217,47 @@ impl QueryEngine {
216217
Ok(view)
217218
}
218219

220+
/// Execute a SQL query with optional temp table registry access
221+
pub fn execute_with_temp_tables(
222+
&self,
223+
table: Arc<DataTable>,
224+
sql: &str,
225+
temp_tables: Option<&TempTableRegistry>,
226+
) -> Result<DataView> {
227+
let (view, _plan) = self.execute_with_plan_and_temp_tables(table, sql, temp_tables)?;
228+
Ok(view)
229+
}
230+
219231
/// Execute a parsed SelectStatement on a `DataTable` and return a `DataView`
220232
pub fn execute_statement(
221233
&self,
222234
table: Arc<DataTable>,
223235
statement: SelectStatement,
236+
) -> Result<DataView> {
237+
self.execute_statement_with_temp_tables(table, statement, None)
238+
}
239+
240+
/// Execute a parsed SelectStatement with optional temp table access
241+
pub fn execute_statement_with_temp_tables(
242+
&self,
243+
table: Arc<DataTable>,
244+
statement: SelectStatement,
245+
temp_tables: Option<&TempTableRegistry>,
224246
) -> Result<DataView> {
225247
// First process CTEs to build context
226248
let mut cte_context = HashMap::new();
249+
250+
// Add temp tables to CTE context if provided
251+
if let Some(temp_registry) = temp_tables {
252+
for table_name in temp_registry.list_tables() {
253+
if let Some(temp_table) = temp_registry.get(&table_name) {
254+
debug!("Adding temp table {} to CTE context", table_name);
255+
let view = DataView::new(temp_table);
256+
cte_context.insert(table_name, Arc::new(view));
257+
}
258+
}
259+
}
260+
227261
for cte in &statement.ctes {
228262
debug!("QueryEngine: Pre-processing CTE '{}'...", cte.name);
229263
// Execute the CTE based on its type
@@ -349,6 +383,16 @@ impl QueryEngine {
349383
&self,
350384
table: Arc<DataTable>,
351385
sql: &str,
386+
) -> Result<(DataView, ExecutionPlan)> {
387+
self.execute_with_plan_and_temp_tables(table, sql, None)
388+
}
389+
390+
/// Execute a query with temp tables and return both the result and the execution plan
391+
pub fn execute_with_plan_and_temp_tables(
392+
&self,
393+
table: Arc<DataTable>,
394+
sql: &str,
395+
temp_tables: Option<&TempTableRegistry>,
352396
) -> Result<(DataView, ExecutionPlan)> {
353397
let mut plan_builder = ExecutionPlanBuilder::new();
354398
let start_time = Instant::now();
@@ -372,6 +416,17 @@ impl QueryEngine {
372416
// First process CTEs to build context
373417
let mut cte_context = HashMap::new();
374418

419+
// Add temp tables to CTE context if provided
420+
if let Some(temp_registry) = temp_tables {
421+
for table_name in temp_registry.list_tables() {
422+
if let Some(temp_table) = temp_registry.get(&table_name) {
423+
debug!("Adding temp table {} to CTE context", table_name);
424+
let view = DataView::new(temp_table);
425+
cte_context.insert(table_name, Arc::new(view));
426+
}
427+
}
428+
}
429+
375430
if !statement.ctes.is_empty() {
376431
plan_builder.begin_step(
377432
StepType::CTE,

src/non_interactive.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,12 @@ pub fn execute_script(config: NonInteractiveConfig) -> Result<()> {
902902

903903
// Execute the statement
904904
let service = QueryExecutionService::new(config.case_insensitive, config.auto_hide_empty);
905-
match service.execute(&executable_sql, Some(&dataview), None) {
905+
match service.execute_with_temp_tables(
906+
&executable_sql,
907+
Some(&dataview),
908+
None,
909+
Some(&temp_tables),
910+
) {
906911
Ok(result) => {
907912
let exec_time = stmt_start.elapsed().as_secs_f64() * 1000.0;
908913
let final_view = result.dataview;

src/services/query_execution_service.rs

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::config::config::BehaviorConfig;
22
use crate::data::data_view::DataView;
33
use crate::data::query_engine::QueryEngine;
4+
use crate::data::temp_table_registry::TempTableRegistry;
45
use crate::debug_trace::{DebugContext, DebugLevel, QueryTrace, ScopedTimer};
56
use crate::execution_plan::ExecutionPlan;
67
use anyhow::Result;
@@ -94,13 +95,48 @@ impl QueryExecutionService {
9495
self.execute_with_debug(query, current_dataview, original_source, None)
9596
}
9697

98+
/// Execute a query with temp table support
99+
pub fn execute_with_temp_tables(
100+
&self,
101+
query: &str,
102+
current_dataview: Option<&DataView>,
103+
original_source: Option<&crate::data::datatable::DataTable>,
104+
temp_tables: Option<&TempTableRegistry>,
105+
) -> Result<QueryExecutionResult> {
106+
self.execute_with_temp_tables_and_debug(
107+
query,
108+
current_dataview,
109+
original_source,
110+
temp_tables,
111+
None,
112+
)
113+
}
114+
97115
/// Execute a query with optional debug tracing
98116
pub fn execute_with_debug(
99117
&self,
100118
query: &str,
101119
current_dataview: Option<&DataView>,
102120
original_source: Option<&crate::data::datatable::DataTable>,
103121
debug_context: Option<DebugContext>,
122+
) -> Result<QueryExecutionResult> {
123+
self.execute_with_temp_tables_and_debug(
124+
query,
125+
current_dataview,
126+
original_source,
127+
None,
128+
debug_context,
129+
)
130+
}
131+
132+
/// Execute a query with temp tables and optional debug tracing
133+
pub fn execute_with_temp_tables_and_debug(
134+
&self,
135+
query: &str,
136+
current_dataview: Option<&DataView>,
137+
original_source: Option<&crate::data::datatable::DataTable>,
138+
temp_tables: Option<&TempTableRegistry>,
139+
debug_context: Option<DebugContext>,
104140
) -> Result<QueryExecutionResult> {
105141
// Check if debug context is enabled before moving it
106142
let debug_enabled = debug_context.is_some();
@@ -243,7 +279,11 @@ impl QueryExecutionService {
243279
self.date_notation.clone(),
244280
)
245281
};
246-
let mut new_dataview = engine.execute(table_arc, query)?;
282+
let mut new_dataview = if temp_tables.is_some() {
283+
engine.execute_with_temp_tables(table_arc, query, temp_tables)?
284+
} else {
285+
engine.execute(table_arc, query)?
286+
};
247287
let query_engine_time = query_start.elapsed();
248288

249289
trace.log_timed(
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
-- Test: CTEs can now reference temp tables
2+
-- This demonstrates that temp tables are now first-class citizens
3+
-- that can be used in CTEs just like regular tables
4+
5+
-- Example 1: Simple CTE referencing temp table
6+
WITH data AS (
7+
SELECT value as id, value * 10 as amount
8+
FROM RANGE(1, 5)
9+
)
10+
SELECT * FROM data INTO #sample_data;
11+
GO
12+
13+
WITH filtered AS (
14+
SELECT * FROM #sample_data WHERE amount > 20
15+
)
16+
SELECT * FROM filtered ORDER BY id;
17+
GO
18+
19+
-- Example 2: Window functions in CTEs over temp tables
20+
WITH partitioned AS (
21+
SELECT
22+
id,
23+
amount,
24+
ROW_NUMBER() OVER (ORDER BY amount DESC) as rank
25+
FROM #sample_data
26+
)
27+
SELECT * FROM partitioned ORDER BY rank;
28+
GO
29+
30+
-- Example 3: Multiple CTEs with temp table reference
31+
WITH doubled AS (
32+
SELECT id, amount * 2 as doubled FROM #sample_data
33+
),
34+
tripled AS (
35+
SELECT id, amount * 3 as tripled FROM #sample_data
36+
)
37+
SELECT
38+
doubled.id,
39+
doubled.doubled,
40+
tripled.tripled
41+
FROM doubled
42+
JOIN tripled ON doubled.id = tripled.id
43+
ORDER BY doubled.id;
44+
GO
45+
46+
-- Example 4: Temp table with string filtering
47+
WITH strings AS (
48+
SELECT
49+
value as id,
50+
CASE
51+
WHEN value % 2 = 0 THEN 'even.type'
52+
ELSE 'odd.type'
53+
END as category
54+
FROM RANGE(1, 6)
55+
)
56+
SELECT * FROM strings INTO #categories;
57+
GO
58+
59+
WITH filtered_cats AS (
60+
SELECT * FROM #categories WHERE category.Contains('.')
61+
)
62+
SELECT * FROM filtered_cats ORDER BY id;
63+
GO

0 commit comments

Comments
 (0)