1mod array;
10mod binary_oid;
11mod legacy_vector;
12mod temporal;
13
14use super::conversion::value_to_string_with_control;
15use super::{out_of_range, ArrayValue, Result, SQLError, TemporalValue, Value};
16use crate::ast::RangeSubtype;
17use uqa_core::memory::{Produced, ProductionControl, ProductionString, ProductionVec};
18
19pub fn cast_value(v: &Value, ty: &str) -> Result<Value> {
23 cast_value_from(v, ty, None)
24}
25
26pub fn cast_value_from(v: &Value, ty: &str, source_ty: Option<&str>) -> Result<Value> {
28 cast_value_from_with_control(v, ty, source_ty, &ProductionControl::uncontrolled())?
29 .into_uncontrolled()
30 .map_err(|_| SQLError::Internal("ordinary cast production owner".into()))
31}
32
33#[expect(
34 clippy::too_many_lines,
35 reason = "cast matrix preserves source-target and error precedence"
36)]
37pub fn cast_value_from_with_control(
38 v: &Value,
39 ty: &str,
40 source_ty: Option<&str>,
41 control: &ProductionControl<'_>,
42) -> Result<Produced<Value>> {
43 control.check()?;
44 if ty.trim().strip_suffix("[]").is_some_and(|element| {
45 element.trim().eq_ignore_ascii_case("void")
46 || element.trim().eq_ignore_ascii_case("pg_catalog.void")
47 }) {
48 return Err(SQLError::Routine {
49 sqlstate: "42704".into(),
50 message: "type \"void[]\" does not exist".into(),
51 });
52 }
53 if matches!(v, Value::Null) {
54 return Ok(control.finish(Value::Null, control.empty_reservation())?);
55 }
56 let (base, modifier) = crate::ast::split_type_modifier_with_control(ty, control)?;
57 let target = base
58 .trim()
59 .strip_prefix("pg_catalog.")
60 .unwrap_or(base.trim());
61 if matches!(v, Value::Void)
62 && !matches!(
63 target,
64 "void"
65 | "text"
66 | "name"
67 | "varchar"
68 | "character varying"
69 | "bpchar"
70 | "character"
71 | "char"
72 )
73 {
74 return Err(undefined_cast("void", postgres_type_display_name(target)));
75 }
76 if let Some(element_type) = ty.strip_suffix("[]") {
77 let source_element_type = source_ty
78 .and_then(|source| source.trim().strip_suffix("[]"))
79 .map(str::trim)
80 .or(match v {
81 Value::LegacyVector(vector) => Some(match vector.kind() {
82 uqa_core::LegacyVectorKind::SmallInteger => "smallint",
83 uqa_core::LegacyVectorKind::Oid => "oid",
84 }),
85 _ => None,
86 });
87 let parsed;
88 let array = match v {
89 Value::Array(array) => array,
90 Value::LegacyVector(vector) => vector.as_array(),
91 Value::Str(text) => {
92 parsed = array::parse_pg_array_literal_with_control(text, control)?;
93 &parsed
94 }
95 other => {
96 return Err(SQLError::TypeMismatch(format!(
97 "CAST AS {ty}: expected array, got {other:?}"
98 )))
99 }
100 };
101 let elements = array::cast_array_elements(
102 array.elements(),
103 element_type,
104 source_element_type,
105 control,
106 )?;
107 let normalize_empty = array.elements().is_empty()
108 && !array::binary_compatible_elements(source_element_type, element_type, control)?;
109 let mut bounds = ProductionVec::new(*control);
110 bounds.reserve(array.lower_bounds().len())?;
111 if !normalize_empty {
112 for lower in array.lower_bounds() {
113 bounds.push_copy(*lower)?;
114 }
115 }
116 let array =
117 ArrayValue::with_lower_bounds_with_control(elements, bounds.finish()?, control)?
118 .ok_or_else(|| {
119 SQLError::TypeMismatch("array dimensions changed during cast".into())
120 })?;
121 let (array, memory) = array.into_parts();
122 return Ok(control.finish(Value::Array(array), memory)?);
123 }
124 let value = match &**base {
125 "void" | "pg_catalog.void" => {
126 let source = canonical_cast_source_with_control(source_ty, v, control)?;
127 if matches!(
128 source.as_str(),
129 "unknown" | "text" | "name" | "varchar" | "bpchar" | "void"
130 ) {
131 Ok(Value::Void)
132 } else {
133 Err(undefined_cast(postgres_type_display_name(&source), "void"))
134 }
135 }
136 "smallint" | "int2" | "pg_catalog.int2" => cast_integer(v, "smallint", control),
137 "integer" | "int" | "int4" | "serial" | "serial4" | "pg_catalog.int4" => {
138 binary_oid::cast_integer_from(v, source_ty, control)
139 }
140 "bigint" | "int8" | "bigserial" | "serial8" | "pg_catalog.int8" => {
141 cast_integer(v, "bigint", control)
142 }
143 "real" | "float4" | "pg_catalog.float4" => {
144 super::floating::to_float_with_control(v, super::FloatWidth::Real, control)
145 .map(Value::Float)
146 }
147 "float8" | "double" | "double precision" | "pg_catalog.float8" => {
148 super::floating::to_float_with_control(v, super::FloatWidth::DoublePrecision, control)
149 .map(Value::Float)
150 }
151 "numeric" | "decimal" => {
152 let value = super::conversion::to_decimal_with_control(v, control)?;
153 let value = if let Some(modifier) = modifier {
154 let mut parts = modifier.split(',').map(str::trim);
155 let precision: u32 = parts
156 .next()
157 .and_then(|p| p.parse().ok())
158 .ok_or_else(|| SQLError::TypeMismatch("bad numeric precision".into()))?;
159 let scale: i32 = parts.next().and_then(|s| s.parse().ok()).unwrap_or(0);
160 let rounded = value
161 .round_to_scale_with_control(scale, control)?
162 .ok_or_else(|| out_of_range("numeric"))?;
163 if !rounded.fits_precision_with_control(precision, scale, control)? {
164 return Err(SQLError::Routine { sqlstate: "22003".into(), message: format!("numeric field overflow: A field with precision {precision}, scale {scale} cannot hold value {}", value.to_sql_string()) });
165 }
166 rounded
167 } else {
168 value
169 };
170 let (value, memory) = value.into_parts();
171 return Ok(control.finish(Value::Decimal(value), memory)?);
172 }
173 "regproc" | "regprocedure" | "regrole" | "regtype" if matches!(v, Value::Int(_)) => {
174 Ok(v.clone())
175 }
176 "text"
177 | "refcursor"
178 | "pg_catalog.refcursor"
179 | "name"
180 | "regproc"
181 | "regprocedure"
182 | "regtype"
183 | "pg_node_tree"
184 | "aclitem" => {
185 let source = source_ty
186 .map(str::trim)
187 .map(|source| source.strip_prefix("pg_catalog.").unwrap_or(source));
188 let text = match (source, v) {
189 (Some("int2vector" | "oidvector"), _) => {
190 match super::conversion::vector_value_to_string_with_control(v, control)? {
191 Some(text) => text,
192 None => value_to_string_with_control(v, control)?,
193 }
194 }
195 (
196 Some(
197 "regproc" | "regprocedure" | "regclass" | "regnamespace" | "regrole"
198 | "regtype",
199 ),
200 Value::Int(0),
201 ) => control.copy_text("-")?,
202 _ => cast_text(v, source_ty, control)?,
203 };
204 return text_value(text, false, control);
205 }
206 "int2vector" | "pg_catalog.int2vector" => {
207 return legacy_vector::cast_int2vector(v, source_ty, control)
208 }
209 "oidvector" | "pg_catalog.oidvector" => {
210 return legacy_vector::cast_oidvector(v, source_ty, control)
211 }
212 "oid" | "pg_catalog.oid" => cast_oid(v, source_ty, control),
213 "regclass" | "pg_catalog.regclass" => return cast_regclass(v, source_ty, control),
214 "regnamespace" | "pg_catalog.regnamespace" => {
215 return cast_regnamespace(v, source_ty, control)
216 }
217 "regrole" | "pg_catalog.regrole" => return cast_regrole(v, source_ty, control),
218 "xid" | "pg_catalog.xid" => cast_xid(v, source_ty, control),
219 "\"char\"" => {
220 let text = value_to_string_with_control(v, control)?;
221 let mut characters = text.chars();
222 if let Some(character) = characters.next() {
223 if characters.next().is_some() || !character.is_ascii() {
224 return Err(SQLError::TypeMismatch(format!(
225 "value too long for type character(1): {:?}",
226 text.as_str()
227 )));
228 }
229 }
230 return text_value(text, false, control);
231 }
232 "uuid" => return cast_uuid(v, control),
233 "varchar" | "character varying" => {
234 let text = cast_text(v, source_ty, control)?;
235 let Some(modifier) = modifier else {
236 return text_value(text, false, control);
237 };
238 let limit: usize = modifier
239 .trim()
240 .parse()
241 .map_err(|_| SQLError::TypeMismatch(format!("bad length modifier {modifier}")))?;
242 return character_value(text, limit, false, control);
243 }
244 "bpchar" if modifier.is_none() => {
245 return text_value(cast_text(v, source_ty, control)?, true, control)
246 }
247 "character" | "char" | "bpchar" => {
248 let text = cast_text(v, source_ty, control)?;
249 let limit: usize = match modifier {
250 Some(modifier) => modifier.trim().parse().map_err(|_| {
251 SQLError::TypeMismatch(format!("bad length modifier {modifier}"))
252 })?,
253 None => 1,
254 };
255 if limit == 0 {
256 return Err(SQLError::TypeMismatch(
257 "CHARACTER length must be greater than zero".into(),
258 ));
259 }
260 return character_value(text, limit, true, control);
261 }
262 "date" => cast_date(v, source_ty, control),
263 "time" | "time without time zone" => cast_temporal(
264 v,
265 TemporalCastTarget::Time,
266 TemporalValue::parse_time_with_control,
267 "time",
268 modifier,
269 control,
270 ),
271 "timetz" | "time with time zone" => cast_temporal(
272 v,
273 TemporalCastTarget::TimeTz,
274 TemporalValue::parse_time_tz_with_control,
275 "time with time zone",
276 modifier,
277 control,
278 ),
279 "timestamp" | "datetime" | "timestamp without time zone" => cast_temporal(
280 v,
281 TemporalCastTarget::Timestamp,
282 TemporalValue::parse_timestamp_with_control,
283 "timestamp",
284 modifier,
285 control,
286 ),
287 "timestamptz" | "timestamp with time zone" => cast_temporal(
288 v,
289 TemporalCastTarget::TimestampTz,
290 TemporalValue::parse_timestamp_tz_with_control,
291 "timestamp with time zone",
292 modifier,
293 control,
294 ),
295 "interval" => temporal::cast_interval(v, ty, control),
296 name if name.starts_with("interval ") => temporal::cast_interval(v, ty, control),
297 "int4range" => return cast_range(v, source_ty, RangeSubtype::Integer, control),
298 "int8range" => return cast_range(v, source_ty, RangeSubtype::BigInteger, control),
299 "numrange" => return cast_range(v, source_ty, RangeSubtype::Numeric, control),
300 "daterange" => return cast_range(v, source_ty, RangeSubtype::Date, control),
301 "tsrange" => return cast_range(v, source_ty, RangeSubtype::Timestamp, control),
302 "tstzrange" => return cast_range(v, source_ty, RangeSubtype::TimestampTz, control),
303 "int4multirange" => return cast_multirange(v, source_ty, RangeSubtype::Integer, control),
304 "int8multirange" => {
305 return cast_multirange(v, source_ty, RangeSubtype::BigInteger, control)
306 }
307 "nummultirange" => return cast_multirange(v, source_ty, RangeSubtype::Numeric, control),
308 "datemultirange" => return cast_multirange(v, source_ty, RangeSubtype::Date, control),
309 "tsmultirange" => return cast_multirange(v, source_ty, RangeSubtype::Timestamp, control),
310 "tstzmultirange" => {
311 return cast_multirange(v, source_ty, RangeSubtype::TimestampTz, control)
312 }
313 "json" => return super::json::cast_json_value_with_control(v, false, control),
314 "jsonb" => return super::json::cast_json_value_with_control(v, true, control),
315 "bytea" => return cast_bytea(v, source_ty, control),
316 "boolean" | "bool" => cast_boolean(v),
317 other => Err(SQLError::Unsupported(format!("CAST AS {other}"))),
318 }?;
319 Ok(control.finish(value, control.empty_reservation())?)
320}
321
322fn text_value(
323 text: Produced<String>,
324 fixed: bool,
325 control: &ProductionControl<'_>,
326) -> Result<Produced<Value>> {
327 let (text, memory) = text.into_parts();
328 Ok(control.finish(
329 if fixed {
330 Value::FixedChar(text)
331 } else {
332 Value::Str(text)
333 },
334 memory,
335 )?)
336}
337
338fn character_value(
339 text: Produced<String>,
340 limit: usize,
341 fixed: bool,
342 control: &ProductionControl<'_>,
343) -> Result<Produced<Value>> {
344 let mut count = 0;
345 let mut end = 0;
346 for (index, character) in text.char_indices().take(limit) {
347 control.check()?;
348 count += 1;
349 end = index + character.len_utf8();
350 }
351 let mut output = ProductionString::from_produced(text, *control)?;
352 output.truncate(end)?;
353 if fixed {
354 for _ in count..limit {
355 output.push(' ')?;
356 }
357 }
358 text_value(output.finish()?, fixed, control)
359}
360
361fn cast_range(
362 v: &Value,
363 source_ty: Option<&str>,
364 subtype: RangeSubtype,
365 control: &ProductionControl<'_>,
366) -> Result<Produced<Value>> {
367 let source = source_ty
368 .map(|source| canonical_type_name(source, control))
369 .transpose()?;
370 let source = source.as_ref().map(|source| source.as_str());
371 if source.is_some_and(|source| {
372 source != subtype.range_name() && !matches!(source, "unknown" | "cstring")
373 }) {
374 return Err(undefined_cast(
375 source.unwrap_or("unknown"),
376 subtype.range_name(),
377 ));
378 }
379 let (Value::Str(text) | Value::FixedChar(text)) = v else {
380 return Err(undefined_cast(
381 source.unwrap_or("unknown"),
382 subtype.range_name(),
383 ));
384 };
385 text_value(
386 super::range::canonical_range_text_with_control(text, subtype, control)?,
387 false,
388 control,
389 )
390}
391
392fn cast_multirange(
393 v: &Value,
394 source_ty: Option<&str>,
395 subtype: RangeSubtype,
396 control: &ProductionControl<'_>,
397) -> Result<Produced<Value>> {
398 let source = source_ty
399 .map(|source| canonical_type_name(source, control))
400 .transpose()?;
401 let source = source.as_ref().map(|source| source.as_str());
402 let (Value::Str(text) | Value::FixedChar(text)) = v else {
403 return Err(undefined_cast(
404 source.unwrap_or("unknown"),
405 subtype.multirange_name(),
406 ));
407 };
408 let text = match source {
409 Some(source) if source == subtype.range_name() => {
410 super::range::canonical_range_as_multirange_text_with_control(text, subtype, control)?
411 }
412 None | Some("unknown" | "cstring") => {
413 super::range::canonical_multirange_text_with_control(text, subtype, control)?
414 }
415 Some(source) if source == subtype.multirange_name() => {
416 super::range::canonical_multirange_text_with_control(text, subtype, control)?
417 }
418 Some(source) => return Err(undefined_cast(source, subtype.multirange_name())),
419 };
420 text_value(text, false, control)
421}
422
423fn canonical_type_name(
424 type_name: &str,
425 control: &ProductionControl<'_>,
426) -> Result<Produced<String>> {
427 let mut normalized = ProductionString::new(*control);
428 for character in type_name.trim().chars() {
429 normalized.push(character.to_ascii_lowercase())?;
430 }
431 Ok(control.copy_text(
432 normalized
433 .strip_prefix("pg_catalog.")
434 .unwrap_or(&normalized),
435 )?)
436}
437
438pub fn negate_value(value: &Value, source_ty: Option<&str>) -> Result<Value> {
440 negate_value_with_control(value, source_ty, &ProductionControl::uncontrolled())?
441 .into_uncontrolled()
442 .map_err(|_| SQLError::Internal("ordinary negation owner".into()))
443}
444
445pub fn negate_value_with_control(
446 value: &Value,
447 source_ty: Option<&str>,
448 control: &ProductionControl<'_>,
449) -> Result<Produced<Value>> {
450 control.check()?;
451 if matches!(value, Value::Null) {
452 return Ok(control.finish(Value::Null, control.empty_reservation())?);
453 }
454 let source = canonical_cast_source_with_control(source_ty, value, control)?;
455 let result = match (source.as_str(), value) {
456 ("int2", Value::Int(value)) => i16::try_from(*value)
457 .ok()
458 .and_then(i16::checked_neg)
459 .map(|value| Value::Int(i64::from(value)))
460 .ok_or_else(|| out_of_range("smallint")),
461 ("int4", Value::Int(value)) => i32::try_from(*value)
462 .ok()
463 .and_then(i32::checked_neg)
464 .map(|value| Value::Int(i64::from(value)))
465 .ok_or_else(|| out_of_range("integer")),
466 ("int8", Value::Int(value)) => value
467 .checked_neg()
468 .map(Value::Int)
469 .ok_or_else(|| out_of_range("bigint")),
470 ("float4" | "float8", Value::Float(value)) => Ok(Value::Float(-value)),
471 ("numeric", Value::Decimal(value)) => {
472 let (value, memory) = value.negated_with_control(control)?.into_parts();
473 return Ok(control.finish(Value::Decimal(value), memory)?);
474 }
475 (
476 "interval",
477 Value::Temporal(TemporalValue::Interval {
478 months,
479 days,
480 micros,
481 }),
482 ) => Ok(Value::Temporal(TemporalValue::Interval {
483 months: months
484 .checked_neg()
485 .ok_or_else(|| out_of_range("interval"))?,
486 days: days.checked_neg().ok_or_else(|| out_of_range("interval"))?,
487 micros: micros
488 .checked_neg()
489 .ok_or_else(|| out_of_range("interval"))?,
490 })),
491 _ => Err(SQLError::TypeMismatch(format!(
492 "operator does not exist: - {}",
493 source.as_str()
494 ))),
495 }?;
496 Ok(control.finish(result, control.empty_reservation())?)
497}
498
499fn canonical_cast_source_with_control(
500 source_ty: Option<&str>,
501 value: &Value,
502 control: &ProductionControl<'_>,
503) -> Result<Produced<String>> {
504 let source = source_ty.unwrap_or(match value {
505 Value::Str(_) | Value::FixedChar(_) => "unknown",
506 Value::Int(_) => "integer",
507 Value::Bool(_) => "boolean",
508 Value::Float(_) => "double precision",
509 Value::Decimal(_) => "numeric",
510 Value::Bytes(_) => "bytea",
511 Value::Temporal(TemporalValue::Interval { .. }) => "interval",
512 Value::Temporal(_) => "timestamp",
513 Value::Json(_) => "json",
514 Value::JsonB(_) => "jsonb",
515 Value::Array(_) => "anyarray",
516 Value::LegacyVector(vector) => vector.kind().type_name(),
517 Value::List(_) => "anyarray",
518 Value::Row(_) | Value::Record(_) => "record",
519 Value::Map(_) => "jsonb",
520 Value::Null => "unknown",
521 Value::Void => "void",
522 });
523 let (source, _) = crate::ast::split_type_modifier_with_control(source, control)?;
524 let mut normalized = ProductionString::new(*control);
525 for (index, word) in source.split_whitespace().enumerate() {
526 if index != 0 {
527 normalized.push(' ')?;
528 }
529 for character in word.chars() {
530 normalized.push(character.to_ascii_lowercase())?;
531 }
532 }
533 let source = normalized
534 .strip_prefix("pg_catalog.")
535 .unwrap_or(&normalized);
536 let canonical = match source {
537 "smallint" | "int2" => "int2",
538 "integer" | "int" | "int4" | "serial" | "serial4" => "int4",
539 "bigint" | "int8" | "bigserial" | "serial8" => "int8",
540 "character varying" | "varchar" => "varchar",
541 "character" | "char" | "bpchar" => "bpchar",
542 "boolean" | "bool" => "bool",
543 "double" | "double precision" | "float8" => "float8",
544 "real" | "float4" => "float4",
545 other => other,
546 };
547 Ok(control.copy_text(canonical)?)
548}
549
550fn undefined_cast(source: &str, target: &str) -> SQLError {
551 SQLError::Routine {
552 sqlstate: "42846".into(),
553 message: format!("cannot cast type {source} to {target}"),
554 }
555}
556
557fn postgres_type_display_name(name: &str) -> &str {
558 match name {
559 "int2" => "smallint",
560 "int4" => "integer",
561 "int8" => "bigint",
562 "float4" => "real",
563 "float8" => "double precision",
564 "bool" => "boolean",
565 "varchar" => "character varying",
566 "bpchar" => "character",
567 other => other,
568 }
569}
570
571fn cast_text(
572 value: &Value,
573 source: Option<&str>,
574 control: &ProductionControl<'_>,
575) -> Result<Produced<String>> {
576 if let (Value::Float(value), Some(source)) = (value, source) {
577 let source = crate::ast::ColumnType::from_sql_name_with_control(source, control);
578 match source {
579 Ok(source) if matches!(&*source, crate::ast::ColumnType::Real) => {
580 return super::floating::format_real_with_control(*value as f32, control)
581 }
582 Err(error) if matches!(error.sqlstate(), Some("53200" | "57014")) => return Err(error),
583 _ => {}
584 }
585 }
586 value_to_string_with_control(value, control)
587}
588
589fn cast_uuid(value: &Value, control: &ProductionControl<'_>) -> Result<Produced<Value>> {
590 let text = match value {
591 Value::Str(text) | Value::FixedChar(text) => text,
592 other => {
593 return Err(SQLError::TypeMismatch(format!(
594 "cannot cast {other:?} to uuid"
595 )))
596 }
597 };
598 text_value(
599 super::uuid::canonicalize_uuid_with_control(text, control)?,
600 false,
601 control,
602 )
603}
604
605pub(super) fn cast_integer(
610 v: &Value,
611 target: &str,
612 control: &ProductionControl<'_>,
613) -> Result<Value> {
614 control.check()?;
615 let n: i64 = match v {
616 Value::Int(n) => *n,
617 Value::Bool(b) => i64::from(*b),
618 Value::Float(f) => {
619 if !f.is_finite() {
620 return Err(out_of_range(target));
621 }
622 let rounded = f.round_ties_even();
623 if rounded < i64::MIN as f64 || rounded >= 9_223_372_036_854_775_808.0 {
627 return Err(out_of_range(target));
628 }
629 rounded as i64
630 }
631 Value::Decimal(d) => d
632 .round_to_scale_with_control(0, control)?
633 .ok_or_else(|| out_of_range(target))?
634 .to_i64_trunc_with_control(control)?
635 .ok_or_else(|| out_of_range(target))?,
636 Value::Str(s) | Value::FixedChar(s) => {
637 s.trim().parse::<i64>().map_err(|_| SQLError::Routine {
638 sqlstate: "22P02".into(),
639 message: format!("invalid input syntax for type {target}: \"{s}\""),
640 })?
641 }
642 Value::Bytes(bytes) => bytea_to_integer(bytes, target)?,
643 other => {
644 return Err(SQLError::TypeMismatch(format!(
645 "cannot cast {other:?} to {target}"
646 )));
647 }
648 };
649 let in_range = match target {
650 "smallint" => i16::try_from(n).is_ok(),
651 "integer" => i32::try_from(n).is_ok(),
652 _ => true,
653 };
654 if !in_range {
655 return Err(out_of_range(target));
656 }
657 Ok(Value::Int(n))
658}
659
660pub(super) fn cast_boolean(v: &Value) -> Result<Value> {
664 match v {
665 Value::Bool(b) => Ok(Value::Bool(*b)),
666 Value::Int(n) => Ok(Value::Bool(*n != 0)),
667 Value::Float(f) => Ok(Value::Bool(*f != 0.0)),
668 Value::Decimal(d) => Ok(Value::Bool(!d.is_zero())),
669 Value::Str(s) | Value::FixedChar(s) => {
670 let text = s.trim();
671 let matches_prefix = |word: &str| {
672 !text.is_empty()
673 && word
674 .get(..text.len())
675 .is_some_and(|prefix| prefix.eq_ignore_ascii_case(text))
676 };
677 let value = if matches_prefix("true") || matches_prefix("yes") || text == "1" {
678 Some(true)
679 } else if matches_prefix("false") || matches_prefix("no") || text == "0" {
680 Some(false)
681 } else if text.eq_ignore_ascii_case("on") {
682 Some(true)
683 } else if matches_prefix("off") && text.len() >= 2 {
684 Some(false)
685 } else {
686 None
687 };
688 value.map(Value::Bool).ok_or_else(|| SQLError::Routine {
689 sqlstate: "22P02".into(),
690 message: format!("invalid input syntax for type boolean: \"{s}\""),
691 })
692 }
693 other => Err(SQLError::TypeMismatch(format!(
694 "cannot cast {other:?} to boolean"
695 ))),
696 }
697}
698
699pub use array::{array_dimensions, parse_pg_array_literal, parse_pg_array_literal_with_control};
700use binary_oid::{
701 bytea_to_integer, cast_bytea, cast_oid, cast_regclass, cast_regnamespace, cast_regrole,
702 cast_xid,
703};
704use temporal::{cast_date, cast_temporal, TemporalCastTarget};
705
706#[cfg(test)]
707mod production_tests;
708#[cfg(test)]
709mod tests;