1mod legacy_vector;
10
11use super::{
12 multirange_from_ranges, out_of_range, parse_json, parse_multirange, parse_range, to_decimal,
13 to_f64, typed_json_value, value_to_json, value_to_string, vector_value_to_string, ArrayValue,
14 Result, SQLError, TemporalValue, Value,
15};
16use crate::ast::RangeSubtype;
17
18pub fn cast_value(v: &Value, ty: &str) -> Result<Value> {
22 cast_value_from(v, ty, None)
23}
24
25pub fn cast_value_from(v: &Value, ty: &str, source_ty: Option<&str>) -> Result<Value> {
27 if matches!(v, Value::Null) {
28 return Ok(Value::Null);
29 }
30 if let Some(elem_ty) = ty.strip_suffix("[]") {
31 let source_elem_ty = source_ty
32 .and_then(|source| source.trim().strip_suffix("[]"))
33 .map(str::trim);
34 let array = match v {
35 Value::Array(array) => array.clone(),
36 Value::Str(s) => parse_pg_array_literal(s)?,
37 other => {
38 return Err(SQLError::TypeMismatch(format!(
39 "CAST AS {ty}: expected array, got {other:?}"
40 )));
41 }
42 };
43 let elements = cast_array_elements(array.elements(), elem_ty, source_elem_ty)?;
44 return ArrayValue::with_lower_bounds(elements, array.lower_bounds().to_vec())
45 .map(Value::Array)
46 .ok_or_else(|| SQLError::TypeMismatch("array dimensions changed during cast".into()));
47 }
48 let (base, modifier) = split_type_modifier(ty);
49 match base {
50 "smallint" | "int2" | "pg_catalog.int2" => cast_integer(v, "smallint"),
51 "integer" | "int" | "int4" | "serial" | "serial4" | "pg_catalog.int4" => {
52 cast_integer(v, "integer")
53 }
54 "bigint" | "int8" | "bigserial" | "serial8" | "pg_catalog.int8" => {
55 cast_integer(v, "bigint")
56 }
57 "real" | "float4" | "float8" | "double" | "double precision" => {
58 Ok(Value::Float(to_f64(v)?))
59 }
60 "numeric" | "decimal" => {
61 let value = to_decimal(v)?;
62 if let Some(modifier) = modifier {
63 let mut parts = modifier.split(',').map(str::trim);
64 let precision: u32 = parts
65 .next()
66 .and_then(|p| p.parse().ok())
67 .ok_or_else(|| SQLError::TypeMismatch("bad numeric precision".into()))?;
68 let scale: i32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
69 let rounded = value
70 .round_to_scale(scale)
71 .ok_or_else(|| out_of_range("numeric"))?;
72 if !rounded.fits_precision(precision, scale) {
73 return Err(SQLError::Routine {
74 sqlstate: "22003".into(),
75 message: format!(
76 "numeric field overflow: A field with precision {precision}, scale {scale} cannot hold value {}",
77 value.to_sql_string()
78 ),
79 });
80 }
81 return Ok(Value::Decimal(rounded));
82 }
83 Ok(Value::Decimal(value))
84 }
85 "regproc" | "regtype" if matches!(v, Value::Int(_)) => Ok(v.clone()),
86 "text"
87 | "refcursor"
88 | "pg_catalog.refcursor"
89 | "name"
90 | "regproc"
91 | "regtype"
92 | "pg_node_tree"
93 | "aclitem" => {
94 let source = source_ty
95 .map(str::trim)
96 .map(|source| source.strip_prefix("pg_catalog.").unwrap_or(source));
97 let text = match (source, v) {
98 (Some("int2vector" | "oidvector"), _) => {
99 vector_value_to_string(v).unwrap_or_else(|| value_to_string(v))
100 }
101 (Some("regproc" | "regclass" | "regnamespace" | "regtype"), Value::Int(0)) => {
102 "-".into()
103 }
104 _ => value_to_string(v),
105 };
106 Ok(Value::Str(text))
107 }
108 "int2vector" | "pg_catalog.int2vector" => legacy_vector::cast_int2vector(v, source_ty),
109 "oidvector" | "pg_catalog.oidvector" => legacy_vector::cast_oidvector(v, source_ty),
110 "oid" | "pg_catalog.oid" => cast_oid(v, source_ty),
111 "regclass" | "pg_catalog.regclass" => cast_regclass(v, source_ty),
112 "regnamespace" | "pg_catalog.regnamespace" => cast_regnamespace(v, source_ty),
113 "xid" | "pg_catalog.xid" => cast_xid(v, source_ty),
114 "\"char\"" => {
115 let text = value_to_string(v);
116 let mut characters = text.chars();
117 let Some(character) = characters.next() else {
118 return Ok(Value::Str(String::new()));
119 };
120 if characters.next().is_some() || !character.is_ascii() {
121 return Err(SQLError::TypeMismatch(format!(
122 "value too long for type character(1): {text:?}"
123 )));
124 }
125 Ok(Value::Str(character.to_string()))
126 }
127 "uuid" => cast_uuid(v),
128 "varchar" | "character varying" => {
130 let text = value_to_string(v);
131 let Some(modifier) = modifier else {
132 return Ok(Value::Str(text));
133 };
134 let limit: usize = modifier
135 .trim()
136 .parse()
137 .map_err(|_| SQLError::TypeMismatch(format!("bad length modifier {modifier}")))?;
138 Ok(Value::Str(text.chars().take(limit).collect()))
139 }
140 "bpchar" if modifier.is_none() => Ok(Value::FixedChar(value_to_string(v))),
143 "character" | "char" | "bpchar" => {
144 let text = value_to_string(v);
145 let limit: usize = match modifier {
146 Some(modifier) => modifier.trim().parse().map_err(|_| {
147 SQLError::TypeMismatch(format!("bad length modifier {modifier}"))
148 })?,
149 None => 1,
150 };
151 if limit == 0 {
152 return Err(SQLError::TypeMismatch(
153 "CHARACTER length must be greater than zero".into(),
154 ));
155 }
156 let mut text = text.chars().take(limit).collect::<String>();
157 text.extend(std::iter::repeat_n(
158 ' ',
159 limit.saturating_sub(text.chars().count()),
160 ));
161 Ok(Value::FixedChar(text))
162 }
163 "date" => cast_date(v, source_ty),
164 "time" | "time without time zone" => cast_temporal(
165 v,
166 TemporalCastTarget::Time,
167 TemporalValue::parse_time,
168 "time",
169 ),
170 "timetz" | "time with time zone" => cast_temporal(
171 v,
172 TemporalCastTarget::TimeTz,
173 TemporalValue::parse_time_tz,
174 "time with time zone",
175 ),
176 "timestamp" | "datetime" | "timestamp without time zone" => cast_temporal(
177 v,
178 TemporalCastTarget::Timestamp,
179 TemporalValue::parse_timestamp,
180 "timestamp",
181 ),
182 "timestamptz" | "timestamp with time zone" => cast_temporal(
183 v,
184 TemporalCastTarget::TimestampTz,
185 TemporalValue::parse_timestamp_tz,
186 "timestamp with time zone",
187 ),
188 "interval" => cast_temporal(
189 v,
190 TemporalCastTarget::Interval,
191 TemporalValue::parse_interval,
192 "interval",
193 ),
194 "int4range" => cast_range(v, source_ty, RangeSubtype::Integer),
195 "int8range" => cast_range(v, source_ty, RangeSubtype::BigInteger),
196 "numrange" => cast_range(v, source_ty, RangeSubtype::Numeric),
197 "daterange" => cast_range(v, source_ty, RangeSubtype::Date),
198 "tsrange" => cast_range(v, source_ty, RangeSubtype::Timestamp),
199 "tstzrange" => cast_range(v, source_ty, RangeSubtype::TimestampTz),
200 "int4multirange" => cast_multirange(v, source_ty, RangeSubtype::Integer),
201 "int8multirange" => cast_multirange(v, source_ty, RangeSubtype::BigInteger),
202 "nummultirange" => cast_multirange(v, source_ty, RangeSubtype::Numeric),
203 "datemultirange" => cast_multirange(v, source_ty, RangeSubtype::Date),
204 "tsmultirange" => cast_multirange(v, source_ty, RangeSubtype::Timestamp),
205 "tstzmultirange" => cast_multirange(v, source_ty, RangeSubtype::TimestampTz),
206 "json" => {
207 if let Value::Json(text) = v {
208 return Ok(Value::Json(text.clone()));
209 }
210 if let Value::Str(text) | Value::FixedChar(text) = v {
211 let _validated = parse_json(text)?;
212 return Ok(Value::Json(text.clone()));
213 }
214 typed_json_value(&value_to_json(v), false)
215 }
216 "jsonb" => {
217 if let Value::JsonB(text) = v {
218 return Ok(Value::JsonB(text.clone()));
219 }
220 let parsed = match v {
221 Value::Json(text) | Value::Str(text) | Value::FixedChar(text) => parse_json(text)?,
222 other => value_to_json(other),
223 };
224 typed_json_value(&parsed, true)
225 }
226 "bytea" => cast_bytea(v, source_ty),
227 "boolean" | "bool" => cast_boolean(v),
228 other => Err(SQLError::Unsupported(format!("CAST AS {other}"))),
229 }
230}
231
232fn cast_range(v: &Value, source_ty: Option<&str>, subtype: RangeSubtype) -> Result<Value> {
233 let source = source_ty.map(canonical_type_name);
234 if source.as_deref().is_some_and(|source| {
235 source != subtype.range_name() && !matches!(source, "unknown" | "cstring")
236 }) {
237 return Err(undefined_cast(
238 source.as_deref().unwrap_or("unknown"),
239 subtype.range_name(),
240 ));
241 }
242 let (Value::Str(text) | Value::FixedChar(text)) = v else {
243 return Err(undefined_cast(
244 source.as_deref().unwrap_or("unknown"),
245 subtype.range_name(),
246 ));
247 };
248 parse_range(text, subtype).map(|range| Value::Str(range.to_text()))
249}
250
251fn cast_multirange(v: &Value, source_ty: Option<&str>, subtype: RangeSubtype) -> Result<Value> {
252 let source = source_ty.map(canonical_type_name);
253 let (Value::Str(text) | Value::FixedChar(text)) = v else {
254 return Err(undefined_cast(
255 source.as_deref().unwrap_or("unknown"),
256 subtype.multirange_name(),
257 ));
258 };
259 match source.as_deref() {
260 Some(source) if source == subtype.range_name() => {
261 let range = parse_range(text, subtype)?;
262 Ok(Value::Str(
263 multirange_from_ranges(subtype, [range]).to_text(),
264 ))
265 }
266 None | Some("unknown" | "cstring") => {
267 parse_multirange(text, subtype).map(|multirange| Value::Str(multirange.to_text()))
268 }
269 Some(source) if source == subtype.multirange_name() => {
270 parse_multirange(text, subtype).map(|multirange| Value::Str(multirange.to_text()))
271 }
272 Some(source) => Err(undefined_cast(source, subtype.multirange_name())),
273 }
274}
275
276fn canonical_type_name(type_name: &str) -> String {
277 let normalized = type_name.trim().to_ascii_lowercase();
278 normalized
279 .strip_prefix("pg_catalog.")
280 .unwrap_or(&normalized)
281 .to_string()
282}
283
284pub fn negate_value(value: &Value, source_ty: Option<&str>) -> Result<Value> {
286 if matches!(value, Value::Null) {
287 return Ok(Value::Null);
288 }
289 let source = canonical_cast_source(source_ty, value);
290 match (source.as_str(), value) {
291 ("int2", Value::Int(value)) => i16::try_from(*value)
292 .ok()
293 .and_then(i16::checked_neg)
294 .map(|value| Value::Int(i64::from(value)))
295 .ok_or_else(|| out_of_range("smallint")),
296 ("int4", Value::Int(value)) => i32::try_from(*value)
297 .ok()
298 .and_then(i32::checked_neg)
299 .map(|value| Value::Int(i64::from(value)))
300 .ok_or_else(|| out_of_range("integer")),
301 ("int8", Value::Int(value)) => value
302 .checked_neg()
303 .map(Value::Int)
304 .ok_or_else(|| out_of_range("bigint")),
305 ("float4" | "float8", Value::Float(value)) => Ok(Value::Float(-value)),
306 ("numeric", Value::Decimal(value)) => uqa_core::DecimalValue::from_i64(0)
307 .checked_sub(value)
308 .map(Value::Decimal)
309 .ok_or_else(|| out_of_range("numeric")),
310 (
311 "interval",
312 Value::Temporal(TemporalValue::Interval {
313 months,
314 days,
315 micros,
316 }),
317 ) => Ok(Value::Temporal(TemporalValue::Interval {
318 months: months
319 .checked_neg()
320 .ok_or_else(|| out_of_range("interval"))?,
321 days: days.checked_neg().ok_or_else(|| out_of_range("interval"))?,
322 micros: micros
323 .checked_neg()
324 .ok_or_else(|| out_of_range("interval"))?,
325 })),
326 _ => Err(SQLError::TypeMismatch(format!(
327 "operator does not exist: - {source}"
328 ))),
329 }
330}
331
332fn canonical_cast_source(source_ty: Option<&str>, value: &Value) -> String {
333 let source = source_ty.unwrap_or(match value {
334 Value::Str(_) | Value::FixedChar(_) => "unknown",
335 Value::Int(_) => "integer",
336 Value::Bool(_) => "boolean",
337 Value::Float(_) => "double precision",
338 Value::Decimal(_) => "numeric",
339 Value::Bytes(_) => "bytea",
340 Value::Temporal(TemporalValue::Interval { .. }) => "interval",
341 Value::Temporal(_) => "timestamp",
342 Value::Json(_) => "json",
343 Value::JsonB(_) => "jsonb",
344 Value::Array(_) => "anyarray",
345 Value::List(_) => "anyarray",
346 Value::Row(_) | Value::Record(_) => "record",
347 Value::Map(_) => "jsonb",
348 Value::Null => "unknown",
349 });
350 let (source, _) = split_type_modifier(source);
351 let source = source
352 .trim()
353 .to_ascii_lowercase()
354 .split_whitespace()
355 .collect::<Vec<_>>()
356 .join(" ");
357 let source = source.strip_prefix("pg_catalog.").unwrap_or(&source);
358 match source {
359 "smallint" | "int2" => "int2".into(),
360 "integer" | "int" | "int4" | "serial" | "serial4" => "int4".into(),
361 "bigint" | "int8" | "bigserial" | "serial8" => "int8".into(),
362 "character varying" | "varchar" => "varchar".into(),
363 "character" | "char" | "bpchar" => "bpchar".into(),
364 "boolean" | "bool" => "bool".into(),
365 "double" | "double precision" | "float8" => "float8".into(),
366 "real" | "float4" => "float4".into(),
367 other => other.into(),
368 }
369}
370
371fn cast_oid(value: &Value, source_ty: Option<&str>) -> Result<Value> {
372 let source = canonical_cast_source(source_ty, value);
373 match (source.as_str(), value) {
374 (
375 "unknown" | "text" | "varchar" | "bpchar" | "name",
376 Value::Str(text) | Value::FixedChar(text),
377 ) => parse_uint32_input(text, "oid"),
378 ("int2", Value::Int(value)) => {
379 let value = i16::try_from(*value).map_err(|_| out_of_range("smallint"))?;
380 Ok(Value::Int(i64::from(i32::from(value) as u32)))
381 }
382 ("int4", Value::Int(value)) => {
383 let value = i32::try_from(*value).map_err(|_| out_of_range("integer"))?;
384 Ok(Value::Int(i64::from(value as u32)))
385 }
386 ("int8", Value::Int(value)) => u32::try_from(*value)
387 .map(|value| Value::Int(i64::from(value)))
388 .map_err(|_| SQLError::Routine {
389 sqlstate: "22003".into(),
390 message: "OID out of range".into(),
391 }),
392 (
393 "oid" | "regclass" | "regcollation" | "regconfig" | "regdictionary" | "regnamespace"
394 | "regoper" | "regoperator" | "regproc" | "regprocedure" | "regrole" | "regtype",
395 Value::Int(value),
396 ) => u32::try_from(*value)
397 .map(|value| Value::Int(i64::from(value)))
398 .map_err(|_| out_of_range("oid")),
399 _ => Err(undefined_cast(&source, "oid")),
400 }
401}
402
403fn cast_regclass(value: &Value, source_ty: Option<&str>) -> Result<Value> {
404 let source = canonical_cast_source(source_ty, value);
405 match (source.as_str(), value) {
406 (
407 "unknown" | "text" | "varchar" | "bpchar" | "name" | "regclass",
408 Value::Str(text) | Value::FixedChar(text),
409 ) => Ok(Value::Str(text.clone())),
410 (_, Value::Int(_)) => cast_oid(value, source_ty),
411 _ => Err(undefined_cast(&source, "regclass")),
412 }
413}
414
415fn cast_regnamespace(value: &Value, source_ty: Option<&str>) -> Result<Value> {
416 let source = canonical_cast_source(source_ty, value);
417 match (source.as_str(), value) {
418 (
419 "unknown" | "text" | "varchar" | "bpchar" | "name" | "regnamespace",
420 Value::Str(text) | Value::FixedChar(text),
421 ) => Ok(Value::Str(text.clone())),
422 (_, Value::Int(_)) => cast_oid(value, source_ty),
423 _ => Err(undefined_cast(&source, "regnamespace")),
424 }
425}
426
427fn cast_xid(value: &Value, source_ty: Option<&str>) -> Result<Value> {
428 let source = canonical_cast_source(source_ty, value);
429 match (source.as_str(), value) {
430 (
431 "unknown" | "text" | "varchar" | "bpchar" | "name",
432 Value::Str(text) | Value::FixedChar(text),
433 ) => parse_uint32_input(text, "xid"),
434 ("xid", Value::Int(value)) => u32::try_from(*value)
435 .map(|value| Value::Int(i64::from(value)))
436 .map_err(|_| out_of_range("xid")),
437 _ => Err(undefined_cast(&source, "xid")),
438 }
439}
440
441fn cast_bytea(value: &Value, source_ty: Option<&str>) -> Result<Value> {
442 let source = canonical_cast_source(source_ty, value);
443 match (source.as_str(), value) {
444 ("bytea", Value::Bytes(bytes)) => Ok(Value::Bytes(bytes.clone())),
445 ("int2" | "int4" | "int8", Value::Int(value)) => integer_to_bytea(*value, Some(&source)),
446 (
447 "unknown" | "text" | "varchar" | "bpchar" | "name",
448 Value::Str(text) | Value::FixedChar(text),
449 ) => parse_bytea_input(text),
450 _ => Err(undefined_cast(&source, "bytea")),
451 }
452}
453
454fn parse_bytea_input(text: &str) -> Result<Value> {
455 if let Some(hex) = text.strip_prefix("\\x") {
456 if !hex.len().is_multiple_of(2) {
457 return Err(invalid_bytea(
458 "invalid hexadecimal data: odd number of digits",
459 ));
460 }
461 let mut bytes = Vec::with_capacity(hex.len() / 2);
462 for pair in hex.as_bytes().chunks_exact(2) {
463 let hi = (pair[0] as char)
464 .to_digit(16)
465 .ok_or_else(|| invalid_bytea("invalid hexadecimal digit"))?;
466 let lo = (pair[1] as char)
467 .to_digit(16)
468 .ok_or_else(|| invalid_bytea("invalid hexadecimal digit"))?;
469 bytes.push((hi * 16 + lo) as u8);
470 }
471 return Ok(Value::Bytes(bytes));
472 }
473
474 let input = text.as_bytes();
475 let mut output = Vec::with_capacity(input.len());
476 let mut index = 0;
477 while index < input.len() {
478 if input[index] != b'\\' {
479 output.push(input[index]);
480 index += 1;
481 continue;
482 }
483 if input.get(index + 1) == Some(&b'\\') {
484 output.push(b'\\');
485 index += 2;
486 continue;
487 }
488 let Some(octal) = input.get(index + 1..index + 4) else {
489 return Err(invalid_bytea("invalid input syntax for type bytea"));
490 };
491 if !matches!(octal[0], b'0'..=b'3')
492 || !octal[1..].iter().all(|byte| matches!(byte, b'0'..=b'7'))
493 {
494 return Err(invalid_bytea("invalid input syntax for type bytea"));
495 }
496 output.push((octal[0] - b'0') * 64 + (octal[1] - b'0') * 8 + (octal[2] - b'0'));
497 index += 4;
498 }
499 Ok(Value::Bytes(output))
500}
501
502fn invalid_bytea(message: &str) -> SQLError {
503 SQLError::Routine {
504 sqlstate: "22023".into(),
505 message: message.into(),
506 }
507}
508
509fn parse_uint32_input(text: &str, target: &str) -> Result<Value> {
510 let trimmed = text.trim();
511 let digits = trimmed
512 .strip_prefix('+')
513 .or_else(|| trimmed.strip_prefix('-'))
514 .unwrap_or(trimmed);
515 if digits.is_empty() || !digits.bytes().all(|byte| byte.is_ascii_digit()) {
516 return Err(SQLError::Routine {
517 sqlstate: "22P02".into(),
518 message: format!("invalid input syntax for type {target}: \"{text}\""),
519 });
520 }
521 let parsed = trimmed.parse::<i128>().map_err(|_| SQLError::Routine {
522 sqlstate: "22003".into(),
523 message: format!("value \"{text}\" is out of range for type {target}"),
524 })?;
525 if !((i128::from(i32::MIN))..=i128::from(u32::MAX)).contains(&parsed) {
526 return Err(SQLError::Routine {
527 sqlstate: "22003".into(),
528 message: format!("value \"{text}\" is out of range for type {target}"),
529 });
530 }
531 let value = if parsed < 0 {
532 u32::from_ne_bytes((parsed as i32).to_ne_bytes())
533 } else {
534 parsed as u32
535 };
536 Ok(Value::Int(i64::from(value)))
537}
538
539fn undefined_cast(source: &str, target: &str) -> SQLError {
540 SQLError::Routine {
541 sqlstate: "42846".into(),
542 message: format!("cannot cast type {source} to {target}"),
543 }
544}
545
546fn integer_to_bytea(value: i64, source_ty: Option<&str>) -> Result<Value> {
547 let source = source_ty
548 .map(split_type_modifier)
549 .map(|(base, _)| base)
550 .unwrap_or("integer");
551 let bytes = match source {
552 "smallint" | "int2" | "pg_catalog.int2" => i16::try_from(value)
553 .map(i16::to_be_bytes)
554 .map(|bytes| bytes.to_vec())
555 .map_err(|_| out_of_range("smallint"))?,
556 "bigint" | "int8" | "bigserial" | "serial8" | "pg_catalog.int8" => {
557 value.to_be_bytes().to_vec()
558 }
559 "integer" | "int" | "int4" | "serial" | "serial4" | "pg_catalog.int4" => {
560 i32::try_from(value)
561 .map(i32::to_be_bytes)
562 .map(|bytes| bytes.to_vec())
563 .map_err(|_| out_of_range("integer"))?
564 }
565 other => {
566 return Err(SQLError::TypeMismatch(format!(
567 "cannot cast {other} to bytea"
568 )));
569 }
570 };
571 Ok(Value::Bytes(bytes))
572}
573
574fn cast_uuid(value: &Value) -> Result<Value> {
575 let text = match value {
576 Value::Str(text) | Value::FixedChar(text) => text,
577 other => {
578 return Err(SQLError::TypeMismatch(format!(
579 "cannot cast {other:?} to uuid"
580 )))
581 }
582 };
583 super::uuid::canonicalize_uuid(text).map(Value::Str)
584}
585
586pub(super) fn split_type_modifier(ty: &str) -> (&str, Option<&str>) {
588 match (ty.find('('), ty.rfind(')')) {
589 (Some(open), Some(close)) if close > open => {
590 (ty[..open].trim_end(), Some(&ty[open + 1..close]))
591 }
592 _ => (ty, None),
593 }
594}
595
596pub(super) fn cast_integer(v: &Value, target: &str) -> Result<Value> {
601 let n: i64 = match v {
602 Value::Int(n) => *n,
603 Value::Bool(b) => i64::from(*b),
604 Value::Float(f) => {
605 if !f.is_finite() {
606 return Err(out_of_range(target));
607 }
608 let rounded = f.round_ties_even();
609 if rounded < i64::MIN as f64 || rounded >= 9_223_372_036_854_775_808.0 {
613 return Err(out_of_range(target));
614 }
615 rounded as i64
616 }
617 Value::Decimal(d) => d
618 .round_dp(0)
619 .to_i64_trunc()
620 .ok_or_else(|| out_of_range(target))?,
621 Value::Str(s) | Value::FixedChar(s) => {
622 s.trim().parse::<i64>().map_err(|_| SQLError::Routine {
623 sqlstate: "22P02".into(),
624 message: format!("invalid input syntax for type {target}: \"{s}\""),
625 })?
626 }
627 Value::Bytes(bytes) => bytea_to_integer(bytes, target)?,
628 other => {
629 return Err(SQLError::TypeMismatch(format!(
630 "cannot cast {other:?} to {target}"
631 )));
632 }
633 };
634 let in_range = match target {
635 "smallint" => i16::try_from(n).is_ok(),
636 "integer" => i32::try_from(n).is_ok(),
637 _ => true,
638 };
639 if !in_range {
640 return Err(out_of_range(target));
641 }
642 Ok(Value::Int(n))
643}
644
645fn bytea_to_integer(bytes: &[u8], target: &str) -> Result<i64> {
646 let width = match target {
647 "smallint" => 2,
648 "integer" => 4,
649 _ => 8,
650 };
651 if bytes.len() > width {
652 return Err(out_of_range(target));
653 }
654 let mut extended = [0_u8; 8];
655 let offset = width - bytes.len();
656 extended[8 - width + offset..].copy_from_slice(bytes);
657 Ok(match width {
658 2 => i64::from(i16::from_be_bytes([extended[6], extended[7]])),
659 4 => i64::from(i32::from_be_bytes([
660 extended[4],
661 extended[5],
662 extended[6],
663 extended[7],
664 ])),
665 _ => i64::from_be_bytes(extended),
666 })
667}
668
669pub(super) fn cast_boolean(v: &Value) -> Result<Value> {
673 match v {
674 Value::Bool(b) => Ok(Value::Bool(*b)),
675 Value::Int(n) => Ok(Value::Bool(*n != 0)),
676 Value::Float(f) => Ok(Value::Bool(*f != 0.0)),
677 Value::Decimal(d) => Ok(Value::Bool(!d.is_zero())),
678 Value::Str(s) | Value::FixedChar(s) => {
679 let text = s.trim().to_ascii_lowercase();
680 let matches_prefix = |word: &str| !text.is_empty() && word.starts_with(&text);
681 let value = if matches_prefix("true") || matches_prefix("yes") || text == "1" {
682 Some(true)
683 } else if matches_prefix("false") || matches_prefix("no") || text == "0" {
684 Some(false)
685 } else if "on" == text {
686 Some(true)
687 } else if matches_prefix("off") && text.len() >= 2 {
688 Some(false)
689 } else {
690 None
691 };
692 value.map(Value::Bool).ok_or_else(|| SQLError::Routine {
693 sqlstate: "22P02".into(),
694 message: format!("invalid input syntax for type boolean: \"{s}\""),
695 })
696 }
697 other => Err(SQLError::TypeMismatch(format!(
698 "cannot cast {other:?} to boolean"
699 ))),
700 }
701}
702
703pub fn parse_pg_array_literal(text: &str) -> Result<ArrayValue> {
707 let mut parser = PgArrayLiteralParser::new(text);
708 let (declared_dimensions, items) = parser.parse()?;
709 if let Err(error) = array_shape(&items) {
710 return Err(SQLError::Routine {
711 sqlstate: "22P02".into(),
712 message: format!("malformed array literal: \"{text}\" ({})", error.message()),
713 });
714 }
715 let array = ArrayValue::try_new(items).ok_or_else(|| SQLError::Routine {
716 sqlstate: "22P02".into(),
717 message: format!("malformed array literal: \"{text}\""),
718 })?;
719 let Some(declared_dimensions) = declared_dimensions else {
720 return Ok(array);
721 };
722 let declared_lengths = declared_dimensions
723 .iter()
724 .map(|(_, length)| *length)
725 .collect::<Vec<_>>();
726 if declared_lengths != array.dimensions() {
727 return Err(SQLError::Routine {
728 sqlstate: "22P02".into(),
729 message: format!(
730 "malformed array literal: \"{text}\" (specified array dimensions do not match array contents)"
731 ),
732 });
733 }
734 let lower_bounds = declared_dimensions
735 .into_iter()
736 .map(|(lower, _)| lower)
737 .collect();
738 ArrayValue::with_lower_bounds(array.into_elements(), lower_bounds).ok_or_else(|| {
739 SQLError::Routine {
740 sqlstate: "22P02".into(),
741 message: format!("malformed array literal: \"{text}\""),
742 }
743 })
744}
745
746fn cast_array_elements(
747 items: &[Value],
748 element_type: &str,
749 source_element_type: Option<&str>,
750) -> Result<Vec<Value>> {
751 items
752 .iter()
753 .map(|item| match item {
754 Value::List(nested) => {
755 cast_array_elements(nested, element_type, source_element_type).map(Value::List)
756 }
757 other => cast_value_from(other, element_type, source_element_type),
758 })
759 .collect()
760}
761
762pub(super) struct PgArrayLiteralParser<'a> {
763 source: &'a str,
764 chars: std::iter::Peekable<std::str::Chars<'a>>,
765}
766
767type ParsedArrayLiteral = (Option<Vec<(i32, usize)>>, Vec<Value>);
768
769impl<'a> PgArrayLiteralParser<'a> {
770 fn new(source: &'a str) -> Self {
771 Self {
772 source,
773 chars: source.chars().peekable(),
774 }
775 }
776
777 fn parse(&mut self) -> Result<ParsedArrayLiteral> {
778 self.skip_whitespace();
779 let dimensions = self.parse_dimension_declaration()?;
780 let items = self.parse_array()?;
781 self.skip_whitespace();
782 if self.chars.peek().is_some() {
783 return Err(self.error("unexpected content after closing brace"));
784 }
785 Ok((dimensions, items))
786 }
787
788 fn parse_dimension_declaration(&mut self) -> Result<Option<Vec<(i32, usize)>>> {
789 if self.chars.peek() != Some(&'[') {
790 return Ok(None);
791 }
792 let mut dimensions = Vec::new();
793 while self.chars.next_if_eq(&'[').is_some() {
794 self.skip_whitespace();
795 let lower = self.parse_dimension_bound()?;
796 self.skip_whitespace();
797 if self.chars.next() != Some(':') {
798 return Err(self.error("array dimension must contain `:`"));
799 }
800 self.skip_whitespace();
801 let upper = self.parse_dimension_bound()?;
802 self.skip_whitespace();
803 if self.chars.next() != Some(']') {
804 return Err(self.error("array dimension is missing a closing `]`"));
805 }
806 if upper == i32::MAX {
807 return Err(SQLError::Routine {
808 sqlstate: "54000".into(),
809 message: format!("array upper bound is too large: {upper}"),
810 });
811 }
812 if upper < lower {
813 return Err(SQLError::Routine {
814 sqlstate: "2202E".into(),
815 message: "upper bound cannot be less than lower bound".into(),
816 });
817 }
818 let length = i64::from(upper)
819 .checked_sub(i64::from(lower))
820 .and_then(|difference| difference.checked_add(1))
821 .and_then(|length| usize::try_from(length).ok())
822 .ok_or_else(|| self.error("array dimension is out of range"))?;
823 dimensions.push((lower, length));
824 self.skip_whitespace();
825 }
826 if self.chars.next() != Some('=') {
827 return Err(self.error("array dimensions must be followed by `=`"));
828 }
829 self.skip_whitespace();
830 Ok(Some(dimensions))
831 }
832
833 fn parse_dimension_bound(&mut self) -> Result<i32> {
834 let mut text = String::new();
835 if self
836 .chars
837 .peek()
838 .is_some_and(|character| matches!(character, '+' | '-'))
839 {
840 text.push(self.chars.next().expect("peeked array bound sign"));
841 }
842 while self.chars.peek().is_some_and(char::is_ascii_digit) {
843 text.push(self.chars.next().expect("peeked array bound digit"));
844 }
845 if text.is_empty() || matches!(text.as_str(), "+" | "-") {
846 return Err(self.error("array dimension bound must be an integer"));
847 }
848 text.parse()
849 .map_err(|_| self.error("array dimension bound is out of range"))
850 }
851
852 fn parse_array(&mut self) -> Result<Vec<Value>> {
853 if self.chars.next() != Some('{') {
854 return Err(self.error("array value must start with `{`"));
855 }
856 self.skip_whitespace();
857 if self.chars.next_if_eq(&'}').is_some() {
858 return Ok(Vec::new());
859 }
860
861 let mut items = Vec::new();
862 loop {
863 self.skip_whitespace();
864 items.push(self.parse_element()?);
865 self.skip_whitespace();
866 match self.chars.next() {
867 Some(',') => {
868 self.skip_whitespace();
869 if matches!(self.chars.peek(), None | Some('}')) {
870 return Err(self.error("array contains a missing element"));
871 }
872 }
873 Some('}') => break,
874 Some(_) => {
875 return Err(self.error("array elements must be separated by commas"));
876 }
877 None => return Err(self.error("array is missing a closing `}`")),
878 }
879 }
880 Ok(items)
881 }
882
883 fn parse_element(&mut self) -> Result<Value> {
884 match self.chars.peek() {
885 Some('{') => self.parse_array().map(Value::List),
886 Some('"') => self.parse_quoted_element().map(Value::Str),
887 Some(',') | Some('}') | None => Err(self.error("array contains a missing element")),
888 Some(_) => self.parse_unquoted_element(),
889 }
890 }
891
892 fn parse_quoted_element(&mut self) -> Result<String> {
893 let _opening_quote = self.chars.next();
894 let mut value = String::new();
895 loop {
896 match self.chars.next() {
897 Some('"') => return Ok(value),
898 Some('\\') => value.push(
899 self.chars
900 .next()
901 .ok_or_else(|| self.error("quoted element ends with an escape"))?,
902 ),
903 Some(character) => value.push(character),
904 None => return Err(self.error("array contains an unterminated quoted element")),
905 }
906 }
907 }
908
909 fn parse_unquoted_element(&mut self) -> Result<Value> {
910 let mut value = String::new();
911 let mut significant_len = 0;
912 let mut was_escaped = false;
913 while let Some(character) = self.chars.peek().copied() {
914 match character {
915 ',' | '}' => break,
916 '{' | '"' => {
917 return Err(self.error("array contains an unescaped special character"));
918 }
919 '\\' => {
920 let _escape = self.chars.next();
921 let escaped = self
922 .chars
923 .next()
924 .ok_or_else(|| self.error("array element ends with an escape"))?;
925 value.push(escaped);
926 significant_len = value.len();
927 was_escaped = true;
928 }
929 _ => {
930 let _character = self.chars.next();
931 value.push(character);
932 if !character.is_whitespace() {
933 significant_len = value.len();
934 }
935 }
936 }
937 }
938 value.truncate(significant_len);
939 if value.is_empty() {
940 return Err(self.error("array contains a missing element"));
941 }
942 if !was_escaped && value.eq_ignore_ascii_case("null") {
943 Ok(Value::Null)
944 } else {
945 Ok(Value::Str(value))
946 }
947 }
948
949 fn skip_whitespace(&mut self) {
950 while self
951 .chars
952 .next_if(|character| character.is_whitespace())
953 .is_some()
954 {}
955 }
956
957 fn error(&self, detail: &str) -> SQLError {
958 SQLError::Routine {
959 sqlstate: "22P02".into(),
960 message: format!("malformed array literal: \"{}\" ({detail})", self.source),
961 }
962 }
963}
964
965#[derive(Clone, Copy, Debug, PartialEq, Eq)]
966pub(super) enum ArrayShapeError {
967 MixedNesting,
968 MismatchedDimensions,
969}
970
971impl ArrayShapeError {
972 fn message(self) -> &'static str {
973 match self {
974 Self::MixedNesting => "cannot mix nested arrays and scalar elements",
975 Self::MismatchedDimensions => "multidimensional arrays must have matching dimensions",
976 }
977 }
978}
979
980pub(super) fn array_shape(items: &[Value]) -> std::result::Result<Vec<usize>, ArrayShapeError> {
981 let mut dimensions = vec![items.len()];
982 let mut nested_shape: Option<Vec<usize>> = None;
983 let mut has_scalar = false;
984 for item in items {
985 if let Value::List(nested) = item {
986 let shape = array_shape(nested)?;
987 if has_scalar {
988 return Err(ArrayShapeError::MixedNesting);
989 }
990 if nested_shape
991 .as_ref()
992 .is_some_and(|expected| *expected != shape)
993 {
994 return Err(ArrayShapeError::MismatchedDimensions);
995 }
996 nested_shape = Some(shape);
997 } else {
998 if nested_shape.is_some() {
999 return Err(ArrayShapeError::MixedNesting);
1000 }
1001 has_scalar = true;
1002 }
1003 }
1004 if let Some(shape) = nested_shape {
1005 dimensions.extend(shape);
1006 }
1007 Ok(dimensions)
1008}
1009
1010pub fn array_dimensions(items: &[Value]) -> Result<Vec<usize>> {
1015 array_shape(items).map_err(|error| SQLError::TypeMismatch(error.message().to_string()))
1016}
1017
1018#[derive(Clone, Copy)]
1019pub(super) enum TemporalCastTarget {
1020 Date,
1021 Time,
1022 TimeTz,
1023 Timestamp,
1024 TimestampTz,
1025 Interval,
1026}
1027
1028pub(super) fn cast_temporal(
1029 v: &Value,
1030 target: TemporalCastTarget,
1031 parse: fn(&str) -> Option<TemporalValue>,
1032 ty: &str,
1033) -> Result<Value> {
1034 match v {
1035 Value::Temporal(value) => cast_temporal_kind(value, target)
1036 .map(Value::Temporal)
1037 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot cast {v:?} to {ty}"))),
1038 other => parse(&value_to_string(other))
1039 .map(Value::Temporal)
1040 .ok_or_else(|| SQLError::TypeMismatch(format!("cannot cast {v:?} to {ty}"))),
1041 }
1042}
1043
1044fn cast_date(v: &Value, source_ty: Option<&str>) -> Result<Value> {
1045 match v {
1046 Value::Temporal(value) => cast_temporal_kind(value, TemporalCastTarget::Date)
1047 .map(Value::Temporal)
1048 .ok_or_else(|| undefined_cast(&canonical_cast_source(source_ty, v), "date")),
1049 Value::Str(text) | Value::FixedChar(text) => TemporalValue::try_parse_date(text)
1050 .map(Value::Temporal)
1051 .map_err(|error| {
1052 let field_overflow = matches!(
1053 error.kind(),
1054 chrono::format::ParseErrorKind::OutOfRange
1055 | chrono::format::ParseErrorKind::Impossible
1056 );
1057 SQLError::Routine {
1058 sqlstate: if field_overflow { "22008" } else { "22007" }.into(),
1059 message: if field_overflow {
1060 format!("date/time field value out of range: \"{text}\"")
1061 } else {
1062 format!("invalid input syntax for type date: \"{text}\"")
1063 },
1064 }
1065 }),
1066 _ => Err(undefined_cast(&canonical_cast_source(source_ty, v), "date")),
1067 }
1068}
1069
1070fn cast_temporal_kind(value: &TemporalValue, target: TemporalCastTarget) -> Option<TemporalValue> {
1071 const MICROS_PER_DAY: i64 = 86_400_000_000;
1072 match (target, value) {
1073 (TemporalCastTarget::Date, TemporalValue::Date { days }) => {
1074 Some(TemporalValue::Date { days: *days })
1075 }
1076 (
1077 TemporalCastTarget::Date,
1078 TemporalValue::Timestamp { micros } | TemporalValue::TimestampTz { micros },
1079 ) => Some(TemporalValue::Date {
1080 days: i32::try_from(micros.div_euclid(MICROS_PER_DAY)).ok()?,
1081 }),
1082 (TemporalCastTarget::Time, TemporalValue::Time { micros })
1083 | (TemporalCastTarget::Time, TemporalValue::TimeTz { micros, .. })
1084 | (
1085 TemporalCastTarget::Time,
1086 TemporalValue::Timestamp { micros } | TemporalValue::TimestampTz { micros },
1087 )
1088 | (TemporalCastTarget::Time, TemporalValue::Interval { micros, .. }) => {
1089 Some(TemporalValue::Time {
1090 micros: micros.rem_euclid(MICROS_PER_DAY),
1091 })
1092 }
1093 (
1094 TemporalCastTarget::TimeTz,
1095 TemporalValue::TimeTz {
1096 micros,
1097 offset_minutes,
1098 },
1099 ) => Some(TemporalValue::TimeTz {
1100 micros: *micros,
1101 offset_minutes: *offset_minutes,
1102 }),
1103 (TemporalCastTarget::TimeTz, TemporalValue::Time { micros })
1104 | (TemporalCastTarget::TimeTz, TemporalValue::TimestampTz { micros }) => {
1105 Some(TemporalValue::TimeTz {
1106 micros: micros.rem_euclid(MICROS_PER_DAY),
1107 offset_minutes: 0,
1108 })
1109 }
1110 (TemporalCastTarget::Timestamp, TemporalValue::Timestamp { micros })
1111 | (TemporalCastTarget::Timestamp, TemporalValue::TimestampTz { micros }) => {
1112 Some(TemporalValue::Timestamp { micros: *micros })
1113 }
1114 (TemporalCastTarget::Timestamp, TemporalValue::Date { days }) => {
1115 Some(TemporalValue::Timestamp {
1116 micros: i64::from(*days).checked_mul(MICROS_PER_DAY)?,
1117 })
1118 }
1119 (TemporalCastTarget::TimestampTz, TemporalValue::TimestampTz { micros })
1120 | (TemporalCastTarget::TimestampTz, TemporalValue::Timestamp { micros }) => {
1121 Some(TemporalValue::TimestampTz { micros: *micros })
1122 }
1123 (TemporalCastTarget::TimestampTz, TemporalValue::Date { days }) => {
1124 Some(TemporalValue::TimestampTz {
1125 micros: i64::from(*days).checked_mul(MICROS_PER_DAY)?,
1126 })
1127 }
1128 (
1129 TemporalCastTarget::Interval,
1130 TemporalValue::Interval {
1131 months,
1132 days,
1133 micros,
1134 },
1135 ) => Some(TemporalValue::Interval {
1136 months: *months,
1137 days: *days,
1138 micros: *micros,
1139 }),
1140 (TemporalCastTarget::Interval, TemporalValue::Time { micros }) => {
1141 Some(TemporalValue::Interval {
1142 months: 0,
1143 days: 0,
1144 micros: *micros,
1145 })
1146 }
1147 _ => None,
1148 }
1149}
1150
1151#[cfg(test)]
1152mod tests {
1153 use super::*;
1154
1155 #[test]
1156 fn array_literal_rejects_postgresql_unrepresentable_upper_bound() {
1157 let error = parse_pg_array_literal("[2147483647:2147483647]={1}").unwrap_err();
1158 assert_eq!(error.sqlstate(), Some("54000"));
1159 assert_eq!(
1160 error.to_string(),
1161 "array upper bound is too large: 2147483647"
1162 );
1163 assert!(parse_pg_array_literal("[2147483646:2147483646]={1}").is_ok());
1164 }
1165 use uqa_core::DecimalValue;
1166
1167 #[test]
1168 fn temporal_cross_casts_convert_the_carrier_kind() {
1169 let date = Value::Temporal(TemporalValue::parse_date("2020-01-02").unwrap());
1170 assert_eq!(
1171 cast_value(&date, "timestamp").unwrap(),
1172 Value::Temporal(TemporalValue::parse_timestamp("2020-01-02 00:00:00").unwrap())
1173 );
1174 let timestamp =
1175 Value::Temporal(TemporalValue::parse_timestamp("2020-01-02 03:04:05").unwrap());
1176 assert_eq!(
1177 cast_value(×tamp, "date").unwrap(),
1178 Value::Temporal(TemporalValue::parse_date("2020-01-02").unwrap())
1179 );
1180 assert_eq!(
1181 cast_value(×tamp, "time").unwrap(),
1182 Value::Temporal(TemporalValue::parse_time("03:04:05").unwrap())
1183 );
1184 let interval = Value::Temporal(TemporalValue::parse_interval("1 day 25:02:03").unwrap());
1185 assert_eq!(
1186 cast_value(&interval, "time").unwrap(),
1187 Value::Temporal(TemporalValue::parse_time("01:02:03").unwrap())
1188 );
1189 }
1190
1191 #[test]
1192 fn uuid_cast_matches_postgresql_input_and_canonical_output() {
1193 for input in [
1194 "A0EEBC99-9C0B-4EF8-BB6D-6BB9BD380A11",
1195 "a0eebc999c0b4ef8bb6d6bb9bd380a11",
1196 "{a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11}",
1197 "a0ee-bc99-9c0b-4ef8-bb6d-6bb9-bd38-0a11",
1198 ] {
1199 assert_eq!(
1200 cast_value(&Value::Str(input.into()), "uuid").unwrap(),
1201 Value::Str("a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11".into())
1202 );
1203 }
1204 }
1205
1206 #[test]
1207 fn uuid_cast_rejects_postgresql_invalid_forms() {
1208 for input in [
1209 " a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 ",
1210 "a0e-ebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
1211 "not-a-uuid",
1212 ] {
1213 let error = cast_value(&Value::Str(input.into()), "uuid").unwrap_err();
1214 assert_eq!(error.sqlstate(), Some("22P02"));
1215 }
1216 }
1217
1218 #[test]
1219 fn oid_cast_preserves_postgresql_source_type_rules() {
1220 assert_eq!(
1221 cast_value_from(&Value::Int(-1), "oid", Some("smallint")).unwrap(),
1222 Value::Int(i64::from(u32::MAX))
1223 );
1224 assert_eq!(
1225 cast_value_from(&Value::Int(-1), "oid", Some("integer")).unwrap(),
1226 Value::Int(i64::from(u32::MAX))
1227 );
1228 assert_eq!(
1229 cast_value_from(&Value::Int(i64::from(u32::MAX)), "oid", Some("bigint")).unwrap(),
1230 Value::Int(i64::from(u32::MAX))
1231 );
1232 let error = cast_value_from(&Value::Int(-1), "oid", Some("bigint")).unwrap_err();
1233 assert_eq!(error.sqlstate(), Some("22003"));
1234 assert_eq!(error.to_string(), "OID out of range");
1235 for source in ["boolean", "numeric", "double precision"] {
1236 let value = match source {
1237 "boolean" => Value::Bool(true),
1238 "numeric" => Value::Decimal(DecimalValue::from_i64(1)),
1239 _ => Value::Float(1.0),
1240 };
1241 let error = cast_value_from(&value, "oid", Some(source)).unwrap_err();
1242 assert_eq!(error.sqlstate(), Some("42846"));
1243 }
1244 }
1245
1246 #[test]
1247 fn regclass_cast_preserves_bound_relation_names_and_oid_carriers() {
1248 assert_eq!(
1249 cast_value_from(
1250 &Value::Str("app.items".into()),
1251 "pg_catalog.regclass",
1252 Some("unknown")
1253 )
1254 .unwrap(),
1255 Value::Str("app.items".into())
1256 );
1257 assert_eq!(
1258 cast_value_from(&Value::Int(2205), "regclass", Some("oid")).unwrap(),
1259 Value::Int(2205)
1260 );
1261 }
1262
1263 #[test]
1264 fn regtype_zero_uses_postgresql_dash_text_output() {
1265 for source in ["regproc", "regclass", "regnamespace", "regtype"] {
1266 assert_eq!(
1267 cast_value_from(&Value::Int(0), "text", Some(source)).unwrap(),
1268 Value::Str("-".into()),
1269 "{source}"
1270 );
1271 }
1272 assert_eq!(
1273 cast_value_from(&Value::Int(42), "text", Some("regproc")).unwrap(),
1274 Value::Str("42".into())
1275 );
1276 }
1277
1278 #[test]
1279 fn oid_and_xid_text_input_use_postgresql_uint32_syntax() {
1280 for target in ["oid", "xid"] {
1281 assert_eq!(
1282 cast_value(&Value::Str("-1".into()), target).unwrap(),
1283 Value::Int(i64::from(u32::MAX))
1284 );
1285 assert_eq!(
1286 cast_value(&Value::Str(i32::MIN.to_string()), target).unwrap(),
1287 Value::Int(i64::from(i32::MIN as u32))
1288 );
1289 assert_eq!(
1290 cast_value(&Value::Str(u32::MAX.to_string()), target).unwrap(),
1291 Value::Int(i64::from(u32::MAX))
1292 );
1293 for input in ["-2147483649", "4294967296"] {
1294 let error = cast_value(&Value::Str(input.into()), target).unwrap_err();
1295 assert_eq!(error.sqlstate(), Some("22003"));
1296 }
1297 let error = cast_value(&Value::Str("1.0".into()), target).unwrap_err();
1298 assert_eq!(error.sqlstate(), Some("22P02"));
1299 }
1300 }
1301
1302 #[test]
1303 fn xid_rejects_integer_and_oid_cast_sources() {
1304 for source in ["smallint", "integer", "bigint", "oid"] {
1305 let error = cast_value_from(&Value::Int(1), "xid", Some(source)).unwrap_err();
1306 assert_eq!(error.sqlstate(), Some("42846"));
1307 }
1308 }
1309
1310 #[test]
1311 fn legacy_vector_text_casts_use_postgresql_space_separation() {
1312 let vector = Value::List(vec![Value::Int(23), Value::Int(25)]);
1313 assert_eq!(
1314 cast_value_from(&vector, "text", Some("oidvector")).unwrap(),
1315 Value::Str("23 25".into())
1316 );
1317 let stored = Value::Array(ArrayValue::try_new(vec![Value::Int(1), Value::Int(3)]).unwrap());
1318 assert_eq!(
1319 cast_value_from(&stored, "text", Some("int2vector")).unwrap(),
1320 Value::Str("1 3".into())
1321 );
1322 assert_eq!(
1323 cast_value_from(
1324 &Value::List(Vec::new()),
1325 "text",
1326 Some("pg_catalog.int2vector")
1327 )
1328 .unwrap(),
1329 Value::Str(String::new())
1330 );
1331 }
1332
1333 #[test]
1334 fn bytea_cast_preserves_postgresql_source_type_and_input_rules() {
1335 assert_eq!(
1336 cast_value_from(&Value::Int(-1), "bytea", Some("smallint")).unwrap(),
1337 Value::Bytes(vec![0xff, 0xff])
1338 );
1339 assert_eq!(
1340 cast_value_from(&Value::Int(-1), "bytea", Some("integer")).unwrap(),
1341 Value::Bytes(vec![0xff; 4])
1342 );
1343 assert_eq!(
1344 cast_value_from(&Value::Int(-1), "bytea", Some("bigint")).unwrap(),
1345 Value::Bytes(vec![0xff; 8])
1346 );
1347 assert_eq!(
1348 cast_value_from(&Value::Str("\\x6162".into()), "bytea", Some("text")).unwrap(),
1349 Value::Bytes(b"ab".to_vec())
1350 );
1351 assert_eq!(
1352 cast_value_from(&Value::Str("a\\\\b\\141".into()), "bytea", Some("text")).unwrap(),
1353 Value::Bytes(b"a\\ba".to_vec())
1354 );
1355 for (value, source) in [
1356 (Value::Bool(true), "boolean"),
1357 (Value::Decimal(DecimalValue::from_i64(1)), "numeric"),
1358 (Value::Float(1.0), "double precision"),
1359 ] {
1360 let error = cast_value_from(&value, "bytea", Some(source)).unwrap_err();
1361 assert_eq!(error.sqlstate(), Some("42846"));
1362 }
1363 for input in ["\\x1", "\\xzz", "\\9"] {
1364 let error = cast_value(&Value::Str(input.into()), "bytea").unwrap_err();
1365 assert_eq!(error.sqlstate(), Some("22023"));
1366 }
1367 }
1368
1369 #[test]
1370 fn unary_minus_preserves_integer_width_and_overflow() {
1371 for (source, input, expected) in [
1372 ("smallint", 1_i64, -1_i64),
1373 ("integer", 1_i64, -1_i64),
1374 ("bigint", 1_i64, -1_i64),
1375 ] {
1376 assert_eq!(
1377 negate_value(&Value::Int(input), Some(source)).unwrap(),
1378 Value::Int(expected)
1379 );
1380 }
1381 for (source, minimum) in [
1382 ("smallint", i64::from(i16::MIN)),
1383 ("integer", i64::from(i32::MIN)),
1384 ("bigint", i64::MIN),
1385 ] {
1386 let error = negate_value(&Value::Int(minimum), Some(source)).unwrap_err();
1387 assert_eq!(error.sqlstate(), Some("22003"));
1388 }
1389 }
1390
1391 #[test]
1392 fn unary_minus_preserves_interval_fields() {
1393 assert_eq!(
1394 negate_value(
1395 &Value::Temporal(TemporalValue::Interval {
1396 months: 2,
1397 days: -3,
1398 micros: 4,
1399 }),
1400 Some("interval"),
1401 )
1402 .unwrap(),
1403 Value::Temporal(TemporalValue::Interval {
1404 months: -2,
1405 days: 3,
1406 micros: -4,
1407 })
1408 );
1409 }
1410}