1use crate::SQLError;
10use uqa_core::{
11 memory::{Produced, ProductionControl, ProductionVec},
12 Value,
13};
14
15use crate::ast::ColumnType;
16
17#[derive(Debug, Clone)]
19pub enum SQLParam {
20 Scalar(Value),
21 TypedScalar {
23 value: Value,
24 ty: ColumnType,
25 },
26 Vector(Vec<f32>),
27 Tensor(Vec<Vec<f32>>),
28}
29
30impl SQLParam {
31 pub fn scalar(value: Value) -> Self {
32 Self::Scalar(value)
33 }
34
35 #[must_use]
36 pub fn typed_scalar(value: Value, ty: ColumnType) -> Self {
37 Self::TypedScalar { value, ty }
38 }
39
40 #[must_use]
42 pub fn scalar_value(&self) -> Option<&Value> {
43 match self {
44 Self::Scalar(value) | Self::TypedScalar { value, .. } => Some(value),
45 Self::Vector(_) | Self::Tensor(_) => None,
46 }
47 }
48
49 #[must_use]
51 pub fn declared_scalar_type(&self) -> Option<&ColumnType> {
52 match self {
53 Self::TypedScalar { ty, .. } => Some(ty),
54 Self::Scalar(_) | Self::Vector(_) | Self::Tensor(_) => None,
55 }
56 }
57
58 pub fn to_value(&self) -> Result<Value, SQLError> {
60 self.to_value_with_control(&ProductionControl::uncontrolled())
61 .map(|value| value.into_uncontrolled().expect("ordinary parameter value"))
62 }
63
64 pub fn to_value_with_control(
66 &self,
67 control: &ProductionControl<'_>,
68 ) -> Result<Produced<Value>, SQLError> {
69 control.check()?;
70 match self {
71 Self::Scalar(value) | Self::TypedScalar { value, .. } => Ok(control.copy_value(value)?),
72 Self::Vector(values) => vector_value(values, control),
73 Self::Tensor(vectors) => {
74 let mut output = ProductionVec::new(*control);
75 output.reserve(vectors.len())?;
76 for values in vectors {
77 output.push_produced(vector_value(values, control)?)?;
78 }
79 let (values, memory) = output.finish()?.into_parts();
80 Ok(control.finish(Value::List(values), memory)?)
81 }
82 }
83 }
84
85 pub fn vector(v: Vec<f32>) -> Self {
86 Self::Vector(v)
87 }
88
89 pub fn tensor(v: Vec<Vec<f32>>) -> Self {
90 Self::Tensor(v)
91 }
92}
93
94fn vector_value(
95 values: &[f32],
96 control: &ProductionControl<'_>,
97) -> Result<Produced<Value>, SQLError> {
98 let mut output = ProductionVec::new(*control);
99 output.reserve(values.len())?;
100 for value in values {
101 output.push_produced(
102 control.finish(Value::Float(f64::from(*value)), control.empty_reservation())?,
103 )?;
104 }
105 let (values, memory) = output.finish()?.into_parts();
106 Ok(control.finish(Value::List(values), memory)?)
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 #[test]
114 fn typed_scalar_preserves_declared_type_without_changing_scalar_access() {
115 let value = Value::Int(7);
116 let typed = SQLParam::typed_scalar(value.clone(), ColumnType::SmallInteger);
117 assert_eq!(typed.scalar_value(), Some(&value));
118 assert_eq!(
119 typed.declared_scalar_type(),
120 Some(&ColumnType::SmallInteger)
121 );
122
123 let scalar = SQLParam::scalar(value.clone());
124 assert_eq!(scalar.scalar_value(), Some(&value));
125 assert_eq!(scalar.declared_scalar_type(), None);
126 }
127
128 #[test]
129 fn parameter_value_owners_keep_tensor_and_scalar_payloads_until_drop() {
130 use uqa_core::{memory::MemoryBudget, CancellationToken};
131 let budget = MemoryBudget::new(1 << 16);
132 let token = CancellationToken::new();
133 let control = ProductionControl::new(&budget, &token, &token);
134 for (parameter, expected) in [
135 (
136 SQLParam::typed_scalar(Value::Str("payload".repeat(32)), ColumnType::Text),
137 Value::Str("payload".repeat(32)),
138 ),
139 (
140 SQLParam::Vector(vec![1.0, 2.5]),
141 Value::List(vec![Value::Float(1.0), Value::Float(2.5)]),
142 ),
143 (
144 SQLParam::Tensor(vec![vec![1.0, 2.5], vec![]]),
145 Value::List(vec![
146 Value::List(vec![Value::Float(1.0), Value::Float(2.5)]),
147 Value::List(vec![]),
148 ]),
149 ),
150 ] {
151 let value = parameter.to_value_with_control(&control).unwrap();
152 assert_eq!(*value, expected);
153 assert!(value.reserved_bytes() > 0);
154 assert_eq!(budget.used(), value.reserved_bytes());
155 drop(value);
156 assert_eq!(budget.used(), 0);
157 assert_eq!(parameter.to_value().unwrap(), expected);
158 }
159 }
160
161 #[test]
162 fn parameter_production_releases_partial_output_on_quota_and_both_tokens() {
163 use uqa_core::{memory::MemoryBudget, CancellationToken};
164 let budget = MemoryBudget::new(256);
165 let original = CancellationToken::new();
166 let invoking = CancellationToken::new();
167 let control = ProductionControl::new(&budget, &original, &invoking);
168 let held = control.copy_text("held").unwrap();
169 let parameter = SQLParam::Tensor(vec![vec![1.0; 64], vec![2.0; 64]]);
170 assert_eq!(
171 parameter
172 .to_value_with_control(&control)
173 .unwrap_err()
174 .sqlstate(),
175 Some("53200")
176 );
177 assert_eq!(budget.used(), held.reserved_bytes());
178 for token in [&original, &invoking] {
179 token.cancel();
180 assert_eq!(
181 parameter
182 .to_value_with_control(&control)
183 .unwrap_err()
184 .sqlstate(),
185 Some("57014")
186 );
187 assert_eq!(budget.used(), held.reserved_bytes());
188 token.reset();
189 }
190 assert_eq!(
191 parameter.to_value().unwrap(),
192 Value::List(vec![
193 Value::List(vec![Value::Float(1.0); 64]),
194 Value::List(vec![Value::Float(2.0); 64])
195 ])
196 );
197 }
198}