1use super::{
10 hex_encode, out_of_range, value_to_json, ArrayValue, DecimalValue, Result, SQLError, Value,
11};
12
13pub fn value_to_string(v: &Value) -> String {
14 match v {
15 Value::Null => "".into(),
16 Value::Void => "".into(),
17 Value::Int(i) => i.to_string(),
18 Value::Float(f) => uqa_core::format_float_pg(*f),
19 Value::Decimal(d) => d.to_sql_string(),
20 Value::Str(s) => s.clone(),
21 Value::FixedChar(s) => s.trim_end_matches(' ').to_string(),
22 Value::Bool(b) => (if *b { "true" } else { "false" }).into(),
23 Value::Temporal(t) => t.to_sql_string(),
24 Value::Json(text) | Value::JsonB(text) => text.clone(),
25 Value::Array(array) => array_value_to_string(array),
26 Value::List(_) | Value::Map(_) => value_to_json(v).to_string(),
27 Value::Row(values) => composite_value_to_string(values.iter()),
28 Value::Record(fields) => composite_value_to_string(fields.iter().map(|(_, value)| value)),
29 Value::Bytes(b) => format!("\\x{}", hex_encode(b)),
31 }
32}
33
34pub fn vector_value_to_string(value: &Value) -> Option<String> {
36 let elements = match value {
37 Value::List(elements) => elements.as_slice(),
38 Value::Array(array) if array.dimensions().len() <= 1 => array.elements(),
39 _ => return None,
40 };
41 Some(
42 elements
43 .iter()
44 .map(value_to_string)
45 .collect::<Vec<_>>()
46 .join(" "),
47 )
48}
49
50pub fn array_value_to_string(array: &ArrayValue) -> String {
51 let dimensions = if array
52 .lower_bounds()
53 .iter()
54 .any(|lower_bound| *lower_bound != 1)
55 {
56 array
57 .lower_bounds()
58 .iter()
59 .zip(array.dimensions())
60 .map(|(lower, length)| {
61 let upper = i64::from(*lower) + i64::try_from(*length).unwrap_or(i64::MAX) - 1;
62 format!("[{lower}:{upper}]")
63 })
64 .collect::<String>()
65 + "="
66 } else {
67 String::new()
68 };
69 format!("{dimensions}{}", array_elements_to_string(array.elements()))
70}
71
72fn array_elements_to_string(elements: &[Value]) -> String {
73 let rendered = elements
74 .iter()
75 .map(|value| match value {
76 Value::Null => "NULL".to_string(),
77 Value::Bool(value) => if *value { "t" } else { "f" }.to_string(),
78 Value::List(nested) => array_elements_to_string(nested),
79 Value::Array(nested) => array_value_to_string(nested),
80 other => {
81 let text = value_to_string(other);
82 let requires_quotes = text.is_empty()
83 || text.eq_ignore_ascii_case("null")
84 || text.chars().any(|character| {
85 character.is_whitespace()
86 || matches!(character, ',' | '{' | '}' | '"' | '\\')
87 });
88 if requires_quotes {
89 format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
90 } else {
91 text
92 }
93 }
94 })
95 .collect::<Vec<_>>();
96 format!("{{{}}}", rendered.join(","))
97}
98
99fn composite_value_to_string<'a>(values: impl IntoIterator<Item = &'a Value>) -> String {
100 let fields = values
101 .into_iter()
102 .map(|value| {
103 if matches!(value, Value::Null) {
104 return String::new();
105 }
106 let text = match value {
107 Value::Bool(true) => "t".to_string(),
108 Value::Bool(false) => "f".to_string(),
109 other => value_to_string(other),
110 };
111 if text.is_empty()
112 || text.bytes().any(|byte| {
113 matches!(byte, b',' | b'(' | b')' | b'"' | b'\\') || byte.is_ascii_whitespace()
114 })
115 {
116 format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\"\""))
117 } else {
118 text
119 }
120 })
121 .collect::<Vec<_>>();
122 format!("({})", fields.join(","))
123}
124
125pub(super) fn expect_str(args: &[Value], idx: usize) -> Result<String> {
126 args.get(idx)
127 .map(value_to_string)
128 .ok_or_else(|| SQLError::TypeMismatch(format!("missing arg #{idx}")))
129}
130
131pub(super) fn string1<F: FnOnce(&str) -> String>(args: &[Value], f: F) -> Result<Value> {
132 if args.is_empty() {
133 return Err(SQLError::TypeMismatch("string fn needs 1 arg".into()));
134 }
135 if matches!(args[0], Value::Null) {
136 return Ok(Value::Null);
137 }
138 let s = value_to_string(&args[0]);
139 Ok(Value::Str(f(&s)))
140}
141
142pub(super) fn float1<F: FnOnce(f64) -> f64>(args: &[Value], name: &str, f: F) -> Result<Value> {
143 if args.len() != 1 {
144 return Err(SQLError::TypeMismatch(format!("{name} takes 1 arg")));
145 }
146 if matches!(args[0], Value::Null) {
147 return Ok(Value::Null);
148 }
149 Ok(Value::Float(f(to_f64(&args[0])?)))
150}
151
152pub(super) fn initcap_str(s: &str) -> String {
153 let mut out = String::with_capacity(s.len());
154 let mut start = true;
155 for ch in s.chars() {
156 if ch.is_whitespace() {
157 out.push(ch);
158 start = true;
159 continue;
160 }
161 if start {
162 for c in ch.to_uppercase() {
163 out.push(c);
164 }
165 start = false;
166 } else {
167 for c in ch.to_lowercase() {
168 out.push(c);
169 }
170 }
171 }
172 out
173}
174
175pub(super) fn to_i64(v: &Value) -> Result<i64> {
176 match v {
177 Value::Int(n) => Ok(*n),
178 Value::Float(f) => float_to_i64_trunc(*f),
179 Value::Decimal(d) => d
180 .to_i64_trunc()
181 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot cast {v:?} to integer"))),
182 Value::Bool(b) => Ok(i64::from(*b)),
183 Value::Str(s) | Value::FixedChar(s) => s
184 .trim()
185 .parse()
186 .map_err(|_| SQLError::TypeMismatch(format!("cannot parse {s:?} as integer"))),
187 other => Err(SQLError::TypeMismatch(format!(
188 "expected integer, got {other:?}"
189 ))),
190 }
191}
192
193pub(super) fn nonnegative_usize(value: i64, label: &str) -> Result<usize> {
194 usize::try_from(value).map_err(|_| SQLError::Routine {
195 sqlstate: "22003".into(),
196 message: format!("{label} exceeds the platform addressable range"),
197 })
198}
199
200pub(super) fn allocation_error(label: &str) -> SQLError {
201 SQLError::Routine {
202 sqlstate: "53200".into(),
203 message: format!("{label} result exceeds available memory"),
204 }
205}
206
207pub(crate) fn to_f64(v: &Value) -> Result<f64> {
208 super::floating::to_float(v, super::FloatWidth::DoublePrecision)
209}
210
211pub(super) fn to_decimal(v: &Value) -> Result<DecimalValue> {
212 match v {
213 Value::Decimal(d) => Ok(d.clone()),
214 Value::Int(n) => Ok(DecimalValue::from_i64(*n)),
215 Value::Float(f) => DecimalValue::from_f64_lossy(*f)
216 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot cast {v:?} to numeric"))),
217 Value::Bool(b) => Ok(DecimalValue::from_bool(*b)),
218 Value::Str(s) | Value::FixedChar(s) => {
219 DecimalValue::parse(s).ok_or_else(|| SQLError::Routine {
220 sqlstate: "22P02".into(),
221 message: format!("invalid input syntax for type numeric: \"{s}\""),
222 })
223 }
224 other => Err(SQLError::TypeMismatch(format!(
225 "expected number, got {other:?}"
226 ))),
227 }
228}
229
230pub(super) fn float_to_i64_trunc(value: f64) -> Result<i64> {
231 if !value.is_finite() || value < i64::MIN as f64 || value >= 9_223_372_036_854_775_808.0 {
232 return Err(out_of_range("bigint"));
233 }
234 Ok(value.trunc() as i64)
235}
236
237pub(super) fn float_to_i64_rounded(value: f64, type_name: &str) -> Result<i64> {
238 let rounded = value.round();
239 if !rounded.is_finite() || rounded < i64::MIN as f64 || rounded >= 9_223_372_036_854_775_808.0 {
240 return Err(out_of_range(type_name));
241 }
242 Ok(rounded as i64)
243}
244
245pub(super) fn gcd_i64(a: i64, b: i64) -> Result<i64> {
246 let mut a = a.unsigned_abs();
247 let mut b = b.unsigned_abs();
248 while b != 0 {
249 let r = a % b;
250 a = b;
251 b = r;
252 }
253 i64::try_from(a).map_err(|_| out_of_range("bigint"))
254}
255
256pub(super) fn coerce_i64(v: &Value) -> Option<i64> {
259 match v {
260 Value::Int(n) => Some(*n),
261 Value::Float(f) => float_to_i64_trunc(*f).ok(),
262 Value::Decimal(d) => d.to_i64_trunc(),
263 Value::Bool(b) => Some(i64::from(*b)),
264 Value::Str(s) | Value::FixedChar(s) => s.parse().ok(),
265 _ => None,
266 }
267}
268
269pub fn value_to_vector(v: &Value) -> Result<Vec<f32>> {
273 let items = match v {
274 Value::List(items) => items.as_slice(),
275 Value::Array(array) if array.dimensions().len() <= 1 => array.elements(),
276 Value::Array(array) => {
277 return Err(SQLError::TypeMismatch(format!(
278 "expected one-dimensional vector input, got {} dimensions",
279 array.dimensions().len()
280 )))
281 }
282 other => {
283 return Err(SQLError::TypeMismatch(format!(
284 "expected vector (numeric array), got {other:?}"
285 )))
286 }
287 };
288 {
289 let mut out = Vec::with_capacity(items.len());
290 for item in items {
291 let x = match item {
292 Value::Float(f) => numeric_f64_to_f32(*f, item)?,
293 Value::Int(i) => *i as f32,
294 Value::Decimal(d) => numeric_f64_to_f32(
295 d.to_f64().ok_or_else(|| {
296 SQLError::TypeMismatch(format!("vector element must fit f32, got {item:?}"))
297 })?,
298 item,
299 )?,
300 other => {
301 return Err(SQLError::TypeMismatch(format!(
302 "vector element must be numeric, got {other:?}"
303 )))
304 }
305 };
306 out.push(x);
307 }
308 Ok(out)
309 }
310}
311
312pub(super) fn numeric_f64_to_f32(value: f64, source: &Value) -> Result<f32> {
313 if !value.is_finite() || value < -(f32::MAX as f64) || value > f32::MAX as f64 {
314 return Err(SQLError::TypeMismatch(format!(
315 "vector element must be finite and fit f32, got {source:?}"
316 )));
317 }
318 Ok(value as f32)
319}
320
321pub fn value_to_tensor(v: &Value) -> Result<Vec<Vec<f32>>> {
325 let items = match v {
326 Value::List(items) => items.as_slice(),
327 Value::Array(array) if array.dimensions().is_empty() || array.dimensions().len() == 2 => {
328 array.elements()
329 }
330 Value::Array(array) => {
331 return Err(SQLError::TypeMismatch(format!(
332 "expected two-dimensional tensor input, got {} dimensions",
333 array.dimensions().len()
334 )))
335 }
336 other => {
337 return Err(SQLError::TypeMismatch(format!(
338 "expected tensor (array of numeric arrays), got {other:?}"
339 )))
340 }
341 };
342 {
343 let mut out = Vec::with_capacity(items.len());
344 for item in items {
345 out.push(value_to_vector(item)?);
346 }
347 Ok(out)
348 }
349}