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