rudb/lib.rs
1//! The embedding API: connections, prepared statements, configuration and results.
2//!
3//! Rank 13 in the layer rule. See `xtask/layers.toml` and `spec/18-package-layout.md`.
4//!
5//! This is the crate somebody who wants a database depends on. Everything below it is an
6//! implementation detail that happens to be published, and everything above it is a different way
7//! of reaching this same API: `rudb-c-api` is this over the C ABI and `rudb-cli` is this behind a
8//! prompt.
9//!
10//! ```
11//! use rudb::{Database, Field, LogicalType, Value};
12//!
13//! let db = Database::new();
14//! db.create_table("t", vec![Field::new("x", LogicalType::Integer)])?;
15//! db.append("t", &[vec![Value::Integer(1)], vec![Value::Integer(7)]])?;
16//!
17//! let result = db.query("SELECT x FROM t WHERE x > 5")?;
18//! assert_eq!(result.len(), 1);
19//! assert_eq!(result.value_at(0, 0), Value::Integer(7));
20//! # Ok::<(), rudb::Error>(())
21//! ```
22//!
23//! # What a query is today
24//!
25//! Parse, bind, optimize, execute. The optimizer is one pass, which is column pruning, so a scan
26//! reads the columns something above it asks for and the plan is otherwise the shape the binder
27//! built it. The rest of `spec/09-optimizer.md`'s sequence is M1 work. There are no transactions, and `rudb-txn` is in the dependency list for the same
28//! reason: the seam is where it will be and nothing goes through it yet.
29//!
30//! [`Database::create_table`] and [`Database::append`] are how rows get in without SQL, and they
31//! are a real part of the API rather than a test helper, since an embedded analytical database gets
32//! most of its data from a program rather than from a string of SQL. The DDL statements bind to the
33//! same catalog calls these make.
34//!
35//! # Prepared statements
36//!
37//! [`Database::prepare`] and [`Connection::prepare`] parse a statement once and hand back a
38//! [`Prepared`] that runs with values for its parameters, written `?`, `?1`, `$1` or `$name`. The
39//! statement is bound again for each set of values rather than planned once and filled in, because
40//! an analytical plan depends on what the values are: a scan that keeps one row in a million and a
41//! scan that keeps half the table want different plans, and the binder is cheap next to either.
42//!
43//! # Threading
44//!
45//! A [`Database`] is a handle. Cloning one, or calling [`Database::connect`], gives another handle
46//! on the same database, and every method takes `&self`, so the program embedding this is the one
47//! that decides how many threads there are. The catalog is behind a reader writer lock: any number
48//! of queries read at once and a statement that writes has the database to itself while it runs.
49//!
50//! That lock is the whole of the concurrency story until `rudb-txn` has one. A statement that writes
51//! is serialized against every reader rather than isolated from them, which is correct and is
52//! coarse, and the thing that makes it finer is a transaction rather than a different lock.
53//!
54//! # Stopping a query
55//!
56//! Two ways, and they answer two different questions. [`Config::with_query_timeout`] is a limit the
57//! statement enforces on itself, which is what a harness running somebody else's SQL wants, because
58//! the thing it is guarding against is a query that never ends rather than a person who changed
59//! their mind. [`Connection::interrupt`] is the other end of a token somebody else holds, which is
60//! what a signal handler wants, and it is DuckDB's model as well: `duckdb_interrupt` takes a
61//! connection.
62//!
63//! ```
64//! use std::time::Duration;
65//! use rudb::{Config, Database};
66//!
67//! let db = Database::with_config(Config::new().with_query_timeout(Duration::from_millis(50)));
68//! let error = db.query("SELECT count(*) FROM range(100000000000)").expect_err("too slow");
69//! assert_eq!(error.code().duckdb_name(), "Interrupt Error");
70//! ```
71//!
72//! A query stops at its next chunk boundary rather than immediately, which is a thousand rows of
73//! work later, and the reason is in [`Cancel`]. Nothing is rolled back, because there are no
74//! transactions yet: a stopped `INSERT` has written nothing, since the source runs to completion
75//! before anything is appended, and a stopped `CREATE TABLE AS SELECT` leaves no table behind for
76//! the same reason. That stops being true the day the writes stream, and the thing that makes it
77//! true again is a transaction.
78//!
79//! # Running out of memory
80//!
81//! The third way a query stops. [`Config::with_memory_limit`] is a budget for the whole database,
82//! and the operators that buffer without bound charge what they hold against it. A query that asks
83//! for more than is left stops with an `Out of Memory Error` rather than being killed from outside,
84//! which is the difference between a harness that reports a result for a file and a harness that
85//! reports nothing because the process died.
86//!
87//! There is a budget without anybody setting one. It is eighty percent of what the machine has, the
88//! way DuckDB's is, and [`Config::memory_limit`] says what it is on this machine and what to do to
89//! turn it off. The default is the whole point of the error: a limit that has to be typed is a limit
90//! that is not there on the machine where the query went wrong.
91//!
92//! ```
93//! use rudb::{Config, Database};
94//!
95//! let db = Database::with_config(Config::new().with_memory_limit(1 << 20));
96//! let error = db.query("SELECT * FROM range(10000000) ORDER BY range").expect_err("too large");
97//! assert_eq!(error.code().duckdb_name(), "Out of Memory Error");
98//! // And the budget is given back, so the connection is still usable.
99//! assert_eq!(db.memory().used(), 0);
100//! assert_eq!(db.value("SELECT 1").expect("a small query still runs"), rudb::Value::Integer(1));
101//! ```
102//!
103//! What is counted is what the operators said they were holding, which is not the resident size of
104//! the process. [`rudb_common::Memory`] says exactly what that covers and which direction it errs
105//! in.
106
107#![forbid(unsafe_code)]
108
109mod config;
110mod connection;
111mod database;
112mod prepared;
113mod result;
114mod settings;
115mod statements;
116mod syntax;
117
118#[cfg(test)]
119mod tests;
120
121pub use config::{Config, parse_size};
122pub use connection::Connection;
123pub use database::Database;
124pub use prepared::Prepared;
125pub use result::QueryResult;
126pub use statements::{Statement, is_complete, statements};
127pub use syntax::{RowOrder, accepts, line_and_column, parses, row_order, split, where_it_happened};
128
129// The types the API deals in, so a program that embeds rudb depends on this crate and nothing else.
130// `rudb-compat` and `rudb-bench` driving the library through one crate is the point of #110, and a
131// caller who had to reach for `rudb-common` to name the type of a value would not be doing that.
132pub use rudb_common::{Cancel, Error, ErrorCode, Field, LogicalType, Result, Span, Value};
133pub use rudb_vector::Chunk;
134
135/// Every optimizer pass, by the name `SET disabled_optimizers` knows it by, in the order they run.
136///
137/// What a caller does with it is turn the optimizer off: `SET disabled_optimizers` takes DuckDB's
138/// comma separated spelling, and the whole list joined by commas is every rewrite off and the bound
139/// plan running as the binder produced it. `spec/09-optimizer.md` section 9.1 makes that a gate
140/// rather than a curiosity, because the unoptimized answer is the right answer by construction and
141/// any query that answers differently with the passes on is a pass that changed an answer. The
142/// corpus in `tamnd/rudb-compat` runs both ways and compares, and it needs the names to do it.
143///
144/// DuckDB spells the same question `SELECT name FROM duckdb_optimizers()`, which is a table
145/// function rudb does not have yet. When it arrives it reads this.
146///
147/// ```
148/// use rudb::Database;
149///
150/// let db = Database::new();
151/// db.execute(&format!("SET disabled_optimizers = '{}'", rudb::optimizers().join(",")))?;
152/// // The bound plan, with nothing folded, so the addition is still a call.
153/// assert!(db.plan("SELECT 1 + 2")?.contains("\"+\""));
154/// # Ok::<(), rudb::Error>(())
155/// ```
156#[must_use]
157pub fn optimizers() -> Vec<&'static str> {
158 rudb_opt::PASSES.iter().map(|pass| pass.name()).collect()
159}
160
161/// The seams, which are the parts of the engine there is more than one published way to build.
162///
163/// A module rather than a flat re-export, because `Settings` here is which implementation runs at
164/// each seam and `Settings` in `rudb-cli` is how a result is printed, and a name that has to be
165/// read in context is a name worth qualifying. [`Database::seams`] is what hands one back.
166pub mod seam {
167 pub use rudb_seam::{
168 ChoiceReason, Determinism, Policy, PolicyMode, Provenance, SEAM_PREFIX, SeamId, Settings,
169 };
170}
171
172/// What one execution reported about itself, which is what [`QueryResult::metrics`] hands back.
173///
174/// A module rather than a flat re-export, for the same reason the seams are one: `Operator` here is
175/// a row of measurements and `Operator` in the executor is a thing that runs, and the two want
176/// telling apart at a call site. The shell writes [`metrics::Document::render`] out under
177/// `--metrics`, which is the file `rudb-bench` reads.
178pub mod metrics {
179 pub use rudb_metrics::{Document, Operator, Pipeline};
180}
181
182/// Arrow interchange, which is what [`QueryResult::to_arrow`] hands back.
183///
184/// A module rather than a flat re-export because Arrow has a `Field` and a `Schema` of its own and
185/// so do we, and two types called `Field` in one namespace is a worse trade than four extra
186/// characters at the call site.
187pub mod arrow {
188 pub use rudb_arrow::{Array, DataType, Field, RecordBatch, Schema, TimeUnit};
189}