1use alloc::format;
12use alloc::string::{String, ToString};
13use alloc::vec::Vec;
14
15use spg_sql::ast::CastTarget;
16use spg_storage::Value;
17
18use super::{
19 EvalError, decode_tsquery_external, decode_tsvector_external, parse_date_literal,
20 parse_timestamp_literal, value_to_text,
21};
22
23pub fn cast_value(v: Value<'static>, target: CastTarget) -> Result<Value<'static>, EvalError> {
25 if matches!(v, Value::Null) {
26 return Ok(Value::Null);
27 }
28 match target {
29 CastTarget::Vector => cast_to_vector(v),
30 CastTarget::Text => Ok(Value::text(value_to_text(&v))),
31 CastTarget::Int => cast_numeric_to_int(v),
32 CastTarget::BigInt => cast_numeric_to_bigint(v),
33 CastTarget::Float => cast_numeric_to_float(v),
34 CastTarget::Bool => cast_to_bool(v),
35 CastTarget::Date => cast_to_date(v),
36 CastTarget::Timestamp | CastTarget::Timestamptz => cast_to_timestamp(v),
39 CastTarget::Interval => cast_to_interval(v),
43 CastTarget::Json | CastTarget::Jsonb => match v {
47 Value::Json(s) => Ok(Value::json(s)),
48 Value::Text(s) => Ok(Value::json(s)),
49 other => Err(EvalError::TypeMismatch {
50 detail: alloc::format!(
51 "::json / ::jsonb only accepts TEXT-shape inputs, got {:?}",
52 other.data_type()
53 ),
54 }),
55 },
56 CastTarget::RegType | CastTarget::RegClass => match v {
74 Value::Text(s) => {
75 let bare = s.rsplit('.').next().unwrap_or(&s).to_string();
80 Ok(Value::text(bare))
81 }
82 Value::Int(n) => Ok(Value::text(alloc::format!("{n}"))),
83 Value::BigInt(n) => Ok(Value::text(alloc::format!("{n}"))),
84 other => Err(EvalError::TypeMismatch {
85 detail: alloc::format!(
86 "::regtype / ::regclass accepts TEXT (name) or integer (oid), got {:?}",
87 other.data_type()
88 ),
89 }),
90 },
91 CastTarget::TextArray => match v {
95 Value::TextArray(items) => Ok(Value::TextArray(items)),
96 Value::Text(s) => decode_text_array_external(&s).map(Value::TextArray),
97 other => Err(EvalError::TypeMismatch {
98 detail: alloc::format!(
99 "::TEXT[] only accepts TEXT / TEXT[] inputs, got {:?}",
100 other.data_type()
101 ),
102 }),
103 },
104 CastTarget::IntArray => cast_to_int_array(v),
108 CastTarget::BigIntArray => cast_to_bigint_array(v),
109 CastTarget::TsVector => match v {
116 Value::TsVector(items) => Ok(Value::TsVector(items)),
117 Value::Text(s) => decode_tsvector_external(&s).map(Value::TsVector),
118 other => Err(EvalError::TypeMismatch {
119 detail: alloc::format!(
120 "::tsvector only accepts TEXT / tsvector inputs, got {:?}",
121 other.data_type()
122 ),
123 }),
124 },
125 CastTarget::TsQuery => match v {
126 Value::TsQuery(ast) => Ok(Value::TsQuery(ast)),
127 Value::Text(s) => decode_tsquery_external(&s).map(Value::TsQuery),
128 other => Err(EvalError::TypeMismatch {
129 detail: alloc::format!(
130 "::tsquery only accepts TEXT / tsquery inputs, got {:?}",
131 other.data_type()
132 ),
133 }),
134 },
135 CastTarget::Uuid => match v {
140 Value::Uuid(b) => Ok(Value::Uuid(b)),
141 Value::Text(s) => match spg_storage::parse_uuid_str(&s) {
142 Some(b) => Ok(Value::Uuid(b)),
143 None => Err(EvalError::TypeMismatch {
144 detail: alloc::format!("invalid input syntax for type uuid: {s:?}"),
145 }),
146 },
147 other => Err(EvalError::TypeMismatch {
148 detail: alloc::format!(
149 "::uuid only accepts TEXT / uuid inputs, got {:?}",
150 other.data_type()
151 ),
152 }),
153 },
154 CastTarget::Bytea => match v {
160 Value::Bytes(b) => Ok(Value::bytes(b)),
161 Value::Text(s) => match crate::conversions::decode_bytea_literal(&s) {
162 Ok(b) => Ok(Value::bytes(b)),
163 Err(msg) => Err(EvalError::TypeMismatch {
164 detail: alloc::format!("invalid input syntax for type bytea: {msg}"),
165 }),
166 },
167 other => Err(EvalError::TypeMismatch {
168 detail: alloc::format!(
169 "::bytea only accepts TEXT / bytea inputs, got {:?}",
170 other.data_type()
171 ),
172 }),
173 },
174 CastTarget::Named(name) => {
175 let dt = crate::conversions::type_name_to_data_type(&name).ok_or_else(|| {
181 EvalError::TypeMismatch {
182 detail: alloc::format!("unsupported cast target `::{name}`"),
183 }
184 })?;
185 crate::conversions::coerce_value(v, dt, &name, 0).map_err(|e| EvalError::TypeMismatch {
186 detail: alloc::format!("{e}"),
187 })
188 }
189 }
190}
191
192fn cast_to_int_array(v: Value) -> Result<Value, EvalError> {
193 match v {
194 Value::IntArray(items) => Ok(Value::IntArray(items)),
195 Value::BigIntArray(items) => {
196 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
197 for item in items {
198 match item {
199 None => out.push(None),
200 Some(n) => match i32::try_from(n) {
201 Ok(x) => out.push(Some(x)),
202 Err(_) => {
203 return Err(EvalError::TypeMismatch {
204 detail: alloc::format!("::INT[] element {n} overflows i32"),
205 });
206 }
207 },
208 }
209 }
210 Ok(Value::IntArray(out))
211 }
212 Value::Text(s) => decode_int_array_external(&s).map(Value::IntArray),
213 Value::TextArray(items) => {
214 let mut out: Vec<Option<i32>> = Vec::with_capacity(items.len());
215 for item in items {
216 match item {
217 None => out.push(None),
218 Some(s) => match s.parse::<i32>() {
219 Ok(n) => out.push(Some(n)),
220 Err(_) => {
221 return Err(EvalError::TypeMismatch {
222 detail: alloc::format!("::INT[] cannot parse {s:?}"),
223 });
224 }
225 },
226 }
227 }
228 Ok(Value::IntArray(out))
229 }
230 other => Err(EvalError::TypeMismatch {
231 detail: alloc::format!("::INT[] does not accept {:?}", other.data_type()),
232 }),
233 }
234}
235
236fn cast_to_bigint_array(v: Value) -> Result<Value, EvalError> {
237 match v {
238 Value::BigIntArray(items) => Ok(Value::BigIntArray(items)),
239 Value::IntArray(items) => Ok(Value::BigIntArray(
240 items.into_iter().map(|x| x.map(i64::from)).collect(),
241 )),
242 Value::Text(s) => decode_bigint_array_external(&s).map(Value::BigIntArray),
243 Value::TextArray(items) => {
244 let mut out: Vec<Option<i64>> = Vec::with_capacity(items.len());
245 for item in items {
246 match item {
247 None => out.push(None),
248 Some(s) => match s.parse::<i64>() {
249 Ok(n) => out.push(Some(n)),
250 Err(_) => {
251 return Err(EvalError::TypeMismatch {
252 detail: alloc::format!("::BIGINT[] cannot parse {s:?}"),
253 });
254 }
255 },
256 }
257 }
258 Ok(Value::BigIntArray(out))
259 }
260 other => Err(EvalError::TypeMismatch {
261 detail: alloc::format!("::BIGINT[] does not accept {:?}", other.data_type()),
262 }),
263 }
264}
265
266fn decode_int_array_external(s: &str) -> Result<Vec<Option<i32>>, EvalError> {
267 let trimmed = s.trim();
268 let inner = trimmed
269 .strip_prefix('{')
270 .and_then(|x| x.strip_suffix('}'))
271 .ok_or_else(|| EvalError::TypeMismatch {
272 detail: alloc::format!("INT[] literal {s:?} must be enclosed in '{{...}}'"),
273 })?;
274 if inner.trim().is_empty() {
275 return Ok(Vec::new());
276 }
277 inner
278 .split(',')
279 .map(|part| {
280 let p = part.trim();
281 if p.eq_ignore_ascii_case("NULL") {
282 Ok(None)
283 } else {
284 p.parse::<i32>()
285 .map(Some)
286 .map_err(|_| EvalError::TypeMismatch {
287 detail: alloc::format!("INT[] element {p:?} is not an i32"),
288 })
289 }
290 })
291 .collect()
292}
293
294fn decode_bigint_array_external(s: &str) -> Result<Vec<Option<i64>>, EvalError> {
295 let trimmed = s.trim();
296 let inner = trimmed
297 .strip_prefix('{')
298 .and_then(|x| x.strip_suffix('}'))
299 .ok_or_else(|| EvalError::TypeMismatch {
300 detail: alloc::format!("BIGINT[] literal {s:?} must be enclosed in '{{...}}'"),
301 })?;
302 if inner.trim().is_empty() {
303 return Ok(Vec::new());
304 }
305 inner
306 .split(',')
307 .map(|part| {
308 let p = part.trim();
309 if p.eq_ignore_ascii_case("NULL") {
310 Ok(None)
311 } else {
312 p.parse::<i64>()
313 .map(Some)
314 .map_err(|_| EvalError::TypeMismatch {
315 detail: alloc::format!("BIGINT[] element {p:?} is not an i64"),
316 })
317 }
318 })
319 .collect()
320}
321
322fn decode_text_array_external(s: &str) -> Result<Vec<Option<String>>, EvalError> {
327 let trimmed = s.trim();
328 let inner = trimmed
329 .strip_prefix('{')
330 .and_then(|x| x.strip_suffix('}'))
331 .ok_or_else(|| EvalError::TypeMismatch {
332 detail: alloc::format!("TEXT[] literal {s:?} must be enclosed in '{{...}}'"),
333 })?;
334 let mut out: Vec<Option<String>> = Vec::new();
335 if inner.trim().is_empty() {
336 return Ok(out);
337 }
338 let bytes = inner.as_bytes();
339 let mut i = 0;
340 while i <= bytes.len() {
341 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
342 i += 1;
343 }
344 if i < bytes.len() && bytes[i] == b'"' {
345 i += 1;
346 let mut buf = String::new();
347 while i < bytes.len() && bytes[i] != b'"' {
348 if bytes[i] == b'\\' && i + 1 < bytes.len() {
349 buf.push(bytes[i + 1] as char);
350 i += 2;
351 } else {
352 buf.push(bytes[i] as char);
353 i += 1;
354 }
355 }
356 if i >= bytes.len() {
357 return Err(EvalError::TypeMismatch {
358 detail: "unterminated quoted element in TEXT[] literal".into(),
359 });
360 }
361 i += 1;
362 out.push(Some(buf));
363 } else {
364 let start = i;
365 while i < bytes.len() && bytes[i] != b',' {
366 i += 1;
367 }
368 let raw = inner[start..i].trim();
369 if raw.eq_ignore_ascii_case("NULL") {
370 out.push(None);
371 } else {
372 out.push(Some(raw.to_string()));
373 }
374 }
375 while i < bytes.len() && (bytes[i] == b' ' || bytes[i] == b'\t') {
376 i += 1;
377 }
378 if i >= bytes.len() {
379 break;
380 }
381 if bytes[i] != b',' {
382 return Err(EvalError::TypeMismatch {
383 detail: "expected ',' between TEXT[] elements".into(),
384 });
385 }
386 i += 1;
387 }
388 Ok(out)
389}
390
391fn cast_to_interval(v: Value) -> Result<Value, EvalError> {
392 match v {
393 Value::Interval {
394 months,
395 days,
396 micros,
397 } => Ok(Value::Interval {
398 months,
399 days,
400 micros,
401 }),
402 Value::Text(s) => {
403 let (months, days, micros) =
404 spg_sql::parser::parse_interval_text(&s).ok_or_else(|| {
405 EvalError::TypeMismatch {
406 detail: alloc::format!("cannot parse {s:?} as INTERVAL"),
407 }
408 })?;
409 Ok(Value::Interval {
410 months,
411 days,
412 micros,
413 })
414 }
415 other => Err(EvalError::TypeMismatch {
416 detail: alloc::format!(
417 "::INTERVAL only accepts TEXT-shape inputs, got {:?}",
418 other.data_type()
419 ),
420 }),
421 }
422}
423
424fn cast_to_date(v: Value) -> Result<Value, EvalError> {
425 match v {
426 Value::Date(d) => Ok(Value::Date(d)),
427 Value::Int(n) => Ok(Value::Date(n)),
430 Value::BigInt(n) => {
431 i32::try_from(n)
432 .map(Value::Date)
433 .map_err(|_| EvalError::TypeMismatch {
434 detail: "bigint days-since-epoch out of DATE range".into(),
435 })
436 }
437 Value::Timestamp(t) => {
439 let days = t.div_euclid(86_400_000_000);
440 i32::try_from(days)
441 .map(Value::Date)
442 .map_err(|_| EvalError::TypeMismatch {
443 detail: "timestamp out of DATE range".into(),
444 })
445 }
446 Value::Text(s) => parse_date_literal(&s)
447 .map(Value::Date)
448 .ok_or(EvalError::TypeMismatch {
449 detail: format!("cannot parse {s:?} as DATE (expected YYYY-MM-DD)"),
450 }),
451 other => Err(EvalError::TypeMismatch {
452 detail: format!("cannot cast {:?} to DATE", other.data_type()),
453 }),
454 }
455}
456
457fn cast_to_timestamp(v: Value) -> Result<Value, EvalError> {
458 match v {
459 Value::Timestamp(t) => Ok(Value::Timestamp(t)),
460 Value::Int(n) => Ok(Value::Timestamp(i64::from(n))),
464 Value::BigInt(n) => Ok(Value::Timestamp(n)),
465 Value::Date(d) => Ok(Value::Timestamp(i64::from(d) * 86_400_000_000)),
467 Value::Text(s) => {
468 parse_timestamp_literal(&s)
469 .map(Value::Timestamp)
470 .ok_or(EvalError::TypeMismatch {
471 detail: format!(
472 "cannot parse {s:?} as TIMESTAMP \
473 (expected YYYY-MM-DD[ HH:MM:SS[.ffffff]])"
474 ),
475 })
476 }
477 other => Err(EvalError::TypeMismatch {
478 detail: format!("cannot cast {:?} to TIMESTAMP", other.data_type()),
479 }),
480 }
481}
482
483fn cast_numeric_to_int(v: Value) -> Result<Value, EvalError> {
484 match v {
485 Value::Int(n) => Ok(Value::Int(n)),
486 Value::BigInt(n) => i32::try_from(n)
487 .map(Value::Int)
488 .map_err(|_| EvalError::TypeMismatch {
489 detail: format!("bigint {n} does not fit in int"),
490 }),
491 #[allow(clippy::cast_possible_truncation)]
492 Value::Float(x) => Ok(Value::Int(x as i32)),
493 Value::Text(s) => {
494 s.trim()
495 .parse::<i32>()
496 .map(Value::Int)
497 .map_err(|_| EvalError::TypeMismatch {
498 detail: format!("cannot parse {s:?} as int"),
499 })
500 }
501 Value::Bool(b) => Ok(Value::Int(i32::from(b))),
502 other => Err(EvalError::TypeMismatch {
503 detail: format!("cannot cast {:?} to int", other.data_type()),
504 }),
505 }
506}
507
508fn cast_numeric_to_bigint(v: Value) -> Result<Value, EvalError> {
509 match v {
510 Value::Int(n) => Ok(Value::BigInt(i64::from(n))),
511 Value::BigInt(n) => Ok(Value::BigInt(n)),
512 #[allow(clippy::cast_possible_truncation)]
513 Value::Float(x) => Ok(Value::BigInt(x as i64)),
514 Value::Text(s) => {
515 s.trim()
516 .parse::<i64>()
517 .map(Value::BigInt)
518 .map_err(|_| EvalError::TypeMismatch {
519 detail: format!("cannot parse {s:?} as bigint"),
520 })
521 }
522 Value::Bool(b) => Ok(Value::BigInt(i64::from(b))),
523 other => Err(EvalError::TypeMismatch {
524 detail: format!("cannot cast {:?} to bigint", other.data_type()),
525 }),
526 }
527}
528
529fn cast_numeric_to_float(v: Value) -> Result<Value, EvalError> {
530 match v {
531 Value::Int(n) => Ok(Value::Float(f64::from(n))),
532 #[allow(clippy::cast_precision_loss)]
533 Value::BigInt(n) => Ok(Value::Float(n as f64)),
534 Value::Float(x) => Ok(Value::Float(x)),
535 Value::Text(s) => {
536 s.trim()
537 .parse::<f64>()
538 .map(Value::Float)
539 .map_err(|_| EvalError::TypeMismatch {
540 detail: format!("cannot parse {s:?} as float"),
541 })
542 }
543 other => Err(EvalError::TypeMismatch {
544 detail: format!("cannot cast {:?} to float", other.data_type()),
545 }),
546 }
547}
548
549fn cast_to_bool(v: Value) -> Result<Value, EvalError> {
550 match v {
551 Value::Bool(b) => Ok(Value::Bool(b)),
552 Value::Int(n) => Ok(Value::Bool(n != 0)),
553 Value::BigInt(n) => Ok(Value::Bool(n != 0)),
554 Value::Text(s) => {
555 let lo = s.trim().to_ascii_lowercase();
556 match lo.as_str() {
557 "true" | "t" | "yes" | "y" | "1" | "on" => Ok(Value::Bool(true)),
558 "false" | "f" | "no" | "n" | "0" | "off" => Ok(Value::Bool(false)),
559 _ => Err(EvalError::TypeMismatch {
560 detail: format!("cannot parse {s:?} as bool"),
561 }),
562 }
563 }
564 other => Err(EvalError::TypeMismatch {
565 detail: format!("cannot cast {:?} to bool", other.data_type()),
566 }),
567 }
568}
569
570pub fn cast_to_vector(v: Value) -> Result<Value<'static>, EvalError> {
573 match v {
574 Value::Null => Ok(Value::Null),
575 Value::Vector(v) => Ok(Value::vector(v.into_owned())),
576 Value::Text(s) => parse_vector_text(&s)
577 .map(Value::vector)
578 .ok_or(EvalError::TypeMismatch {
579 detail: format!("cannot parse {s:?} as a vector literal"),
580 }),
581 other => Err(EvalError::TypeMismatch {
582 detail: format!("::vector requires text input, got {:?}", other.data_type()),
583 }),
584 }
585}
586
587pub fn parse_vector_text(s: &str) -> Option<Vec<f32>> {
589 let trimmed = s.trim();
590 let inner = trimmed.strip_prefix('[')?.strip_suffix(']')?;
591 let trimmed_inner = inner.trim();
592 if trimmed_inner.is_empty() {
593 return Some(Vec::new());
594 }
595 let mut out = Vec::new();
596 for part in trimmed_inner.split(',') {
597 let f: f32 = part.trim().parse().ok()?;
598 out.push(f);
599 }
600 Some(out)
601}