use crate::ast::{ColumnType, Expr};
use crate::{RowSchema, SQLError};
use uqa_core::Value;
use super::SchemaBindingContext;
pub fn validate_default_expression(
context: &SchemaBindingContext<'_, '_>,
expression: &mut Expr,
target: &ColumnType,
column: &str,
) -> Result<bool, SQLError> {
let plan = crate::plan::ExpressionPlan::lower(expression.clone());
if !plan.subqueries.is_empty() {
return Err(default_error(
"0A000",
"cannot use subquery in DEFAULT expression",
));
}
if crate::semantics::windows::expr_has_window(&plan.scalar) {
return Err(default_error(
"42P20",
"window functions are not allowed in DEFAULT expressions",
));
}
if crate::semantics::aggregates::contains_aggregate(context.catalog, &plan.scalar) {
return Err(default_error(
"42803",
"aggregate functions are not allowed in DEFAULT expressions",
));
}
if crate::semantics::aggregates::expr_references_columns(&plan.scalar) {
return Err(default_error(
"0A000",
"cannot use column reference in DEFAULT expression",
));
}
if crate::semantics::sets::validation::expression_may_return_set(
context.catalog,
context.catalog,
&plan.scalar,
&RowSchema::default(),
&[],
)? {
return Err(default_error(
"0A000",
"set-returning functions are not allowed in DEFAULT expressions",
));
}
crate::catalog::regrole_dependencies::reject_stored_regrole_constants(
context.catalog,
expression,
Some(target),
)?;
if !cook_constant(context, expression, target)? {
return Ok(false);
}
let original = crate::plan::ExpressionPlan::lower_with(expression.clone(), &|name: &str| {
context.catalog.has_registered_aggregate_function(name)
});
let mut bound = original.clone();
let source = crate::binding::analyze_default_inputs(
context.catalog,
context.catalog,
context.catalog,
&mut bound,
context.binding,
)?;
if let Some(source) = source {
check_assignable(&source, target, column, "default expression")?;
}
crate::binding::bind_expression_plan_routines_for_storage(
context.catalog,
&mut bound,
&[],
context.binding,
&RowSchema::default(),
)?;
let sites = crate::binding::syntax_sites::expression_syntax_sites(&original, &bound)?;
crate::catalog::stored_ast::bind_stored_expression_sites(expression, &sites)?;
Ok(true)
}
pub fn check_assignable(
source: &ColumnType,
target: &ColumnType,
column: &str,
expression: &str,
) -> Result<(), SQLError> {
if crate::type_resolution::assignment_type_compatible(source, target) {
return Ok(());
}
Err(SQLError::Diagnostic {
sqlstate: "42804".into(),
message: format!(
"column \"{column}\" is of type {} but {expression} is of type {}",
target.regtype_name(),
source.regtype_name()
),
detail: None,
hint: Some("You will need to rewrite or cast the expression.".into()),
})
}
pub fn cook_constant(
context: &SchemaBindingContext<'_, '_>,
expression: &mut Expr,
target: &ColumnType,
) -> Result<bool, SQLError> {
match expression {
Expr::Literal(Value::Null) => return Ok(false),
Expr::TypedLiteral {
value: Value::Null, ..
} => return Ok(false),
Expr::Literal(Value::Str(_)) => {
crate::catalog::stored_ast::read_unknown_stored_literal(
crate::FunctionTypeResolver::enum_labels(context.catalog),
crate::FunctionTypeResolver::catalog_input_functions(context.catalog),
expression,
target,
false,
)?;
}
Expr::Cast { expr, ty, .. }
if matches!(expr.as_ref(), Expr::Literal(Value::Str(_) | Value::Null)) =>
{
let cast_type = crate::expr::EngineHook::resolve_type_name(context.catalog, ty)
.map_err(SQLError::Internal)?
.map_or_else(|| ColumnType::from_sql_name(ty), Ok)?;
let domain_cast = matches!(cast_type, ColumnType::Domain { .. });
if matches!(expr.as_ref(), Expr::Literal(Value::Null)) && !domain_cast {
return Ok(false);
}
if crate::type_resolution::catalog_input_type(&cast_type) {
return Ok(true);
}
crate::catalog::stored_ast::read_unknown_stored_literal(
crate::FunctionTypeResolver::enum_labels(context.catalog),
crate::FunctionTypeResolver::catalog_input_functions(context.catalog),
expr,
&cast_type,
true,
)?;
if domain_cast {
return Ok(true);
}
let cooked = std::mem::replace(expr.as_mut(), Expr::Literal(Value::Null));
*expression = cooked;
}
_ => {}
}
Ok(true)
}
pub fn bind_stored_schema_expression(
context: &SchemaBindingContext<'_, '_>,
expression: &mut Expr,
typed_expression: Expr,
) -> Result<bool, SQLError> {
let lowered = crate::plan::ExpressionPlan::lower_with(typed_expression, &|name: &str| {
context.catalog.has_registered_aggregate_function(name)
});
let mut plan = lowered.clone();
crate::binding::analyze_stored_expression_inputs(
context.catalog,
&mut plan,
context.binding,
&RowSchema::default(),
)?;
crate::binding::bind_expression_plan_routines_for_storage(
context.catalog,
&mut plan,
&[],
context.binding,
&RowSchema::default(),
)?;
let sites = crate::binding::syntax_sites::expression_syntax_sites(&lowered, &plan)?;
let changed = crate::catalog::stored_ast::bind_stored_expression_sites(expression, &sites)?;
let before_inputs = expression.clone();
crate::schema::dependencies::oid_alias::read_oid_alias_constants(context.catalog, expression)?;
Ok(changed || *expression != before_inputs)
}
fn default_error(sqlstate: &str, message: &str) -> SQLError {
SQLError::Routine {
sqlstate: sqlstate.into(),
message: message.into(),
}
}