use rudb_catalog::{Catalog, Entry, QualifiedName, duplicate_check, same_name};
use rudb_common::bounds::End;
use rudb_common::{
Bound as ColumnBound, Clustering, Error, Field, LogicalType, Result, Session, Stat, Value,
Width,
};
use rudb_parse::ast::{self, Ast};
use rudb_parse::{NONE, deparse, parse_ast};
use rudb_plan::{Expr, ExprRef, Node, Plan, SortKey};
use crate::binder::Binder;
use crate::parameters::Parameters;
#[derive(Debug)]
pub enum Bound {
Query(Plan),
CreateTable(CreateTable),
CreateView(CreateView),
DropTable(DropTable),
Insert(Insert),
Setting(Setting),
Checkpoint,
Explain { plan: Plan, analyze: bool, statistics: bool },
}
#[derive(Debug)]
pub struct Setting {
pub name: String,
pub scope: ast::Scope,
pub value: Option<Value>,
pub pragma: bool,
}
#[derive(Debug)]
pub struct CreateTable {
pub name: QualifiedName,
pub columns: Vec<Field>,
pub source: Option<Plan>,
pub if_not_exists: bool,
pub or_replace: bool,
}
#[derive(Debug)]
pub struct CreateView {
pub name: QualifiedName,
pub sql: String,
pub statement: String,
pub aliases: Vec<String>,
pub if_not_exists: bool,
pub or_replace: bool,
pub columns: Vec<Field>,
}
#[derive(Debug)]
pub struct DropTable {
pub names: Vec<QualifiedName>,
pub kind: Entry,
}
#[derive(Debug)]
pub struct Insert {
pub name: QualifiedName,
pub source: Plan,
}
pub fn bind_statement(ast: &Ast, catalog: &Catalog) -> Result<Bound> {
bind_statement_with(ast, catalog, &Parameters::new(), &Session::new())
}
pub fn bind_statement_with(
ast: &Ast,
catalog: &Catalog,
parameters: &Parameters,
session: &Session,
) -> Result<Bound> {
bind_one(ast, catalog, parameters, session, false)
}
pub fn bind_statement_outlined(
ast: &Ast,
catalog: &Catalog,
parameters: &Parameters,
session: &Session,
) -> Result<Bound> {
bind_one(ast, catalog, parameters, session, true)
}
fn bind_one(
ast: &Ast,
catalog: &Catalog,
parameters: &Parameters,
session: &Session,
outlined: bool,
) -> Result<Bound> {
let statement = match ast.statements.as_slice() {
[statement] => *statement,
[] => return Err(Error::binder("no statement to bind")),
_ => return Err(Error::not_implemented("a script of more than one statement")),
};
match statement {
ast::Statement::Query(query) => {
let mut binder = Binder::with(catalog, parameters, session);
binder.outlined = outlined;
let (root, _) = binder.bind_query(ast, query)?;
Ok(Bound::Query(finish(binder, root)?))
}
ast::Statement::CreateTable(index) => {
create_table(ast, catalog, parameters, session, index)
}
ast::Statement::CreateView(index) => create_view(ast, catalog, parameters, session, index),
ast::Statement::DropTable(index) => drop_table(ast, catalog, index),
ast::Statement::Insert(index) => insert(ast, catalog, parameters, session, index),
ast::Statement::Set(index) | ast::Statement::Reset(index) => {
setting(ast, catalog, parameters, session, index)
}
ast::Statement::Checkpoint => Ok(Bound::Checkpoint),
ast::Statement::Explain { query, analyze, statistics } => {
let mut binder = Binder::with(catalog, parameters, session);
let (root, _) = binder.bind_query(ast, query)?;
Ok(Bound::Explain { plan: finish(binder, root)?, analyze, statistics })
}
}
}
pub fn bind_statement_sql(sql: &str, catalog: &Catalog) -> Result<Bound> {
let ast = parse_ast(sql)?;
bind_statement(&ast, catalog)
}
fn finish(binder: Binder<'_>, root: rudb_plan::NodeRef) -> Result<Plan> {
let mut plan = binder.into_plan();
plan.set_root(root);
plan.validate()?;
Ok(plan)
}
fn create_table(
ast: &Ast,
catalog: &Catalog,
parameters: &Parameters,
session: &Session,
index: ast::CreateTableRef,
) -> Result<Bound> {
let written = ast.create_table(index);
let parts: Vec<&str> = ast.name(written.name).collect();
let name = if written.temporary {
catalog.resolve_for_create_temporary(&parts)?
} else {
catalog.resolve_for_create(&parts)?
};
let defs = ast.column_defs(written.columns);
let (columns, source) = if written.query == NONE {
let mut columns = Vec::with_capacity(defs.len());
for def in defs {
let text = ast.string(def.ty);
if text.is_empty() {
return Err(Error::binder(format!(
"Column \"{}\" was declared without a type",
ast.string(def.name)
)));
}
let ty = LogicalType::parse(text)?;
let column = ast.string(def.name);
columns.push(if def.not_null {
Field::required(column, ty)
} else {
Field::new(column, ty)
});
}
(columns, None)
} else {
let mut binder = Binder::with(catalog, parameters, session);
let (root, scope) = binder.bind_query(ast, written.query)?;
if defs.len() > scope.len() {
return Err(Error::binder("Target table has more colum names than query result."));
}
let mut columns = Vec::with_capacity(scope.len());
for (at, column) in scope.columns.iter().enumerate() {
let named = match defs.get(at) {
Some(def) => ast.string(def.name).to_string(),
None => column.name.clone(),
};
columns.push(Field::new(named, column.ty.clone()));
}
if defs.is_empty() {
deduplicate(&mut columns);
}
(columns, Some(finish(binder, root)?))
};
duplicate_check(&columns)?;
Ok(Bound::CreateTable(CreateTable {
name,
columns,
source,
if_not_exists: written.if_not_exists,
or_replace: written.or_replace,
}))
}
fn deduplicate(columns: &mut [Field]) {
for at in 0..columns.len() {
let taken = |name: &str, upto: usize, columns: &[Field]| {
columns[..upto].iter().any(|held| same_name(&held.name, name))
};
if !taken(&columns[at].name, at, columns) {
continue;
}
let mut suffix = 1;
let mut candidate = format!("{}_{suffix}", columns[at].name);
while taken(&candidate, at, columns) {
suffix += 1;
candidate = format!("{}_{suffix}", columns[at].name);
}
columns[at].name = candidate;
}
}
fn create_view(
ast: &Ast,
catalog: &Catalog,
parameters: &Parameters,
session: &Session,
index: ast::CreateViewRef,
) -> Result<Bound> {
let written = ast.create_view(index);
let parts: Vec<&str> = ast.name(written.name).collect();
let name = if written.temporary {
catalog.resolve_for_create_temporary(&parts)?
} else {
catalog.resolve_for_create(&parts)?
};
let aliases: Vec<String> = ast.name(written.columns).map(str::to_string).collect();
let mut binder = Binder::with(catalog, parameters, session);
binder.outlined = true;
let (_, mut scope) = binder.bind_query(ast, written.query)?;
if aliases.len() > scope.len() {
return Err(Error::binder("More VIEW aliases than columns in query result"));
}
if !aliases.is_empty() {
let written: Vec<&str> = aliases.iter().map(String::as_str).collect();
scope.rename(&written, "unnamed_subquery")?;
}
Ok(Bound::CreateView(CreateView {
name,
sql: ast.string(written.sql).to_string(),
statement: deparse::create_view(ast, index),
aliases,
if_not_exists: written.if_not_exists,
or_replace: written.or_replace,
columns: scope.fields(),
}))
}
fn drop_table(ast: &Ast, catalog: &Catalog, index: ast::DropTableRef) -> Result<Bound> {
let written = ast.drop_table(index);
let kind = if written.view { Entry::View } else { Entry::Table };
let mut names = Vec::new();
for &name in ast.name_list(written.names) {
let parts: Vec<&str> = ast.name(name).collect();
match catalog.resolve_as(&parts, kind) {
Ok(resolved) => names.push(resolved),
Err(error) if written.if_exists => drop(error),
Err(error) => return Err(error),
}
}
Ok(Bound::DropTable(DropTable { names, kind }))
}
fn setting(
ast: &Ast,
catalog: &Catalog,
parameters: &Parameters,
session: &Session,
index: ast::SettingRef,
) -> Result<Bound> {
let written = ast.setting(index);
let name = ast.string(written.name).to_string();
let value = if written.value == NONE {
None
} else {
let mut binder = Binder::with(catalog, parameters, session);
let bound = binder.bind_setting_value(ast, written.value)?;
let Expr::Constant(value) = *binder.plan().expr(bound) else {
return Err(Error::not_implemented(format!(
"a value for {name} that is not a constant"
)));
};
Some(binder.plan().value(value).clone())
};
Ok(Bound::Setting(Setting { name, scope: written.scope, value, pragma: written.pragma }))
}
fn clustered(
binder: &mut Binder<'_>,
input: rudb_plan::NodeRef,
scope: &crate::scope::Scope,
clustering: &Clustering,
targets: &[usize],
fields: &[Field],
) -> Result<rudb_plan::NodeRef> {
let mut keys: Vec<SortKey> = Vec::with_capacity(clustering.columns().len());
for (at, &column) in clustering.columns().iter().enumerate() {
let Some(from) = targets.iter().position(|&target| target == column as usize) else {
continue;
};
let source = &scope.columns[from];
let expr = binder.plan_mut().add_expr(Expr::Column(source.binding), source.ty.clone());
let expr = binder.checked_cast_to(expr, &fields[column as usize].ty, false)?;
let expr =
if at == 0 { bucketed(binder, expr, clustering.width(), fields, column) } else { expr };
keys.push(SortKey { expr, descending: false, nulls_first: false });
}
if keys.is_empty() {
return Ok(input);
}
let keys = binder.plan_mut().add_sort_keys(&keys);
Ok(binder.plan_mut().add_node(Node::Sort { input, keys }))
}
fn fitted(
binder: &Binder<'_>,
scope: &crate::scope::Scope,
clustering: &Clustering,
targets: &[usize],
) -> Clustering {
if clustering.width() != Width::Auto {
return clustering.clone();
}
let Some(from) = targets.iter().position(|&target| target == clustering.partition() as usize)
else {
return clustering.fitted(0, 0);
};
let source = &scope.columns[from];
let Some(zones) = binder.plan().sole_zones() else {
return clustering.fitted(0, 0);
};
let Some(at) = zones.column(&source.name) else {
return clustering.fitted(0, 0);
};
let rows = zones.surviving(&[]).unwrap_or(0);
let days = span(&zones.extreme(at, End::Low), &zones.extreme(at, End::High)).unwrap_or(0);
clustering.fitted(rows, days)
}
fn span(low: &Stat<ColumnBound>, high: &Stat<ColumnBound>) -> Option<u64> {
let (Stat::Known { value: low, .. }, Stat::Known { value: high, .. }) = (low, high) else {
return None;
};
let days = match (low, high) {
(ColumnBound::Int(low), ColumnBound::Int(high)) => high.checked_sub(*low)?,
(
ColumnBound::Scaled { unscaled: low, scale: at },
ColumnBound::Scaled { unscaled: high, scale: to },
) if at == to => {
let day = 86_400_i128.checked_mul(10_i128.checked_pow(u32::from(*at))?)?;
high.checked_sub(*low)? / day
}
_ => return None,
};
u64::try_from(days).ok()
}
fn bucketed(
binder: &mut Binder<'_>,
expr: ExprRef,
width: Width,
fields: &[Field],
column: u32,
) -> ExprRef {
if width == Width::Exact {
return expr;
}
let unit = binder.plan_mut().add_value(Value::Varchar(width.to_string().to_lowercase()));
let unit = binder.plan_mut().add_expr(Expr::Constant(unit), LogicalType::Varchar);
let args = binder.plan_mut().add_expr_list(&[unit, expr]);
let name = binder.plan_mut().intern("date_trunc");
let ty = fields[column as usize].ty.clone();
binder.plan_mut().add_expr(Expr::Function { name, args }, ty)
}
fn insert(
ast: &Ast,
catalog: &Catalog,
parameters: &Parameters,
session: &Session,
index: ast::InsertRef,
) -> Result<Bound> {
let written = ast.insert(index);
let parts: Vec<&str> = ast.name(written.name).collect();
let name = catalog.resolve(&parts)?;
if catalog.entry(&name)? == Entry::View {
return Err(Error::catalog(format!("{} is not an table", name.table)));
}
let target = catalog.table(&name)?;
let fields: Vec<Field> = target.columns().to_vec();
let clustering = target.clustering().cloned();
let targets: Vec<usize> = if written.columns.is_empty() {
(0..fields.len()).collect()
} else {
let mut targets = Vec::new();
for column in ast.name(written.columns) {
let at = fields.iter().position(|field| same_name(&field.name, column)).ok_or_else(
|| {
Error::binder(format!(
"Table \"{}\" does not have a column named \"{column}\"",
name.table
))
},
)?;
if targets.contains(&at) {
return Err(Error::binder(format!(
"Column \"{column}\" is named twice in the same INSERT"
)));
}
targets.push(at);
}
targets
};
let mut binder = Binder::with(catalog, parameters, session);
let (root, scope) = binder.bind_query(ast, written.source)?;
if scope.len() != targets.len() {
return Err(Error::binder(format!(
"Table \"{}\" has {} columns but {} values were supplied",
name.table,
targets.len(),
scope.len()
)));
}
let root = match &clustering {
None => root,
Some(clustering) => {
let fitted = fitted(&binder, &scope, clustering, &targets);
clustered(&mut binder, root, &scope, &fitted, &targets, &fields)?
}
};
let mut exprs: Vec<ExprRef> = Vec::with_capacity(fields.len());
let mut names = Vec::with_capacity(fields.len());
for (at, field) in fields.iter().enumerate() {
let expr = match targets.iter().position(|&target| target == at) {
Some(from) => {
let column = &scope.columns[from];
let expr =
binder.plan_mut().add_expr(Expr::Column(column.binding), column.ty.clone());
binder.checked_cast_to(expr, &field.ty, false)?
}
None => {
let value = binder.plan_mut().add_value(Value::Null);
binder.plan_mut().add_expr(Expr::Constant(value), field.ty.clone())
}
};
exprs.push(expr);
let interned = binder.plan_mut().intern(&field.name);
names.push(interned);
}
let exprs = binder.plan_mut().add_expr_list(&exprs);
let names = binder.plan_mut().add_name_list(&names);
let index = binder.fresh_index();
let root = binder.plan_mut().add_node(Node::Project { input: root, index, exprs, names });
Ok(Bound::Insert(Insert { name, source: finish(binder, root)? }))
}