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