1use super::BindingContext;
10use crate::{
11 plan::{CommandPlan, UnifiedPlan},
12 routines::RoutineResolution,
13 RowSchema, SQLError, SQLParam, ScalarExpr,
14};
15use uqa_core::Value;
16
17pub trait StatementBindingScope {
19 fn binding_context(&self) -> Result<BindingContext<'_>, SQLError>;
20}
21pub type StatementAnalysisOperation<'a> =
22 &'a mut dyn FnMut(&dyn StatementBindingScope) -> Result<(), SQLError>;
23
24pub trait StatementAnalysisScopes {
26 fn with_scope(&self, analyze: StatementAnalysisOperation<'_>) -> Result<(), SQLError>;
27}
28pub struct StatementAnalysisContext<'a> {
29 pub scopes: &'a dyn StatementAnalysisScopes,
30 pub routines: &'a dyn RoutineResolution,
31 pub aliases: &'a dyn crate::schema::dependencies::oid_alias::OidAliasInput,
32}
33
34#[derive(Debug, Clone, PartialEq)]
36pub enum AnalyzedResult {
37 Rows(Vec<Option<crate::ColumnType>>),
38 Schema(RowSchema),
40 Command,
41}
42
43impl AnalyzedResult {
44 pub fn column_types(&self) -> Option<&[Option<crate::ColumnType>]> {
45 match self {
46 Self::Rows(types) => Some(types),
47 Self::Schema(schema) => Some(schema.column_types()),
48 Self::Command => None,
49 }
50 }
51
52 pub fn record_fields(&self, column: usize) -> Option<&crate::schema::RecordFields> {
53 match self {
54 Self::Schema(schema) => schema.record_fields(column),
55 _ => None,
56 }
57 }
58}
59
60impl StatementBindingScope for super::snapshot::BindingSnapshot {
61 fn binding_context(&self) -> Result<BindingContext<'_>, SQLError> {
62 Ok(self.context())
63 }
64}
65
66pub fn analyze_executable_plan(
67 context: &StatementAnalysisContext<'_>,
68 plan: &mut UnifiedPlan,
69 params: &[SQLParam],
70) -> Result<AnalyzedResult, SQLError> {
71 analyze_executable_plan_for_cache(context, plan, params).map(|(result, _)| result)
72}
73
74mod procedural;
75pub use procedural::{analyze_procedural_plan, ProceduralPlanAnalysis};
76
77pub fn analyze_executable_plan_for_cache(
80 context: &StatementAnalysisContext<'_>,
81 plan: &mut UnifiedPlan,
82 params: &[SQLParam],
83) -> Result<(AnalyzedResult, bool), SQLError> {
84 let mut result = None;
85 let mut reusable = params.is_empty();
86 context.scopes.with_scope(&mut |scope| {
87 result = Some(match plan {
88 UnifiedPlan::Command(command) => match command.as_mut() {
89 CommandPlan::Explain { body, .. } => {
91 let (_, body_reusable) =
92 analyze_executable_plan_for_cache(context, body, params)?;
93 reusable &= body_reusable;
94 AnalyzedResult::Command
95 }
96 _ => analyze_plan_result_inner(
97 context.routines,
98 context.aliases,
99 plan,
100 params,
101 scope,
102 &mut reusable,
103 )?,
104 },
105 UnifiedPlan::Query(_) => analyze_plan_result_inner(
106 context.routines,
107 context.aliases,
108 plan,
109 params,
110 scope,
111 &mut reusable,
112 )?,
113 });
114 Ok(())
115 })?;
116 result
117 .map(|result| (result, reusable))
118 .ok_or_else(|| SQLError::Internal("statement analysis scope did not run".into()))
119}
120
121pub fn analyze_plan_result(
124 routines: &dyn RoutineResolution,
125 aliases: &dyn crate::schema::dependencies::oid_alias::OidAliasInput,
126 plan: &mut UnifiedPlan,
127 params: &[SQLParam],
128 scope: &dyn StatementBindingScope,
129) -> Result<AnalyzedResult, SQLError> {
130 analyze_plan_result_inner(routines, aliases, plan, params, scope, &mut true)
131}
132
133fn analyze_plan_result_inner(
134 routines: &dyn RoutineResolution,
135 aliases: &dyn crate::schema::dependencies::oid_alias::OidAliasInput,
136 plan: &mut UnifiedPlan,
137 params: &[SQLParam],
138 scope: &dyn StatementBindingScope,
139 reusable: &mut bool,
140) -> Result<AnalyzedResult, SQLError> {
141 let binding = scope.binding_context()?;
142 *reusable &=
143 super::preparation::read_executable_inputs(routines, plan, params, &binding, aliases)?;
144 if let UnifiedPlan::Command(command) = plan {
145 if let CommandPlan::Explain { body, .. } = command.as_mut() {
146 analyze_plan_result_inner(routines, aliases, body, params, scope, reusable)?;
147 return Ok(AnalyzedResult::Command);
148 }
149 }
150 analyze_bound_result(routines, plan, params, &binding)
151}
152
153fn analyze_bound_result(
154 routines: &dyn RoutineResolution,
155 plan: &UnifiedPlan,
156 params: &[SQLParam],
157 binding: &BindingContext<'_>,
158) -> Result<AnalyzedResult, SQLError> {
159 let rows = AnalyzedResult::Schema;
160 match plan {
161 UnifiedPlan::Query(query) => {
162 super::analyze_query_plan_schema(routines, query, params, binding, None).map(rows)
163 }
164 UnifiedPlan::Command(command) => match command.as_ref() {
165 CommandPlan::CreateView { query, .. }
166 | CommandPlan::CreateTableAs { query, .. }
167 | CommandPlan::CreateMaterializedView { query, .. }
168 | CommandPlan::DeclareCursor { query, .. } => {
169 super::analyze_query_plan_schema(routines, query, params, binding, None)?;
170 Ok(AnalyzedResult::Command)
171 }
172 _ => Ok(
173 super::analyze_prepared_command_schema(routines, command, params, binding)?
174 .map_or(AnalyzedResult::Command, rows),
175 ),
176 },
177 }
178}
179
180pub fn analyze_command_parameters(
181 routines: &dyn RoutineResolution,
182 command: &CommandPlan,
183 params: &[SQLParam],
184 scope: &dyn StatementBindingScope,
185) -> Result<(), SQLError> {
186 let declared = parameter_input_types(params)?;
187 super::infer_prepared_parameter_types(
188 routines,
189 &UnifiedPlan::Command(Box::new(command.clone())),
190 &declared,
191 &scope.binding_context()?,
192 )?;
193 Ok(())
194}
195
196pub(super) fn parameter_input_types(
197 params: &[SQLParam],
198) -> Result<Vec<Option<crate::ColumnType>>, SQLError> {
199 let schema = RowSchema::default();
200 (1..=params.len())
201 .map(|index| match ¶ms[index - 1] {
202 SQLParam::Scalar(Value::Str(_) | Value::Null) => Ok(None),
203 _ => crate::scalar_type(&ScalarExpr::Param(index), &schema, params),
204 })
205 .collect()
206}
207
208#[cfg(test)]
209mod tests;