Skip to main content

nodedb_sql/
lib.rs

1//! nodedb-sql: SQL parser, planner, and optimizer for NodeDB.
2//!
3//! Parses SQL via sqlparser-rs, resolves against a catalog, and produces
4//! `SqlPlan` — an intermediate representation that both Origin (server)
5//! and Lite (embedded) map to their own execution model.
6//!
7//! ```text
8//! SQL → parse → resolve → plan → optimize → SqlPlan
9//! ```
10
11pub mod catalog;
12pub mod engine_rules;
13pub mod error;
14pub mod functions;
15pub mod optimizer;
16pub mod params;
17pub mod parser;
18pub mod planner;
19pub mod resolver;
20pub mod types;
21
22pub use catalog::{SqlCatalog, SqlCatalogError};
23pub use error::{Result, SqlError};
24pub use params::ParamValue;
25pub use types::*;
26
27use functions::registry::FunctionRegistry;
28use parser::preprocess;
29use parser::statement::{StatementKind, classify, parse_sql};
30
31/// Plan one or more SQL statements against the given catalog.
32///
33/// Handles NodeDB-specific syntax (UPSERT, `{ }` object literals) via
34/// pre-processing before handing to sqlparser.
35pub fn plan_sql(sql: &str, catalog: &dyn SqlCatalog) -> Result<Vec<SqlPlan>> {
36    let preprocessed = preprocess::preprocess(sql);
37    let effective_sql = preprocessed.as_ref().map_or(sql, |p| p.sql.as_str());
38    let is_upsert = preprocessed.as_ref().is_some_and(|p| p.is_upsert);
39
40    let statements = parse_sql(effective_sql)?;
41    plan_statements(&statements, is_upsert, catalog)
42}
43
44/// Plan SQL with bound parameters (prepared statement execution).
45///
46/// Parses the SQL (which may contain `$1`, `$2`, ... placeholders), substitutes
47/// placeholder AST nodes with concrete literal values from `params`, then plans
48/// normally. This avoids SQL text substitution entirely — parameters are bound
49/// at the AST level, not the string level.
50pub fn plan_sql_with_params(
51    sql: &str,
52    params: &[ParamValue],
53    catalog: &dyn SqlCatalog,
54) -> Result<Vec<SqlPlan>> {
55    let preprocessed = preprocess::preprocess(sql);
56    let effective_sql = preprocessed.as_ref().map_or(sql, |p| p.sql.as_str());
57    let is_upsert = preprocessed.as_ref().is_some_and(|p| p.is_upsert);
58
59    let mut statements = parse_sql(effective_sql)?;
60    for stmt in &mut statements {
61        params::bind_params(stmt, params);
62    }
63    plan_statements(&statements, is_upsert, catalog)
64}
65
66/// Plan a list of parsed statements.
67fn plan_statements(
68    statements: &[sqlparser::ast::Statement],
69    is_upsert: bool,
70    catalog: &dyn SqlCatalog,
71) -> Result<Vec<SqlPlan>> {
72    let functions = FunctionRegistry::new();
73    let mut plans = Vec::new();
74
75    for stmt in statements {
76        match classify(stmt) {
77            StatementKind::Select(query) => {
78                let plan = planner::select::plan_query(query, catalog, &functions)?;
79                let plan = optimizer::optimize(plan);
80                plans.push(plan);
81            }
82            StatementKind::Insert(ins) => {
83                let mut dml_plans = if is_upsert {
84                    planner::dml::plan_upsert(ins, catalog)?
85                } else {
86                    planner::dml::plan_insert(ins, catalog)?
87                };
88                plans.append(&mut dml_plans);
89            }
90            StatementKind::Update(stmt) => {
91                let mut update_plans = planner::dml::plan_update(stmt, catalog)?;
92                plans.append(&mut update_plans);
93            }
94            StatementKind::Delete(stmt) => {
95                let mut delete_plans = planner::dml::plan_delete(stmt, catalog)?;
96                plans.append(&mut delete_plans);
97            }
98            StatementKind::Truncate(stmt) => {
99                let mut trunc_plans = planner::dml::plan_truncate_stmt(stmt)?;
100                plans.append(&mut trunc_plans);
101            }
102            StatementKind::Other => {
103                return Err(SqlError::Unsupported {
104                    detail: format!("statement type: {stmt}"),
105                });
106            }
107        }
108    }
109
110    Ok(plans)
111}