1use super::{out_of_range, ArrayValue, DecimalValue, Result, SQLError, Value};
10
11use uqa_core::memory::{Produced, ProductionControl, ProductionString, ProductionVec};
12
13pub fn value_to_string(value: &Value) -> Result<String> {
15 value_to_string_with_control(value, &ProductionControl::uncontrolled())
16 .map(|text| text.into_uncontrolled().expect("ordinary value text"))
17}
18
19pub fn value_to_string_with_control(
20 value: &Value,
21 control: &ProductionControl<'_>,
22) -> Result<Produced<String>> {
23 control.check()?;
24 Ok(match value {
25 Value::Null | Value::Void => control.copy_text("")?,
26 Value::Int(value) => control.format(format_args!("{value}"))?,
27 Value::Float(value) => uqa_core::format_float_pg_with_control(*value, control)?,
28 Value::Decimal(value) => value.to_sql_string_with_control(control)?,
29 Value::Str(value) | Value::Json(value) | Value::JsonB(value) => control.copy_text(value)?,
30 Value::FixedChar(value) => control.copy_text(value.trim_end_matches(' '))?,
31 Value::Bool(value) => control.copy_text(if *value { "true" } else { "false" })?,
32 Value::Temporal(value) => value.to_sql_string_with_control(control)?,
33 Value::Array(value) => array_value_to_string_with_control(value, control)?,
34 Value::LegacyVector(_) => {
35 vector_value_to_string_with_control(value, control)?.expect("validated legacy vector")
36 }
37 Value::List(_) | Value::Map(_) => {
38 return super::json::format_value_as_json_with_control(value, control)
39 }
40 Value::Row(values) => composite_value_to_string(values.iter(), control)?,
41 Value::Record(fields) => {
42 composite_value_to_string(fields.iter().map(|(_, value)| value), control)?
43 }
44 Value::Bytes(values) => {
45 const HEX: &[u8; 16] = b"0123456789abcdef";
46 let mut text = ProductionString::new(*control);
47 text.push_str("\\x")?;
48 for byte in values {
49 text.push(char::from(HEX[usize::from(byte >> 4)]))?;
50 text.push(char::from(HEX[usize::from(byte & 0xf)]))?;
51 }
52 text.finish()?
53 }
54 })
55}
56
57pub fn vector_value_to_string(value: &Value) -> Result<Option<String>> {
59 vector_value_to_string_with_control(value, &ProductionControl::uncontrolled())
60 .map(|text| text.map(|text| text.into_uncontrolled().expect("ordinary vector text")))
61}
62
63pub(super) fn vector_value_to_string_with_control(
64 value: &Value,
65 control: &ProductionControl<'_>,
66) -> Result<Option<Produced<String>>> {
67 control.check()?;
68 let elements = match value {
69 Value::LegacyVector(vector) => {
70 if !vector.has_vector_layout() {
71 return Err(SQLError::Routine {
72 sqlstate: "42804".into(),
73 message: format!("array is not a valid {}", vector.kind().type_name()),
74 });
75 }
76 vector.elements()
77 }
78 Value::List(elements) => elements.as_slice(),
79 Value::Array(array) if array.dimensions().len() <= 1 => array.elements(),
80 _ => return Ok(None),
81 };
82 let mut text = ProductionString::new(*control);
83 for (index, value) in elements.iter().enumerate() {
84 if index != 0 {
85 text.push(' ')?;
86 }
87 text.push_str(&value_to_string_with_control(value, control)?)?;
88 }
89 Ok(Some(text.finish()?))
90}
91
92pub fn array_value_to_string(array: &ArrayValue) -> Result<String> {
93 array_value_to_string_with_control(array, &ProductionControl::uncontrolled())
94 .map(|text| text.into_uncontrolled().expect("ordinary array text"))
95}
96
97pub(super) fn array_value_to_string_with_control(
98 array: &ArrayValue,
99 control: &ProductionControl<'_>,
100) -> Result<Produced<String>> {
101 let mut text = ProductionString::new(*control);
102 append_array(&mut text, array, control)?;
103 Ok(text.finish()?)
104}
105
106fn append_array(
107 text: &mut ProductionString<'_>,
108 array: &ArrayValue,
109 control: &ProductionControl<'_>,
110) -> Result<()> {
111 if !array.elements().is_empty() && array.lower_bounds().iter().any(|lower| *lower != 1) {
112 for (lower, length) in array.lower_bounds().iter().zip(array.dimensions()) {
113 let upper = i64::from(*lower) + i64::try_from(*length).unwrap_or(i64::MAX) - 1;
114 text.push_str(&control.format(format_args!("[{lower}:{upper}]"))?)?;
115 }
116 text.push('=')?;
117 }
118 append_array_elements(text, array.elements(), control)
119}
120
121fn append_array_elements(
122 text: &mut ProductionString<'_>,
123 elements: &[Value],
124 control: &ProductionControl<'_>,
125) -> Result<()> {
126 text.push('{')?;
127 for (index, value) in elements.iter().enumerate() {
128 if index != 0 {
129 text.push(',')?;
130 }
131 match value {
132 Value::Null => text.push_str("NULL")?,
133 Value::Bool(value) => text.push_str(if *value { "t" } else { "f" })?,
134 Value::List(values) => append_array_elements(text, values, control)?,
135 Value::Array(array) => append_array(text, array, control)?,
136 other => {
137 let value = value_to_string_with_control(other, control)?;
138 let mut quoted = value.is_empty() || value.eq_ignore_ascii_case("null");
139 for character in value.chars() {
140 control.check()?;
141 quoted |= character.is_whitespace()
142 || matches!(character, ',' | '{' | '}' | '"' | '\\');
143 }
144 append_escaped(text, &value, quoted, false)?;
145 }
146 }
147 }
148 text.push('}')?;
149 Ok(())
150}
151
152fn composite_value_to_string<'a>(
153 values: impl IntoIterator<Item = &'a Value>,
154 control: &ProductionControl<'_>,
155) -> Result<Produced<String>> {
156 let mut text = ProductionString::new(*control);
157 text.push('(')?;
158 for (index, value) in values.into_iter().enumerate() {
159 if index != 0 {
160 text.push(',')?;
161 }
162 if matches!(value, Value::Null) {
163 continue;
164 }
165 let value = match value {
166 Value::Bool(value) => control.copy_text(if *value { "t" } else { "f" })?,
167 other => value_to_string_with_control(other, control)?,
168 };
169 let mut quoted = value.is_empty();
170 for byte in value.bytes() {
171 control.check()?;
172 quoted |=
173 matches!(byte, b',' | b'(' | b')' | b'"' | b'\\') || byte.is_ascii_whitespace();
174 }
175 append_escaped(&mut text, &value, quoted, true)?;
176 }
177 text.push(')')?;
178 Ok(text.finish()?)
179}
180
181fn append_escaped(
182 text: &mut ProductionString<'_>,
183 value: &str,
184 quoted: bool,
185 composite: bool,
186) -> Result<()> {
187 if !quoted {
188 text.push_str(value)?;
189 return Ok(());
190 }
191 text.push('"')?;
192 for character in value.chars() {
193 if character == '\\' || (character == '"' && !composite) {
194 text.push('\\')?;
195 }
196 if character == '"' && composite {
197 text.push('"')?;
198 }
199 text.push(character)?;
200 }
201 text.push('"')?;
202 Ok(())
203}
204
205pub(super) fn float1_with_control<F: FnOnce(f64) -> f64>(
206 args: &[Value],
207 name: &str,
208 f: F,
209 control: &ProductionControl<'_>,
210) -> Result<Value> {
211 control.check()?;
212 if args.len() != 1 {
213 return Err(SQLError::TypeMismatch(format!("{name} takes 1 arg")));
214 }
215 if matches!(args[0], Value::Null) {
216 return Ok(Value::Null);
217 }
218 Ok(Value::Float(f(to_f64_with_control(&args[0], control)?)))
219}
220
221pub(super) fn to_i64(v: &Value) -> Result<i64> {
222 to_i64_with_control(v, &ProductionControl::uncontrolled())
223}
224
225pub(super) fn to_i64_with_control(v: &Value, control: &ProductionControl<'_>) -> Result<i64> {
226 control.check()?;
227 match v {
228 Value::Int(n) => Ok(*n),
229 Value::Float(f) => float_to_i64_trunc(*f),
230 Value::Decimal(d) => d
231 .to_i64_trunc_with_control(control)?
232 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot cast {v:?} to integer"))),
233 Value::Bool(b) => Ok(i64::from(*b)),
234 Value::Str(s) | Value::FixedChar(s) => s
235 .trim()
236 .parse()
237 .map_err(|_| SQLError::TypeMismatch(format!("cannot parse {s:?} as integer"))),
238 other => Err(SQLError::TypeMismatch(format!(
239 "expected integer, got {other:?}"
240 ))),
241 }
242}
243
244pub(super) fn nonnegative_usize(value: i64, label: &str) -> Result<usize> {
245 usize::try_from(value).map_err(|_| SQLError::Routine {
246 sqlstate: "22003".into(),
247 message: format!("{label} exceeds the platform addressable range"),
248 })
249}
250
251pub(super) fn allocation_error(label: &str) -> SQLError {
252 SQLError::Routine {
253 sqlstate: "53200".into(),
254 message: format!("{label} result exceeds available memory"),
255 }
256}
257
258pub(crate) fn to_f64(v: &Value) -> Result<f64> {
259 to_f64_with_control(v, &ProductionControl::uncontrolled())
260}
261
262pub(crate) fn to_f64_with_control(v: &Value, control: &ProductionControl<'_>) -> Result<f64> {
263 super::floating::to_float_with_control(v, super::FloatWidth::DoublePrecision, control)
264}
265
266pub(super) fn to_decimal(value: &Value) -> Result<DecimalValue> {
267 to_decimal_with_control(value, &ProductionControl::uncontrolled())?
268 .into_uncontrolled()
269 .map_err(|_| SQLError::Internal("ordinary numeric production owner".into()))
270}
271
272pub(super) fn to_decimal_with_control(
273 value: &Value,
274 control: &ProductionControl<'_>,
275) -> Result<Produced<DecimalValue>> {
276 control.check()?;
277 match value {
278 Value::Decimal(value) => Ok(value.clone_with_control(control)?),
279 Value::Int(value) => Ok(DecimalValue::from_i64_with_control(*value, control)?),
280 Value::Bool(value) => Ok(DecimalValue::from_i64_with_control(
281 i64::from(*value),
282 control,
283 )?),
284 Value::Float(number) => DecimalValue::from_f64_lossy_with_control(*number, control)?
285 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot cast {value:?} to numeric"))),
286 Value::Str(text) | Value::FixedChar(text) => {
287 DecimalValue::parse_with_control(text, control)?.ok_or_else(|| SQLError::Routine {
288 sqlstate: "22P02".into(),
289 message: format!("invalid input syntax for type numeric: \"{text}\""),
290 })
291 }
292 other => Err(SQLError::TypeMismatch(format!(
293 "expected number, got {other:?}"
294 ))),
295 }
296}
297
298pub(super) fn float_to_i64_trunc(value: f64) -> Result<i64> {
299 if !value.is_finite() || value < i64::MIN as f64 || value >= 9_223_372_036_854_775_808.0 {
300 return Err(out_of_range("bigint"));
301 }
302 Ok(value.trunc() as i64)
303}
304
305pub(super) fn float_to_i64_rounded(value: f64, type_name: &str) -> Result<i64> {
306 let rounded = value.round();
307 if !rounded.is_finite() || rounded < i64::MIN as f64 || rounded >= 9_223_372_036_854_775_808.0 {
308 return Err(out_of_range(type_name));
309 }
310 Ok(rounded as i64)
311}
312
313pub(super) fn gcd_i64(a: i64, b: i64) -> Result<i64> {
314 let mut a = a.unsigned_abs();
315 let mut b = b.unsigned_abs();
316 while b != 0 {
317 let r = a % b;
318 a = b;
319 b = r;
320 }
321 i64::try_from(a).map_err(|_| out_of_range("bigint"))
322}
323
324pub(super) fn coerce_i64(v: &Value) -> Option<i64> {
327 match v {
328 Value::Int(n) => Some(*n),
329 Value::Float(f) => float_to_i64_trunc(*f).ok(),
330 Value::Decimal(d) => d.to_i64_trunc(),
331 Value::Bool(b) => Some(i64::from(*b)),
332 Value::Str(s) | Value::FixedChar(s) => s.parse().ok(),
333 _ => None,
334 }
335}
336
337pub fn value_to_vector(v: &Value) -> Result<Vec<f32>> {
341 value_to_vector_with_control(v, &ProductionControl::uncontrolled())?
342 .into_uncontrolled()
343 .map_err(|_| SQLError::Internal("ordinary vector owner".into()))
344}
345
346pub fn value_to_vector_with_control(
347 v: &Value,
348 control: &ProductionControl<'_>,
349) -> Result<Produced<Vec<f32>>> {
350 let items = vector_items(v)?;
351 let mut out = ProductionVec::new(*control);
352 out.reserve(items.len())?;
353 for item in items {
354 out.push_copy(vector_element_with_control(item, control)?)?;
355 }
356 Ok(out.finish()?)
357}
358
359pub(crate) fn vector_items(v: &Value) -> Result<&[Value]> {
360 match v {
361 Value::List(items) => Ok(items.as_slice()),
362 Value::Array(array) if array.dimensions().len() <= 1 => Ok(array.elements()),
363 Value::Array(array) => Err(SQLError::TypeMismatch(format!(
364 "expected one-dimensional vector input, got {} dimensions",
365 array.dimensions().len()
366 ))),
367 other => Err(SQLError::TypeMismatch(format!(
368 "expected vector (numeric array), got {other:?}"
369 ))),
370 }
371}
372
373pub(crate) fn vector_element(item: &Value) -> Result<f32> {
374 vector_element_with_control(item, &ProductionControl::uncontrolled())
375}
376
377pub(crate) fn vector_element_with_control(
378 item: &Value,
379 control: &ProductionControl<'_>,
380) -> Result<f32> {
381 control.check()?;
382 match item {
383 Value::Float(f) => numeric_f64_to_f32(*f, item),
384 Value::Int(i) => Ok(*i as f32),
385 Value::Decimal(d) => numeric_f64_to_f32(
386 d.to_f64_with_control(control)?.ok_or_else(|| {
387 SQLError::TypeMismatch(format!("vector element must fit f32, got {item:?}"))
388 })?,
389 item,
390 ),
391 other => Err(SQLError::TypeMismatch(format!(
392 "vector element must be numeric, got {other:?}"
393 ))),
394 }
395}
396
397pub(super) fn numeric_f64_to_f32(value: f64, source: &Value) -> Result<f32> {
398 if !value.is_finite() || value < -(f32::MAX as f64) || value > f32::MAX as f64 {
399 return Err(SQLError::TypeMismatch(format!(
400 "vector element must be finite and fit f32, got {source:?}"
401 )));
402 }
403 Ok(value as f32)
404}
405
406pub fn value_to_tensor(v: &Value) -> Result<Vec<Vec<f32>>> {
410 value_to_tensor_with_control(v, &ProductionControl::uncontrolled())?
411 .into_uncontrolled()
412 .map_err(|_| SQLError::Internal("ordinary tensor owner".into()))
413}
414
415pub fn value_to_tensor_with_control(
416 v: &Value,
417 control: &ProductionControl<'_>,
418) -> Result<Produced<Vec<Vec<f32>>>> {
419 let items = tensor_items(v)?;
420 let mut out = ProductionVec::new(*control);
421 out.reserve(items.len())?;
422 for item in items {
423 out.push_produced(value_to_vector_with_control(item, control)?)?;
424 }
425 Ok(out.finish()?)
426}
427
428pub(crate) fn tensor_items(v: &Value) -> Result<&[Value]> {
429 match v {
430 Value::List(items) => Ok(items.as_slice()),
431 Value::Array(array) if array.dimensions().is_empty() || array.dimensions().len() == 2 => {
432 Ok(array.elements())
433 }
434 Value::Array(array) => Err(SQLError::TypeMismatch(format!(
435 "expected two-dimensional tensor input, got {} dimensions",
436 array.dimensions().len()
437 ))),
438 other => Err(SQLError::TypeMismatch(format!(
439 "expected tensor (array of numeric arrays), got {other:?}"
440 ))),
441 }
442}