1use crate::{
10 expr::composites::retained::{capture_descriptor, capture_type, project_value, Descriptors},
11 plan::UnifiedPlan,
12 type_resolution::FunctionTypeResolver,
13 ColumnType, SQLError, ScalarExpr,
14};
15use std::sync::Arc;
16use uqa_core::Value;
17
18#[derive(Clone, Debug, Default)]
20pub struct CompositeInputs {
21 original: Arc<Descriptors>,
22 planned: Option<Arc<Descriptors>>,
23 planned_scope: Option<Arc<Descriptors>>,
24}
25
26impl CompositeInputs {
27 pub(super) fn has_inputs(&self) -> bool {
28 !self.original.is_empty()
29 }
30
31 pub(super) fn tracks_oid(&self, oid: u32) -> bool {
32 self.original.contains_key(&oid)
33 }
34
35 pub(super) fn tracks_type(&self, ty: &ColumnType) -> bool {
36 match ty {
37 ColumnType::Array(element) => self.tracks_type(element),
38 ColumnType::Composite(reference) => self.tracks_oid(reference.oid),
39 _ => false,
40 }
41 }
42
43 pub(super) fn tracks_name(&self, name: &str) -> bool {
44 crate::ast::UserTypeIdentity::parse(name).is_some_and(|identity| {
45 identity.kind == crate::ast::UserTypeKind::Composite && self.tracks_oid(identity.oid)
46 })
47 }
48
49 pub fn capture(plan: &UnifiedPlan, types: &dyn FunctionTypeResolver) -> Result<Self, SQLError> {
50 let mut original = Descriptors::new();
51 let mut failure = None;
52 plan.visit_scalar_expressions(&mut |expression| {
53 expression.visit(&mut |node| {
54 if failure.is_some() {
55 return;
56 }
57 let result = match node {
58 ScalarExpr::TypedLiteral {
59 ty,
60 bound_type,
61 parameter_index: None,
62 ..
63 }
64 | ScalarExpr::CompositeRow {
65 binding: crate::ast::CompositeRowBinding { ty, .. },
66 bound_type,
67 ..
68 } => match bound_type {
69 Some(ty) => capture_type(ty, types.composite_types(), &mut original),
70 None => types.resolve_type_name(ty).and_then(|ty| {
71 ty.map_or(Ok(()), |ty| {
72 capture_type(&ty, types.composite_types(), &mut original)
73 })
74 }),
75 },
76 ScalarExpr::Func {
77 binding: Some(binding),
78 ..
79 } => binding.composite_field.as_ref().map_or(Ok(()), |field| {
80 capture_descriptor(field.type_oid, types.composite_types(), &mut original)?;
81 capture_type(&field.result_type, types.composite_types(), &mut original)
82 }),
83 _ => Ok(()),
84 };
85 if let Err(error) = result {
86 failure = Some(error);
87 }
88 });
89 });
90 failure.map_or(
91 Ok(Self {
92 original: Arc::new(original),
93 planned: None,
94 planned_scope: None,
95 }),
96 Err,
97 )
98 }
99
100 pub fn project_logical(
102 &self,
103 plan: &UnifiedPlan,
104 types: &dyn FunctionTypeResolver,
105 ) -> Result<Option<UnifiedPlan>, SQLError> {
106 project_plan(plan, types, &self.original, true, None)
107 }
108
109 pub fn project_for_generic(
111 &self,
112 plan: &UnifiedPlan,
113 types: &dyn FunctionTypeResolver,
114 ) -> Result<Option<UnifiedPlan>, SQLError> {
115 project_plan(plan, types, &self.original, false, None)
116 }
117
118 pub fn with_generic(
120 &self,
121 plan: &UnifiedPlan,
122 types: &dyn FunctionTypeResolver,
123 ) -> Result<Self, SQLError> {
124 let mut planned = Self::capture(plan, types)?.original.as_ref().clone();
125 for (oid, descriptor) in &mut planned {
126 let Some(original) = self.original.get(oid) else {
127 continue;
128 };
129 for attribute in &mut Arc::make_mut(descriptor).attributes {
130 if let Some(source) = original
131 .attributes
132 .iter()
133 .find(|source| source.number == attribute.number)
134 {
135 attribute.ty.clone_from(&source.ty);
136 }
137 }
138 }
139 Ok(Self {
140 original: self.original.clone(),
141 planned: Some(Arc::new(planned)),
142 planned_scope: Some(Arc::new(current_descriptors(&self.original, types)?)),
143 })
144 }
145
146 pub fn generic_requires_rebuild(
148 &self,
149 types: &dyn FunctionTypeResolver,
150 ) -> Result<bool, SQLError> {
151 let Some(planned) = &self.planned_scope else {
152 return Ok(false);
153 };
154 let current = current_descriptors(&self.original, types)?;
155 for (oid, original) in self.original.iter() {
156 let Some(at_plan) = planned.get(oid) else {
157 continue;
158 };
159 let Some(current) = current.get(oid) else {
160 continue;
161 };
162 if current.attributes.iter().any(|attribute| {
163 original
164 .attributes
165 .iter()
166 .any(|old| old.number == attribute.number)
167 && !at_plan
168 .attributes
169 .iter()
170 .any(|old| old.number == attribute.number)
171 }) {
172 return Ok(true);
173 }
174 }
175 Ok(false)
176 }
177
178 pub fn project_generic(
180 &self,
181 plan: &UnifiedPlan,
182 types: &dyn FunctionTypeResolver,
183 ) -> Result<Option<UnifiedPlan>, SQLError> {
184 self.planned.as_ref().map_or(Ok(None), |original| {
185 project_plan(plan, types, original, true, self.planned_scope.as_deref())
186 })
187 }
188}
189
190fn project_plan(
191 plan: &UnifiedPlan,
192 types: &dyn FunctionTypeResolver,
193 original: &Descriptors,
194 interpret_datums: bool,
195 binding_scope: Option<&Descriptors>,
196) -> Result<Option<UnifiedPlan>, SQLError> {
197 if original.is_empty() {
198 return Ok(None);
199 }
200 let current = current_descriptors(original, types)?;
201 let values_changed = *original != current;
202 let bindings_changed = binding_scope.is_some_and(|scope| {
203 current
204 .iter()
205 .any(|(oid, descriptor)| scope.get(oid) != Some(descriptor))
206 });
207 if !values_changed && !bindings_changed {
208 return Ok(None);
209 }
210 let mut projected = plan.clone();
211 let mut failure = None;
212 projected.rewrite_scalar_expressions(&mut |node| {
213 if failure.is_some() {
214 return;
215 }
216 if let ScalarExpr::TypedLiteral {
217 value,
218 ty,
219 parameter_index: None,
220 bound_type,
221 composite_source,
222 } = node
223 {
224 if !values_changed {
225 return;
226 }
227 let result = bound_type
228 .clone()
229 .map_or_else(|| types.resolve_type_name(ty), |ty| Ok(Some(ty)))
230 .and_then(|ty| {
231 ty.map_or_else(
232 || Ok(value.clone()),
233 |ty| {
234 project_literal(
235 value,
236 &ty,
237 composite_source,
238 original,
239 types,
240 interpret_datums,
241 )
242 },
243 )
244 });
245 match result {
246 Ok(projected) => *value = projected,
247 Err(error) => failure = Some(error),
248 }
249 }
250 if let ScalarExpr::Func {
251 binding: Some(binding),
252 args,
253 ..
254 } = node
255 {
256 if let Some(field) = &mut binding.composite_field {
257 if let Some(descriptor) = current.get(&field.type_oid) {
258 let attribute = descriptor
259 .attributes
260 .iter()
261 .find(|attribute| attribute.number == field.number);
262 field.dropped = attribute.is_none();
263 field.changed_type = attribute
264 .filter(|attribute| {
265 crate::catalog::type_metadata::pg_type_oid(&attribute.ty)
266 != crate::catalog::type_metadata::pg_type_oid(&field.result_type)
267 })
268 .map(|attribute| attribute.ty.clone());
269 if let (Some(attribute), Some(ScalarExpr::Literal(Value::Str(name)))) =
270 (attribute, args.get_mut(1))
271 {
272 name.clone_from(&attribute.name);
273 }
274 }
275 }
276 }
277 });
278 failure.map_or(Ok(Some(projected)), Err)
279}
280
281fn project_literal(
282 value: &Value,
283 ty: &ColumnType,
284 source: &mut Option<Box<crate::expr::composites::CompositeConstantSource>>,
285 original: &Descriptors,
286 types: &dyn FunctionTypeResolver,
287 interpret_datums: bool,
288) -> Result<Value, SQLError> {
289 if !crate::expr::composites::literal::contains_records(value) {
290 return Ok(value.clone());
291 }
292 if source.is_none() {
293 *source = Some(Box::new(crate::expr::composites::CompositeConstantSource {
294 value: crate::expr::enums::retain_enum_oids(types.enum_labels(), value)?,
295 descriptors: original
296 .values()
297 .map(|value| value.as_ref().clone())
298 .collect(),
299 }));
300 }
301 let source = source.as_mut().expect("captured composite source");
302 source.retain_enum_oids(types.enum_labels())?;
303 let source_descriptors = source
304 .descriptors
305 .iter()
306 .map(|descriptor| (descriptor.type_oid, Arc::new(descriptor.clone())))
307 .collect::<Descriptors>();
308 let live = current_descriptors(&source_descriptors, types)?;
309 project_value(
310 &source.value,
311 ty,
312 &source_descriptors,
313 &live,
314 interpret_datums,
315 )
316}
317
318fn current_descriptors(
319 original: &Descriptors,
320 types: &dyn FunctionTypeResolver,
321) -> Result<Descriptors, SQLError> {
322 if original.is_empty() {
323 return Ok(Descriptors::new());
324 }
325 let catalog = types
326 .composite_types()
327 .ok_or_else(|| SQLError::Internal("prepared composite catalog unavailable".into()))?;
328 let mut current = Descriptors::new();
329 for oid in original.keys() {
330 if let Some(descriptor) = catalog.composite_type(*oid)? {
332 current.insert(*oid, descriptor);
333 }
334 }
335 Ok(current)
336}
337
338#[cfg(test)]
339fn project(
340 value: &Value,
341 ty: &ColumnType,
342 original: &Descriptors,
343 current: &Descriptors,
344) -> Result<Value, SQLError> {
345 project_value(value, ty, original, current, true)
346}
347
348#[cfg(test)]
349mod tests;