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