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