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