Skip to content

Commit 8028dc6

Browse files
authored
feat: support common table expressions (#379)
* feat: support non-recursive CTEs * feat: support recursive CTEs * test: cover recursive CTE operators and errors * refactor: decouple recursive CTEs from spill
1 parent bccf67f commit 8028dc6

19 files changed

Lines changed: 1257 additions & 31 deletions

File tree

src/binder/expr.rs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -134,25 +134,7 @@ impl<'a, T: Transaction, A: AsRef<[(&'static str, DataValue)]>> Binder<'a, '_, T
134134
&mut PlanArena<'arena>,
135135
) -> Result<LogicalPlan, DatabaseError>,
136136
{
137-
let BinderContext {
138-
table_cache,
139-
view_cache,
140-
transaction,
141-
scala_functions,
142-
table_functions,
143-
..
144-
} = &self.context;
145-
let mut binder = Binder::new(
146-
BinderContext::new(
147-
table_cache,
148-
view_cache,
149-
*transaction,
150-
scala_functions,
151-
table_functions,
152-
),
153-
self.args,
154-
Some(&self.context),
155-
);
137+
let mut binder = Binder::new(self.context.fork_empty(), self.args, Some(&self.context));
156138
let sub_query = build(&mut binder, arena)?;
157139
let correlated = binder.context.has_outer_refs();
158140
Ok((sub_query, correlated))

src/binder/mod.rs

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ macro_rules! with_query_bind_step {
2222
}};
2323
}
2424

25+
#[cfg(feature = "orm")]
2526
pub(crate) use with_query_bind_step;
2627

2728
pub mod aggregate;
@@ -65,7 +66,7 @@ use crate::errors::DatabaseError;
6566
use crate::expression::ScalarExpression;
6667
use crate::planner::operator::join::JoinType;
6768
use crate::planner::operator::mark_apply::MarkApplyQuantifier;
68-
use crate::planner::{LogicalPlan, PlanArena};
69+
use crate::planner::{LogicalPlan, PlanArena, PlanRef};
6970
use crate::storage::{TableCache, Transaction, ViewCache};
7071
use crate::types::tuple::Schema;
7172
use crate::types::value::DataValue;
@@ -134,6 +135,19 @@ pub struct BoundSource<'a> {
134135
pub(crate) source: Source<'a>,
135136
}
136137

