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) => f.to_string(),
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 match v {
209 Value::Int(n) => Ok(*n as f64),
210 Value::Float(f) => Ok(*f),
211 Value::Decimal(d) => d.to_f64().ok_or_else(|| {
212 SQLError::TypeMismatch(format!("cannot cast {v:?} to double precision"))
213 }),
214 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
215 Value::Str(s) | Value::FixedChar(s) => {
218 let text = s.trim();
219 let lowered = text.to_ascii_lowercase();
220 match lowered.as_str() {
221 "infinity" | "inf" | "+infinity" | "+inf" => Ok(f64::INFINITY),
222 "-infinity" | "-inf" => Ok(f64::NEG_INFINITY),
223 "nan" => Ok(f64::NAN),
224 _ => text.parse().map_err(|_| SQLError::Routine {
225 sqlstate: "22P02".into(),
226 message: format!("invalid input syntax for type double precision: \"{s}\""),
227 }),
228 }
229 }
230 other => Err(SQLError::TypeMismatch(format!(
231 "expected number, got {other:?}"
232 ))),
233 }
234}
235
236pub(super) fn to_decimal(v: &Value) -> Result<DecimalValue> {
237 match v {
238 Value::Decimal(d) => Ok(d.clone()),
239 Value::Int(n) => Ok(DecimalValue::from_i64(*n)),
240 Value::Float(f) => DecimalValue::from_f64_lossy(*f)
241 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot cast {v:?} to numeric"))),
242 Value::Bool(b) => Ok(DecimalValue::from_bool(*b)),
243 Value::Str(s) | Value::FixedChar(s) => {
244 DecimalValue::parse(s).ok_or_else(|| SQLError::Routine {
245 sqlstate: "22P02".into(),
246 message: format!("invalid input syntax for type numeric: \"{s}\""),
247 })
248 }
249 other => Err(SQLError::TypeMismatch(format!(
250 "expected number, got {other:?}"
251 ))),
252 }
253}
254
255pub(super) fn float_to_i64_trunc(value: f64) -> Result<i64> {
256 if !value.is_finite() || value < i64::MIN as f64 || value >= 9_223_372_036_854_775_808.0 {
257 return Err(out_of_range("bigint"));
258 }
259 Ok(value.trunc() as i64)
260}
261
262pub(super) fn float_to_i64_rounded(value: f64, type_name: &str) -> Result<i64> {
263 let rounded = value.round();
264 if !rounded.is_finite() || rounded < i64::MIN as f64 || rounded >= 9_223_372_036_854_775_808.0 {
265 return Err(out_of_range(type_name));
266 }
267 Ok(rounded as i64)
268}
269
270pub(super) fn gcd_i64(a: i64, b: i64) -> Result<i64> {
271 let mut a = a.unsigned_abs();
272 let mut b = b.unsigned_abs();
273 while b != 0 {
274 let r = a % b;
275 a = b;
276 b = r;
277 }
278 i64::try_from(a).map_err(|_| out_of_range("bigint"))
279}
280
281pub(super) fn coerce_i64(v: &Value) -> Option<i64> {
284 match v {
285 Value::Int(n) => Some(*n),
286 Value::Float(f) => float_to_i64_trunc(*f).ok(),
287 Value::Decimal(d) => d.to_i64_trunc(),
288 Value::Bool(b) => Some(i64::from(*b)),
289 Value::Str(s) | Value::FixedChar(s) => s.parse().ok(),
290 _ => None,
291 }
292}
293
294pub fn value_to_vector(v: &Value) -> Result<Vec<f32>> {
298 let items = match v {
299 Value::List(items) => items.as_slice(),
300 Value::Array(array) if array.dimensions().len() <= 1 => array.elements(),
301 Value::Array(array) => {
302 return Err(SQLError::TypeMismatch(format!(
303 "expected one-dimensional vector input, got {} dimensions",
304 array.dimensions().len()
305 )))
306 }
307 other => {
308 return Err(SQLError::TypeMismatch(format!(
309 "expected vector (numeric array), got {other:?}"
310 )))
311 }
312 };
313 {
314 let mut out = Vec::with_capacity(items.len());
315 for item in items {
316 let x = match item {
317 Value::Float(f) => numeric_f64_to_f32(*f, item)?,
318 Value::Int(i) => *i as f32,
319 Value::Decimal(d) => numeric_f64_to_f32(
320 d.to_f64().ok_or_else(|| {
321 SQLError::TypeMismatch(format!("vector element must fit f32, got {item:?}"))
322 })?,
323 item,
324 )?,
325 other => {
326 return Err(SQLError::TypeMismatch(format!(
327 "vector element must be numeric, got {other:?}"
328 )))
329 }
330 };
331 out.push(x);
332 }
333 Ok(out)
334 }
335}
336
337pub(super) fn numeric_f64_to_f32(value: f64, source: &Value) -> Result<f32> {
338 if !value.is_finite() || value < -(f32::MAX as f64) || value > f32::MAX as f64 {
339 return Err(SQLError::TypeMismatch(format!(
340 "vector element must be finite and fit f32, got {source:?}"
341 )));
342 }
343 Ok(value as f32)
344}
345
346pub fn value_to_tensor(v: &Value) -> Result<Vec<Vec<f32>>> {
350 let items = match v {
351 Value::List(items) => items.as_slice(),
352 Value::Array(array) if array.dimensions().is_empty() || array.dimensions().len() == 2 => {
353 array.elements()
354 }
355 Value::Array(array) => {
356 return Err(SQLError::TypeMismatch(format!(
357 "expected two-dimensional tensor input, got {} dimensions",
358 array.dimensions().len()
359 )))
360 }
361 other => {
362 return Err(SQLError::TypeMismatch(format!(
363 "expected tensor (array of numeric arrays), got {other:?}"
364 )))
365 }
366 };
367 {
368 let mut out = Vec::with_capacity(items.len());
369 for item in items {
370 out.push(value_to_vector(item)?);
371 }
372 Ok(out)
373 }
374}