1use crate::{
10 ast::{ColumnType, CompositeRowBinding},
11 expr::EngineHook,
12 SQLError,
13};
14use uqa_core::{
15 memory::{Produced, ProductionControl, ProductionVec},
16 Value,
17};
18
19pub fn evaluate_with_control(
20 binding: &CompositeRowBinding,
21 arguments: usize,
22 engine: Option<&dyn EngineHook>,
23 control: &ProductionControl<'_>,
24 mut evaluate: impl FnMut(usize) -> Result<Produced<Value>, SQLError>,
25) -> Result<Produced<Value>, SQLError> {
26 control.check()?;
27 if binding.attributes.len() != arguments
28 || binding.attributes.iter().any(|number| *number <= 0)
29 || binding.attributes.windows(2).any(|pair| pair[0] >= pair[1])
30 {
31 return Err(SQLError::Internal(
32 "invalid stored composite constructor positions".into(),
33 ));
34 }
35 let engine =
36 engine.ok_or_else(|| SQLError::Internal("composite constructor has no catalog".into()))?;
37 let resolved = engine
38 .resolve_type_name(&binding.ty)
39 .map_err(SQLError::Internal)?
40 .map(|ty| ty.retain_external_with_control(control))
41 .transpose()?;
42 control.check()?;
43 let Some(ColumnType::Composite(reference)) = resolved.as_deref() else {
44 return Err(SQLError::Internal(
45 "composite constructor type disappeared".into(),
46 ));
47 };
48 let descriptor = super::descriptor(engine.composite_types(), reference.oid)?;
49 let mut fields = ProductionVec::new(*control);
50 fields.reserve(descriptor.attributes.len())?;
51 for attribute in &descriptor.attributes {
52 let value = match binding.attributes.binary_search(&attribute.number) {
53 Ok(index) => evaluate(index)?,
54 Err(_) => control.finish(Value::Null, control.empty_reservation())?,
55 };
56 let name = control.copy_text(&attribute.name)?;
57 let (value, value_memory) = value.into_parts();
58 let (name, name_memory) = name.into_parts();
59 fields.push_produced(
60 control.finish((name, value), control.combine(name_memory, value_memory))?,
61 )?;
62 }
63 let (fields, memory) = fields.finish()?.into_parts();
64 control
65 .finish(Value::Record(fields), memory)
66 .map_err(Into::into)
67}
68
69#[cfg(test)]
70mod tests {
71 use super::*;
72 use crate::ast::CompositeTypeReference;
73 use crate::expr::composites::{
74 CompositeAttribute, CompositeTypeCatalog, CompositeTypeDescriptor,
75 };
76 use std::sync::Arc;
77 use uqa_core::{memory::MemoryBudget, CancellationToken};
78
79 struct Catalog;
80 fn reference() -> ColumnType {
81 ColumnType::Composite(CompositeTypeReference {
82 schema: "public".into(),
83 name: "pair".into(),
84 oid: 20_001,
85 array_oid: 20_002,
86 relation_oid: 20_003,
87 })
88 }
89 impl CompositeTypeCatalog for Catalog {
90 fn composite_type(&self, _: u32) -> Result<Option<Arc<CompositeTypeDescriptor>>, SQLError> {
91 Ok(Some(Arc::new(CompositeTypeDescriptor {
92 type_oid: 20_001,
93 relation_oid: 20_003,
94 attributes: [1, 3, 4]
95 .into_iter()
96 .map(|number| CompositeAttribute {
97 name: format!("a{number}"),
98 ty: ColumnType::Integer,
99 number,
100 })
101 .collect(),
102 })))
103 }
104 }
105 impl EngineHook for Catalog {
106 fn nextval(&self, _: &str) -> Result<i64, SQLError> {
107 unreachable!()
108 }
109 fn currval(&self, _: &str) -> Result<i64, SQLError> {
110 unreachable!()
111 }
112 fn setval(&self, _: &str, _: i64, _: bool) -> Result<i64, SQLError> {
113 unreachable!()
114 }
115 fn resolve_type_name(&self, _: &str) -> Result<Option<ColumnType>, String> {
116 Ok(Some(reference()))
117 }
118 fn composite_types(&self) -> Option<&dyn CompositeTypeCatalog> {
119 Some(self)
120 }
121 }
122
123 #[test]
124 fn stored_positions_extend_nulls_without_evaluating_removed_fields() {
125 let budget = MemoryBudget::new(4096);
126 let cancellation = CancellationToken::new();
127 let control = ProductionControl::new(&budget, &cancellation, &cancellation);
128 let binding = CompositeRowBinding {
129 ty: "composite#20001".into(),
130 attributes: vec![1, 2, 3],
131 };
132 let mut evaluated = Vec::new();
133 let value = evaluate_with_control(&binding, 3, Some(&Catalog), &control, |index| {
134 evaluated.push(index);
135 control
136 .finish(Value::Int(index as i64), control.empty_reservation())
137 .map_err(Into::into)
138 })
139 .unwrap();
140 assert_eq!(evaluated, vec![0, 2]);
141 assert_eq!(
142 *value,
143 Value::Record(vec![
144 ("a1".into(), Value::Int(0)),
145 ("a3".into(), Value::Int(2)),
146 ("a4".into(), Value::Null)
147 ])
148 );
149 assert!(budget.used() > 0);
150 drop(value);
151 assert_eq!(budget.used(), 0);
152 }
153
154 #[test]
155 fn constructor_checks_cancellation_and_admission_before_evaluating_arguments() {
156 let budget = MemoryBudget::new(0);
157 let cancellation = CancellationToken::new();
158 let control = ProductionControl::new(&budget, &cancellation, &cancellation);
159 let binding = CompositeRowBinding {
160 ty: "composite#20001".into(),
161 attributes: vec![1],
162 };
163 let error =
164 evaluate_with_control(&binding, 1, Some(&Catalog), &control, |_| unreachable!())
165 .unwrap_err();
166 assert_eq!(error.sqlstate(), Some("53200"));
167 cancellation.cancel();
168 let error =
169 evaluate_with_control(&binding, 1, Some(&Catalog), &control, |_| unreachable!())
170 .unwrap_err();
171 assert_eq!(error.sqlstate(), Some("57014"));
172 }
173}