138+
#[derive(Debug, Clone)]
139+
pub(crate) struct CteBinding {
140+
pub(crate) table_name: TableName,
141+
pub(crate) depth: usize,
142+
pub(crate) plan_ref: PlanRef,
143+
}
144+
145+
#[derive(Clone, Copy)]
146+
pub(crate) struct CteCheckpoint {
147+
len: usize,
148+
depth: usize,
149+
}
150+
137151
impl BoundSource<'_> {
138152
pub(crate) fn matches_name(&self, table_name: &str) -> bool {
139153
self.table_name.as_ref() == table_name
@@ -231,6 +245,8 @@ pub struct BinderContext<'a, T: Transaction> {
231245
// Tips: retain binding order so wildcard expansion and position derivation
232246
// follow FROM/JOIN order directly.
233247
pub(crate) bind_table: Vec<BoundSource<'a>>,
248+
ctes: Vec<CteBinding>,
249+
pub(crate) cte_depth: usize,
234250
// alias
235251
expr_aliases: BTreeMap<(Option<String>, String), ScalarExpression>,
236252
table_aliases: HashMap<TableName, TableName>,
@@ -295,6 +311,8 @@ impl<'a, T: Transaction> BinderContext<'a, T> {
295311
view_cache,
296312
transaction,
297313
bind_table: Default::default(),
314+
ctes: Default::default(),
315+
cte_depth: 0,
298316
expr_aliases: Default::default(),
299317
table_aliases: Default::default(),
300318
group_by_exprs: vec![],
@@ -319,6 +337,8 @@ impl<'a, T: Transaction> BinderContext<'a, T> {
319337
view_cache: self.view_cache,
320338
transaction: self.transaction,
321339
bind_table: self.bind_table.clone(),
340+
ctes: self.ctes.clone(),
341+
cte_depth: self.cte_depth,
322342
expr_aliases: self.expr_aliases.clone(),
323343
table_aliases: self.table_aliases.clone(),
324344
group_by_exprs: self.group_by_exprs.clone(),
@@ -336,13 +356,52 @@ impl<'a, T: Transaction> BinderContext<'a, T> {
336356
/// This is used while binding an independent input, such as the right side
337357
/// of a join, before merging its newly bound sources into the parent scope.
338358
pub(crate) fn fork_empty(&self) -> Self {
339-
BinderContext::new(
359+
let mut context = BinderContext::new(
340360
self.table_cache,
341361
self.view_cache,
342362
self.transaction,
343363
self.scala_functions,
344364
self.table_functions,
345-
)
365+
);
366+
context.ctes = self.ctes.clone();
367+
context.cte_depth = self.cte_depth;
368+
context
369+
}
370+
371+
pub(crate) fn cte(&self, table_name: &TableName) -> Option<&CteBinding> {
372+
self.ctes
373+
.iter()
374+
.rev()
375+
.find(|cte| cte.table_name == *table_name)
376+
}
377+
378+
pub(crate) fn add_cte(&mut self, cte: CteBinding) -> Result<(), DatabaseError> {
379+
if self
380+
.ctes
381+
.iter()
382+
.rev()
383+
.take_while(|binding| binding.depth == cte.depth)
384+
.any(|binding| binding.table_name == cte.table_name)
385+
{
386+
return Err(DatabaseError::UnsupportedStmt(format!(
387+
"duplicate CTE name: {}",
388+
cte.table_name
389+
)));
390+
}
391+
self.ctes.push(cte);
392+
Ok(())
393+
}
394+
395+
pub(crate) fn cte_checkpoint(&self) -> CteCheckpoint {
396+
CteCheckpoint {
397+
len: self.ctes.len(),
398+
depth: self.cte_depth,
399+
}
400+
}
401+
402+
pub(crate) fn restore_ctes(&mut self, checkpoint: CteCheckpoint) {
403+
self.ctes.truncate(checkpoint.len);
404+
self.cte_depth = checkpoint.depth;
346405
}
347406

348407
pub fn step(&mut self, bind_step: QueryBindStep) {

src/binder/parser.rs

Lines changed: 191 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use super::select::{
1616
BindPlanAggregated, BindPlanComplete, BindPlanDistinct, BindPlanFiltered, BindPlanFrom,
1717
BindPlanProjected, BindPlanSelectList, BindPlanStart, JoinConstraintInput, TableAliasInput,
1818
};
19-
use super::{is_valid_identifier, with_query_bind_step, Binder, QueryBindStep, SetOperatorKind};
19+
use super::{is_valid_identifier, Binder, CteBinding, QueryBindStep, SetOperatorKind};
2020
#[cfg(feature = "copy")]
2121
use crate::binder::copy::{ExtSource, FileFormat};
2222
use crate::catalog::{ColumnCatalog, ColumnDesc, ColumnRef, TableName};
@@ -34,6 +34,7 @@ use crate::planner::operator::alter_table::change_column::{DefaultChange, NotNul
3434
use crate::planner::operator::join::{JoinCondition, JoinOperator as LJoinOperator, JoinType};
3535
use crate::planner::operator::mark_apply::MarkApplyQuantifier;
3636
use crate::planner::operator::project::ProjectOperator;
37+
use crate::planner::operator::recursive_cte::{RecursiveCteOperator, RecursiveScanOperator};
3738
use crate::planner::operator::sort::SortField;
3839
use crate::planner::operator::Operator;
3940
use crate::planner::{Childrens, LogicalPlan, PlanArena};
@@ -2832,15 +2833,171 @@ impl<'a, 'parent, T: Transaction, A: AsRef<[(&'static str, DataValue)]>> Binder<
28322833
Ok(select_items)
28332834
}
28342835

2836+
fn bind_cte_plan(
2837+
&mut self,
2838+
cte: &sqlparser::ast::Cte,
2839+
alias: &TableAliasInput,
2840+
arena: &mut PlanArena,
2841+
) -> Result<LogicalPlan, DatabaseError> {
2842+
let mut binder = Binder::new(self.context.fork(), self.args, Some(&self.context));
2843+
let plan = binder.bind_query(&cte.query, arena)?;
2844+
let source_name = arena.temp_table();
2845+
binder.bind_alias(plan, &alias.columns, alias.name.clone(), source_name, arena)
2846+
}
2847+
2848+
fn add_cte_plan(
2849+
&mut self,
2850+
table_name: TableName,
2851+
plan: LogicalPlan,
2852+
arena: &mut PlanArena,
2853+
) -> Result<(), DatabaseError> {
2854+
let plan_ref = arena.alloc_plan(plan);
2855+
self.context.add_cte(CteBinding {
2856+
table_name,
2857+
depth: self.context.cte_depth,
2858+
plan_ref,
2859+
})
2860+
}
2861+
2862+
fn bind_recursive_cte_plan(
2863+
&mut self,
2864+
cte: &sqlparser::ast::Cte,
2865+
alias: &TableAliasInput,
2866+
arena: &mut PlanArena,
2867+
) -> Result<LogicalPlan, DatabaseError> {
2868+
if cte.query.with.is_some()
2869+
|| cte.query.order_by.is_some()
2870+
|| cte.query.limit_clause.is_some()
2871+
{
2872+
return Err(DatabaseError::UnsupportedStmt(
2873+
"recursive CTE query clauses are not supported".to_string(),
2874+
));
2875+
}
2876+
2877+
let SetExpr::SetOperation {
2878+
op: SetOperator::Union,
2879+
set_quantifier: SetQuantifier::All,
2880+
left,
2881+
right,
2882+
} = cte.query.body.as_ref()
2883+
else {
2884+
return Err(DatabaseError::UnsupportedStmt(
2885+
"recursive CTEs require a top-level UNION ALL".to_string(),
2886+
));
2887+
};
2888+
2889+
let mut anchor_binder = Binder::new(self.context.fork(), self.args, Some(&self.context));
2890+
let anchor = anchor_binder.bind_set_expr(left, arena)?;
2891+
if anchor
2892+
.referenced_table()
2893+
.iter()
2894+
.any(|table| table == &alias.name)
2895+
{
2896+
return Err(DatabaseError::UnsupportedStmt(
2897+
"the recursive CTE cannot be referenced by its anchor".to_string(),
2898+
));
2899+
}
2900+
2901+
let source_name = arena.temp_table();
2902+
let mut anchor = anchor_binder.bind_alias(
2903+
anchor,
2904+
&alias.columns,
2905+
alias.name.clone(),
2906+
source_name,
2907+
arena,
2908+
)?;
2909+
let schema = anchor.output_schema(arena).clone();
2910+
let scan = LogicalPlan::new(
2911+
Operator::RecursiveScan(RecursiveScanOperator {
2912+
schema_ref: schema.clone(),
2913+
}),
2914+
Childrens::None,
2915+
);
2916+
2917+
let cte_checkpoint = self.context.cte_checkpoint();
2918+
self.add_cte_plan(alias.name.clone(), scan, arena)?;
2919+
let recursive = {
2920+
let mut binder = Binder::new(self.context.fork(), self.args, Some(&self.context));
2921+
binder.bind_set_expr(right, arena)
2922+
};
2923+
self.context.restore_ctes(cte_checkpoint);
2924+
let mut recursive = recursive?;
2925+
2926+
fn recursive_scan_count(plan: &LogicalPlan) -> usize {
2927+
usize::from(matches!(&plan.operator, Operator::RecursiveScan(_)))
2928+
+ plan
2929+
.childrens
2930+
.iter()
2931+
.map(recursive_scan_count)
2932+
.sum::<usize>()
2933+
}
2934+
2935+
if recursive_scan_count(&recursive) != 1 {
2936+
return Err(DatabaseError::UnsupportedStmt(
2937+
"the recursive term must reference its CTE exactly once".to_string(),
2938+
));
2939+
}
2940+
2941+
let recursive_schema = recursive.output_schema(arena);
2942+
if schema.len() != recursive_schema.len() {
2943+
return Err(DatabaseError::MisMatch(
2944+
"the anchor column count",
2945+
"the recursive column count",
2946+
));
2947+
}
2948+
if !schema
2949+
.iter()
2950+
.zip(recursive_schema)
2951+
.all(|(anchor, recursive)| {
2952+
arena.column(*anchor).datatype() == arena.column(*recursive).datatype()
2953+
})
2954+
{
2955+
return Err(DatabaseError::UnsupportedStmt(
2956+
"recursive CTE column types must match the anchor".to_string(),
2957+
));
2958+
}
2959+
2960+
Ok(RecursiveCteOperator::build(schema, anchor, recursive))
2961+
}
2962+
28352963
pub(crate) fn bind_query(
28362964
&mut self,
28372965
query: &Query,
28382966
arena: &mut PlanArena,
28392967
) -> Result<LogicalPlan, DatabaseError> {
28402968
let origin_step = self.context.step_now();
2969+
let cte_checkpoint = self.context.cte_checkpoint();
2970+
if let Some(with) = &query.with {
2971+
if with.recursive && with.cte_tables.len() != 1 {
2972+
return Err(DatabaseError::UnsupportedStmt(
2973+
"only one recursive CTE is supported".to_string(),
2974+
));
2975+
}
2976+
2977+
self.context.cte_depth += 1;
2978+
for cte in &with.cte_tables {
2979+
if cte.from.is_some() {
2980+
return Err(DatabaseError::UnsupportedStmt(
2981+
"CTE FROM clauses are not supported".to_string(),
2982+
));
2983+
}
2984+
if matches!(
2985+
cte.materialized,
2986+
Some(sqlparser::ast::CteAsMaterialized::Materialized)
2987+
) {
2988+
return Err(DatabaseError::UnsupportedStmt(
2989+
"materialized CTEs are not supported".to_string(),
2990+
));
2991+
}
28412992

2842-
if let Some(_with) = &query.with {
2843-
// TODO support with clause.
2993+
let alias = sql_table_alias(cte.alias.clone());
2994+
let plan = if with.recursive {
2995+
self.bind_recursive_cte_plan(cte, &alias, arena)?
2996+
} else {
2997+
self.bind_cte_plan(cte, &alias, arena)?
2998+
};
2999+
self.add_cte_plan(alias.name, plan, arena)?;
3000+
}
28443001
}
28453002

28463003
let order_by_exprs = if let Some(order_by) = &query.order_by {
@@ -2883,6 +3040,7 @@ impl<'a, 'parent, T: Transaction, A: AsRef<[(&'static str, DataValue)]>> Binder<
28833040
plan = self.bind_limit(plan, limit_clause, arena)?;
28843041
}
28853042

3043+
self.context.restore_ctes(cte_checkpoint);
28863044
self.context.step(origin_step);
28873045
Ok(plan)
28883046
}
@@ -3160,6 +3318,36 @@ mod tests {
31603318
Ok(())
31613319
}
31623320

3321+
#[test]
3322+
fn test_bind_non_recursive_ctes() -> Result<(), DatabaseError> {
3323+
let tables = build_t1_table()?;
3324+
let mut arena = PlanArena::new(&tables.table_arena);
3325+
let mut plan = tables.plan_with_arena(
3326+
"with first(a) as (select c1 from t1), \
3327+
second as (select a from first) select a from second",
3328+
&mut arena,
3329+
)?;
3330+
3331+
let schema = plan.output_schema(&mut arena);
3332+
assert_eq!(schema.len(), 1);
3333+
assert_eq!(arena.column(schema[0]).name(), "a");
3334+
3335+
assert_unsupported(
3336+
tables
3337+
.plan("with recursive cte as (select 1) select * from cte")
3338+
.unwrap_err(),
3339+
"recursive CTEs",
3340+
);
3341+
assert_unsupported(
3342+
tables
3343+
.plan("with cte as (select 1), cte as (select 2) select * from cte")
3344+
.unwrap_err(),
3345+
"duplicate CTE name",
3346+
);
3347+
3348+
Ok(())
3349+
}
3350+
31633351
#[test]
31643352
fn force_nest_loop_join_marks_join_operator() -> Result<(), DatabaseError> {
31653353
let tables = build_t1_table()?;

0 commit comments

Comments
 (0)