use super::{
routines::apply_routine_reference, BTreeSet, Expr, SQLError, Statement, StoredAstVisitor,
};
use crate::ast::UserTypeIdentity;
use crate::binding::syntax_sites::{SyntaxSites, ValueSite};
use uqa_core::Value;
pub fn bind_stored_expression_sites(
expression: &mut Expr,
sites: &SyntaxSites,
) -> Result<bool, SQLError> {
apply_sites(sites, Syntax::Expression(expression))
}
pub fn bind_stored_statement_sites(
statement: &mut Statement,
sites: &SyntaxSites,
) -> Result<bool, SQLError> {
apply_sites(sites, Syntax::Statement(statement))
}
enum Syntax<'a> {
Expression(&'a mut Expr),
Statement(&'a mut Statement),
}
fn apply_sites(sites: &SyntaxSites, syntax: Syntax<'_>) -> Result<bool, SQLError> {
let mut routines = sites.routines.iter();
let mut values = sites.values.iter().peekable();
let mut routines_changed = false;
let mut values_changed = false;
let mut relation = |_: &mut String| -> Result<(), SQLError> { Ok(()) };
let mut routine = |name: &mut String,
binding: Option<&mut Option<crate::ast::FunctionBinding>>|
-> Result<(), SQLError> {
let reference = routines.next().ok_or_else(|| {
SQLError::Internal(format!(
"stored catalog routine binding has no entry for call `{name}`"
))
})?;
routines_changed |= apply_routine_reference(name, binding, reference)?;
Ok(())
};
let mut expression = |node: &mut Expr| -> Result<(), SQLError> {
values_changed |= apply_value_site(node, &mut values)?;
Ok(())
};
let mut visitor = StoredAstVisitor {
source: None,
merge: None,
expression: Some(&mut expression),
projection: None,
ty: None,
relation: &mut relation,
routine: &mut routine,
};
match syntax {
Syntax::Expression(expression) => visitor.bind_expr(expression, &BTreeSet::new())?,
Syntax::Statement(statement) => visitor.bind_statement(statement)?,
}
if let Some(reference) = routines.next() {
return Err(SQLError::Internal(format!(
"stored catalog routine binding entry `{}` has no matching call",
reference.name
)));
}
if values.next().is_some() {
return Err(SQLError::Internal(
"stored catalog binding has an expression site without matching syntax".into(),
));
}
Ok(routines_changed || values_changed)
}
fn apply_value_site<'a>(
node: &mut Expr,
sites: &mut std::iter::Peekable<impl Iterator<Item = &'a ValueSite>>,
) -> Result<bool, SQLError> {
let mismatch = |what: &str| {
SQLError::Internal(format!(
"stored catalog binding does not match the {what} of its syntax"
))
};
if let Some(ValueSite::CompositeRow(binding)) = sites.peek() {
let binding = (*binding).clone();
sites.next();
return bind_composite_constructor(node, binding);
}
if let Some(ValueSite::Membership { array_items }) = sites.peek() {
let shape = crate::type_resolution::membership::MembershipShape {
array_items: array_items.clone(),
};
sites.next();
let Expr::InList {
expr,
list,
negated,
} = std::mem::replace(node, Expr::Literal(Value::Null))
else {
return Err(mismatch("membership comparison"));
};
*node = crate::type_resolution::membership::rewrite(
expr,
list,
negated,
&shape,
&uqa_core::memory::ProductionControl::uncontrolled(),
)?
.into_uncontrolled()
.expect("ordinary stored syntax");
apply_value_site(node, sites)?;
return Ok(true);
}
if let Some(ValueSite::Relabel(ty)) = sites.peek() {
let ty = (*ty).clone();
sites.next();
let inner = std::mem::replace(node, Expr::Literal(Value::Null));
*node = Expr::Cast {
implicit: true,
expr: Box::new(inner),
ty,
};
return Ok(true);
}
match node {
Expr::Cast { ty, .. } => {
let Some(ValueSite::Cast(bound)) = sites.next() else {
return Err(mismatch("cast"));
};
if UserTypeIdentity::parse(bound).is_some() && ty != bound {
ty.clone_from(bound);
return Ok(true);
}
Ok(false)
}
Expr::Literal(Value::Str(_) | Value::Null) => match sites.next() {
Some(ValueSite::Literal) => Ok(false),
Some(ValueSite::Constant { value, ty }) => {
*node = Expr::TypedLiteral {
value: value.clone(),
ty: ty.clone(),
};
Ok(true)
}
_ => Err(mismatch("literal")),
},
Expr::Func { order_syntax, .. } => {
let Some(ValueSite::FunctionOrder(bound)) = sites.next() else {
return Err(mismatch("function ordering"));
};
if order_syntax == bound {
return Ok(false);
}
if !order_syntax.is_legacy() {
return Err(mismatch("function ordering"));
}
*order_syntax = *bound;
Ok(true)
}
_ => match sites.next() {
Some(ValueSite::Node) => Ok(false),
_ => Err(mismatch("expression")),
},
}
}
fn bind_composite_constructor(
node: &mut Expr,
binding: crate::ast::CompositeRowBinding,
) -> Result<bool, SQLError> {
if let Expr::CompositeRow {
binding: current, ..
} = node
{
let changed = *current != binding;
*current = binding;
return Ok(changed);
}
let original = std::mem::replace(node, Expr::Literal(Value::Null));
let row = match original {
Expr::Cast { expr, .. } => *expr,
row => row,
};
let Expr::Row(items) = row else {
return Err(SQLError::Internal(
"stored catalog binding does not match the composite constructor of its syntax".into(),
));
};
*node = Expr::CompositeRow { items, binding };
Ok(true)
}
#[cfg(test)]
mod tests;
pub fn fold_assigned_stored_literal(
expression: &mut Expr,
target: &crate::ast::ColumnType,
catalog: Option<&dyn crate::expr::enums::EnumLabelCatalog>,
) -> Result<bool, SQLError> {
let Expr::Literal(value @ (Value::Str(_) | Value::Null)) = expression else {
return Ok(false);
};
let mut base = target;
while let crate::ast::ColumnType::Domain { base: inner, .. } = base {
base = inner;
}
if !crate::expr::enums::is_enum_bearing(base) {
return Ok(false);
}
let Some(catalog) = catalog else {
return Ok(false);
};
let Some(value) = crate::expr::enums::fold_unknown_literal(Some(catalog), value, base)? else {
return Ok(false);
};
*expression = Expr::TypedLiteral {
value,
ty: base.catalog_name(),
};
Ok(true)
}