use super::{
BindingContext, ColumnType, OidAliasInput, ParameterTypes, Preparation, RoutineResolution,
RowSchema, SQLError, SchemaScope, UnifiedPlan,
};
pub(crate) fn analyze_routine_body_inputs(
routines: &dyn RoutineResolution,
plan: &UnifiedPlan,
params: &[crate::SQLParam],
binding: &BindingContext<'_>,
aliases: &dyn OidAliasInput,
parameters: Option<&crate::binding::RoutineParameterScope>,
) -> Result<crate::binding::statements::AnalyzedResult, SQLError> {
prepare_routine_body_inputs(
routines,
&mut plan.clone(),
params,
binding,
aliases,
parameters,
)
}
pub(crate) fn prepare_routine_body_inputs(
routines: &dyn RoutineResolution,
plan: &mut UnifiedPlan,
params: &[crate::SQLParam],
binding: &BindingContext<'_>,
aliases: &dyn OidAliasInput,
parameters: Option<&crate::binding::RoutineParameterScope>,
) -> Result<crate::binding::statements::AnalyzedResult, SQLError> {
let declared = params
.iter()
.map(|parameter| parameter.declared_scalar_type().cloned())
.collect::<Vec<_>>();
let mut analysis = Preparation {
routines,
scope: SchemaScope::for_analysis(binding)?,
parameters: ParameterTypes::with_input_constants(
&declared,
Some(aliases),
routines.enum_labels(),
routines.catalog_input_functions(),
),
schema_expression: None,
};
analysis.scope.routine_parameters = parameters.cloned();
let result = match plan {
UnifiedPlan::Query(query) => Some(analysis.query(
query,
parameters.map(crate::binding::RoutineParameterScope::schema),
)?),
UnifiedPlan::Command(command) => analysis.command(command)?,
};
let constants = analysis.parameters.take_input_constants();
analysis.parameters.finish()?;
constants.apply(plan)?;
plan.normalize_window_definitions()?;
Ok(result.map_or(
crate::binding::statements::AnalyzedResult::Command,
|schema| crate::binding::statements::AnalyzedResult::Schema(schema),
))
}
pub(crate) fn analyze_routine_body_argument(
routines: &dyn RoutineResolution,
expression: &crate::plan::ExpressionPlan,
params: &[crate::SQLParam],
binding: &BindingContext<'_>,
aliases: &dyn OidAliasInput,
target: Option<&ColumnType>,
) -> Result<Option<ColumnType>, SQLError> {
let declared = params
.iter()
.map(|parameter| parameter.declared_scalar_type().cloned())
.collect::<Vec<_>>();
let mut analysis = Preparation {
routines,
scope: SchemaScope::for_analysis(binding)?,
parameters: ParameterTypes::with_input_constants(
&declared,
Some(aliases),
routines.enum_labels(),
routines.catalog_input_functions(),
),
schema_expression: None,
};
let mut observed = analysis.expression(
&expression.scalar,
&RowSchema::default(),
&expression.subqueries,
)?;
if let Some(target) = target {
analysis.parameters.coerce_unknown(&mut observed, target)?;
}
analysis.parameters.finish()?;
Ok(observed.ty)
}
pub(crate) fn prepare_procedure_call(
context: &crate::binding::statements::StatementAnalysisContext<'_>,
overloads: &crate::routines::resolution::RoutineOverloadContext<'_>,
types: &dyn crate::routines::declaration::RoutineTypeCatalog,
plan: &mut UnifiedPlan,
params: &[crate::SQLParam],
binding: &BindingContext<'_>,
) -> Result<
(
crate::binding::statements::AnalyzedResult,
crate::prepared::dependencies::PreparedAnalysisDependencies,
),
SQLError,
> {
use crate::{
ir::{analyze_expression_call_arguments, ScalarExpr},
plan::CommandPlan,
routines::{call::ProcedureCallAnalysis, invocation::call_output_schema},
};
let UnifiedPlan::Command(command) = &*plan else {
unreachable!("CALL plan")
};
let CommandPlan::Call { name, args } = command.as_ref() else {
unreachable!("CALL command")
};
let declared = super::super::statements::parameter_input_types(params)?;
let mut scope = SchemaScope::for_analysis(binding)?;
scope.prepared_dependencies =
Some(crate::prepared::dependencies::PreparedAnalysisDependencies::default());
let mut analysis = Preparation {
routines: context.routines,
scope,
parameters: ParameterTypes::with_input_constants(
&declared,
Some(context.aliases),
context.routines.enum_labels(),
context.routines.catalog_input_functions(),
),
schema_expression: None,
};
let resolved = ProcedureCallAnalysis::new(args)?.resolve(name, overloads, &mut |argument| {
let scalar = crate::ir::scalar_call_argument(&argument.scalar)?.value;
analysis
.expression(scalar, &RowSchema::default(), &argument.subqueries)
.map(|expression| expression.ty)
})?;
let (decoded, _) = analyze_expression_call_arguments(args)?;
for (argument, target) in decoded.iter().zip(&resolved.invocation.argument_targets) {
if matches!(
argument.value,
ScalarExpr::Literal(uqa_core::Value::Str(_) | uqa_core::Value::Null)
) {
let target = types.resolve_catalog_column_type_name(target)?;
let mut expression = analysis.expression(argument.value, &RowSchema::default(), &[])?;
analysis
.parameters
.coerce_unknown(&mut expression, &target)?;
}
}
let schema = call_output_schema(
types,
&resolved.function.def,
&resolved.invocation.parameter_types,
)?;
let constants = analysis.parameters.take_input_constants();
analysis.parameters.finish()?;
constants.apply(plan)?;
let mut dependencies = analysis.scope.prepared_dependencies.unwrap_or_default();
if let Some(identity) = resolved.function.def.object_id {
dependencies.routines.insert(identity);
}
plan.visit_scalar_expressions(&mut |expression| dependencies.include_expression(expression));
Ok((
schema.map_or(
crate::binding::statements::AnalyzedResult::Command,
crate::binding::statements::AnalyzedResult::Schema,
),
dependencies,
))
}