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};
76mod cache;
77pub use cache::{analyze_for_statement_reuse, AnalyzedStatement};
78
79pub fn analyze_executable_plan_for_cache(
82 context: &StatementAnalysisContext<'_>,
83 plan: &mut UnifiedPlan,
84 params: &[SQLParam],
85) -> Result<(AnalyzedResult, bool), SQLError> {
86 analyze_reusable_inputs(context, plan, params)
87 .map(|(result, reusable)| (result, reusable && params.is_empty()))
88}
89
90fn analyze_reusable_inputs(
91 context: &StatementAnalysisContext<'_>,
92 plan: &mut UnifiedPlan,
93 params: &[SQLParam],
94) -> Result<(AnalyzedResult, bool), SQLError> {
95 let mut result = None;
96 let mut reusable = true;
97 context.scopes.with_scope(&mut |scope| {
98 result = Some(match plan {
99 UnifiedPlan::Command(command) => match command.as_mut() {
100 CommandPlan::Explain { body, .. } => {
102 let (_, body_reusable) = analyze_reusable_inputs(context, body, params)?;
103 reusable &= body_reusable;
104 AnalyzedResult::Command
105 }
106 _ => analyze_plan_result_inner(
107 context.routines,
108 context.aliases,
109 plan,
110 params,
111 scope,
112 &mut reusable,
113 )?,
114 },
115 UnifiedPlan::Query(_) => analyze_plan_result_inner(
116 context.routines,
117 context.aliases,
118 plan,
119 params,
120 scope,
121 &mut reusable,
122 )?,
123 });
124 Ok(())
125 })?;
126 result
127 .map(|result| (result, reusable))
128 .ok_or_else(|| SQLError::Internal("statement analysis scope did not run".into()))
129}
130
131pub fn analyze_plan_result(
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) -> Result<AnalyzedResult, SQLError> {
140 analyze_plan_result_inner(routines, aliases, plan, params, scope, &mut true)
141}
142
143fn analyze_plan_result_inner(
144 routines: &dyn RoutineResolution,
145 aliases: &dyn crate::schema::dependencies::oid_alias::OidAliasInput,
146 plan: &mut UnifiedPlan,
147 params: &[SQLParam],
148 scope: &dyn StatementBindingScope,
149 reusable: &mut bool,
150) -> Result<AnalyzedResult, SQLError> {
151 let binding = scope.binding_context()?;
152 *reusable &=
153 super::preparation::read_executable_inputs(routines, plan, params, &binding, aliases)?;
154 if let UnifiedPlan::Command(command) = plan {
155 if let CommandPlan::Explain { body, .. } = command.as_mut() {
156 analyze_plan_result_inner(routines, aliases, body, params, scope, reusable)?;
157 return Ok(AnalyzedResult::Command);
158 }
159 }
160 analyze_bound_result(routines, plan, params, &binding)
161}
162
163fn analyze_bound_result(
164 routines: &dyn RoutineResolution,
165 plan: &UnifiedPlan,
166 params: &[SQLParam],
167 binding: &BindingContext<'_>,
168) -> Result<AnalyzedResult, SQLError> {
169 let rows = AnalyzedResult::Schema;
170 match plan {
171 UnifiedPlan::Query(query) => {
172 super::analyze_query_plan_schema(routines, query, params, binding, None).map(rows)
173 }
174 UnifiedPlan::Command(command) => match command.as_ref() {
175 CommandPlan::CreateView { query, .. }
176 | CommandPlan::CreateTableAs { query, .. }
177 | CommandPlan::CreateMaterializedView { query, .. }
178 | CommandPlan::DeclareCursor { query, .. } => {
179 super::analyze_query_plan_schema(routines, query, params, binding, None)?;
180 Ok(AnalyzedResult::Command)
181 }
182 _ => Ok(
183 super::analyze_prepared_command_schema(routines, command, params, binding)?
184 .map_or(AnalyzedResult::Command, rows),
185 ),
186 },
187 }
188}
189
190pub fn analyze_command_parameters(
191 routines: &dyn RoutineResolution,
192 command: &CommandPlan,
193 params: &[SQLParam],
194 scope: &dyn StatementBindingScope,
195) -> Result<(), SQLError> {
196 let declared = parameter_input_types(params)?;
197 super::infer_prepared_parameter_types(
198 routines,
199 &UnifiedPlan::Command(Box::new(command.clone())),
200 &declared,
201 &scope.binding_context()?,
202 )?;
203 Ok(())
204}
205
206pub(super) fn parameter_input_types(
207 params: &[SQLParam],
208) -> Result<Vec<Option<crate::ColumnType>>, SQLError> {
209 let schema = RowSchema::default();
210 (1..=params.len())
211 .map(|index| match ¶ms[index - 1] {
212 SQLParam::Scalar(Value::Str(_) | Value::Null) => Ok(None),
213 _ => crate::scalar_type(&ScalarExpr::Param(index), &schema, params),
214 })
215 .collect()
216}
217
218#[cfg(test)]
219mod tests;