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::Cast { ty, .. } => independent_type_name(ty) || composites.tracks_name(ty),
143 ScalarExpr::TypedLiteral {
144 value,
145 ty,
146 bound_type,
147 parameter_index,
148 ..
149 } => {
150 parameter_index.is_none()
151 && ((bound_type
152 .as_ref()
153 .is_some_and(|ty| composites.tracks_type(ty))
154 || composites.tracks_name(ty))
155 || (independent_value(value)
156 && bound_type
157 .as_ref()
158 .map_or_else(|| independent_type_name(ty), independent_type)))
159 }
160 ScalarExpr::CompositeRow {
161 bound_type,
162 binding,
163 ..
164 } => {
165 bound_type
166 .as_ref()
167 .is_some_and(|ty| composites.tracks_type(ty))
168 || composites.tracks_name(&binding.ty)
169 }
170 ScalarExpr::Func {
171 binding,
172 args,
173 distinct,
174 order_by,
175 filter,
176 ..
177 } => {
178 !*distinct
179 && order_by.is_empty()
180 && filter.is_none()
181 && binding.as_ref().map_or(!executable, |binding| {
182 tracked_field(binding, composites)
183 || (independent_binding(binding)
184 && (!executable
185 || (args.len() == binding.argument_types.len()
186 && crate::fixed_builtin_return_type(binding)
187 .is_some_and(|ty| independent_type(&ty)))))
188 })
189 }
190 ScalarExpr::Column(_)
191 | ScalarExpr::QualifiedColumn { .. }
192 | ScalarExpr::Param(_)
193 | ScalarExpr::Array(_)
194 | ScalarExpr::Binary { .. }
195 | ScalarExpr::UnaryMinus(_)
196 | ScalarExpr::Not(_)
197 | ScalarExpr::And(_)
198 | ScalarExpr::Or(_)
199 | ScalarExpr::IsNull { .. }
200 | ScalarExpr::Between { .. }
201 | ScalarExpr::InList { .. }
202 | ScalarExpr::Case { .. } => true,
203 _ => false,
204 };
205 });
206 independent
207}
208
209fn tracked_field(binding: &FunctionBinding, composites: &CompositeInputs) -> bool {
210 binding.builtin
211 && binding.object_id.is_none()
212 && binding.dispatch == Some(crate::ast::FunctionDispatch::FieldSelect)
213 && binding.invocation.is_none()
214 && binding.resolution_error.is_none()
215 && binding.composite_field.as_ref().is_some_and(|field| {
216 composites.tracks_oid(field.type_oid)
217 && (independent_type(&field.result_type)
218 || composites.tracks_type(&field.result_type))
219 })
220}
221
222fn independent_binding(binding: &FunctionBinding) -> bool {
223 binding.builtin
224 && binding.object_id.is_none()
225 && binding.dispatch.is_none()
226 && binding.invocation.is_none()
227 && binding.resolution_error.is_none()
228 && binding
229 .argument_types
230 .iter()
231 .all(|ty| independent_type_name(ty))
232}
233
234fn independent_value(value: &Value) -> bool {
235 match value {
236 Value::Null
237 | Value::Bool(_)
238 | Value::Int(_)
239 | Value::Float(_)
240 | Value::Str(_)
241 | Value::FixedChar(_)
242 | Value::Bytes(_)
243 | Value::Decimal(_)
244 | Value::Json(_)
245 | Value::JsonB(_) => true,
246 Value::Array(array) => array.elements().iter().all(independent_value),
247 _ => false,
248 }
249}
250
251fn independent_type_name(name: &str) -> bool {
252 ColumnType::from_sql_name(name).is_ok_and(|ty| independent_type(&ty))
253}
254
255fn independent_type(ty: &ColumnType) -> bool {
257 match ty {
258 ColumnType::Array(element) => independent_type(element),
259 ColumnType::SmallInteger
260 | ColumnType::Integer
261 | ColumnType::BigInteger
262 | ColumnType::Boolean
263 | ColumnType::Text
264 | ColumnType::Name
265 | ColumnType::Uuid
266 | ColumnType::Varchar(_)
267 | ColumnType::Bpchar
268 | ColumnType::Character(_)
269 | ColumnType::Real
270 | ColumnType::DoublePrecision
271 | ColumnType::Numeric { .. }
272 | ColumnType::Json
273 | ColumnType::JsonB
274 | ColumnType::Bytea
275 | ColumnType::InternalChar => true,
276 _ => false,
277 }
278}
279
280#[cfg(test)]
281mod tests;