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 ..
223 } = node
224 {
225 if !values_changed {
226 return;
227 }
228 let result = bound_type
229 .clone()
230 .map_or_else(|| types.resolve_type_name(ty), |ty| Ok(Some(ty)))
231 .and_then(|ty| {
232 ty.map_or_else(
233 || Ok(value.clone()),
234 |ty| {
235 project_literal(
236 value,
237 &ty,
238 composite_source,
239 original,
240 types,
241 interpret_datums,
242 )
243 },
244 )
245 });
246 match result {
247 Ok(projected) => *value = projected,
248 Err(error) => failure = Some(error),
249 }
250 }
251 if let ScalarExpr::Func {
252 binding: Some(binding),
253 args,
254 ..
255 } = node
256 {
257 if let Some(field) = &mut binding.composite_field {
258 if let Some(descriptor) = current.get(&field.type_oid) {
259 let attribute = descriptor
260 .attributes
261 .iter()
262 .find(|attribute| attribute.number == field.number);
263 field.dropped = attribute.is_none();
264 field.changed_type = attribute
265 .filter(|attribute| {
266 crate::catalog::type_metadata::pg_type_oid(&attribute.ty)
267 != crate::catalog::type_metadata::pg_type_oid(&field.result_type)
268 })
269 .map(|attribute| attribute.ty.clone());
270 if let (Some(attribute), Some(ScalarExpr::Literal(Value::Str(name)))) =
271 (attribute, args.get_mut(1))
272 {
273 name.clone_from(&attribute.name);
274 }
275 }
276 }
277 }
278 });
279 failure.map_or(Ok(Some(projected)), Err)
280}
281
282fn project_literal(
283 value: &Value,
284 ty: &ColumnType,
285 source: &mut Option<Box<crate::expr::composites::CompositeConstantSource>>,
286 original: &Descriptors,
287 types: &dyn FunctionTypeResolver,
288 interpret_datums: bool,
289) -> Result<Value, SQLError> {
290 if !crate::expr::composites::literal::contains_records(value) {
291 return Ok(value.clone());
292 }
293 if source.is_none() {
294 *source = Some(Box::new(crate::expr::composites::CompositeConstantSource {
295 value: crate::expr::enums::retain_enum_oids(types.enum_labels(), value)?,
296 descriptors: original
297 .values()
298 .map(|value| value.as_ref().clone())
299 .collect(),
300 }));
301 }
302 let source = source.as_mut().expect("captured composite source");
303 source.retain_enum_oids(types.enum_labels())?;
304 let source_descriptors = source
305 .descriptors
306 .iter()
307 .map(|descriptor| (descriptor.type_oid, Arc::new(descriptor.clone())))
308 .collect::<Descriptors>();
309 let live = current_descriptors(&source_descriptors, types)?;
310 project_value(
311 &source.value,
312 ty,
313 &source_descriptors,
314 &live,
315 interpret_datums,
316 )
317}
318
319fn current_descriptors(
320 original: &Descriptors,
321 types: &dyn FunctionTypeResolver,
322) -> Result<Descriptors, SQLError> {
323 if original.is_empty() {
324 return Ok(Descriptors::new());
325 }
326 let catalog = types
327 .composite_types()
328 .ok_or_else(|| SQLError::Internal("prepared composite catalog unavailable".into()))?;
329 let mut current = Descriptors::new();
330 for oid in original.keys() {
331 if let Some(descriptor) = catalog.composite_type(*oid)? {
333 current.insert(*oid, descriptor);
334 }
335 }
336 Ok(current)
337}
338
339#[cfg(test)]
340fn project(
341 value: &Value,
342 ty: &ColumnType,
343 original: &Descriptors,
344 current: &Descriptors,
345) -> Result<Value, SQLError> {
346 project_value(value, ty, original, current, true)
347}
348
349#[cfg(test)]
350mod tests;