use rudb_catalog::{Catalog, Entry, QualifiedName, duplicate_check, same_name};
use rudb_common::{Error, Field, LogicalType, Result, Value};
use rudb_parse::ast::{self, Ast};
use rudb_parse::{NONE, parse_ast};
use rudb_plan::{Expr, ExprRef, Node, Plan};
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),
}
#[derive(Debug)]
pub struct Setting {
pub name: String,
pub scope: ast::Scope,
pub value: Option<Value>,
}
#[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 aliases: Vec<String>,
pub if_not_exists: bool,
pub or_replace: bool,
}
#[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())
}
pub fn bind_statement_with(ast: &Ast, catalog: &Catalog, parameters: &Parameters) -> 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);
let (root, _) = binder.bind_query(ast, query)?;
Ok(Bound::Query(finish(binder, root)?))
}
ast::Statement::CreateTable(index) => create_table(ast, catalog, parameters, index),
ast::Statement::CreateView(index) => create_view(ast, catalog, parameters, index),
ast::Statement::DropTable(index) => drop_table(ast, catalog, index),
ast::Statement::Insert(index) => insert(ast, catalog, parameters, index),
ast::Statement::Set(index) | ast::Statement::Reset(index) => {
setting(ast, catalog, parameters, index)
}
}
}
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,
index: ast::CreateTableRef,
) -> Result<Bound> {
let written = ast.create_table(index);
if written.temporary {
return Err(Error::not_implemented("CREATE TEMPORARY TABLE"));
}
let parts: Vec<&str> = ast.name(written.name).collect();
let name = 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);
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,
index: ast::CreateViewRef,
) -> Result<Bound> {
let written = ast.create_view(index);
if written.temporary {
return Err(Error::not_implemented("CREATE TEMPORARY VIEW"));
}
let parts: Vec<&str> = ast.name(written.name).collect();
let name = 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);
let (_, scope) = binder.bind_query(ast, written.query)?;
if aliases.len() > scope.len() {
return Err(Error::binder("More VIEW aliases than columns in query result"));
}
Ok(Bound::CreateView(CreateView {
name,
sql: ast.string(written.sql).to_string(),
aliases,
if_not_exists: written.if_not_exists,
or_replace: written.or_replace,
}))
}
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,
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);
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 }))
}
fn insert(
ast: &Ast,
catalog: &Catalog,
parameters: &Parameters,
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 fields: Vec<Field> = catalog.table(&name)?.columns().to_vec();
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);
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 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.cast_to(expr, &field.ty)
}
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)? }))
}