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, QualifiedName, 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;
24
25/// One statement, bound.
26///
27/// Not `#[non_exhaustive]`. A new variant here is a new kind of statement, and the compiler
28/// pointing at every place that has to decide what to do with it is the whole value of the enum.
29#[derive(Debug)]
30pub enum Bound {
31    /// A query, which is the only one of these that produces rows.
32    Query(Plan),
33    /// `CREATE TABLE`.
34    CreateTable(CreateTable),
35    /// `DROP TABLE`.
36    DropTable(DropTable),
37    /// `INSERT INTO`.
38    Insert(Insert),
39}
40
41/// A bound `CREATE TABLE`.
42#[derive(Debug)]
43pub struct CreateTable {
44    /// The full name the table gets.
45    pub name: QualifiedName,
46    /// The columns, in order, with the types already resolved. For a `CREATE TABLE AS` these are
47    /// the query's output types under whatever names the statement or the query gave them.
48    pub columns: Vec<Field>,
49    /// The query to fill it from, for a `CREATE TABLE AS`.
50    pub source: Option<Plan>,
51    /// Whether an existing table of that name is left alone rather than being an error.
52    pub if_not_exists: bool,
53    /// Whether an existing table of that name is dropped first.
54    pub or_replace: bool,
55}
56
57/// A bound `DROP TABLE`.
58#[derive(Debug)]
59pub struct DropTable {
60    /// The tables to drop, already resolved. With `IF EXISTS` a name that does not resolve is not
61    /// in here at all, which is what makes running this a sequence of drops that cannot fail.
62    pub names: Vec<QualifiedName>,
63}
64
65/// A bound `INSERT`.
66#[derive(Debug)]
67pub struct Insert {
68    /// The table to append to.
69    pub name: QualifiedName,
70    /// The rows to append. The output is the table's columns, in the table's order, with the
71    /// table's types, so nothing between here and the append has a decision left to make.
72    pub source: Plan,
73}
74
75/// Binds one parsed statement against a catalog.
76///
77/// # Errors
78///
79/// If the script does not hold exactly one statement, if a name does not resolve, if a type does
80/// not work out, or if the statement uses something that is not bound yet.
81pub fn bind_statement(ast: &Ast, catalog: &Catalog) -> Result<Bound> {
82    let statement = match ast.statements.as_slice() {
83        [statement] => *statement,
84        [] => return Err(Error::binder("no statement to bind")),
85        _ => return Err(Error::not_implemented("a script of more than one statement")),
86    };
87    match statement {
88        ast::Statement::Query(query) => {
89            let mut binder = Binder::new(catalog);
90            let (root, _) = binder.bind_query(ast, query)?;
91            Ok(Bound::Query(finish(binder, root)?))
92        }
93        ast::Statement::CreateTable(index) => create_table(ast, catalog, index),
94        ast::Statement::DropTable(index) => drop_table(ast, catalog, index),
95        ast::Statement::Insert(index) => insert(ast, catalog, index),
96    }
97}
98
99/// Parses and binds one statement, which is the whole front end in one call.
100///
101/// # Errors
102///
103/// Anything the parser or the binder reports.
104pub fn bind_statement_sql(sql: &str, catalog: &Catalog) -> Result<Bound> {
105    let ast = parse_ast(sql)?;
106    bind_statement(&ast, catalog)
107}
108
109/// Roots a binder's plan and checks it.
110fn finish(binder: Binder<'_>, root: rudb_plan::NodeRef) -> Result<Plan> {
111    let mut plan = binder.into_plan();
112    plan.set_root(root);
113    plan.validate()?;
114    Ok(plan)
115}
116
117fn create_table(ast: &Ast, catalog: &Catalog, index: ast::CreateTableRef) -> Result<Bound> {
118    let written = ast.create_table(index);
119    if written.temporary {
120        // A temporary table lives in the `temp` catalog and is dropped when the connection goes,
121        // and there is neither a `temp` catalog nor a connection yet. Making one in `memory` that
122        // never goes away would answer a later `SELECT` with rows DuckDB would not have.
123        return Err(Error::not_implemented("CREATE TEMPORARY TABLE"));
124    }
125    if written.if_not_exists && written.or_replace {
126        return Err(Error::binder("OR REPLACE cannot be used together with IF NOT EXISTS"));
127    }
128    let parts: Vec<&str> = ast.name(written.name).collect();
129    let name = catalog.resolve_for_create(&parts)?;
130    let defs = ast.column_defs(written.columns);
131    for def in defs {
132        if def.not_null {
133            // Nothing carries a nullability yet, so accepting this would mean an insert of a null
134            // succeeding where DuckDB raises. `rudb_common::Field` is where it goes when it lands.
135            return Err(Error::not_implemented("a NOT NULL column constraint"));
136        }
137    }
138    let (columns, source) = if written.query == NONE {
139        let mut columns = Vec::with_capacity(defs.len());
140        for def in defs {
141            let text = ast.string(def.ty);
142            if text.is_empty() {
143                return Err(Error::binder(format!(
144                    "Column \"{}\" was declared without a type",
145                    ast.string(def.name)
146                )));
147            }
148            columns.push(Field::new(ast.string(def.name), LogicalType::parse(text)?));
149        }
150        (columns, None)
151    } else {
152        let mut binder = Binder::new(catalog);
153        let (root, scope) = binder.bind_query(ast, written.query)?;
154        if !defs.is_empty() && defs.len() != scope.len() {
155            return Err(Error::binder(format!(
156                "Table \"{}\" has {} columns but the query produces {}",
157                name.table,
158                defs.len(),
159                scope.len()
160            )));
161        }
162        let mut columns = Vec::with_capacity(scope.len());
163        for (at, column) in scope.columns.iter().enumerate() {
164            let named = match defs.get(at) {
165                Some(def) => ast.string(def.name).to_string(),
166                None => column.name.clone(),
167            };
168            columns.push(Field::new(named, column.ty.clone()));
169        }
170        (columns, Some(finish(binder, root)?))
171    };
172    duplicate_check(&columns)?;
173    Ok(Bound::CreateTable(CreateTable {
174        name,
175        columns,
176        source,
177        if_not_exists: written.if_not_exists,
178        or_replace: written.or_replace,
179    }))
180}
181
182/// The same check the catalog makes, made here so the message arrives before anything is created.
183fn duplicate_check(columns: &[Field]) -> Result<()> {
184    for (at, column) in columns.iter().enumerate() {
185        if columns[..at].iter().any(|held| same_name(&held.name, &column.name)) {
186            return Err(Error::binder(format!(
187                "Duplicate column name \"{}\" in a table definition",
188                column.name
189            )));
190        }
191    }
192    Ok(())
193}
194
195fn drop_table(ast: &Ast, catalog: &Catalog, index: ast::DropTableRef) -> Result<Bound> {
196    let written = ast.drop_table(index);
197    let mut names = Vec::new();
198    for &name in ast.name_list(written.names) {
199        let parts: Vec<&str> = ast.name(name).collect();
200        match catalog.resolve(&parts) {
201            Ok(resolved) => names.push(resolved),
202            Err(error) if written.if_exists => drop(error),
203            Err(error) => return Err(error),
204        }
205    }
206    Ok(Bound::DropTable(DropTable { names }))
207}
208
209fn insert(ast: &Ast, catalog: &Catalog, index: ast::InsertRef) -> Result<Bound> {
210    let written = ast.insert(index);
211    let parts: Vec<&str> = ast.name(written.name).collect();
212    let name = catalog.resolve(&parts)?;
213    let fields: Vec<Field> = catalog.table(&name)?.columns().to_vec();
214
215    // Which table column each source column lands in. Without a column list that is the first n
216    // columns in order, and with one it is whatever the list says, which is also the check that
217    // the list names columns the table has and names none of them twice.
218    let targets: Vec<usize> = if written.columns.is_empty() {
219        (0..fields.len()).collect()
220    } else {
221        let mut targets = Vec::new();
222        for column in ast.name(written.columns) {
223            let at = fields.iter().position(|field| same_name(&field.name, column)).ok_or_else(
224                || {
225                    Error::binder(format!(
226                        "Table \"{}\" does not have a column named \"{column}\"",
227                        name.table
228                    ))
229                },
230            )?;
231            if targets.contains(&at) {
232                return Err(Error::binder(format!(
233                    "Column \"{column}\" is named twice in the same INSERT"
234                )));
235            }
236            targets.push(at);
237        }
238        targets
239    };
240
241    let mut binder = Binder::new(catalog);
242    let (root, scope) = binder.bind_query(ast, written.source)?;
243    if scope.len() != targets.len() {
244        return Err(Error::binder(format!(
245            "Table \"{}\" has {} columns but {} values were supplied",
246            name.table,
247            targets.len(),
248            scope.len()
249        )));
250    }
251
252    // The projection that makes the source look exactly like the table. Every column the statement
253    // did not name becomes a null of the column's own type, so the append never has to know that a
254    // column list was written at all.
255    let mut exprs: Vec<ExprRef> = Vec::with_capacity(fields.len());
256    let mut names = Vec::with_capacity(fields.len());
257    for (at, field) in fields.iter().enumerate() {
258        let expr = match targets.iter().position(|&target| target == at) {
259            Some(from) => {
260                let column = &scope.columns[from];
261                let expr =
262                    binder.plan_mut().add_expr(Expr::Column(column.binding), column.ty.clone());
263                binder.cast_to(expr, &field.ty)
264            }
265            None => {
266                // A typed null rather than `add_constant`, which would give it the null type and
267                // make the column's type depend on whether a row happened to be inserted into it.
268                let value = binder.plan_mut().add_value(Value::Null);
269                binder.plan_mut().add_expr(Expr::Constant(value), field.ty.clone())
270            }
271        };
272        exprs.push(expr);
273        let interned = binder.plan_mut().intern(&field.name);
274        names.push(interned);
275    }
276    let exprs = binder.plan_mut().add_expr_list(&exprs);
277    let names = binder.plan_mut().add_name_list(&names);
278    let index = binder.fresh_index();
279    let root = binder.plan_mut().add_node(Node::Project { input: root, index, exprs, names });
280    Ok(Bound::Insert(Insert { name, source: finish(binder, root)? }))
281}