1use std::borrow::Cow;
11use std::fmt;
12use std::sync::LazyLock;
13
14use chrono::{DateTime, NaiveDateTime, NaiveTime, Utc};
15use mz_expr_derive::sqlfunc;
16use mz_ore::cast::CastFrom;
17use mz_ore::result::ResultExt;
18use mz_ore::str::StrExt;
19use mz_repr::adt::char::{Char, format_str_trim};
20use mz_repr::adt::date::Date;
21use mz_repr::adt::interval::Interval;
22use mz_repr::adt::jsonb::Jsonb;
23use mz_repr::adt::numeric::{self, Numeric, NumericMaxScale};
24use mz_repr::adt::pg_legacy_name::PgLegacyName;
25use mz_repr::adt::regex::Regex;
26use mz_repr::adt::system::{Oid, PgLegacyChar};
27use mz_repr::adt::timestamp::{CheckedTimestamp, TimestampPrecision};
28use mz_repr::adt::varchar::{VarChar, VarCharMaxLength};
29use mz_repr::{Datum, RowArena, SqlColumnType, SqlScalarType, strconv};
30use serde::{Deserialize, Serialize};
31use uuid::Uuid;
32
33use crate::func::{binary, regexp_match_static};
34use crate::scalar::func::{
35 EagerUnaryFunc, LazyUnaryFunc, array_create_scalar, regexp_split_to_array_re,
36};
37use crate::{Eval, EvalError, MirScalarExpr, UnaryFunc, like_pattern};
38
39#[sqlfunc(
40 sqlname = "text_to_boolean",
41 preserves_uniqueness = false,
42 inverse = to_unary!(super::CastBoolToString)
43)]
44fn cast_string_to_bool<'a>(a: &'a str) -> Result<bool, EvalError> {
45 strconv::parse_bool(a).err_into()
46}
47
48#[sqlfunc(
49 sqlname = "text_to_\"char\"",
50 preserves_uniqueness = false,
54 inverse = to_unary!(super::CastPgLegacyCharToString)
55)]
56fn cast_string_to_pg_legacy_char<'a>(a: &'a str) -> PgLegacyChar {
57 PgLegacyChar(a.as_bytes().get(0).copied().unwrap_or(0))
58}
59
60#[sqlfunc(sqlname = "text_to_name", preserves_uniqueness = false)]
61fn cast_string_to_pg_legacy_name<'a>(a: &'a str) -> PgLegacyName<String> {
62 PgLegacyName(strconv::parse_pg_legacy_name(a))
63}
64
65#[sqlfunc(
66 sqlname = "text_to_bytea",
67 preserves_uniqueness = false,
73 inverse = to_unary!(super::CastBytesToString)
74)]
75fn cast_string_to_bytes<'a>(a: &'a str) -> Result<Vec<u8>, EvalError> {
76 strconv::parse_bytes(a).err_into()
77}
78
79#[sqlfunc(
80 sqlname = "text_to_smallint",
81 preserves_uniqueness = false,
82 inverse = to_unary!(super::CastInt16ToString)
83)]
84fn cast_string_to_int16<'a>(a: &'a str) -> Result<i16, EvalError> {
85 strconv::parse_int16(a).err_into()
86}
87
88#[sqlfunc(
89 sqlname = "text_to_integer",
90 preserves_uniqueness = false,
91 inverse = to_unary!(super::CastInt32ToString)
92)]
93fn cast_string_to_int32<'a>(a: &'a str) -> Result<i32, EvalError> {
94 strconv::parse_int32(a).err_into()
95}
96
97#[sqlfunc(
98 sqlname = "text_to_bigint",
99 preserves_uniqueness = false,
100 inverse = to_unary!(super::CastInt64ToString)
101)]
102fn cast_string_to_int64<'a>(a: &'a str) -> Result<i64, EvalError> {
103 strconv::parse_int64(a).err_into()
104}
105
106#[sqlfunc(
107 sqlname = "text_to_real",
108 preserves_uniqueness = false,
109 inverse = to_unary!(super::CastFloat32ToString)
110)]
111fn cast_string_to_float32<'a>(a: &'a str) -> Result<f32, EvalError> {
112 strconv::parse_float32(a).err_into()
113}
114
115#[sqlfunc(
116 sqlname = "text_to_double",
117 preserves_uniqueness = false,
118 inverse = to_unary!(super::CastFloat64ToString)
119)]
120fn cast_string_to_float64<'a>(a: &'a str) -> Result<f64, EvalError> {
121 strconv::parse_float64(a).err_into()
122}
123
124#[sqlfunc(
125 sqlname = "text_to_oid",
126 preserves_uniqueness = false,
127 inverse = to_unary!(super::CastOidToString)
128)]
129fn cast_string_to_oid<'a>(a: &'a str) -> Result<Oid, EvalError> {
130 Ok(Oid(strconv::parse_oid(a)?))
131}
132
133#[sqlfunc(
134 sqlname = "text_to_uint2",
135 preserves_uniqueness = false,
136 inverse = to_unary!(super::CastUint16ToString)
137)]
138fn cast_string_to_uint16(a: &str) -> Result<u16, EvalError> {
139 strconv::parse_uint16(a).err_into()
140}
141
142#[sqlfunc(
143 sqlname = "text_to_uint4",
144 preserves_uniqueness = false,
145 inverse = to_unary!(super::CastUint32ToString)
146)]
147fn cast_string_to_uint32(a: &str) -> Result<u32, EvalError> {
148 strconv::parse_uint32(a).err_into()
149}
150
151#[sqlfunc(
152 sqlname = "text_to_uint8",
153 preserves_uniqueness = false,
154 inverse = to_unary!(super::CastUint64ToString)
155)]
156fn cast_string_to_uint64(a: &str) -> Result<u64, EvalError> {
157 strconv::parse_uint64(a).err_into()
158}
159
160#[sqlfunc(preserves_uniqueness = true, inverse = to_unary!(Reverse))]
161fn reverse<'a>(a: &'a str) -> String {
162 a.chars().rev().collect()
163}
164
165#[derive(
166 Ord,
167 PartialOrd,
168 Clone,
169 Debug,
170 Eq,
171 PartialEq,
172 Serialize,
173 Deserialize,
174 Hash
175)]
176pub struct CastStringToNumeric(pub Option<NumericMaxScale>);
177
178impl EagerUnaryFunc for CastStringToNumeric {
179 type Input<'a> = &'a str;
180 type Output<'a> = Result<Numeric, EvalError>;
181
182 fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
183 let mut d = strconv::parse_numeric(a)?;
184 if let Some(scale) = self.0 {
185 if numeric::rescale(&mut d.0, scale.into_u8()).is_err() {
186 return Err(EvalError::NumericFieldOverflow);
187 }
188 }
189 Ok(d.into_inner())
190 }
191
192 fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
193 SqlScalarType::Numeric { max_scale: self.0 }.nullable(input.nullable)
194 }
195
196 fn inverse(&self) -> Option<crate::UnaryFunc> {
197 to_unary!(super::CastNumericToString)
198 }
199}
200
201impl fmt::Display for CastStringToNumeric {
202 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
203 f.write_str("text_to_numeric")
204 }
205}
206
207#[sqlfunc(
208 sqlname = "text_to_date",
209 preserves_uniqueness = false,
210 inverse = to_unary!(super::CastDateToString)
211)]
212fn cast_string_to_date<'a>(a: &'a str) -> Result<Date, EvalError> {
213 strconv::parse_date(a).err_into()
214}
215
216#[sqlfunc(
217 sqlname = "text_to_time",
218 preserves_uniqueness = false,
219 inverse = to_unary!(super::CastTimeToString)
220)]
221fn cast_string_to_time<'a>(a: &'a str) -> Result<NaiveTime, EvalError> {
222 strconv::parse_time(a).err_into()
223}
224
225#[derive(
226 Ord,
227 PartialOrd,
228 Clone,
229 Debug,
230 Eq,
231 PartialEq,
232 Serialize,
233 Deserialize,
234 Hash
235)]
236pub struct CastStringToTimestamp(pub Option<TimestampPrecision>);
237
238impl EagerUnaryFunc for CastStringToTimestamp {
239 type Input<'a> = &'a str;
240 type Output<'a> = Result<CheckedTimestamp<NaiveDateTime>, EvalError>;
241
242 fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
243 let out = strconv::parse_timestamp(a)?;
244 let updated = out.round_to_precision(self.0)?;
245 Ok(updated)
246 }
247
248 fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
249 SqlScalarType::Timestamp { precision: self.0 }.nullable(input.nullable)
250 }
251
252 fn inverse(&self) -> Option<crate::UnaryFunc> {
253 to_unary!(super::CastTimestampToString)
254 }
255}
256
257impl fmt::Display for CastStringToTimestamp {
258 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
259 f.write_str("text_to_timestamp")
260 }
261}
262
263#[sqlfunc(sqlname = "try_parse_monotonic_iso8601_timestamp")]
264fn try_parse_monotonic_iso8601_timestamp<'a>(
270 a: &'a str,
271) -> Option<CheckedTimestamp<NaiveDateTime>> {
272 let ts = mz_persist_types::timestamp::try_parse_monotonic_iso8601_timestamp(a)?;
273 let ts = CheckedTimestamp::from_timestamplike(ts)
274 .expect("monotonic_iso8601 range is a subset of CheckedTimestamp domain");
275 Some(ts)
276}
277
278#[derive(
279 Ord,
280 PartialOrd,
281 Clone,
282 Debug,
283 Eq,
284 PartialEq,
285 Serialize,
286 Deserialize,
287 Hash
288)]
289pub struct CastStringToTimestampTz(pub Option<TimestampPrecision>);
290
291impl EagerUnaryFunc for CastStringToTimestampTz {
292 type Input<'a> = &'a str;
293 type Output<'a> = Result<CheckedTimestamp<DateTime<Utc>>, EvalError>;
294
295 fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
296 let out = strconv::parse_timestamptz(a)?;
297 let updated = out.round_to_precision(self.0)?;
298 Ok(updated)
299 }
300
301 fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
302 SqlScalarType::TimestampTz { precision: self.0 }.nullable(input.nullable)
303 }
304
305 fn inverse(&self) -> Option<crate::UnaryFunc> {
306 to_unary!(super::CastTimestampTzToString)
307 }
308}
309
310impl fmt::Display for CastStringToTimestampTz {
311 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
312 f.write_str("text_to_timestamp_with_time_zone")
313 }
314}
315
316#[sqlfunc(
317 sqlname = "text_to_interval",
318 preserves_uniqueness = false,
319 inverse = to_unary!(super::CastIntervalToString)
320)]
321fn cast_string_to_interval<'a>(a: &'a str) -> Result<Interval, EvalError> {
322 strconv::parse_interval(a).err_into()
323}
324
325#[sqlfunc(
326 sqlname = "text_to_uuid",
327 preserves_uniqueness = false,
328 inverse = to_unary!(super::CastUuidToString)
329)]
330fn cast_string_to_uuid<'a>(a: &'a str) -> Result<Uuid, EvalError> {
331 strconv::parse_uuid(a).err_into()
332}
333
334#[derive(
335 Ord,
336 PartialOrd,
337 Clone,
338 Debug,
339 Eq,
340 PartialEq,
341 Serialize,
342 Deserialize,
343 Hash
344)]
345pub struct CastStringToArray {
346 pub return_ty: SqlScalarType,
348 pub cast_expr: Box<MirScalarExpr>,
351}
352
353impl LazyUnaryFunc for CastStringToArray {
354 fn eval<'a>(
355 &'a self,
356 datums: &[Datum<'a>],
357 temp_storage: &'a RowArena,
358 a: &'a impl Eval,
359 ) -> Result<Datum<'a>, EvalError> {
360 let a = a.eval(datums, temp_storage)?;
361 if a.is_null() {
362 return Ok(Datum::Null);
363 }
364 let (datums, dims) = strconv::parse_array(
365 a.unwrap_str(),
366 || Datum::Null,
367 |elem_text| {
368 let elem_text = match elem_text {
369 Cow::Owned(s) => temp_storage.push_string(s),
370 Cow::Borrowed(s) => s,
371 };
372 self.cast_expr
373 .eval(&[Datum::String(elem_text)], temp_storage)
374 },
375 )?;
376
377 Ok(temp_storage.try_make_datum(|packer| packer.try_push_array(&dims, datums))?)
378 }
379
380 fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
382 self.return_ty.clone().nullable(input_type.nullable)
383 }
384
385 fn propagates_nulls(&self) -> bool {
387 true
388 }
389
390 fn introduces_nulls(&self) -> bool {
392 false
393 }
394
395 fn preserves_uniqueness(&self) -> bool {
397 false
398 }
399
400 fn inverse(&self) -> Option<crate::UnaryFunc> {
401 to_unary!(super::CastArrayToString {
402 ty: self.return_ty.clone(),
403 })
404 }
405
406 fn is_monotone(&self) -> bool {
407 false
408 }
409
410 fn is_eliminable_cast(&self) -> bool {
411 false
412 }
413}
414
415impl fmt::Display for CastStringToArray {
416 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
417 f.write_str("strtoarray")
418 }
419}
420
421#[derive(
422 Ord,
423 PartialOrd,
424 Clone,
425 Debug,
426 Eq,
427 PartialEq,
428 Serialize,
429 Deserialize,
430 Hash
431)]
432pub struct CastStringToList {
433 pub return_ty: SqlScalarType,
435 pub cast_expr: Box<MirScalarExpr>,
438}
439
440impl LazyUnaryFunc for CastStringToList {
441 fn eval<'a>(
442 &'a self,
443 datums: &[Datum<'a>],
444 temp_storage: &'a RowArena,
445 a: &'a impl Eval,
446 ) -> Result<Datum<'a>, EvalError> {
447 let a = a.eval(datums, temp_storage)?;
448 if a.is_null() {
449 return Ok(Datum::Null);
450 }
451 let parsed_datums = strconv::parse_list(
452 a.unwrap_str(),
453 matches!(
454 self.return_ty.unwrap_list_element_type(),
455 SqlScalarType::List { .. }
456 ),
457 || Datum::Null,
458 |elem_text| {
459 let elem_text = match elem_text {
460 Cow::Owned(s) => temp_storage.push_string(s),
461 Cow::Borrowed(s) => s,
462 };
463 self.cast_expr
464 .eval(&[Datum::String(elem_text)], temp_storage)
465 },
466 )?;
467
468 Ok(temp_storage.make_datum(|packer| packer.push_list(parsed_datums)))
469 }
470
471 fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
473 self.return_ty
474 .without_modifiers()
475 .nullable(input_type.nullable)
476 }
477
478 fn propagates_nulls(&self) -> bool {
480 true
481 }
482
483 fn introduces_nulls(&self) -> bool {
485 false
486 }
487
488 fn preserves_uniqueness(&self) -> bool {
490 false
491 }
492
493 fn inverse(&self) -> Option<crate::UnaryFunc> {
494 to_unary!(super::CastListToString {
495 ty: self.return_ty.clone(),
496 })
497 }
498
499 fn is_monotone(&self) -> bool {
500 false
501 }
502
503 fn is_eliminable_cast(&self) -> bool {
504 false
505 }
506}
507
508impl fmt::Display for CastStringToList {
509 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
510 f.write_str("strtolist")
511 }
512}
513
514#[derive(
515 Ord,
516 PartialOrd,
517 Clone,
518 Debug,
519 Eq,
520 PartialEq,
521 Serialize,
522 Deserialize,
523 Hash
524)]
525pub struct CastStringToMap {
526 pub return_ty: SqlScalarType,
528 pub cast_expr: Box<MirScalarExpr>,
531}
532
533impl LazyUnaryFunc for CastStringToMap {
534 fn eval<'a>(
535 &'a self,
536 datums: &[Datum<'a>],
537 temp_storage: &'a RowArena,
538 a: &'a impl Eval,
539 ) -> Result<Datum<'a>, EvalError> {
540 let a = a.eval(datums, temp_storage)?;
541 if a.is_null() {
542 return Ok(Datum::Null);
543 }
544 let parsed_map = strconv::parse_map(
545 a.unwrap_str(),
546 matches!(
547 self.return_ty.unwrap_map_value_type(),
548 SqlScalarType::Map { .. }
549 ),
550 |value_text| -> Result<Datum, EvalError> {
551 let value_text = match value_text {
552 Some(Cow::Owned(s)) => Datum::String(temp_storage.push_string(s)),
553 Some(Cow::Borrowed(s)) => Datum::String(s),
554 None => Datum::Null,
555 };
556 self.cast_expr.eval(&[value_text], temp_storage)
557 },
558 )?;
559 let mut pairs: Vec<(String, Datum)> = parsed_map.into_iter().map(|(k, v)| (k, v)).collect();
560 pairs.sort_by(|(k1, _v1), (k2, _v2)| k1.cmp(k2));
561 pairs.dedup_by(|(k1, _v1), (k2, _v2)| k1 == k2);
562 Ok(temp_storage.make_datum(|packer| {
563 packer.push_dict_with(|packer| {
564 for (k, v) in pairs {
565 packer.push(Datum::String(&k));
566 packer.push(v);
567 }
568 })
569 }))
570 }
571
572 fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
574 self.return_ty.clone().nullable(input_type.nullable)
575 }
576
577 fn propagates_nulls(&self) -> bool {
579 true
580 }
581
582 fn introduces_nulls(&self) -> bool {
584 false
585 }
586
587 fn preserves_uniqueness(&self) -> bool {
589 false
590 }
591
592 fn inverse(&self) -> Option<crate::UnaryFunc> {
593 to_unary!(super::CastMapToString {
594 ty: self.return_ty.clone(),
595 })
596 }
597
598 fn is_monotone(&self) -> bool {
599 false
600 }
601
602 fn is_eliminable_cast(&self) -> bool {
603 false
604 }
605}
606
607impl fmt::Display for CastStringToMap {
608 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
609 f.write_str("strtomap")
610 }
611}
612
613#[derive(
614 Ord,
615 PartialOrd,
616 Clone,
617 Debug,
618 Eq,
619 PartialEq,
620 Serialize,
621 Deserialize,
622 Hash
623)]
624pub struct CastStringToChar {
625 pub length: Option<mz_repr::adt::char::CharLength>,
626 pub fail_on_len: bool,
627}
628
629impl EagerUnaryFunc for CastStringToChar {
630 type Input<'a> = &'a str;
631 type Output<'a> = Result<Char<String>, EvalError>;
632
633 fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
634 let s = format_str_trim(a, self.length, self.fail_on_len).map_err(|_| {
635 assert!(self.fail_on_len);
636 EvalError::StringValueTooLong {
637 target_type: "character".into(),
638 length: usize::cast_from(self.length.unwrap().into_u32()),
639 }
640 })?;
641
642 Ok(Char(s))
643 }
644
645 fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
646 SqlScalarType::Char {
647 length: self.length,
648 }
649 .nullable(input.nullable)
650 }
651
652 fn could_error(&self) -> bool {
653 self.fail_on_len && self.length.is_some()
654 }
655
656 fn inverse(&self) -> Option<crate::UnaryFunc> {
657 to_unary!(super::CastCharToString)
658 }
659
660 fn is_eliminable_cast(&self) -> bool {
661 false
663 }
664}
665
666impl fmt::Display for CastStringToChar {
667 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
668 match self.length {
669 Some(length) => {
670 write!(
671 f,
672 "text_to_char[len={}, fail_on_len={}]",
673 length.into_u32(),
674 self.fail_on_len
675 )
676 }
677 None => f.write_str("text_to_char[len=unbounded]"),
678 }
679 }
680}
681
682#[derive(
683 Ord,
684 PartialOrd,
685 Clone,
686 Debug,
687 Eq,
688 PartialEq,
689 Serialize,
690 Deserialize,
691 Hash
692)]
693pub struct CastStringToRange {
694 pub return_ty: SqlScalarType,
696 pub cast_expr: Box<MirScalarExpr>,
699}
700
701impl LazyUnaryFunc for CastStringToRange {
702 fn eval<'a>(
703 &'a self,
704 datums: &[Datum<'a>],
705 temp_storage: &'a RowArena,
706 a: &'a impl Eval,
707 ) -> Result<Datum<'a>, EvalError> {
708 let a = a.eval(datums, temp_storage)?;
709 if a.is_null() {
710 return Ok(Datum::Null);
711 }
712 let mut range = strconv::parse_range(a.unwrap_str(), |elem_text| {
713 let elem_text = match elem_text {
714 Cow::Owned(s) => temp_storage.push_string(s),
715 Cow::Borrowed(s) => s,
716 };
717 self.cast_expr
718 .eval(&[Datum::String(elem_text)], temp_storage)
719 })?;
720
721 range.canonicalize()?;
722
723 Ok(temp_storage.make_datum(|packer| {
724 packer
725 .push_range(range)
726 .expect("must have already handled errors")
727 }))
728 }
729
730 fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
732 self.return_ty
733 .without_modifiers()
734 .nullable(input_type.nullable)
735 }
736
737 fn propagates_nulls(&self) -> bool {
739 true
740 }
741
742 fn introduces_nulls(&self) -> bool {
744 false
745 }
746
747 fn preserves_uniqueness(&self) -> bool {
749 false
750 }
751
752 fn inverse(&self) -> Option<crate::UnaryFunc> {
753 to_unary!(super::CastRangeToString {
754 ty: self.return_ty.clone(),
755 })
756 }
757
758 fn is_monotone(&self) -> bool {
759 false
760 }
761
762 fn is_eliminable_cast(&self) -> bool {
763 false
764 }
765}
766
767impl fmt::Display for CastStringToRange {
768 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
769 f.write_str("strtorange")
770 }
771}
772
773#[derive(
774 Ord,
775 PartialOrd,
776 Clone,
777 Debug,
778 Eq,
779 PartialEq,
780 Serialize,
781 Deserialize,
782 Hash
783)]
784pub struct CastStringToVarChar {
785 pub length: Option<VarCharMaxLength>,
786 pub fail_on_len: bool,
787}
788
789impl EagerUnaryFunc for CastStringToVarChar {
790 type Input<'a> = &'a str;
791 type Output<'a> = Result<VarChar<&'a str>, EvalError>;
792
793 fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
794 let s =
795 mz_repr::adt::varchar::format_str(a, self.length, self.fail_on_len).map_err(|_| {
796 assert!(self.fail_on_len);
797 EvalError::StringValueTooLong {
798 target_type: "character varying".into(),
799 length: usize::cast_from(self.length.unwrap().into_u32()),
800 }
801 })?;
802
803 Ok(VarChar(s))
804 }
805
806 fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
807 SqlScalarType::VarChar {
808 max_length: self.length,
809 }
810 .nullable(input.nullable)
811 }
812
813 fn could_error(&self) -> bool {
814 self.fail_on_len && self.length.is_some()
815 }
816
817 fn preserves_uniqueness(&self) -> bool {
818 self.length.is_none()
819 }
820
821 fn inverse(&self) -> Option<crate::UnaryFunc> {
822 to_unary!(super::CastVarCharToString)
823 }
824
825 fn is_eliminable_cast(&self) -> bool {
826 self.length.is_none()
827 }
828}
829
830impl fmt::Display for CastStringToVarChar {
831 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
832 match self.length {
833 Some(length) => {
834 write!(
835 f,
836 "text_to_varchar[len={}, fail_on_len={}]",
837 length.into_u32(),
838 self.fail_on_len
839 )
840 }
841 None => f.write_str("text_to_varchar[len=unbounded]"),
842 }
843 }
844}
845
846static INT2VECTOR_CAST_EXPR: LazyLock<MirScalarExpr> = LazyLock::new(|| MirScalarExpr::CallUnary {
849 func: UnaryFunc::CastStringToInt16(CastStringToInt16),
850 expr: Box::new(MirScalarExpr::column(0)),
851});
852
853#[derive(
854 Ord,
855 PartialOrd,
856 Clone,
857 Debug,
858 Eq,
859 PartialEq,
860 Serialize,
861 Deserialize,
862 Hash
863)]
864pub struct CastStringToInt2Vector;
865
866impl LazyUnaryFunc for CastStringToInt2Vector {
867 fn eval<'a>(
868 &'a self,
869 datums: &[Datum<'a>],
870 temp_storage: &'a RowArena,
871 a: &'a impl Eval,
872 ) -> Result<Datum<'a>, EvalError> {
873 let a = a.eval(datums, temp_storage)?;
874 if a.is_null() {
875 return Ok(Datum::Null);
876 }
877
878 let datums = strconv::parse_legacy_vector(a.unwrap_str(), |elem_text| {
879 let elem_text = match elem_text {
880 Cow::Owned(s) => temp_storage.push_string(s),
881 Cow::Borrowed(s) => s,
882 };
883 INT2VECTOR_CAST_EXPR.eval(&[Datum::String(elem_text)], temp_storage)
884 })?;
885 array_create_scalar(&datums, temp_storage)
886 }
887
888 fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
890 SqlScalarType::Int2Vector.nullable(input_type.nullable)
891 }
892
893 fn propagates_nulls(&self) -> bool {
895 true
896 }
897
898 fn introduces_nulls(&self) -> bool {
900 false
901 }
902
903 fn preserves_uniqueness(&self) -> bool {
905 false
906 }
907
908 fn inverse(&self) -> Option<crate::UnaryFunc> {
909 to_unary!(super::CastInt2VectorToString)
910 }
911
912 fn is_monotone(&self) -> bool {
913 false
914 }
915
916 fn is_eliminable_cast(&self) -> bool {
917 false
918 }
919}
920
921impl fmt::Display for CastStringToInt2Vector {
922 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
923 f.write_str("strtoint2vector")
924 }
925}
926
927#[sqlfunc(
928 sqlname = "text_to_jsonb",
929 preserves_uniqueness = false,
930 inverse = to_unary!(super::CastJsonbToString)
931)]
932fn cast_string_to_jsonb<'a>(a: &'a str) -> Result<Jsonb, EvalError> {
934 Ok(strconv::parse_jsonb(a)?)
935}
936
937#[sqlfunc(sqlname = "btrim")]
938fn trim_whitespace<'a>(a: &'a str) -> &'a str {
939 a.trim_matches(' ')
940}
941
942#[sqlfunc(sqlname = "ltrim")]
943fn trim_leading_whitespace<'a>(a: &'a str) -> &'a str {
944 a.trim_start_matches(' ')
945}
946
947#[sqlfunc(sqlname = "rtrim")]
948fn trim_trailing_whitespace<'a>(a: &'a str) -> &'a str {
949 a.trim_end_matches(' ')
950}
951
952#[sqlfunc(sqlname = "initcap")]
953fn initcap<'a>(a: &'a str) -> String {
954 let mut out = String::new();
955 let mut capitalize_next = true;
956 for ch in a.chars() {
957 if capitalize_next {
958 out.extend(ch.to_uppercase())
959 } else {
960 out.extend(ch.to_lowercase())
961 };
962 capitalize_next = !ch.is_alphanumeric();
963 }
964 out
965}
966
967#[sqlfunc(sqlname = "ascii")]
968fn ascii<'a>(a: &'a str) -> i32 {
969 a.chars()
970 .next()
971 .and_then(|c| i32::try_from(u32::from(c)).ok())
972 .unwrap_or(0)
973}
974
975#[sqlfunc(sqlname = "char_length")]
976fn char_length<'a>(a: &'a str) -> Result<i32, EvalError> {
977 let length = a.chars().count();
978 i32::try_from(length).or_else(|_| Err(EvalError::Int32OutOfRange(length.to_string().into())))
979}
980
981#[sqlfunc(sqlname = "bit_length")]
982fn bit_length_string<'a>(a: &'a str) -> Result<i32, EvalError> {
983 let length = a.as_bytes().len() * 8;
984 i32::try_from(length).or_else(|_| Err(EvalError::Int32OutOfRange(length.to_string().into())))
985}
986
987#[sqlfunc(sqlname = "octet_length")]
988fn byte_length_string<'a>(a: &'a str) -> Result<i32, EvalError> {
989 let length = a.as_bytes().len();
990 i32::try_from(length).or_else(|_| Err(EvalError::Int32OutOfRange(length.to_string().into())))
991}
992
993#[sqlfunc]
994fn upper<'a>(a: &'a str) -> String {
995 a.to_uppercase()
996}
997
998#[sqlfunc]
999fn lower<'a>(a: &'a str) -> String {
1000 a.to_lowercase()
1001}
1002
1003#[sqlfunc]
1004fn normalize(text: &str, form_str: &str) -> Result<String, EvalError> {
1005 use unicode_normalization::UnicodeNormalization;
1006
1007 match form_str.to_uppercase().as_str() {
1008 "NFC" => Ok(text.nfc().collect()),
1009 "NFD" => Ok(text.nfd().collect()),
1010 "NFKC" => Ok(text.nfkc().collect()),
1011 "NFKD" => Ok(text.nfkd().collect()),
1012 _ => Err(EvalError::InvalidParameterValue(
1013 format!("invalid normalization form: {}", form_str).into(),
1014 )),
1015 }
1016}
1017
1018#[derive(
1019 Ord,
1020 PartialOrd,
1021 Clone,
1022 Debug,
1023 Eq,
1024 PartialEq,
1025 Serialize,
1026 Deserialize,
1027 Hash
1028)]
1029pub struct IsLikeMatch(pub like_pattern::Matcher);
1030
1031impl EagerUnaryFunc for IsLikeMatch {
1032 type Input<'a> = &'a str;
1033 type Output<'a> = bool;
1034
1035 fn call<'a>(&self, haystack: Self::Input<'a>) -> Self::Output<'a> {
1036 self.0.is_match(haystack)
1037 }
1038
1039 fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
1040 SqlScalarType::Bool.nullable(input.nullable)
1041 }
1042}
1043
1044impl fmt::Display for IsLikeMatch {
1045 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1046 write!(
1047 f,
1048 "{}like[{}]",
1049 if self.0.case_insensitive { "i" } else { "" },
1050 self.0.pattern.escaped()
1051 )
1052 }
1053}
1054
1055#[derive(
1056 Ord,
1057 PartialOrd,
1058 Clone,
1059 Debug,
1060 Eq,
1061 PartialEq,
1062 Serialize,
1063 Deserialize,
1064 Hash
1065)]
1066pub struct IsRegexpMatch(pub Regex);
1067
1068impl EagerUnaryFunc for IsRegexpMatch {
1069 type Input<'a> = &'a str;
1070 type Output<'a> = bool;
1071
1072 fn call<'a>(&self, haystack: Self::Input<'a>) -> Self::Output<'a> {
1073 self.0.is_match(haystack)
1074 }
1075
1076 fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
1077 SqlScalarType::Bool.nullable(input.nullable)
1078 }
1079}
1080
1081impl fmt::Display for IsRegexpMatch {
1082 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1083 write!(
1084 f,
1085 "is_regexp_match[{}, case_insensitive={}]",
1086 self.0.pattern().escaped(),
1087 self.0.case_insensitive
1088 )
1089 }
1090}
1091
1092#[derive(
1093 Ord,
1094 PartialOrd,
1095 Clone,
1096 Debug,
1097 Eq,
1098 PartialEq,
1099 Serialize,
1100 Deserialize,
1101 Hash
1102)]
1103pub struct RegexpMatch(pub Regex);
1104
1105impl LazyUnaryFunc for RegexpMatch {
1106 fn eval<'a>(
1107 &'a self,
1108 datums: &[Datum<'a>],
1109 temp_storage: &'a RowArena,
1110 a: &'a impl Eval,
1111 ) -> Result<Datum<'a>, EvalError> {
1112 let haystack = a.eval(datums, temp_storage)?;
1113 if haystack.is_null() {
1114 return Ok(Datum::Null);
1115 }
1116 regexp_match_static(haystack, temp_storage, &self.0)
1117 }
1118
1119 fn output_sql_type(&self, _input_type: SqlColumnType) -> SqlColumnType {
1121 SqlScalarType::Array(Box::new(SqlScalarType::String)).nullable(true)
1122 }
1123
1124 fn propagates_nulls(&self) -> bool {
1126 true
1127 }
1128
1129 fn introduces_nulls(&self) -> bool {
1131 true
1133 }
1134
1135 fn preserves_uniqueness(&self) -> bool {
1137 false
1138 }
1139
1140 fn inverse(&self) -> Option<crate::UnaryFunc> {
1141 None
1142 }
1143
1144 fn is_monotone(&self) -> bool {
1145 false
1146 }
1147
1148 fn is_eliminable_cast(&self) -> bool {
1149 false
1150 }
1151}
1152
1153impl fmt::Display for RegexpMatch {
1154 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1155 write!(
1156 f,
1157 "regexp_match[{}, case_insensitive={}]",
1158 self.0.pattern().escaped(),
1159 self.0.case_insensitive
1160 )
1161 }
1162}
1163
1164#[derive(
1165 Ord,
1166 PartialOrd,
1167 Clone,
1168 Debug,
1169 Eq,
1170 PartialEq,
1171 Serialize,
1172 Deserialize,
1173 Hash
1174)]
1175pub struct RegexpSplitToArray(pub Regex);
1176
1177impl LazyUnaryFunc for RegexpSplitToArray {
1178 fn eval<'a>(
1179 &'a self,
1180 datums: &[Datum<'a>],
1181 temp_storage: &'a RowArena,
1182 a: &'a impl Eval,
1183 ) -> Result<Datum<'a>, EvalError> {
1184 let haystack = a.eval(datums, temp_storage)?;
1185 if haystack.is_null() {
1186 return Ok(Datum::Null);
1187 }
1188 regexp_split_to_array_re(haystack.unwrap_str(), &self.0, temp_storage)
1189 }
1190
1191 fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
1193 SqlScalarType::Array(Box::new(SqlScalarType::String)).nullable(input_type.nullable)
1194 }
1195
1196 fn propagates_nulls(&self) -> bool {
1198 true
1199 }
1200
1201 fn introduces_nulls(&self) -> bool {
1203 false
1204 }
1205
1206 fn preserves_uniqueness(&self) -> bool {
1208 false
1209 }
1210
1211 fn inverse(&self) -> Option<crate::UnaryFunc> {
1212 None
1213 }
1214
1215 fn is_monotone(&self) -> bool {
1216 false
1217 }
1218
1219 fn is_eliminable_cast(&self) -> bool {
1220 false
1221 }
1222}
1223
1224impl fmt::Display for RegexpSplitToArray {
1225 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1226 write!(
1227 f,
1228 "regexp_split_to_array[{}, case_insensitive={}]",
1229 self.0.pattern().escaped(),
1230 self.0.case_insensitive
1231 )
1232 }
1233}
1234
1235#[sqlfunc(sqlname = "mz_panic")]
1236fn panic<'a>(a: &'a str) -> String {
1237 print!("{}", a);
1238 panic!("{}", a)
1239}
1240
1241#[sqlfunc(sqlname = "quote_ident", preserves_uniqueness = true)]
1242fn quote_ident<'a>(a: &'a str) -> Result<String, EvalError> {
1243 let i = mz_sql_parser::ast::Ident::new(a).map_err(|err| EvalError::InvalidIdentifier {
1244 ident: a.into(),
1245 detail: Some(err.to_string().into()),
1246 })?;
1247 Ok(i.to_string())
1248}
1249
1250#[derive(
1251 Ord,
1252 PartialOrd,
1253 Clone,
1254 Debug,
1255 Eq,
1256 PartialEq,
1257 Serialize,
1258 Deserialize,
1259 Hash
1260)]
1261pub struct RegexpReplace {
1262 pub regex: Regex,
1263 pub limit: usize,
1264}
1265
1266impl binary::EagerBinaryFunc for RegexpReplace {
1267 type Input<'a> = (&'a str, &'a str);
1268 type Output<'a> = Cow<'a, str>;
1269
1270 fn call<'a>(
1271 &self,
1272 (source, replacement): Self::Input<'a>,
1273 _temp_storage: &'a RowArena,
1274 ) -> Self::Output<'a> {
1275 self.regex.replacen(source, self.limit, replacement)
1279 }
1280
1281 fn output_sql_type(&self, input_types: &[SqlColumnType]) -> SqlColumnType {
1282 use mz_repr::AsColumnType;
1283 let output = <Self::Output<'_> as AsColumnType>::as_column_type();
1284 let propagates_nulls = binary::EagerBinaryFunc::propagates_nulls(self);
1285 let nullable = output.nullable;
1286 let input_nullable = input_types.iter().any(|t| t.nullable);
1287 output.nullable(nullable || (propagates_nulls && input_nullable))
1288 }
1289}
1290
1291impl fmt::Display for RegexpReplace {
1292 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1293 write!(
1294 f,
1295 "regexp_replace[{}, case_insensitive={}, limit={}]",
1296 self.regex.pattern().escaped(),
1297 self.regex.case_insensitive,
1298 self.limit
1299 )
1300 }
1301}