rudb/database.rs
1//! The handle everything else hangs off.
2
3use std::sync::{Arc, PoisonError, RwLock, RwLockReadGuard, RwLockWriteGuard};
4
5use rudb_bind::{Bound, Parameters};
6use rudb_catalog::{Catalog, Entry, View};
7use rudb_common::{Cancel, Error, Field, LogicalType, Memory, Result, Value};
8use rudb_metrics::{Document, Report, Span};
9
10use rudb_parse::ast::Ast;
11use rudb_vector::{Chunk, Vector};
12
13use crate::config::Config;
14use crate::connection::{Connection, single};
15use crate::prepared::Prepared;
16use crate::result::QueryResult;
17use crate::settings::Settings;
18
19/// The name that means no file, which is DuckDB's spelling and SQLite's before it.
20const MEMORY: &str = ":memory:";
21
22/// An in process database.
23///
24/// One catalog, held in memory, with no file behind it. `ATTACH` and the storage format are E2, and
25/// the shape of this type does not change when they arrive: a database with a file behind it is a
26/// catalog whose tables read from a block manager rather than from a `Vec` of chunks, which is a
27/// change under [`rudb_catalog::Table`] and not a change here.
28///
29/// A handle rather than the thing itself. Cloning one is cheap and gives another handle on the same
30/// database, and [`Database::connect`] gives a [`Connection`], which is the same sharing with a
31/// name that says what it is for. The catalog is behind a lock, so every method here takes `&self`
32/// and a write from one thread is serialized against a read from another rather than refused by the
33/// compiler. That is what an embedded database has to do, because the program embedding it is the
34/// one that decided how many threads it has.
35#[derive(Debug, Clone)]
36pub struct Database {
37 shared: Shared,
38}
39
40/// The state one database is, however many handles are on it.
41#[derive(Debug, Clone)]
42pub(crate) struct Shared {
43 inner: Arc<Inner>,
44}
45
46#[derive(Debug)]
47struct Inner {
48 catalog: RwLock<Catalog>,
49 settings: Settings,
50 memory: Memory,
51}
52
53impl Default for Database {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl Database {
60 /// An empty database with the default catalog and schema, held in memory.
61 #[must_use]
62 pub fn new() -> Self {
63 Self::with_config(Config::default())
64 }
65
66 /// An empty database held in memory, opened with these settings.
67 #[must_use]
68 pub fn with_config(config: Config) -> Self {
69 let memory = Memory::new(config.memory_limit());
70 let settings = Settings::new(config);
71 let inner = Inner { catalog: RwLock::new(Catalog::new()), settings, memory };
72 Self { shared: Shared { inner: Arc::new(inner) } }
73 }
74
75 /// What this database is running with now.
76 ///
77 /// By value rather than by reference, because `SET` changes it while the database is open and a
78 /// reference into the settings would be a lock held for as long as the caller kept it. A
79 /// `Config` is three numbers, so a copy costs nothing worth avoiding.
80 #[must_use]
81 pub fn config(&self) -> Config {
82 self.shared.inner.settings.config()
83 }
84
85 /// What this database was opened with, which is what `RESET` puts a setting back to.
86 #[must_use]
87 pub fn opened_with(&self) -> Config {
88 self.shared.inner.settings.defaults()
89 }
90
91 /// One setting, by the name `SET` uses for it, in the spelling DuckDB prints.
92 ///
93 /// The Rust side of reading a setting back. `current_setting()` is the SQL side and it is not
94 /// written yet, because a scalar function over engine state is a shape no function in rudb has.
95 ///
96 /// # Errors
97 ///
98 /// For a name that is not a setting, with the names there are.
99 pub fn setting(&self, name: &str) -> Result<String> {
100 self.shared.inner.settings.value(name)
101 }
102
103 /// Which implementation runs at each seam, as this session has left it.
104 ///
105 /// The session half of the three surfaces. The other two reach the same place: a process flag
106 /// is a `SET` the shell runs before anything else, and a per query hint is this with the
107 /// query's own pins laid on top, which is [`Database::seams_for`].
108 #[must_use]
109 pub fn seams(&self) -> rudb_seam::Settings {
110 self.shared.inner.settings.seams()
111 }
112
113 /// The seam settings one query runs under, which is [`Database::seams`] plus its hints.
114 ///
115 /// `SELECT /*+ hash.table(unchained) */ ...` pins a seam for one statement and leaves the
116 /// session alone, which is what a researcher comparing two implementations of one thing over a
117 /// suite needs, because the alternative is a `SET` before every query and a `RESET` after it
118 /// that somebody eventually forgets.
119 ///
120 /// # Errors
121 ///
122 /// A parse error, and everything a hint naming a seam nobody has raises.
123 pub fn seams_for(&self, sql: &str) -> Result<rudb_seam::Settings> {
124 self.shared.seams(sql)
125 }
126
127 /// The memory budget every query against this database is held to.
128 ///
129 /// One budget for the database rather than one per query, which is what
130 /// [`Config::memory_limit`] means: two queries running at once share the limit rather than
131 /// getting one each. Public because [`rudb_common::Memory::used`] is the only way to see what
132 /// is being held, and a program that sets a limit wants to know how close it is.
133 #[must_use]
134 pub fn memory(&self) -> &Memory {
135 &self.shared.inner.memory
136 }
137
138 /// Opens a database by name.
139 ///
140 /// `:memory:` and the empty string are an in memory database, which are DuckDB's two spellings
141 /// of it. Anything else names a file, and a file needs a storage format, which is #103. It is an
142 /// error here rather than a silent in memory database, because a program that opened a file and
143 /// wrote to it would be told nothing until it looked for its data again.
144 ///
145 /// # Errors
146 ///
147 /// When the name is a file.
148 pub fn open(path: &str) -> Result<Self> {
149 Self::open_with(path, Config::default())
150 }
151
152 /// Opens a database by name, with these settings.
153 ///
154 /// # Errors
155 ///
156 /// When the name is a file.
157 pub fn open_with(path: &str, config: Config) -> Result<Self> {
158 if path.is_empty() || path == MEMORY {
159 return Ok(Self::with_config(config));
160 }
161 Err(Error::not_implemented(format!(
162 "cannot open \"{path}\", because there is no storage format yet, see \
163 https://github.com/tamnd/rudb/issues/103"
164 )))
165 }
166
167 /// A connection to this database.
168 #[must_use]
169 pub fn connect(&self) -> Connection {
170 Connection::new(self.shared.clone())
171 }
172
173 /// Parses a statement so it can be run more than once, with values for its parameters.
174 ///
175 /// The same call as [`Connection::prepare`].
176 ///
177 /// # Errors
178 ///
179 /// A parse error. A name that does not resolve or a type that does not work out is an error at
180 /// execution rather than here, because a parameter has no type until it has a value.
181 pub fn prepare(&self, sql: &str) -> Result<Prepared> {
182 Prepared::new(self.shared.clone(), sql)
183 }
184
185 /// Reads the catalog.
186 ///
187 /// A closure rather than a returned reference, because the catalog is behind a lock and a
188 /// reference out of it would outlive the guard. The lock is held for the call and no longer.
189 pub fn with_catalog<T>(&self, read: impl FnOnce(&Catalog) -> T) -> T {
190 read(&self.shared.read())
191 }
192
193 /// Writes the catalog.
194 ///
195 /// Public because a program that builds its own catalog rather than parsing SQL to build one is
196 /// a real thing an embedded database gets used for.
197 pub fn with_catalog_mut<T>(&self, write: impl FnOnce(&mut Catalog) -> T) -> T {
198 write(&mut self.shared.write())
199 }
200
201 /// Defines a table.
202 ///
203 /// The name is `table`, `schema.table` or `catalog.schema.table`, and anything unqualified goes
204 /// to the default catalog and schema, which is what an unqualified name in a query resolves
205 /// against too.
206 ///
207 /// # Errors
208 ///
209 /// If the name has more than three parts, if the catalog or the schema does not exist, if the
210 /// table already exists, or if two of the columns have the same name.
211 pub fn create_table(&self, name: &str, columns: Vec<Field>) -> Result<()> {
212 let parts: Vec<&str> = name.split('.').collect();
213 let mut catalog = self.shared.write();
214 let resolved = catalog.resolve_for_create(&parts)?;
215 catalog.create_table(resolved, columns)
216 }
217
218 /// Drops a table.
219 ///
220 /// # Errors
221 ///
222 /// If the name does not resolve or the table does not exist.
223 pub fn drop_table(&self, name: &str) -> Result<()> {
224 let parts: Vec<&str> = name.split('.').collect();
225 let mut catalog = self.shared.write();
226 let resolved = catalog.resolve(&parts)?;
227 catalog.drop_table(&resolved)
228 }
229
230 /// Appends rows to a table, each row left to right in the table's column order.
231 ///
232 /// The row shaped write path, because the caller with rows in hand is the common case and the
233 /// caller with columns in hand can reach [`Database::with_catalog_mut`] and append a
234 /// [`rudb_vector::Chunk`] directly. Values are converted to the column's type on the way in, so
235 /// an `Integer` lands in a `BIGINT` column.
236 ///
237 /// # Errors
238 ///
239 /// If the name does not resolve, if a row is not as wide as the table, or if a value cannot be
240 /// converted to its column's type.
241 pub fn append(&self, name: &str, rows: &[Vec<Value>]) -> Result<()> {
242 let parts: Vec<&str> = name.split('.').collect();
243 let mut catalog = self.shared.write();
244 let resolved = catalog.resolve(&parts)?;
245 catalog.table_mut(&resolved)?.append_rows(rows)
246 }
247
248 /// How many rows a table holds.
249 ///
250 /// # Errors
251 ///
252 /// If the name does not resolve or the table does not exist.
253 pub fn table_len(&self, name: &str) -> Result<usize> {
254 let parts: Vec<&str> = name.split('.').collect();
255 let catalog = self.shared.read();
256 let resolved = catalog.resolve(&parts)?;
257 Ok(catalog.table(&resolved)?.rows().len())
258 }
259
260 /// Every table in the database, unqualified, in creation order.
261 ///
262 /// Unqualified because that is what a person typing `.tables` wants to read and what they would
263 /// then type into a query. Two tables of the same name in different schemas both appear, which
264 /// is the same thing DuckDB's `.tables` does.
265 #[must_use]
266 pub fn table_names(&self) -> Vec<String> {
267 self.shared.read().tables().map(|table| table.name().table.clone()).collect()
268 }
269
270 /// The `CREATE TABLE` that would define a table as it stands.
271 ///
272 /// Built from the catalog rather than remembered from the statement that made it, so a table
273 /// defined by [`Database::create_table`] describes itself as well as one defined by SQL. It
274 /// carries the column names, the types and `NOT NULL`, and nothing else, because nothing else
275 /// is in the catalog yet. Defaults, primary keys and check constraints appear here the day the
276 /// catalog holds them.
277 ///
278 /// # Errors
279 ///
280 /// If the name does not resolve or the table does not exist.
281 pub fn table_sql(&self, name: &str) -> Result<String> {
282 let parts: Vec<&str> = name.split('.').collect();
283 let catalog = self.shared.read();
284 let resolved = catalog.resolve(&parts)?;
285 let table = catalog.table(&resolved)?;
286 let columns: Vec<String> = table
287 .columns()
288 .iter()
289 .map(|field| {
290 let null = if field.not_null { " NOT NULL" } else { "" };
291 format!("{} {}{null}", field.name, field.ty)
292 })
293 .collect();
294 Ok(format!("CREATE TABLE {}({});", resolved.table, columns.join(", ")))
295 }
296
297 /// Runs one query and returns every row it produced.
298 ///
299 /// The same call as [`Connection::query`], for a program that has one database and no reason to
300 /// name a connection.
301 ///
302 /// The query timeout in [`Database::config`] applies, and nothing can interrupt it, because an
303 /// interrupt needs somebody holding the other end of a token and a bare database hands out no
304 /// token. [`Connection::interrupt`] is that other end.
305 ///
306 /// # Errors
307 ///
308 /// A parse error, a binder error, or anything the operators raise while running, which is
309 /// mostly cast failures and arithmetic that leaves the range of its type.
310 pub fn query(&self, sql: &str) -> Result<QueryResult> {
311 self.shared.query(sql, &self.shared.token())
312 }
313
314 /// Runs one statement, which may change the database.
315 ///
316 /// # Errors
317 ///
318 /// A parse error, a binder error, a catalog error, or anything the operators raise.
319 pub fn execute(&self, sql: &str) -> Result<QueryResult> {
320 self.shared.execute(sql, &self.shared.token())
321 }
322
323 /// The plan for a query, in the textual form `spec/07-execution.md` describes, without running
324 /// it.
325 ///
326 /// The same plan `EXPLAIN` prints, without the estimates and as a `String` rather than a result
327 /// set, which is what the plan tests and the optimizer work read. The text round trips:
328 /// `rudb_plan::Plan::parse` of this string gives back the plan it was printed from, and that is
329 /// why the estimates are not on it.
330 ///
331 /// # Errors
332 ///
333 /// A parse error or a binder error.
334 pub fn plan(&self, sql: &str) -> Result<String> {
335 self.shared.plan(sql)
336 }
337
338 /// Runs a query and returns the single value it produced.
339 ///
340 /// A convenience for `SELECT count(*) FROM t` and the rest of the one cell queries, which are
341 /// most of what a program embedded in something else asks.
342 ///
343 /// # Errors
344 ///
345 /// Everything [`Database::query`] can raise, plus an error if the result is not one row of one
346 /// column.
347 pub fn value(&self, sql: &str) -> Result<Value> {
348 single(&self.query(sql)?)
349 }
350}
351
352impl Shared {
353 /// The catalog, for reading.
354 ///
355 /// A poisoned lock is taken rather than reported. Poisoning says some thread panicked while it
356 /// held the lock, and the catalog is a `Vec` of chunks rather than an invariant somebody was
357 /// halfway through breaking, so refusing every later query would turn one panicked query into a
358 /// dead database.
359 fn read(&self) -> RwLockReadGuard<'_, Catalog> {
360 self.inner.catalog.read().unwrap_or_else(PoisonError::into_inner)
361 }
362
363 /// The catalog, for writing.
364 fn write(&self) -> RwLockWriteGuard<'_, Catalog> {
365 self.inner.catalog.write().unwrap_or_else(PoisonError::into_inner)
366 }
367
368 /// Runs one query and returns every row it produced.
369 ///
370 /// `EXPLAIN` comes through here as well as through [`Shared::execute`], because it answers with
371 /// rows and this is the path that reads rows back. It takes the read lock like any other query,
372 /// since printing a plan changes nothing. A statement that writes is refused here rather than
373 /// run under a read lock.
374 pub(crate) fn query(&self, sql: &str, cancel: &Cancel) -> Result<QueryResult> {
375 let catalog = self.read();
376 let seams = self.seams(sql)?;
377 let context = self.optimizer(&catalog)?;
378 let ast = rudb_parse::parse_ast(sql)?;
379 match rudb_bind::bind_statement_with(&ast, &catalog, &Parameters::new())? {
380 Bound::Query(mut plan) => {
381 rudb_opt::optimize_with(&mut plan, &context)?;
382 run(sql, &plan, &catalog, cancel, &self.inner.memory, context.statistics(), &seams)
383 }
384 Bound::Explain { mut plan, analyze } => {
385 rudb_opt::optimize_with(&mut plan, &context)?;
386 let seams = rudb_opt::explain::Seams::new(&seams, rudb_exec::registries());
387 explaining(
388 &plan,
389 &catalog,
390 cancel,
391 &self.inner.memory,
392 &context,
393 seams,
394 analyze,
395 sql,
396 )
397 }
398 _ => Err(Error::not_implemented("a statement that is not a query, on the query path")),
399 }
400 }
401
402 /// The seam settings a statement runs under, which is the session's with its hints on top.
403 ///
404 /// What the settings choose goes nowhere yet, because no seam has a second implementation to
405 /// choose between until F1 and every one of the twenty seven is unregistered. What they do
406 /// today is fail a statement whose hint names a seam nobody has, and feed the seam section of
407 /// `EXPLAIN`, which is the half of the behaviour worth having before the other half arrives: a
408 /// hint that is quietly ignored is a measurement of the wrong thing.
409 pub(crate) fn seams(&self, sql: &str) -> Result<rudb_seam::Settings> {
410 let mut seams = self.inner.settings.seams();
411 for hint in rudb_parse::hints(sql)? {
412 seams.hint(hint)?;
413 }
414 Ok(seams)
415 }
416
417 /// The passes this database's queries run, as `SET disabled_optimizers` has left them, with
418 /// the row counts the catalog holds.
419 ///
420 /// Rebuilt for each statement rather than held, because the statement before this one may have
421 /// been the `SET` and the statement before that may have been an `INSERT`. It cannot fail: the
422 /// names were checked when they were set, and the `?` is here because nothing stops a later
423 /// version from having a pass that goes away.
424 ///
425 /// The catalog comes in as an argument rather than being read from the lock here, because
426 /// every caller is already holding that lock and one of them is holding it for writing. This
427 /// is also the seam that stops the optimizer from reaching the catalog on its own: what it
428 /// gets is a copy of the counts, which is the whole of what estimation reads today.
429 fn optimizer(&self, catalog: &Catalog) -> Result<rudb_opt::pass::Context> {
430 let mut context =
431 rudb_opt::pass::Context::without(&self.inner.settings.disabled_optimizers())?;
432 let mut statistics = rudb_opt::estimate::Statistics::new();
433 for table in catalog.tables() {
434 let name = table.name();
435 let rows = u64::try_from(table.rows().len()).unwrap_or(u64::MAX);
436 statistics.record(&name.catalog, &name.schema, &name.table, rows);
437 }
438 context.measure(statistics);
439 Ok(context)
440 }
441
442 /// The query timeout this database was opened with.
443 pub(crate) fn timeout(&self) -> Option<std::time::Duration> {
444 self.inner.settings.config().query_timeout()
445 }
446
447 /// The token a statement of this database's runs under, when nobody holds one of their own.
448 ///
449 /// It carries the configured query timeout and nothing can interrupt it, because there is
450 /// nobody holding the other half. [`Connection`] is where the other half lives.
451 pub(crate) fn token(&self) -> Cancel {
452 match self.inner.settings.config().query_timeout() {
453 Some(timeout) => Cancel::after(timeout),
454 None => Cancel::new(),
455 }
456 }
457
458 /// The plan a query runs.
459 pub(crate) fn plan(&self, sql: &str) -> Result<String> {
460 let catalog = self.read();
461 Ok(planned(sql, &catalog, &self.optimizer(&catalog)?)?.to_string())
462 }
463
464 /// Runs one statement, which may change the database.
465 ///
466 /// The write lock is taken for the whole statement rather than for the part that writes,
467 /// because the part that writes is decided by what the part that reads produced. `INSERT INTO t
468 /// SELECT * FROM t` would otherwise read the table under a read lock, let go, and append to
469 /// whatever the table had become in between.
470 pub(crate) fn execute(&self, sql: &str, cancel: &Cancel) -> Result<QueryResult> {
471 let ast = rudb_parse::parse_ast(sql)?;
472 self.execute_ast(&ast, sql, &Parameters::new(), cancel)
473 }
474
475 /// Runs one parsed statement, with values for its parameters.
476 ///
477 /// The prepared statement path, and the path an ordinary statement takes once it is parsed, so
478 /// that there is one description of what running a statement does.
479 pub(crate) fn execute_ast(
480 &self,
481 ast: &Ast,
482 sql: &str,
483 parameters: &Parameters,
484 cancel: &Cancel,
485 ) -> Result<QueryResult> {
486 let seams = self.seams(sql)?;
487 let mut catalog = self.write();
488 let context = self.optimizer(&catalog)?;
489 match rudb_bind::bind_statement_with(ast, &catalog, parameters)? {
490 Bound::Query(mut plan) => {
491 rudb_opt::optimize_with(&mut plan, &context)?;
492 run(sql, &plan, &catalog, cancel, &self.inner.memory, context.statistics(), &seams)
493 }
494 Bound::Explain { mut plan, analyze } => {
495 rudb_opt::optimize_with(&mut plan, &context)?;
496 let seams = rudb_opt::explain::Seams::new(&seams, rudb_exec::registries());
497 explaining(
498 &plan,
499 &catalog,
500 cancel,
501 &self.inner.memory,
502 &context,
503 seams,
504 analyze,
505 sql,
506 )
507 }
508 Bound::Setting(setting) => {
509 let value = setting.value.as_ref();
510 self.inner.settings.apply(
511 &self.inner.memory,
512 &setting.name,
513 setting.scope,
514 value,
515 )?;
516 Ok(QueryResult::empty())
517 }
518 Bound::CreateTable(create) => {
519 create_table(
520 sql,
521 create,
522 &mut catalog,
523 cancel,
524 &self.inner.memory,
525 &context,
526 &seams,
527 )?;
528 Ok(QueryResult::empty())
529 }
530 Bound::CreateView(create) => {
531 create_view(create, &mut catalog)?;
532 Ok(QueryResult::empty())
533 }
534 Bound::DropTable(drop) => {
535 for name in &drop.names {
536 match drop.kind {
537 Entry::Table => catalog.drop_table(name)?,
538 Entry::View => catalog.drop_view(name)?,
539 }
540 }
541 Ok(QueryResult::empty())
542 }
543 Bound::Insert(mut insert) => {
544 // The source runs to completion before anything is appended, which is not an
545 // implementation detail. `INSERT INTO t SELECT * FROM t` reads the table it writes,
546 // and a version of this that appended chunk by chunk would either read its own
547 // output forever or depend on how the scan holds its chunks.
548 rudb_opt::optimize_with(&mut insert.source, &context)?;
549 let result = run(
550 sql,
551 &insert.source,
552 &catalog,
553 cancel,
554 &self.inner.memory,
555 context.statistics(),
556 &seams,
557 )?;
558 let table = catalog.table_mut(&insert.name)?;
559 for chunk in result.into_chunks() {
560 table.append(chunk)?;
561 }
562 Ok(QueryResult::empty())
563 }
564 }
565 }
566}
567
568/// A query bound and then optimized, which is the plan that runs.
569///
570/// What [`Database::plan`] dumps, and it optimizes rather than stopping at the bound plan because a
571/// dump of the bound plan next to a run of the optimized one would make the dump a description of
572/// something nobody executes, which is the one thing a plan dump must not be. `EXPLAIN` and the
573/// query path do the same two steps in that order for the same reason.
574fn planned(
575 sql: &str,
576 catalog: &Catalog,
577 context: &rudb_opt::pass::Context,
578) -> Result<rudb_plan::Plan> {
579 let mut plan = rudb_bind::bind_sql(sql, catalog)?;
580 rudb_opt::optimize_with(&mut plan, context)?;
581 Ok(plan)
582}
583
584/// Builds and drains one plan, stopping if the token says to or if it runs out of memory.
585///
586/// The result is materialized, so it is charged, and the charge is handed to the result and
587/// released when the result is dropped. That is what makes a program holding ten results at once
588/// count as holding ten results: the limit is on the database and a result outlives the query.
589///
590/// This is also the one place a metrics document is made. Everything in it below the top level
591/// comes out of the report the builder filled, and the two spans here are the two things only this
592/// function knows: how long the tree took to build and how long it took to drain. Parsing, binding
593/// and optimizing happened before this was called and their timings stay at zero until the clock
594/// moves up to the statement path.
595///
596/// A query that fails part way through has a document too, and it is thrown away here, because an
597/// error is a [`rudb_common::Error`] and that type is two ranks below the one the document lives
598/// in. Carrying it out of a failure is worth doing and it is a change to how an error is reported
599/// rather than a change to this function.
600fn run(
601 sql: &str,
602 plan: &rudb_plan::Plan,
603 catalog: &Catalog,
604 cancel: &Cancel,
605 memory: &Memory,
606 statistics: &rudb_opt::estimate::Statistics,
607 seams: &rudb_seam::Settings,
608) -> Result<QueryResult> {
609 // The budget is shared by the database and its high-water mark survives a query. Reset it to
610 // what is live now before measuring this execution, otherwise a metrics document either says
611 // zero forever (when nobody copies the mark) or inherits the largest earlier query. A caller
612 // with concurrent statements cannot attribute the shared budget to one query; this field is a
613 // database-level peak in that case. The CLI benchmark path has one statement in flight.
614 memory.forget_peak();
615 let report = Report::new();
616 let building = Span::start();
617 let mut root = rudb_exec::build_measured(plan, catalog, cancel, memory, seams, &report)?;
618 let (built_wall, built_cpu) = building.stop();
619 let names = root.schema().names();
620 let types = root.schema().types();
621 let mut held = memory.reservation();
622 let mut chunks = Vec::new();
623 // This loop is the root pipeline's driver. Every other pipeline is drained by the loop that
624 // fills its sink and that loop reports its own time, and this one is pulled from here, so this
625 // is the only place that can report it. The span it hands back is the whole execution including
626 // the pipelines that ran inside it, which is what `execute_ns` is; what the driver keeps for
627 // itself is that minus what they charged.
628 let driver = report.driving(rudb_plan::ROOT);
629 let driving = driver.running();
630 while let Some(chunk) = root.next()? {
631 if chunk.is_empty() {
632 continue;
633 }
634 // flatten: this is the top of the query and the chunk is about to become a result set that
635 // somebody outside the engine reads. A caller holding a `Result` gets a value at a time, so
636 // a dictionary or a constant here would be a form every one of them has to understand to
637 // read a row. The decode stops at this line and nothing below it sees a flat column.
638 let chunk = chunk.flatten()?;
639 held.grow(u64::try_from(chunk.footprint()).unwrap_or(u64::MAX))?;
640 chunks.push(chunk);
641 }
642 let (ran_wall, ran_cpu) = driving.stop();
643 let mut metrics = Document::new(sql);
644 metrics.settings.memory_limit = memory.limit();
645 metrics.settings.threads = 1;
646 metrics.timing.physical_ns = built_wall;
647 metrics.timing.execute_ns = ran_wall;
648 metrics.timing.total_ns = built_wall.saturating_add(ran_wall);
649 metrics.resource.cpu_ns = built_cpu.saturating_add(ran_cpu);
650 metrics.resource.build_cpu_ns = built_cpu;
651 metrics.resource.peak_bytes = memory.peak();
652 report.fill(&mut metrics);
653 rudb_opt::explain::record_estimates(plan, statistics, &mut metrics);
654 Ok(QueryResult::new(names, types, chunks, held).measured(metrics))
655}
656
657/// The plan `EXPLAIN` prints, run first if `ANALYZE` was asked for.
658///
659/// `ANALYZE` runs the query and throws the rows away. That is the whole difference between the two,
660/// and it is deliberately the only difference: the plan that is printed is the plan that was built
661/// and drained, so a number on a line came from the operator on that line rather than from an
662/// operator something else would have built.
663///
664/// The rows are dropped rather than returned because the result set of `EXPLAIN ANALYZE` is the
665/// plan. DuckDB does the same and calls the row `analyzed_plan`, and a client that gets a query's
666/// rows back from an `EXPLAIN` has no way to tell which it asked for.
667#[allow(clippy::too_many_arguments)]
668fn explaining(
669 plan: &rudb_plan::Plan,
670 catalog: &Catalog,
671 cancel: &Cancel,
672 memory: &Memory,
673 context: &rudb_opt::pass::Context,
674 seams: rudb_opt::explain::Seams<'_>,
675 analyze: bool,
676 sql: &str,
677) -> Result<QueryResult> {
678 let statistics = context.statistics();
679 if !analyze {
680 return explained(
681 "logical_plan",
682 &rudb_opt::explain::explain_with(plan, statistics, seams),
683 );
684 }
685 let result = run(sql, plan, catalog, cancel, memory, statistics, seams.settings())?;
686 let measured = result.metrics().expect("a query that ran reports what it did");
687 let text = rudb_opt::explain::analyzed(plan, statistics, seams, measured);
688 explained("analyzed_plan", &text)
689}
690
691/// One row of two strings, which is the result set `EXPLAIN` hands back.
692///
693/// The column names and the shape are DuckDB's, `explain_key` and `explain_value`, because a
694/// client reading a result set has to cope with whatever comes out and there is no reason to make
695/// it cope with something new. The text in the second column is ours, since
696/// `spec/12-duckdb-compat.md` section 12.5 excludes explain output from the guarantee.
697///
698/// One row rather than one per operator. DuckDB puts its whole tree in a single value and every
699/// shell prints it as a block, and splitting it into rows would mean a shell's column width
700/// deciding where a plan wraps.
701fn explained(key: &str, text: &str) -> Result<QueryResult> {
702 let key = Vector::from_values(LogicalType::Varchar, &[Value::Varchar(key.to_owned())])?;
703 let value = Vector::from_values(LogicalType::Varchar, &[Value::Varchar(text.to_owned())])?;
704 Ok(QueryResult::new(
705 vec!["explain_key".to_owned(), "explain_value".to_owned()],
706 vec![LogicalType::Varchar, LogicalType::Varchar],
707 vec![Chunk::new(vec![key, value])?],
708 Memory::unlimited().reservation(),
709 ))
710}
711
712/// The `CREATE VIEW` half of a statement.
713///
714/// There is nothing to run. The body was bound by the binder to check that it can be, and what is
715/// kept is the text, so this is the two modifiers and a catalog call.
716fn create_view(create: rudb_bind::CreateView, catalog: &mut Catalog) -> Result<()> {
717 if create.if_not_exists && catalog.entry(&create.name).is_ok() {
718 return Ok(());
719 }
720 if create.or_replace && catalog.view(&create.name).is_ok() {
721 catalog.drop_view(&create.name)?;
722 }
723 catalog.create_view(View::new(create.name, create.sql, create.aliases))
724}
725
726/// The `CREATE TABLE` half of a statement.
727fn create_table(
728 sql: &str,
729 mut create: rudb_bind::CreateTable,
730 catalog: &mut Catalog,
731 cancel: &Cancel,
732 memory: &Memory,
733 context: &rudb_opt::pass::Context,
734 seams: &rudb_seam::Settings,
735) -> Result<()> {
736 if create.if_not_exists && catalog.table(&create.name).is_ok() {
737 return Ok(());
738 }
739 // The query runs before the old table is dropped, so `CREATE OR REPLACE TABLE t AS SELECT * FROM
740 // t` reads the table it is about to replace rather than the empty new one.
741 let rows = match &mut create.source {
742 Some(plan) => {
743 rudb_opt::optimize_with(plan, context)?;
744 Some(run(sql, plan, catalog, cancel, memory, context.statistics(), seams)?)
745 }
746 None => None,
747 };
748 if create.or_replace && catalog.table(&create.name).is_ok() {
749 catalog.drop_table(&create.name)?;
750 }
751 catalog.create_table(create.name.clone(), create.columns)?;
752 if let Some(rows) = rows {
753 let table = catalog.table_mut(&create.name)?;
754 for chunk in rows.into_chunks() {
755 table.append(chunk)?;
756 }
757 }
758 Ok(())
759}