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