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::{Error, Field, LogicalType, Result, Value};
19use rudb_parse::ast::{self, Ast};
20use rudb_parse::{NONE, parse_ast};
21use rudb_plan::{Expr, ExprRef, Node, Plan};
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}
45
46/// A bound `SET` or `RESET`.
47///
48/// The value is a [`Value`] rather than an expression, because every setting there is takes a
49/// string or a number and nothing that runs one wants a plan. What a setting does with the value it
50/// gets is the setting's own business and is decided a layer up, since the binder has no idea what
51/// settings exist.
52///
53/// The narrow part of that is that the value has to already be a constant. `SET threads = 2 + 2` is
54/// four in DuckDB and is refused here, because folding it needs the expression rewriter and the
55/// rewriter is two layers above the binder. Nothing writes arithmetic in a `SET` and the refusal
56/// says what it is, so this waits for a reason to move.
57#[derive(Debug)]
58pub struct Setting {
59    /// The setting name, as written.
60    pub name: String,
61    /// The scope word, if one was written.
62    pub scope: ast::Scope,
63    /// The value, or `None` for a `RESET`.
64    pub value: Option<Value>,
65}
66
67/// A bound `CREATE TABLE`.
68#[derive(Debug)]
69pub struct CreateTable {
70    /// The full name the table gets.
71    pub name: QualifiedName,
72    /// The columns, in order, with the types already resolved. For a `CREATE TABLE AS` these are
73    /// the query's output types under whatever names the statement or the query gave them.
74    pub columns: Vec<Field>,
75    /// The query to fill it from, for a `CREATE TABLE AS`.
76    pub source: Option<Plan>,
77    /// Whether an existing table of that name is left alone rather than being an error.
78    pub if_not_exists: bool,
79    /// Whether an existing table of that name is dropped first.
80    pub or_replace: bool,
81}
82
83/// A bound `CREATE VIEW`.
84///
85/// The body is the text that was written rather than the plan it bound to. It was bound once on the
86/// way through here, which is what refuses a view over a table that is not there, and the plan that
87/// came out of that is then thrown away, because a view follows the tables underneath it and a plan
88/// cannot. See [`rudb_catalog::View`].
89#[derive(Debug)]
90pub struct CreateView {
91    /// The full name the view gets.
92    pub name: QualifiedName,
93    /// The body, as written.
94    pub sql: String,
95    /// The column names the statement gave, which rename a prefix of what the body produces.
96    pub aliases: Vec<String>,
97    /// Whether an existing entry of that name is left alone rather than being an error.
98    pub if_not_exists: bool,
99    /// Whether an existing entry of that name is dropped first.
100    pub or_replace: bool,
101}
102
103/// A bound `DROP TABLE` or `DROP VIEW`.
104#[derive(Debug)]
105pub struct DropTable {
106    /// The tables or views to drop, already resolved. With `IF EXISTS` a name that does not resolve
107    /// is not in here at all, which is what makes running this a sequence of drops that cannot
108    /// fail for being missing. Dropping one of these as the wrong type still can, because `DROP
109    /// TABLE IF EXISTS v` where `v` is a view is an error in DuckDB and was measured to be one.
110    pub names: Vec<QualifiedName>,
111    /// Which of the two the statement said it was dropping.
112    pub kind: Entry,
113}
114
115/// A bound `INSERT`.
116#[derive(Debug)]
117pub struct Insert {
118    /// The table to append to.
119    pub name: QualifiedName,
120    /// The rows to append. The output is the table's columns, in the table's order, with the
121    /// table's types, so nothing between here and the append has a decision left to make.
122    pub source: Plan,
123}
124
125/// Binds one parsed statement against a catalog.
126///
127/// # Errors
128///
129/// If the script does not hold exactly one statement, if a name does not resolve, if a type does
130/// not work out, or if the statement uses something that is not bound yet.
131pub fn bind_statement(ast: &Ast, catalog: &Catalog) -> Result<Bound> {
132    bind_statement_with(ast, catalog, &Parameters::new())
133}
134
135/// Binds one parsed statement against a catalog, with values for its parameters.
136///
137/// This is the prepared statement path. The statement is parsed once and bound once per set of
138/// values, so a parameter is a constant by the time the plan exists and everything after the binder
139/// sees an ordinary query. That is why there is no parameter in `rudb_plan::Expr`.
140///
141/// # Errors
142///
143/// Everything [`bind_statement`] reports, plus an error for a parameter that was given no value.
144pub fn bind_statement_with(ast: &Ast, catalog: &Catalog, parameters: &Parameters) -> Result<Bound> {
145    let statement = match ast.statements.as_slice() {
146        [statement] => *statement,
147        [] => return Err(Error::binder("no statement to bind")),
148        _ => return Err(Error::not_implemented("a script of more than one statement")),
149    };
150    match statement {
151        ast::Statement::Query(query) => {
152            let mut binder = Binder::with(catalog, parameters);
153            let (root, _) = binder.bind_query(ast, query)?;
154            Ok(Bound::Query(finish(binder, root)?))
155        }
156        ast::Statement::CreateTable(index) => create_table(ast, catalog, parameters, index),
157        ast::Statement::CreateView(index) => create_view(ast, catalog, parameters, index),
158        ast::Statement::DropTable(index) => drop_table(ast, catalog, index),
159        ast::Statement::Insert(index) => insert(ast, catalog, parameters, index),
160        ast::Statement::Set(index) | ast::Statement::Reset(index) => {
161            setting(ast, catalog, parameters, index)
162        }
163    }
164}
165
166/// Parses and binds one statement, which is the whole front end in one call.
167///
168/// # Errors
169///
170/// Anything the parser or the binder reports.
171pub fn bind_statement_sql(sql: &str, catalog: &Catalog) -> Result<Bound> {
172    let ast = parse_ast(sql)?;
173    bind_statement(&ast, catalog)
174}
175
176/// Roots a binder's plan and checks it.
177fn finish(binder: Binder<'_>, root: rudb_plan::NodeRef) -> Result<Plan> {
178    let mut plan = binder.into_plan();
179    plan.set_root(root);
180    plan.validate()?;
181    Ok(plan)
182}
183
184fn create_table(
185    ast: &Ast,
186    catalog: &Catalog,
187    parameters: &Parameters,
188    index: ast::CreateTableRef,
189) -> Result<Bound> {
190    let written = ast.create_table(index);
191    if written.temporary {
192        // A temporary table lives in the `temp` catalog and is dropped when the connection goes,
193        // and there is neither a `temp` catalog nor a connection yet. Making one in `memory` that
194        // never goes away would answer a later `SELECT` with rows DuckDB would not have.
195        return Err(Error::not_implemented("CREATE TEMPORARY TABLE"));
196    }
197    let parts: Vec<&str> = ast.name(written.name).collect();
198    let name = catalog.resolve_for_create(&parts)?;
199    let defs = ast.column_defs(written.columns);
200    let (columns, source) = if written.query == NONE {
201        let mut columns = Vec::with_capacity(defs.len());
202        for def in defs {
203            let text = ast.string(def.ty);
204            if text.is_empty() {
205                return Err(Error::binder(format!(
206                    "Column \"{}\" was declared without a type",
207                    ast.string(def.name)
208                )));
209            }
210            let ty = LogicalType::parse(text)?;
211            let column = ast.string(def.name);
212            columns.push(if def.not_null {
213                Field::required(column, ty)
214            } else {
215                Field::new(column, ty)
216            });
217        }
218        (columns, None)
219    } else {
220        let mut binder = Binder::with(catalog, parameters);
221        let (root, scope) = binder.bind_query(ast, written.query)?;
222        if defs.len() > scope.len() {
223            // DuckDB's sentence, typo and all. A column list shorter than the query is fine and
224            // renames a prefix, so only this direction is an error.
225            return Err(Error::binder("Target table has more colum names than query result."));
226        }
227        let mut columns = Vec::with_capacity(scope.len());
228        for (at, column) in scope.columns.iter().enumerate() {
229            let named = match defs.get(at) {
230                Some(def) => ast.string(def.name).to_string(),
231                None => column.name.clone(),
232            };
233            columns.push(Field::new(named, column.ty.clone()));
234        }
235        if defs.is_empty() {
236            deduplicate(&mut columns);
237        }
238        (columns, Some(finish(binder, root)?))
239    };
240    duplicate_check(&columns)?;
241    Ok(Bound::CreateTable(CreateTable {
242        name,
243        columns,
244        source,
245        if_not_exists: written.if_not_exists,
246        or_replace: written.or_replace,
247    }))
248}
249
250/// Renames the columns a query repeated, which is what makes `CREATE TABLE t AS SELECT 1 AS a, 2 AS
251/// a` a table rather than an error.
252///
253/// A query is allowed to produce two columns of one name and `SELECT 1 AS a, 2 AS a` prints two
254/// columns called `a`, so a statement that turns a query into a table has to decide what to do with
255/// that, and DuckDB renames rather than refusing. The suffix is `_1`, then `_2`, counting up until
256/// the name is free, so a query that already has an `a_1` in it pushes the renamed column to `a_2`
257/// rather than colliding with it.
258///
259/// This only runs when the statement wrote no column list. With a list, even a short one, duckdb
260/// v1.4.1 takes the names as they come and a repeat is an error, so `CREATE TABLE t (z) AS SELECT 1
261/// AS a, 2 AS a` is a table of `z` and `a` and adding a third `a` to that query is a refusal.
262fn deduplicate(columns: &mut [Field]) {
263    for at in 0..columns.len() {
264        let taken = |name: &str, upto: usize, columns: &[Field]| {
265            columns[..upto].iter().any(|held| same_name(&held.name, name))
266        };
267        if !taken(&columns[at].name, at, columns) {
268            continue;
269        }
270        let mut suffix = 1;
271        let mut candidate = format!("{}_{suffix}", columns[at].name);
272        while taken(&candidate, at, columns) {
273            suffix += 1;
274            candidate = format!("{}_{suffix}", columns[at].name);
275        }
276        columns[at].name = candidate;
277    }
278}
279
280/// Binds a `CREATE VIEW`, which means binding the body and then throwing the plan away.
281///
282/// Throwing it away is the point. The body is bound here so that a view over a table that is not
283/// there is refused now rather than at the first select, and so that the column list can be checked
284/// against what the body actually produces. What the catalog keeps is the text, because a view
285/// follows the tables underneath it and a plan is a photograph of the day it was built.
286fn create_view(
287    ast: &Ast,
288    catalog: &Catalog,
289    parameters: &Parameters,
290    index: ast::CreateViewRef,
291) -> Result<Bound> {
292    let written = ast.create_view(index);
293    if written.temporary {
294        // Same reason as a temporary table: there is no `temp` catalog and no connection for one to
295        // belong to, and a view in `memory` that never goes away is not the thing that was asked
296        // for.
297        return Err(Error::not_implemented("CREATE TEMPORARY VIEW"));
298    }
299    let parts: Vec<&str> = ast.name(written.name).collect();
300    let name = catalog.resolve_for_create(&parts)?;
301    let aliases: Vec<String> = ast.name(written.columns).map(str::to_string).collect();
302
303    let mut binder = Binder::with(catalog, parameters);
304    let (_, scope) = binder.bind_query(ast, written.query)?;
305    if aliases.len() > scope.len() {
306        return Err(Error::binder("More VIEW aliases than columns in query result"));
307    }
308
309    Ok(Bound::CreateView(CreateView {
310        name,
311        sql: ast.string(written.sql).to_string(),
312        aliases,
313        if_not_exists: written.if_not_exists,
314        or_replace: written.or_replace,
315    }))
316}
317
318fn drop_table(ast: &Ast, catalog: &Catalog, index: ast::DropTableRef) -> Result<Bound> {
319    let written = ast.drop_table(index);
320    let kind = if written.view { Entry::View } else { Entry::Table };
321    let mut names = Vec::new();
322    for &name in ast.name_list(written.names) {
323        let parts: Vec<&str> = ast.name(name).collect();
324        // The statement said which of the two it meant, so a name that is not there is a missing
325        // one of those and not a missing table.
326        match catalog.resolve_as(&parts, kind) {
327            Ok(resolved) => names.push(resolved),
328            Err(error) if written.if_exists => drop(error),
329            Err(error) => return Err(error),
330        }
331    }
332    Ok(Bound::DropTable(DropTable { names, kind }))
333}
334
335/// Binds a `SET` or a `RESET`, which is resolving its value and nothing else.
336///
337/// The name is not checked here. The binder knows what tables exist and has no idea what settings
338/// exist, since a setting is a knob on the engine rather than an entry in a catalog, and a version
339/// of this that held the list would be the binder holding a copy of something it cannot enforce.
340fn setting(
341    ast: &Ast,
342    catalog: &Catalog,
343    parameters: &Parameters,
344    index: ast::SettingRef,
345) -> Result<Bound> {
346    let written = ast.setting(index);
347    let name = ast.string(written.name).to_string();
348    let value = if written.value == NONE {
349        None
350    } else {
351        let mut binder = Binder::with(catalog, parameters);
352        let bound = binder.bind_setting_value(ast, written.value)?;
353        let Expr::Constant(value) = *binder.plan().expr(bound) else {
354            return Err(Error::not_implemented(format!(
355                "a value for {name} that is not a constant"
356            )));
357        };
358        Some(binder.plan().value(value).clone())
359    };
360    Ok(Bound::Setting(Setting { name, scope: written.scope, value }))
361}
362
363fn insert(
364    ast: &Ast,
365    catalog: &Catalog,
366    parameters: &Parameters,
367    index: ast::InsertRef,
368) -> Result<Bound> {
369    let written = ast.insert(index);
370    let parts: Vec<&str> = ast.name(written.name).collect();
371    let name = catalog.resolve(&parts)?;
372    if catalog.entry(&name)? == Entry::View {
373        // The binary's sentence, article and all. A view has no rows of its own to append to, and
374        // an updatable view is a rule about rewriting the insert that neither database has.
375        return Err(Error::catalog(format!("{} is not an table", name.table)));
376    }
377    let fields: Vec<Field> = catalog.table(&name)?.columns().to_vec();
378
379    // Which table column each source column lands in. Without a column list that is the first n
380    // columns in order, and with one it is whatever the list says, which is also the check that
381    // the list names columns the table has and names none of them twice.
382    let targets: Vec<usize> = if written.columns.is_empty() {
383        (0..fields.len()).collect()
384    } else {
385        let mut targets = Vec::new();
386        for column in ast.name(written.columns) {
387            let at = fields.iter().position(|field| same_name(&field.name, column)).ok_or_else(
388                || {
389                    Error::binder(format!(
390                        "Table \"{}\" does not have a column named \"{column}\"",
391                        name.table
392                    ))
393                },
394            )?;
395            if targets.contains(&at) {
396                return Err(Error::binder(format!(
397                    "Column \"{column}\" is named twice in the same INSERT"
398                )));
399            }
400            targets.push(at);
401        }
402        targets
403    };
404
405    let mut binder = Binder::with(catalog, parameters);
406    let (root, scope) = binder.bind_query(ast, written.source)?;
407    if scope.len() != targets.len() {
408        return Err(Error::binder(format!(
409            "Table \"{}\" has {} columns but {} values were supplied",
410            name.table,
411            targets.len(),
412            scope.len()
413        )));
414    }
415
416    // The projection that makes the source look exactly like the table. Every column the statement
417    // did not name becomes a null of the column's own type, so the append never has to know that a
418    // column list was written at all.
419    let mut exprs: Vec<ExprRef> = Vec::with_capacity(fields.len());
420    let mut names = Vec::with_capacity(fields.len());
421    for (at, field) in fields.iter().enumerate() {
422        let expr = match targets.iter().position(|&target| target == at) {
423            Some(from) => {
424                let column = &scope.columns[from];
425                let expr =
426                    binder.plan_mut().add_expr(Expr::Column(column.binding), column.ty.clone());
427                binder.cast_to(expr, &field.ty)
428            }
429            None => {
430                // A typed null rather than `add_constant`, which would give it the null type and
431                // make the column's type depend on whether a row happened to be inserted into it.
432                let value = binder.plan_mut().add_value(Value::Null);
433                binder.plan_mut().add_expr(Expr::Constant(value), field.ty.clone())
434            }
435        };
436        exprs.push(expr);
437        let interned = binder.plan_mut().intern(&field.name);
438        names.push(interned);
439    }
440    let exprs = binder.plan_mut().add_expr_list(&exprs);
441    let names = binder.plan_mut().add_name_list(&names);
442    let index = binder.fresh_index();
443    let root = binder.plan_mut().add_node(Node::Project { input: root, index, exprs, names });
444    Ok(Bound::Insert(Insert { name, source: finish(binder, root)? }))
445}