uqa_sql/prepared/entry/
reuse.rs1use super::PreparedStatementPlan;
10use crate::{
11 ast::FunctionBinding,
12 plan::{QueryPlan, RelationalPlan, SourcePlan, UnifiedPlan},
13 ColumnType, ScalarExpr,
14};
15use uqa_core::Value;
16
17impl PreparedStatementPlan {
18 #[must_use]
20 pub fn has_tracked_executable_dependencies(&self) -> bool {
21 if self.needs_analysis
22 || !self.dependencies.routines.is_empty()
23 || !self
24 .parameter_types
25 .iter()
26 .all(|ty| ty.as_ref().is_some_and(independent_type))
27 {
28 return false;
29 }
30 let Some(schema) = &self.result_schema else {
31 return false;
32 };
33 if !schema
34 .column_types()
35 .iter()
36 .all(|ty| ty.as_ref().is_some_and(independent_type))
37 || (0..schema.len()).any(|index| schema.record_fields(index).is_some())
38 {
39 return false;
40 }
41 let tracked_relations = !self.dependencies.relations.is_empty()
42 && self.dependency_snapshot.as_ref().is_some_and(|snapshot| {
43 snapshot.global_catalog.is_some()
44 && self
45 .dependencies
46 .relations
47 .iter()
48 .all(|oid| snapshot.relations.get(oid).is_some_and(Option::is_some))
49 });
50 if !self.dependencies.relations.is_empty() && !tracked_relations {
51 return false;
52 }
53 self.plan
54 .as_ref()
55 .is_some_and(|plan| independent_plan(plan, true, tracked_relations))
56 && independent_plan(&self.source_plan, false, tracked_relations)
57 && independent_plan(&self.logical_plan, false, tracked_relations)
58 }
59}
60
61fn independent_plan(plan: &UnifiedPlan, executable: bool, tracked_relations: bool) -> bool {
62 match plan {
63 UnifiedPlan::Query(query) => independent_query(query, executable, tracked_relations),
64 UnifiedPlan::Command(_) => false,
65 }
66}
67
68fn independent_query(query: &QueryPlan, executable: bool, tracked_relations: bool) -> bool {
69 if !query.ctes.is_empty() {
70 return false;
71 }
72 let admitted = |expression: &ScalarExpr| independent_expression(expression, executable);
73 match &query.root {
74 RelationalPlan::QueryBlock(block) => {
75 block.from.as_ref().is_none_or(|source| {
76 tracked_relations && matches!(source, SourcePlan::Table { .. })
77 }) && block.subqueries.is_empty()
78 && block.windows.is_empty()
79 && block.locking.is_empty()
80 && block
81 .expressions()
82 .iter()
83 .all(|expression| admitted(expression))
84 }
85 RelationalPlan::SetOp {
86 left,
87 right,
88 order_by,
89 limit,
90 offset,
91 subqueries,
92 ..
93 } => {
94 subqueries.is_empty()
95 && independent_query(left, executable, tracked_relations)
96 && independent_query(right, executable, tracked_relations)
97 && order_by
98 .iter()
99 .map(|order| &order.expr)
100 .chain(limit.as_deref())
101 .chain(offset.as_deref())
102 .all(admitted)
103 }
104 RelationalPlan::Values { rows, subqueries } => {
105 subqueries.is_empty() && rows.iter().flatten().all(admitted)
106 }
107 }
108}
109
110fn independent_expression(expression: &ScalarExpr, executable: bool) -> bool {
111 let mut independent = true;
112 expression.visit(&mut |part| {
113 independent &= match part {
114 ScalarExpr::Literal(value) => independent_value(value),
115 ScalarExpr::Cast { ty, .. } => independent_type_name(ty),
116 ScalarExpr::TypedLiteral {
117 value,
118 ty,
119 bound_type,
120 parameter_index,
121 } => {
122 parameter_index.is_none()
123 && independent_value(value)
124 && bound_type
125 .as_ref()
126 .map_or_else(|| independent_type_name(ty), independent_type)
127 }
128 ScalarExpr::Func {
129 binding,
130 args,
131 distinct,
132 order_by,
133 filter,
134 ..
135 } => {
136 !*distinct
137 && order_by.is_empty()
138 && filter.is_none()
139 && binding.as_ref().map_or(!executable, |binding| {
140 independent_binding(binding)
141 && (!executable
142 || (args.len() == binding.argument_types.len()
143 && crate::fixed_builtin_return_type(binding)
144 .is_some_and(|ty| independent_type(&ty))))
145 })
146 }
147 ScalarExpr::Column(_)
148 | ScalarExpr::QualifiedColumn { .. }
149 | ScalarExpr::Param(_)
150 | ScalarExpr::Array(_)
151 | ScalarExpr::Binary { .. }
152 | ScalarExpr::UnaryMinus(_)
153 | ScalarExpr::Not(_)
154 | ScalarExpr::And(_)
155 | ScalarExpr::Or(_)
156 | ScalarExpr::IsNull { .. }
157 | ScalarExpr::Between { .. }
158 | ScalarExpr::InList { .. }
159 | ScalarExpr::Case { .. } => true,
160 _ => false,
161 };
162 });
163 independent
164}
165
166fn independent_binding(binding: &FunctionBinding) -> bool {
167 binding.builtin
168 && binding.object_id.is_none()
169 && binding.dispatch.is_none()
170 && binding.invocation.is_none()
171 && binding.resolution_error.is_none()
172 && binding
173 .argument_types
174 .iter()
175 .all(|ty| independent_type_name(ty))
176}
177
178fn independent_value(value: &Value) -> bool {
179 match value {
180 Value::Null
181 | Value::Bool(_)
182 | Value::Int(_)
183 | Value::Float(_)
184 | Value::Str(_)
185 | Value::FixedChar(_)
186 | Value::Bytes(_)
187 | Value::Decimal(_)
188 | Value::Json(_)
189 | Value::JsonB(_) => true,
190 Value::Array(array) => array.elements().iter().all(independent_value),
191 _ => false,
192 }
193}
194
195fn independent_type_name(name: &str) -> bool {
196 ColumnType::from_sql_name(name).is_ok_and(|ty| independent_type(&ty))
197}
198
199fn independent_type(ty: &ColumnType) -> bool {
201 match ty {
202 ColumnType::Array(element) => independent_type(element),
203 ColumnType::SmallInteger
204 | ColumnType::Integer
205 | ColumnType::BigInteger
206 | ColumnType::Boolean
207 | ColumnType::Text
208 | ColumnType::Name
209 | ColumnType::Uuid
210 | ColumnType::Varchar(_)
211 | ColumnType::Bpchar
212 | ColumnType::Character(_)
213 | ColumnType::Real
214 | ColumnType::DoublePrecision
215 | ColumnType::Numeric { .. }
216 | ColumnType::Json
217 | ColumnType::JsonB
218 | ColumnType::Bytea
219 | ColumnType::InternalChar => true,
220 _ => false,
221 }
222}
223
224#[cfg(test)]
225mod tests;