Skip to main content

rudb_bind/
statement.rs

1//! From an `Ast` to a `Bound`, which is a statement rather than a query.
2//!
3//! A `SELECT` binds to a [`Plan`] and nothing else, and that is why [`bind`](crate::bind) can hand
4//! one back. `CREATE TABLE`, `DROP TABLE` and `INSERT` are not plans and are deliberately not being
5//! made into plans. A `Node::CreateTable` would be a node with no columns, no rows, no cost and no
6//! reason to be pushed past anything, which is to say a node the optimizer has to be told to leave
7//! alone and the executor has to special case at the root. `spec/09-optimizer.md` section 9.1 says
8//! every node in a plan produces rows, and a DDL statement does not, so it goes beside the plan and
9//! not inside it.
10//!
11//! What each variant carries is the statement with every name and type already resolved, so the
12//! thing that runs it does catalog calls and nothing else. An `INSERT` in particular arrives with
13//! a plan whose output is exactly the target's columns in the target's order and the target's
14//! types, with the casts and the nulls for unmentioned columns already in it, so appending is a
15//! loop over chunks.
16
17use rudb_catalog::{Catalog, Entry, QualifiedName, duplicate_check, same_name};
18use rudb_common::{Clustering, Error, Field, LogicalType, Result, Session, Value, Width};
19use rudb_parse::ast::{self, Ast};
20use rudb_parse::{NONE, deparse, parse_ast};
21use rudb_plan::{Expr, ExprRef, Node, Plan, SortKey};
22
23use crate::binder::Binder;
24use crate::parameters::Parameters;
25
26/// One statement, bound.
27///
28/// Not `#[non_exhaustive]`. A new variant here is a new kind of statement, and the compiler
29/// pointing at every place that has to decide what to do with it is the whole value of the enum.
30#[derive(Debug)]
31pub enum Bound {
32    /// A query, which is the only one of these that produces rows.
33    Query(Plan),
34    /// `CREATE TABLE`.
35    CreateTable(CreateTable),
36    /// `CREATE VIEW`.
37    CreateView(CreateView),
38    /// `DROP TABLE` or `DROP VIEW`.
39    DropTable(DropTable),
40    /// `INSERT INTO`.
41    Insert(Insert),
42    /// `SET name = value`, or `RESET name`, which is the same thing with no value.
43    Setting(Setting),
44    /// Flushes a persistent database snapshot.
45    Checkpoint,
46    /// `EXPLAIN` over a query, holding the plan of the query rather than the query.
47    ///
48    /// The same `Plan` a [`Bound::Query`] would have carried, bound the same way and by the same
49    /// code. What makes it an explain is that the layer above optimizes it and prints it instead
50    /// of running it, which is the point: a plan that was built differently because somebody asked
51    /// to see it is not the plan that runs.
52    ///
53    /// With `analyze` set the layer above runs it as well and prints what happened on it. Still the
54    /// same plan, for the same reason.
55    ///
56    /// With `statistics` set it prints what the planner knew as well, which is the use and the class
57    /// behind every number in the plan. That one changes nothing about the plan or the run either.
58    Explain { plan: Plan, analyze: bool, statistics: bool },
59}
60
61/// A bound `SET` or `RESET`.
62///
63/// The value is a [`Value`] rather than an expression, because every setting there is takes a
64/// string or a number and nothing that runs one wants a plan. What a setting does with the value it
65/// gets is the setting's own business and is decided a layer up, since the binder has no idea what
66/// settings exist.
67///
68/// The narrow part of that is that the value has to already be a constant. `SET threads = 2 + 2` is
69/// four in DuckDB and is refused here, because folding it needs the expression rewriter and the
70/// rewriter is two layers above the binder. Nothing writes arithmetic in a `SET` and the refusal
71/// says what it is, so this waits for a reason to move.
72#[derive(Debug)]
73pub struct Setting {
74    /// The setting name, as written.
75    pub name: String,
76    /// The scope word, if one was written.
77    pub scope: ast::Scope,
78    /// The value, or `None` for a `RESET`.
79    pub value: Option<Value>,
80    /// Whether the statement was written as a bare `PRAGMA name`, which carries its value in it.
81    pub pragma: bool,
82}
83
84/// A bound `CREATE TABLE`.
85#[derive(Debug)]
86pub struct CreateTable {
87    /// The full name the table gets.
88    pub name: QualifiedName,
89    /// The columns, in order, with the types already resolved. For a `CREATE TABLE AS` these are
90    /// the query's output types under whatever names the statement or the query gave them.
91    pub columns: Vec<Field>,
92    /// The query to fill it from, for a `CREATE TABLE AS`.
93    pub source: Option<Plan>,
94    /// Whether an existing table of that name is left alone rather than being an error.
95    pub if_not_exists: bool,
96    /// Whether an existing table of that name is dropped first.
97    pub or_replace: bool,
98}
99
100/// A bound `CREATE VIEW`.
101///
102/// The body is the text that was written rather than the plan it bound to. It was bound once on the
103/// way through here, which is what refuses a view over a table that is not there, and the plan that
104/// came out of that is then thrown away, because a view follows the tables underneath it and a plan
105/// cannot. See [`rudb_catalog::View`].
106#[derive(Debug)]
107pub struct CreateView {
108    /// The full name the view gets.
109    pub name: QualifiedName,
110    /// The body, as written.
111    pub sql: String,
112    /// The whole statement written back out, which is what `duckdb_views()` reports as `sql`.
113    ///
114    /// Written here because this is the last place the tree is in reach. See
115    /// [`rudb_catalog::View::statement`] for what the column is and why it is not the text.
116    pub statement: String,
117    /// The column names the statement gave, which rename a prefix of what the body produces.
118    pub aliases: Vec<String>,
119    /// Whether an existing entry of that name is left alone rather than being an error.
120    pub if_not_exists: bool,
121    /// Whether an existing entry of that name is dropped first.
122    pub or_replace: bool,
123    /// The columns binding the body produced, after the alias list was applied.
124    ///
125    /// Worked out here because this is where the body is bound, and carried to the catalog because
126    /// that is where `duckdb_columns()` and `duckdb_views()` read it from. See the doc on
127    /// `rudb_catalog::View` for why the catalog keeps a list it will have to refresh later.
128    pub columns: Vec<Field>,
129}
130
131/// A bound `DROP TABLE` or `DROP VIEW`.
132#[derive(Debug)]
133pub struct DropTable {
134    /// The tables or views to drop, already resolved. With `IF EXISTS` a name that does not resolve
135    /// is not in here at all, which is what makes running this a sequence of drops that cannot
136    /// fail for being missing. Dropping one of these as the wrong type still can, because `DROP
137    /// TABLE IF EXISTS v` where `v` is a view is an error in DuckDB and was measured to be one.
138    pub names: Vec<QualifiedName>,
139    /// Which of the two the statement said it was dropping.
140    pub kind: Entry,
141}
142
143/// A bound `INSERT`.
144#[derive(Debug)]
145pub struct Insert {
146    /// The table to append to.
147    pub name: QualifiedName,
148    /// The rows to append. The output is the table's columns, in the table's order, with the
149    /// table's types, so nothing between here and the append has a decision left to make.
150    pub source: Plan,
151}
152
153/// Binds one parsed statement against a catalog.
154///
155/// # Errors
156///
157/// If the script does not hold exactly one statement, if a name does not resolve, if a type does
158/// not work out, or if the statement uses something that is not bound yet.
159pub fn bind_statement(ast: &Ast, catalog: &Catalog) -> Result<Bound> {
160    bind_statement_with(ast, catalog, &Parameters::new(), &Session::new())
161}
162
163/// Binds one parsed statement against a catalog, with values for its parameters and its settings.
164///
165/// This is the prepared statement path. The statement is parsed once and bound once per set of
166/// values, so a parameter is a constant by the time the plan exists and everything after the binder
167/// sees an ordinary query. That is why there is no parameter in `rudb_plan::Expr`.
168///
169/// # Errors
170///
171/// Everything [`bind_statement`] reports, plus an error for a parameter that was given no value.
172pub fn bind_statement_with(
173    ast: &Ast,
174    catalog: &Catalog,
175    parameters: &Parameters,
176    session: &Session,
177) -> Result<Bound> {
178    let statement = match ast.statements.as_slice() {
179        [statement] => *statement,
180        [] => return Err(Error::binder("no statement to bind")),
181        _ => return Err(Error::not_implemented("a script of more than one statement")),
182    };
183    match statement {
184        ast::Statement::Query(query) => {
185            let mut binder = Binder::with(catalog, parameters, session);
186            let (root, _) = binder.bind_query(ast, query)?;
187            Ok(Bound::Query(finish(binder, root)?))
188        }
189        ast::Statement::CreateTable(index) => {
190            create_table(ast, catalog, parameters, session, index)
191        }
192        ast::Statement::CreateView(index) => create_view(ast, catalog, parameters, session, index),
193        ast::Statement::DropTable(index) => drop_table(ast, catalog, index),
194        ast::Statement::Insert(index) => insert(ast, catalog, parameters, session, index),
195        ast::Statement::Set(index) | ast::Statement::Reset(index) => {
196            setting(ast, catalog, parameters, session, index)
197        }
198        ast::Statement::Checkpoint => Ok(Bound::Checkpoint),
199        ast::Statement::Explain { query, analyze, statistics } => {
200            let mut binder = Binder::with(catalog, parameters, session);
201            let (root, _) = binder.bind_query(ast, query)?;
202            Ok(Bound::Explain { plan: finish(binder, root)?, analyze, statistics })
203        }
204    }
205}
206
207/// Parses and binds one statement, which is the whole front end in one call.
208///
209/// # Errors
210///
211/// Anything the parser or the binder reports.
212pub fn bind_statement_sql(sql: &str, catalog: &Catalog) -> Result<Bound> {
213    let ast = parse_ast(sql)?;
214    bind_statement(&ast, catalog)
215}
216
217/// Roots a binder's plan and checks it.
218fn finish(binder: Binder<'_>, root: rudb_plan::NodeRef) -> Result<Plan> {
219    let mut plan = binder.into_plan();
220    plan.set_root(root);
221    plan.validate()?;
222    Ok(plan)
223}
224
225fn create_table(
226    ast: &Ast,
227    catalog: &Catalog,
228    parameters: &Parameters,
229    session: &Session,
230    index: ast::CreateTableRef,
231) -> Result<Bound> {
232    let written = ast.create_table(index);
233    let parts: Vec<&str> = ast.name(written.name).collect();
234    let name = if written.temporary {
235        catalog.resolve_for_create_temporary(&parts)?
236    } else {
237        catalog.resolve_for_create(&parts)?
238    };
239    let defs = ast.column_defs(written.columns);
240    let (columns, source) = if written.query == NONE {
241        let mut columns = Vec::with_capacity(defs.len());
242        for def in defs {
243            let text = ast.string(def.ty);
244            if text.is_empty() {
245                return Err(Error::binder(format!(
246                    "Column \"{}\" was declared without a type",
247                    ast.string(def.name)
248                )));
249            }
250            let ty = LogicalType::parse(text)?;
251            let column = ast.string(def.name);
252            columns.push(if def.not_null {
253                Field::required(column, ty)
254            } else {
255                Field::new(column, ty)
256            });
257        }
258        (columns, None)
259    } else {
260        let mut binder = Binder::with(catalog, parameters, session);
261        let (root, scope) = binder.bind_query(ast, written.query)?;
262        if defs.len() > scope.len() {
263            // DuckDB's sentence, typo and all. A column list shorter than the query is fine and
264            // renames a prefix, so only this direction is an error.
265            return Err(Error::binder("Target table has more colum names than query result."));
266        }
267        let mut columns = Vec::with_capacity(scope.len());
268        for (at, column) in scope.columns.iter().enumerate() {
269            let named = match defs.get(at) {
270                Some(def) => ast.string(def.name).to_string(),
271                None => column.name.clone(),
272            };
273            columns.push(Field::new(named, column.ty.clone()));
274        }
275        if defs.is_empty() {
276            deduplicate(&mut columns);
277        }
278        (columns, Some(finish(binder, root)?))
279    };
280    duplicate_check(&columns)?;
281    Ok(Bound::CreateTable(CreateTable {
282        name,
283        columns,
284        source,
285        if_not_exists: written.if_not_exists,
286        or_replace: written.or_replace,
287    }))
288}
289
290/// Renames the columns a query repeated, which is what makes `CREATE TABLE t AS SELECT 1 AS a, 2 AS
291/// a` a table rather than an error.
292///
293/// A query is allowed to produce two columns of one name and `SELECT 1 AS a, 2 AS a` prints two
294/// columns called `a`, so a statement that turns a query into a table has to decide what to do with
295/// that, and DuckDB renames rather than refusing. The suffix is `_1`, then `_2`, counting up until
296/// the name is free, so a query that already has an `a_1` in it pushes the renamed column to `a_2`
297/// rather than colliding with it.
298///
299/// This only runs when the statement wrote no column list. With a list, even a short one, duckdb
300/// v1.4.1 takes the names as they come and a repeat is an error, so `CREATE TABLE t (z) AS SELECT 1
301/// AS a, 2 AS a` is a table of `z` and `a` and adding a third `a` to that query is a refusal.
302fn deduplicate(columns: &mut [Field]) {
303    for at in 0..columns.len() {
304        let taken = |name: &str, upto: usize, columns: &[Field]| {
305            columns[..upto].iter().any(|held| same_name(&held.name, name))
306        };
307        if !taken(&columns[at].name, at, columns) {
308            continue;
309        }
310        let mut suffix = 1;
311        let mut candidate = format!("{}_{suffix}", columns[at].name);
312        while taken(&candidate, at, columns) {
313            suffix += 1;
314            candidate = format!("{}_{suffix}", columns[at].name);
315        }
316        columns[at].name = candidate;
317    }
318}
319
320/// Binds a `CREATE VIEW`, which means binding the body and then throwing the plan away.
321///
322/// Throwing it away is the point. The body is bound here so that a view over a table that is not
323/// there is refused now rather than at the first select, and so that the column list can be checked
324/// against what the body actually produces. What the catalog keeps is the text, because a view
325/// follows the tables underneath it and a plan is a photograph of the day it was built.
326fn create_view(
327    ast: &Ast,
328    catalog: &Catalog,
329    parameters: &Parameters,
330    session: &Session,
331    index: ast::CreateViewRef,
332) -> Result<Bound> {
333    let written = ast.create_view(index);
334    let parts: Vec<&str> = ast.name(written.name).collect();
335    let name = if written.temporary {
336        catalog.resolve_for_create_temporary(&parts)?
337    } else {
338        catalog.resolve_for_create(&parts)?
339    };
340    let aliases: Vec<String> = ast.name(written.columns).map(str::to_string).collect();
341
342    let mut binder = Binder::with(catalog, parameters, session);
343    let (_, mut scope) = binder.bind_query(ast, written.query)?;
344    if aliases.len() > scope.len() {
345        return Err(Error::binder("More VIEW aliases than columns in query result"));
346    }
347    if !aliases.is_empty() {
348        let written: Vec<&str> = aliases.iter().map(String::as_str).collect();
349        scope.rename(&written, "unnamed_subquery")?;
350    }
351
352    Ok(Bound::CreateView(CreateView {
353        name,
354        sql: ast.string(written.sql).to_string(),
355        statement: deparse::create_view(ast, index),
356        aliases,
357        if_not_exists: written.if_not_exists,
358        or_replace: written.or_replace,
359        columns: scope.fields(),
360    }))
361}
362
363fn drop_table(ast: &Ast, catalog: &Catalog, index: ast::DropTableRef) -> Result<Bound> {
364    let written = ast.drop_table(index);
365    let kind = if written.view { Entry::View } else { Entry::Table };
366    let mut names = Vec::new();
367    for &name in ast.name_list(written.names) {
368        let parts: Vec<&str> = ast.name(name).collect();
369        // The statement said which of the two it meant, so a name that is not there is a missing
370        // one of those and not a missing table.
371        match catalog.resolve_as(&parts, kind) {
372            Ok(resolved) => names.push(resolved),
373            Err(error) if written.if_exists => drop(error),
374            Err(error) => return Err(error),
375        }
376    }
377    Ok(Bound::DropTable(DropTable { names, kind }))
378}
379
380/// Binds a `SET` or a `RESET`, which is resolving its value and nothing else.
381///
382/// The name is not checked here. The binder knows what tables exist and has no idea what settings
383/// exist, since a setting is a knob on the engine rather than an entry in a catalog, and a version
384/// of this that held the list would be the binder holding a copy of something it cannot enforce.
385fn setting(
386    ast: &Ast,
387    catalog: &Catalog,
388    parameters: &Parameters,
389    session: &Session,
390    index: ast::SettingRef,
391) -> Result<Bound> {
392    let written = ast.setting(index);
393    let name = ast.string(written.name).to_string();
394    let value = if written.value == NONE {
395        None
396    } else {
397        let mut binder = Binder::with(catalog, parameters, session);
398        let bound = binder.bind_setting_value(ast, written.value)?;
399        let Expr::Constant(value) = *binder.plan().expr(bound) else {
400            return Err(Error::not_implemented(format!(
401                "a value for {name} that is not a constant"
402            )));
403        };
404        Some(binder.plan().value(value).clone())
405    };
406    Ok(Bound::Setting(Setting { name, scope: written.scope, value, pragma: written.pragma }))
407}
408
409/// Sorts an insert's rows into the order the target table declared.
410///
411/// Returns the input unchanged when the statement supplies none of the declared columns, because
412/// every one of them is then a constant null and sorting on a constant is a sort that buys nothing
413/// and costs a pass. A statement that supplies some of them sorts on those: the declaration is
414/// about the order the rows are written in, and the columns that are there still order them.
415///
416/// The leading key carries the width. `date_trunc('month', d)` and `d` sort the same rows into the
417/// same fragments for any predicate a month wide or wider, and the difference is what happens
418/// inside a month: bucketed, the second key orders the whole month, which is the key locality the
419/// joins want and the reason the width is part of the declaration at all.
420fn clustered(
421    binder: &mut Binder<'_>,
422    input: rudb_plan::NodeRef,
423    scope: &crate::scope::Scope,
424    clustering: &Clustering,
425    targets: &[usize],
426    fields: &[Field],
427) -> Result<rudb_plan::NodeRef> {
428    let mut keys: Vec<SortKey> = Vec::with_capacity(clustering.columns().len());
429    for (at, &column) in clustering.columns().iter().enumerate() {
430        let Some(from) = targets.iter().position(|&target| target == column as usize) else {
431            continue;
432        };
433        let source = &scope.columns[from];
434        let expr = binder.plan_mut().add_expr(Expr::Column(source.binding), source.ty.clone());
435        // Cast to the column's own type before bucketing, since the source of a load is a file
436        // whose date column can arrive as a timestamp and `date_trunc` gives back the type it was
437        // handed. Sorting on a different type than the column stores would still be an order, but
438        // it would not be the order the declaration names.
439        let expr = binder.checked_cast_to(expr, &fields[column as usize].ty, false)?;
440        let expr =
441            if at == 0 { bucketed(binder, expr, clustering.width(), fields, column) } else { expr };
442        keys.push(SortKey { expr, descending: false, nulls_first: false });
443    }
444    if keys.is_empty() {
445        return Ok(input);
446    }
447    let keys = binder.plan_mut().add_sort_keys(&keys);
448    Ok(binder.plan_mut().add_node(Node::Sort { input, keys }))
449}
450
451/// Wraps a sort key in the calendar bucket its declaration asked for.
452fn bucketed(
453    binder: &mut Binder<'_>,
454    expr: ExprRef,
455    width: Width,
456    fields: &[Field],
457    column: u32,
458) -> ExprRef {
459    if width == Width::Exact {
460        return expr;
461    }
462    let unit = binder.plan_mut().add_value(Value::Varchar(width.to_string().to_lowercase()));
463    let unit = binder.plan_mut().add_expr(Expr::Constant(unit), LogicalType::Varchar);
464    let args = binder.plan_mut().add_expr_list(&[unit, expr]);
465    let name = binder.plan_mut().intern("date_trunc");
466    let ty = fields[column as usize].ty.clone();
467    binder.plan_mut().add_expr(Expr::Function { name, args }, ty)
468}
469
470fn insert(
471    ast: &Ast,
472    catalog: &Catalog,
473    parameters: &Parameters,
474    session: &Session,
475    index: ast::InsertRef,
476) -> Result<Bound> {
477    let written = ast.insert(index);
478    let parts: Vec<&str> = ast.name(written.name).collect();
479    let name = catalog.resolve(&parts)?;
480    if catalog.entry(&name)? == Entry::View {
481        // The binary's sentence, article and all. A view has no rows of its own to append to, and
482        // an updatable view is a rule about rewriting the insert that neither database has.
483        return Err(Error::catalog(format!("{} is not an table", name.table)));
484    }
485    let target = catalog.table(&name)?;
486    let fields: Vec<Field> = target.columns().to_vec();
487    let clustering = target.clustering().cloned();
488
489    // Which table column each source column lands in. Without a column list that is the first n
490    // columns in order, and with one it is whatever the list says, which is also the check that
491    // the list names columns the table has and names none of them twice.
492    let targets: Vec<usize> = if written.columns.is_empty() {
493        (0..fields.len()).collect()
494    } else {
495        let mut targets = Vec::new();
496        for column in ast.name(written.columns) {
497            let at = fields.iter().position(|field| same_name(&field.name, column)).ok_or_else(
498                || {
499                    Error::binder(format!(
500                        "Table \"{}\" does not have a column named \"{column}\"",
501                        name.table
502                    ))
503                },
504            )?;
505            if targets.contains(&at) {
506                return Err(Error::binder(format!(
507                    "Column \"{column}\" is named twice in the same INSERT"
508                )));
509            }
510            targets.push(at);
511        }
512        targets
513    };
514
515    let mut binder = Binder::with(catalog, parameters, session);
516    let (root, scope) = binder.bind_query(ast, written.source)?;
517    if scope.len() != targets.len() {
518        return Err(Error::binder(format!(
519            "Table \"{}\" has {} columns but {} values were supplied",
520            name.table,
521            targets.len(),
522            scope.len()
523        )));
524    }
525
526    // A table that declared what order its rows go in gets the sort here, under the projection
527    // rather than over it, because a projection does not reorder rows and the bindings the sort
528    // keys need are the ones the query just produced. This is the whole of the loader honouring
529    // the declaration: the rows arrive at the writer in order and the per fragment ranges, which
530    // are built from whatever order arrives, come out narrow instead of each covering the table.
531    let root = match &clustering {
532        None => root,
533        Some(clustering) => clustered(&mut binder, root, &scope, clustering, &targets, &fields)?,
534    };
535
536    // The projection that makes the source look exactly like the table. Every column the statement
537    // did not name becomes a null of the column's own type, so the append never has to know that a
538    // column list was written at all.
539    let mut exprs: Vec<ExprRef> = Vec::with_capacity(fields.len());
540    let mut names = Vec::with_capacity(fields.len());
541    for (at, field) in fields.iter().enumerate() {
542        let expr = match targets.iter().position(|&target| target == at) {
543            Some(from) => {
544                let column = &scope.columns[from];
545                let expr =
546                    binder.plan_mut().add_expr(Expr::Column(column.binding), column.ty.clone());
547                binder.checked_cast_to(expr, &field.ty, false)?
548            }
549            None => {
550                // A typed null rather than `add_constant`, which would give it the null type and
551                // make the column's type depend on whether a row happened to be inserted into it.
552                let value = binder.plan_mut().add_value(Value::Null);
553                binder.plan_mut().add_expr(Expr::Constant(value), field.ty.clone())
554            }
555        };
556        exprs.push(expr);
557        let interned = binder.plan_mut().intern(&field.name);
558        names.push(interned);
559    }
560    let exprs = binder.plan_mut().add_expr_list(&exprs);
561    let names = binder.plan_mut().add_name_list(&names);
562    let index = binder.fresh_index();
563    let root = binder.plan_mut().add_node(Node::Project { input: root, index, exprs, names });
564    Ok(Bound::Insert(Insert { name, source: finish(binder, root)? }))
565}