Skip to main content

mz_expr/scalar/func/impls/
string.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10use 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::array::Array;
20use mz_repr::adt::char::{Char, format_str_trim};
21use mz_repr::adt::date::Date;
22use mz_repr::adt::interval::Interval;
23use mz_repr::adt::jsonb::Jsonb;
24use mz_repr::adt::numeric::{self, Numeric, NumericMaxScale};
25use mz_repr::adt::pg_legacy_name::PgLegacyName;
26use mz_repr::adt::regex::Regex;
27use mz_repr::adt::system::{Oid, PgLegacyChar};
28use mz_repr::adt::timestamp::{CheckedTimestamp, TimestampPrecision};
29use mz_repr::adt::varchar::{VarChar, VarCharMaxLength};
30use mz_repr::{Datum, Int2Vector, RowArena, SqlColumnType, SqlScalarType, strconv};
31use serde::{Deserialize, Serialize};
32use uuid::Uuid;
33
34use crate::func::regexp_match_static;
35use crate::scalar::func::{LazyUnaryFunc, array_create_scalar, regexp_split_to_array_re};
36use crate::{Eval, EvalError, MirScalarExpr, UnaryFunc, like_pattern};
37
38#[sqlfunc(
39    sqlname = "text_to_boolean",
40    preserves_uniqueness = false,
41    inverse = super::CastBoolToString
42)]
43fn cast_string_to_bool<'a>(a: &'a str) -> Result<bool, EvalError> {
44    strconv::parse_bool(a).err_into()
45}
46
47#[sqlfunc(
48    sqlname = "text_to_\"char\"",
49    // Not injective: only the first byte is kept (e.g. 'a' and 'abc' both
50    // collapse to 'a'::"char"), so inverse-cast canonicalization of
51    // `c::text = lit` would silently change results.
52    preserves_uniqueness = false,
53    inverse = super::CastPgLegacyCharToString
54)]
55fn cast_string_to_pg_legacy_char<'a>(a: &'a str) -> PgLegacyChar {
56    PgLegacyChar(a.as_bytes().get(0).copied().unwrap_or(0))
57}
58
59#[sqlfunc(sqlname = "text_to_name", preserves_uniqueness = false)]
60fn cast_string_to_pg_legacy_name<'a>(a: &'a str) -> PgLegacyName<String> {
61    PgLegacyName(strconv::parse_pg_legacy_name(a))
62}
63
64#[sqlfunc(
65    sqlname = "text_to_bytea",
66    // Not injective: `parse_bytes` accepts both hex (`\x..`) and the
67    // traditional textual encoding for the same bytes, so distinct text
68    // literals can map to the same bytea. Inverse-cast canonicalization of
69    // `b::text = lit` would otherwise rewrite to a comparison that ignores
70    // the actual textual form.
71    preserves_uniqueness = false,
72    inverse = super::CastBytesToString
73)]
74fn cast_string_to_bytes<'a>(a: &'a str) -> Result<Vec<u8>, EvalError> {
75    strconv::parse_bytes(a).err_into()
76}
77
78#[sqlfunc(
79    sqlname = "text_to_smallint",
80    preserves_uniqueness = false,
81    inverse = super::CastInt16ToString
82)]
83fn cast_string_to_int16<'a>(a: &'a str) -> Result<i16, EvalError> {
84    strconv::parse_int16(a).err_into()
85}
86
87#[sqlfunc(
88    sqlname = "text_to_integer",
89    preserves_uniqueness = false,
90    inverse = super::CastInt32ToString
91)]
92fn cast_string_to_int32<'a>(a: &'a str) -> Result<i32, EvalError> {
93    strconv::parse_int32(a).err_into()
94}
95
96#[sqlfunc(
97    sqlname = "text_to_bigint",
98    preserves_uniqueness = false,
99    inverse = super::CastInt64ToString
100)]
101fn cast_string_to_int64<'a>(a: &'a str) -> Result<i64, EvalError> {
102    strconv::parse_int64(a).err_into()
103}
104
105#[sqlfunc(
106    sqlname = "text_to_real",
107    preserves_uniqueness = false,
108    inverse = super::CastFloat32ToString
109)]
110fn cast_string_to_float32<'a>(a: &'a str) -> Result<f32, EvalError> {
111    strconv::parse_float32(a).err_into()
112}
113
114#[sqlfunc(
115    sqlname = "text_to_double",
116    preserves_uniqueness = false,
117    inverse = super::CastFloat64ToString
118)]
119fn cast_string_to_float64<'a>(a: &'a str) -> Result<f64, EvalError> {
120    strconv::parse_float64(a).err_into()
121}
122
123#[sqlfunc(
124    sqlname = "text_to_oid",
125    preserves_uniqueness = false,
126    inverse = super::CastOidToString
127)]
128fn cast_string_to_oid<'a>(a: &'a str) -> Result<Oid, EvalError> {
129    Ok(Oid(strconv::parse_oid(a)?))
130}
131
132#[sqlfunc(
133    sqlname = "text_to_uint2",
134    preserves_uniqueness = false,
135    inverse = super::CastUint16ToString
136)]
137fn cast_string_to_uint16(a: &str) -> Result<u16, EvalError> {
138    strconv::parse_uint16(a).err_into()
139}
140
141#[sqlfunc(
142    sqlname = "text_to_uint4",
143    preserves_uniqueness = false,
144    inverse = super::CastUint32ToString
145)]
146fn cast_string_to_uint32(a: &str) -> Result<u32, EvalError> {
147    strconv::parse_uint32(a).err_into()
148}
149
150#[sqlfunc(
151    sqlname = "text_to_uint8",
152    preserves_uniqueness = false,
153    inverse = super::CastUint64ToString
154)]
155fn cast_string_to_uint64(a: &str) -> Result<u64, EvalError> {
156    strconv::parse_uint64(a).err_into()
157}
158
159#[sqlfunc(preserves_uniqueness = true, inverse = Reverse)]
160fn reverse<'a>(a: &'a str) -> String {
161    a.chars().rev().collect()
162}
163
164#[derive(
165    Ord,
166    PartialOrd,
167    Clone,
168    Debug,
169    Eq,
170    PartialEq,
171    Serialize,
172    Deserialize,
173    Hash
174)]
175pub struct CastStringToNumeric(pub Option<NumericMaxScale>);
176
177#[sqlfunc(
178    CastStringToNumeric,
179    sqlname = "text_to_numeric",
180    output_type_expr = SqlScalarType::Numeric { max_scale: self.0 }.nullable(input_type.nullable),
181    inverse = super::CastNumericToString
182)]
183fn cast_string_to_numeric<'a>(&self, a: &'a str) -> Result<Numeric, EvalError> {
184    let mut d = strconv::parse_numeric(a)?;
185    if let Some(scale) = self.0 {
186        if numeric::rescale(&mut d.0, scale.into_u8()).is_err() {
187            return Err(EvalError::NumericFieldOverflow);
188        }
189    }
190    Ok(d.into_inner())
191}
192
193#[sqlfunc(
194    sqlname = "text_to_date",
195    preserves_uniqueness = false,
196    inverse = super::CastDateToString
197)]
198fn cast_string_to_date<'a>(a: &'a str) -> Result<Date, EvalError> {
199    strconv::parse_date(a).err_into()
200}
201
202#[sqlfunc(
203    sqlname = "text_to_time",
204    preserves_uniqueness = false,
205    inverse = super::CastTimeToString
206)]
207fn cast_string_to_time<'a>(a: &'a str) -> Result<NaiveTime, EvalError> {
208    strconv::parse_time(a).err_into()
209}
210
211#[derive(
212    Ord,
213    PartialOrd,
214    Clone,
215    Debug,
216    Eq,
217    PartialEq,
218    Serialize,
219    Deserialize,
220    Hash
221)]
222pub struct CastStringToTimestamp(pub Option<TimestampPrecision>);
223
224#[sqlfunc(
225    CastStringToTimestamp,
226    sqlname = "text_to_timestamp",
227    output_type_expr = SqlScalarType::Timestamp { precision: self.0 }.nullable(input_type.nullable),
228    inverse = super::CastTimestampToString
229)]
230fn cast_string_to_timestamp<'a>(
231    &self,
232    a: &'a str,
233) -> Result<CheckedTimestamp<NaiveDateTime>, EvalError> {
234    let out = strconv::parse_timestamp(a)?;
235    let updated = out.round_to_precision(self.0)?;
236    Ok(updated)
237}
238
239#[sqlfunc(sqlname = "try_parse_monotonic_iso8601_timestamp")]
240// TODO: Pretty sure this preserves uniqueness, but not 100%.
241//
242// Ironically, even though this has "monotonic" in the name, it's not quite
243// eligible for `#[is_monotone = true]` because any input could also be
244// mapped to null. So, handle it via SpecialUnary in the interpreter.
245fn try_parse_monotonic_iso8601_timestamp<'a>(
246    a: &'a str,
247) -> Option<CheckedTimestamp<NaiveDateTime>> {
248    let ts = mz_persist_types::timestamp::try_parse_monotonic_iso8601_timestamp(a)?;
249    let ts = CheckedTimestamp::from_timestamplike(ts)
250        .expect("monotonic_iso8601 range is a subset of CheckedTimestamp domain");
251    Some(ts)
252}
253
254#[derive(
255    Ord,
256    PartialOrd,
257    Clone,
258    Debug,
259    Eq,
260    PartialEq,
261    Serialize,
262    Deserialize,
263    Hash
264)]
265pub struct CastStringToTimestampTz(pub Option<TimestampPrecision>);
266
267#[sqlfunc(
268    CastStringToTimestampTz,
269    sqlname = "text_to_timestamp_with_time_zone",
270    output_type_expr = SqlScalarType::TimestampTz { precision: self.0 }
271        .nullable(input_type.nullable),
272    inverse = super::CastTimestampTzToString
273)]
274fn cast_string_to_timestamp_tz<'a>(
275    &self,
276    a: &'a str,
277) -> Result<CheckedTimestamp<DateTime<Utc>>, EvalError> {
278    let out = strconv::parse_timestamptz(a)?;
279    let updated = out.round_to_precision(self.0)?;
280    Ok(updated)
281}
282
283#[sqlfunc(
284    sqlname = "text_to_interval",
285    preserves_uniqueness = false,
286    inverse = super::CastIntervalToString
287)]
288fn cast_string_to_interval<'a>(a: &'a str) -> Result<Interval, EvalError> {
289    strconv::parse_interval(a).err_into()
290}
291
292#[sqlfunc(
293    sqlname = "text_to_uuid",
294    preserves_uniqueness = false,
295    inverse = super::CastUuidToString
296)]
297fn cast_string_to_uuid<'a>(a: &'a str) -> Result<Uuid, EvalError> {
298    strconv::parse_uuid(a).err_into()
299}
300
301#[derive(
302    Ord,
303    PartialOrd,
304    Clone,
305    Debug,
306    Eq,
307    PartialEq,
308    Serialize,
309    Deserialize,
310    Hash
311)]
312pub struct CastStringToArray<E = MirScalarExpr> {
313    // Target array's type.
314    pub return_ty: SqlScalarType,
315    // The expression to cast the discovered array elements to the array's
316    // element type.
317    pub cast_expr: Box<E>,
318}
319
320impl<E: Eval> LazyUnaryFunc for CastStringToArray<E> {
321    fn eval<'a>(
322        &'a self,
323        datums: &[Datum<'a>],
324        temp_storage: &'a RowArena,
325        a: &'a impl Eval,
326    ) -> Result<Datum<'a>, EvalError> {
327        let a = a.eval(datums, temp_storage)?;
328        if a.is_null() {
329            return Ok(Datum::Null);
330        }
331        let (datums, dims) = strconv::parse_array(
332            a.unwrap_str(),
333            || Datum::Null,
334            |elem_text| {
335                let elem_text = match elem_text {
336                    Cow::Owned(s) => temp_storage.push_string(s),
337                    Cow::Borrowed(s) => s,
338                };
339                self.cast_expr
340                    .eval(&[Datum::String(elem_text)], temp_storage)
341            },
342        )?;
343
344        Ok(temp_storage.try_make_datum(|packer| packer.try_push_array(&dims, datums))?)
345    }
346
347    /// The output SqlColumnType of this function
348    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
349        self.return_ty.clone().nullable(input_type.nullable)
350    }
351
352    /// Whether this function will produce NULL on NULL input
353    fn propagates_nulls(&self) -> bool {
354        true
355    }
356
357    /// Whether this function will produce NULL on non-NULL input
358    fn introduces_nulls(&self) -> bool {
359        false
360    }
361
362    /// Whether this function preserves uniqueness
363    fn preserves_uniqueness(&self) -> bool {
364        false
365    }
366
367    fn inverse(&self) -> Option<crate::UnaryFunc> {
368        to_unary!(super::CastArrayToString {
369            ty: self.return_ty.clone(),
370        })
371    }
372
373    fn is_monotone(&self) -> bool {
374        false
375    }
376
377    fn is_eliminable_cast(&self) -> bool {
378        false
379    }
380}
381
382impl<E> CastStringToArray<E> {
383    /// Rebuilds this function with the element cast expression converted to
384    /// `E2`.
385    pub fn try_map_expr<'a, E2: TryFrom<&'a E>>(
386        &'a self,
387    ) -> Result<CastStringToArray<E2>, E2::Error> {
388        Ok(CastStringToArray {
389            return_ty: self.return_ty.clone(),
390            cast_expr: Box::new(E2::try_from(&*self.cast_expr)?),
391        })
392    }
393
394    /// Rebuilds this function with the element cast expression converted to
395    /// `E2`.
396    pub fn map_expr<'a, E2: From<&'a E>>(&'a self) -> CastStringToArray<E2> {
397        CastStringToArray {
398            return_ty: self.return_ty.clone(),
399            cast_expr: Box::new(E2::from(&*self.cast_expr)),
400        }
401    }
402}
403
404impl<E> fmt::Display for CastStringToArray<E> {
405    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
406        f.write_str("strtoarray")
407    }
408}
409
410#[derive(
411    Ord,
412    PartialOrd,
413    Clone,
414    Debug,
415    Eq,
416    PartialEq,
417    Serialize,
418    Deserialize,
419    Hash
420)]
421pub struct CastStringToList<E = MirScalarExpr> {
422    // Target list's type
423    pub return_ty: SqlScalarType,
424    // The expression to cast the discovered list elements to the list's
425    // element type.
426    pub cast_expr: Box<E>,
427}
428
429impl<E: Eval> LazyUnaryFunc for CastStringToList<E> {
430    fn eval<'a>(
431        &'a self,
432        datums: &[Datum<'a>],
433        temp_storage: &'a RowArena,
434        a: &'a impl Eval,
435    ) -> Result<Datum<'a>, EvalError> {
436        let a = a.eval(datums, temp_storage)?;
437        if a.is_null() {
438            return Ok(Datum::Null);
439        }
440        let parsed_datums = strconv::parse_list(
441            a.unwrap_str(),
442            matches!(
443                self.return_ty.unwrap_list_element_type(),
444                SqlScalarType::List { .. }
445            ),
446            || Datum::Null,
447            |elem_text| {
448                let elem_text = match elem_text {
449                    Cow::Owned(s) => temp_storage.push_string(s),
450                    Cow::Borrowed(s) => s,
451                };
452                self.cast_expr
453                    .eval(&[Datum::String(elem_text)], temp_storage)
454            },
455        )?;
456
457        Ok(temp_storage.make_datum(|packer| packer.push_list(parsed_datums)))
458    }
459
460    /// The output SqlColumnType of this function
461    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
462        self.return_ty
463            .without_modifiers()
464            .nullable(input_type.nullable)
465    }
466
467    /// Whether this function will produce NULL on NULL input
468    fn propagates_nulls(&self) -> bool {
469        true
470    }
471
472    /// Whether this function will produce NULL on non-NULL input
473    fn introduces_nulls(&self) -> bool {
474        false
475    }
476
477    /// Whether this function preserves uniqueness
478    fn preserves_uniqueness(&self) -> bool {
479        false
480    }
481
482    fn inverse(&self) -> Option<crate::UnaryFunc> {
483        to_unary!(super::CastListToString {
484            ty: self.return_ty.clone(),
485        })
486    }
487
488    fn is_monotone(&self) -> bool {
489        false
490    }
491
492    fn is_eliminable_cast(&self) -> bool {
493        false
494    }
495}
496
497impl<E> CastStringToList<E> {
498    /// Rebuilds this function with the element cast expression converted to
499    /// `E2`.
500    pub fn try_map_expr<'a, E2: TryFrom<&'a E>>(
501        &'a self,
502    ) -> Result<CastStringToList<E2>, E2::Error> {
503        Ok(CastStringToList {
504            return_ty: self.return_ty.clone(),
505            cast_expr: Box::new(E2::try_from(&*self.cast_expr)?),
506        })
507    }
508
509    /// Rebuilds this function with the element cast expression converted to
510    /// `E2`.
511    pub fn map_expr<'a, E2: From<&'a E>>(&'a self) -> CastStringToList<E2> {
512        CastStringToList {
513            return_ty: self.return_ty.clone(),
514            cast_expr: Box::new(E2::from(&*self.cast_expr)),
515        }
516    }
517}
518
519impl<E> fmt::Display for CastStringToList<E> {
520    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
521        f.write_str("strtolist")
522    }
523}
524
525#[derive(
526    Ord,
527    PartialOrd,
528    Clone,
529    Debug,
530    Eq,
531    PartialEq,
532    Serialize,
533    Deserialize,
534    Hash
535)]
536pub struct CastStringToMap<E = MirScalarExpr> {
537    // Target map's value type
538    pub return_ty: SqlScalarType,
539    // The expression used to cast the discovered values to the map's value
540    // type.
541    pub cast_expr: Box<E>,
542}
543
544impl<E: Eval> LazyUnaryFunc for CastStringToMap<E> {
545    fn eval<'a>(
546        &'a self,
547        datums: &[Datum<'a>],
548        temp_storage: &'a RowArena,
549        a: &'a impl Eval,
550    ) -> Result<Datum<'a>, EvalError> {
551        let a = a.eval(datums, temp_storage)?;
552        if a.is_null() {
553            return Ok(Datum::Null);
554        }
555        let parsed_map = strconv::parse_map(
556            a.unwrap_str(),
557            matches!(
558                self.return_ty.unwrap_map_value_type(),
559                SqlScalarType::Map { .. }
560            ),
561            |value_text| -> Result<Datum, EvalError> {
562                let value_text = match value_text {
563                    Some(Cow::Owned(s)) => Datum::String(temp_storage.push_string(s)),
564                    Some(Cow::Borrowed(s)) => Datum::String(s),
565                    None => Datum::Null,
566                };
567                self.cast_expr.eval(&[value_text], temp_storage)
568            },
569        )?;
570        let mut pairs: Vec<(String, Datum)> = parsed_map.into_iter().map(|(k, v)| (k, v)).collect();
571        pairs.sort_by(|(k1, _v1), (k2, _v2)| k1.cmp(k2));
572        pairs.dedup_by(|(k1, _v1), (k2, _v2)| k1 == k2);
573        Ok(temp_storage.make_datum(|packer| {
574            packer.push_dict_with(|packer| {
575                for (k, v) in pairs {
576                    packer.push(Datum::String(&k));
577                    packer.push(v);
578                }
579            })
580        }))
581    }
582
583    /// The output SqlColumnType of this function
584    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
585        self.return_ty.clone().nullable(input_type.nullable)
586    }
587
588    /// Whether this function will produce NULL on NULL input
589    fn propagates_nulls(&self) -> bool {
590        true
591    }
592
593    /// Whether this function will produce NULL on non-NULL input
594    fn introduces_nulls(&self) -> bool {
595        false
596    }
597
598    /// Whether this function preserves uniqueness
599    fn preserves_uniqueness(&self) -> bool {
600        false
601    }
602
603    fn inverse(&self) -> Option<crate::UnaryFunc> {
604        to_unary!(super::CastMapToString {
605            ty: self.return_ty.clone(),
606        })
607    }
608
609    fn is_monotone(&self) -> bool {
610        false
611    }
612
613    fn is_eliminable_cast(&self) -> bool {
614        false
615    }
616}
617
618impl<E> CastStringToMap<E> {
619    /// Rebuilds this function with the element cast expression converted to
620    /// `E2`.
621    pub fn try_map_expr<'a, E2: TryFrom<&'a E>>(
622        &'a self,
623    ) -> Result<CastStringToMap<E2>, E2::Error> {
624        Ok(CastStringToMap {
625            return_ty: self.return_ty.clone(),
626            cast_expr: Box::new(E2::try_from(&*self.cast_expr)?),
627        })
628    }
629
630    /// Rebuilds this function with the element cast expression converted to
631    /// `E2`.
632    pub fn map_expr<'a, E2: From<&'a E>>(&'a self) -> CastStringToMap<E2> {
633        CastStringToMap {
634            return_ty: self.return_ty.clone(),
635            cast_expr: Box::new(E2::from(&*self.cast_expr)),
636        }
637    }
638}
639
640impl<E> fmt::Display for CastStringToMap<E> {
641    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
642        f.write_str("strtomap")
643    }
644}
645
646#[derive(
647    Ord,
648    PartialOrd,
649    Clone,
650    Debug,
651    Eq,
652    PartialEq,
653    Serialize,
654    Deserialize,
655    Hash
656)]
657pub struct CastStringToChar {
658    pub length: Option<mz_repr::adt::char::CharLength>,
659    pub fail_on_len: bool,
660}
661
662// Not an eliminable cast: even when `length` is `None`, this trims trailing whitespace.
663#[sqlfunc(
664    CastStringToChar,
665    skip_display = true,
666    output_type_expr = SqlScalarType::Char { length: self.length }.nullable(input_type.nullable),
667    could_error = self.fail_on_len && self.length.is_some(),
668    inverse = super::CastCharToString
669)]
670fn cast_string_to_char<'a>(&self, a: &'a str) -> Result<Char<String>, EvalError> {
671    let s = format_str_trim(a, self.length, self.fail_on_len).map_err(|_| {
672        assert!(self.fail_on_len);
673        EvalError::StringValueTooLong {
674            target_type: "character".into(),
675            length: usize::cast_from(self.length.unwrap().into_u32()),
676        }
677    })?;
678
679    Ok(Char(s))
680}
681
682impl fmt::Display for CastStringToChar {
683    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
684        match self.length {
685            Some(length) => {
686                write!(
687                    f,
688                    "text_to_char[len={}, fail_on_len={}]",
689                    length.into_u32(),
690                    self.fail_on_len
691                )
692            }
693            None => f.write_str("text_to_char[len=unbounded]"),
694        }
695    }
696}
697
698#[derive(
699    Ord,
700    PartialOrd,
701    Clone,
702    Debug,
703    Eq,
704    PartialEq,
705    Serialize,
706    Deserialize,
707    Hash
708)]
709pub struct CastStringToRange<E = MirScalarExpr> {
710    // Target range's type
711    pub return_ty: SqlScalarType,
712    // The expression to cast the discovered range elements to the range's
713    // element type.
714    pub cast_expr: Box<E>,
715}
716
717impl<E: Eval> LazyUnaryFunc for CastStringToRange<E> {
718    fn eval<'a>(
719        &'a self,
720        datums: &[Datum<'a>],
721        temp_storage: &'a RowArena,
722        a: &'a impl Eval,
723    ) -> Result<Datum<'a>, EvalError> {
724        let a = a.eval(datums, temp_storage)?;
725        if a.is_null() {
726            return Ok(Datum::Null);
727        }
728        let mut range = strconv::parse_range(a.unwrap_str(), |elem_text| {
729            let elem_text = match elem_text {
730                Cow::Owned(s) => temp_storage.push_string(s),
731                Cow::Borrowed(s) => s,
732            };
733            self.cast_expr
734                .eval(&[Datum::String(elem_text)], temp_storage)
735        })?;
736
737        range.canonicalize()?;
738
739        Ok(temp_storage.make_datum(|packer| {
740            packer
741                .push_range(range)
742                .expect("must have already handled errors")
743        }))
744    }
745
746    /// The output SqlColumnType of this function
747    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
748        self.return_ty
749            .without_modifiers()
750            .nullable(input_type.nullable)
751    }
752
753    /// Whether this function will produce NULL on NULL input
754    fn propagates_nulls(&self) -> bool {
755        true
756    }
757
758    /// Whether this function will produce NULL on non-NULL input
759    fn introduces_nulls(&self) -> bool {
760        false
761    }
762
763    /// Whether this function preserves uniqueness
764    fn preserves_uniqueness(&self) -> bool {
765        false
766    }
767
768    fn inverse(&self) -> Option<crate::UnaryFunc> {
769        to_unary!(super::CastRangeToString {
770            ty: self.return_ty.clone(),
771        })
772    }
773
774    fn is_monotone(&self) -> bool {
775        false
776    }
777
778    fn is_eliminable_cast(&self) -> bool {
779        false
780    }
781}
782
783impl<E> CastStringToRange<E> {
784    /// Rebuilds this function with the element cast expression converted to
785    /// `E2`.
786    pub fn try_map_expr<'a, E2: TryFrom<&'a E>>(
787        &'a self,
788    ) -> Result<CastStringToRange<E2>, E2::Error> {
789        Ok(CastStringToRange {
790            return_ty: self.return_ty.clone(),
791            cast_expr: Box::new(E2::try_from(&*self.cast_expr)?),
792        })
793    }
794
795    /// Rebuilds this function with the element cast expression converted to
796    /// `E2`.
797    pub fn map_expr<'a, E2: From<&'a E>>(&'a self) -> CastStringToRange<E2> {
798        CastStringToRange {
799            return_ty: self.return_ty.clone(),
800            cast_expr: Box::new(E2::from(&*self.cast_expr)),
801        }
802    }
803}
804
805impl<E> fmt::Display for CastStringToRange<E> {
806    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
807        f.write_str("strtorange")
808    }
809}
810
811#[derive(
812    Ord,
813    PartialOrd,
814    Clone,
815    Debug,
816    Eq,
817    PartialEq,
818    Serialize,
819    Deserialize,
820    Hash
821)]
822pub struct CastStringToVarChar {
823    pub length: Option<VarCharMaxLength>,
824    pub fail_on_len: bool,
825}
826
827#[sqlfunc(
828    CastStringToVarChar,
829    skip_display = true,
830    output_type_expr = SqlScalarType::VarChar { max_length: self.length }
831        .nullable(input_type.nullable),
832    could_error = self.fail_on_len && self.length.is_some(),
833    inverse = super::CastVarCharToString,
834    preserves_uniqueness = self.length.is_none(),
835    is_eliminable_cast = self.length.is_none()
836)]
837fn cast_string_to_var_char<'a>(&self, a: &'a str) -> Result<VarChar<&'a str>, EvalError> {
838    let s = mz_repr::adt::varchar::format_str(a, self.length, self.fail_on_len).map_err(|_| {
839        assert!(self.fail_on_len);
840        EvalError::StringValueTooLong {
841            target_type: "character varying".into(),
842            length: usize::cast_from(self.length.unwrap().into_u32()),
843        }
844    })?;
845
846    Ok(VarChar(s))
847}
848
849impl fmt::Display for CastStringToVarChar {
850    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
851        match self.length {
852            Some(length) => {
853                write!(
854                    f,
855                    "text_to_varchar[len={}, fail_on_len={}]",
856                    length.into_u32(),
857                    self.fail_on_len
858                )
859            }
860            None => f.write_str("text_to_varchar[len=unbounded]"),
861        }
862    }
863}
864
865// If we support another vector type, this should likely get hoisted into a
866// position akin to array parsing.
867static INT2VECTOR_CAST_EXPR: LazyLock<MirScalarExpr> = LazyLock::new(|| MirScalarExpr::CallUnary {
868    func: UnaryFunc::CastStringToInt16(CastStringToInt16),
869    expr: Box::new(MirScalarExpr::column(0)),
870});
871
872#[sqlfunc(
873    sqlname = "strtoint2vector",
874    inverse = super::CastInt2VectorToString
875)]
876fn cast_string_to_int2_vector<'a>(
877    a: &'a str,
878    temp_storage: &'a RowArena,
879) -> Result<Int2Vector<'a>, EvalError> {
880    let datums = strconv::parse_legacy_vector(a, |elem_text| {
881        let elem_text = match elem_text {
882            Cow::Owned(s) => temp_storage.push_string(s),
883            Cow::Borrowed(s) => s,
884        };
885        INT2VECTOR_CAST_EXPR.eval(&[Datum::String(elem_text)], temp_storage)
886    })?;
887    Ok(Int2Vector(array_create_scalar(&datums, temp_storage)?))
888}
889
890#[sqlfunc(
891    sqlname = "text_to_jsonb",
892    preserves_uniqueness = false,
893    inverse = super::CastJsonbToString
894)]
895// TODO(jamii): it would be much more efficient to skip the intermediate repr::jsonb::Jsonb.
896fn cast_string_to_jsonb<'a>(a: &'a str) -> Result<Jsonb, EvalError> {
897    Ok(strconv::parse_jsonb(a)?)
898}
899
900#[sqlfunc(sqlname = "btrim")]
901fn trim_whitespace<'a>(a: &'a str) -> &'a str {
902    a.trim_matches(' ')
903}
904
905#[sqlfunc(sqlname = "ltrim")]
906fn trim_leading_whitespace<'a>(a: &'a str) -> &'a str {
907    a.trim_start_matches(' ')
908}
909
910#[sqlfunc(sqlname = "rtrim")]
911fn trim_trailing_whitespace<'a>(a: &'a str) -> &'a str {
912    a.trim_end_matches(' ')
913}
914
915#[sqlfunc(sqlname = "initcap")]
916fn initcap<'a>(a: &'a str) -> String {
917    let mut out = String::new();
918    let mut capitalize_next = true;
919    for ch in a.chars() {
920        if capitalize_next {
921            out.extend(ch.to_uppercase())
922        } else {
923            out.extend(ch.to_lowercase())
924        };
925        capitalize_next = !ch.is_alphanumeric();
926    }
927    out
928}
929
930#[sqlfunc(sqlname = "ascii")]
931fn ascii<'a>(a: &'a str) -> i32 {
932    a.chars()
933        .next()
934        .and_then(|c| i32::try_from(u32::from(c)).ok())
935        .unwrap_or(0)
936}
937
938#[sqlfunc(sqlname = "char_length")]
939fn char_length<'a>(a: &'a str) -> Result<i32, EvalError> {
940    let length = a.chars().count();
941    i32::try_from(length).or_else(|_| Err(EvalError::Int32OutOfRange(length.to_string().into())))
942}
943
944#[sqlfunc(sqlname = "bit_length")]
945fn bit_length_string<'a>(a: &'a str) -> Result<i32, EvalError> {
946    let length = a.as_bytes().len() * 8;
947    i32::try_from(length).or_else(|_| Err(EvalError::Int32OutOfRange(length.to_string().into())))
948}
949
950#[sqlfunc(sqlname = "octet_length")]
951fn byte_length_string<'a>(a: &'a str) -> Result<i32, EvalError> {
952    let length = a.as_bytes().len();
953    i32::try_from(length).or_else(|_| Err(EvalError::Int32OutOfRange(length.to_string().into())))
954}
955
956#[sqlfunc]
957fn upper<'a>(a: &'a str) -> String {
958    a.to_uppercase()
959}
960
961#[sqlfunc]
962fn lower<'a>(a: &'a str) -> String {
963    a.to_lowercase()
964}
965
966#[sqlfunc]
967fn normalize(text: &str, form_str: &str) -> Result<String, EvalError> {
968    use unicode_normalization::UnicodeNormalization;
969
970    match form_str.to_uppercase().as_str() {
971        "NFC" => Ok(text.nfc().collect()),
972        "NFD" => Ok(text.nfd().collect()),
973        "NFKC" => Ok(text.nfkc().collect()),
974        "NFKD" => Ok(text.nfkd().collect()),
975        _ => Err(EvalError::InvalidParameterValue(
976            format!("invalid normalization form: {}", form_str).into(),
977        )),
978    }
979}
980
981#[derive(
982    Ord,
983    PartialOrd,
984    Clone,
985    Debug,
986    Eq,
987    PartialEq,
988    Serialize,
989    Deserialize,
990    Hash
991)]
992pub struct IsLikeMatch(pub like_pattern::Matcher);
993
994#[sqlfunc(IsLikeMatch, skip_display = true)]
995fn is_like_match<'a>(&self, haystack: &'a str) -> bool {
996    self.0.is_match(haystack)
997}
998
999impl fmt::Display for IsLikeMatch {
1000    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1001        write!(
1002            f,
1003            "{}like[{}]",
1004            if self.0.case_insensitive { "i" } else { "" },
1005            self.0.pattern.escaped()
1006        )
1007    }
1008}
1009
1010#[derive(
1011    Ord,
1012    PartialOrd,
1013    Clone,
1014    Debug,
1015    Eq,
1016    PartialEq,
1017    Serialize,
1018    Deserialize,
1019    Hash
1020)]
1021pub struct IsRegexpMatch(pub Regex);
1022
1023#[sqlfunc(IsRegexpMatch, skip_display = true)]
1024fn is_regexp_match<'a>(&self, haystack: &'a str) -> bool {
1025    self.0.is_match(haystack)
1026}
1027
1028impl fmt::Display for IsRegexpMatch {
1029    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1030        write!(
1031            f,
1032            "is_regexp_match[{}, case_insensitive={}]",
1033            self.0.pattern().escaped(),
1034            self.0.case_insensitive
1035        )
1036    }
1037}
1038
1039#[derive(
1040    Ord,
1041    PartialOrd,
1042    Clone,
1043    Debug,
1044    Eq,
1045    PartialEq,
1046    Serialize,
1047    Deserialize,
1048    Hash
1049)]
1050// The serde name distinguishes this precompiled-pattern function from the
1051// generated struct of the dynamic-pattern variadic `regexp_match`, which is
1052// also named `RegexpMatch`. The stable LIR schema registry requires container
1053// names to be unique. Same for `RegexpSplitToArray` and `RegexpReplace` below.
1054#[serde(rename = "RegexpMatchStatic")]
1055pub struct RegexpMatch(pub Regex);
1056
1057#[sqlfunc(
1058    RegexpMatch,
1059    skip_display = true,
1060    // Returns null if the regex did not match.
1061    output_type_expr = SqlScalarType::Array(Box::new(SqlScalarType::String)).nullable(true)
1062)]
1063fn regexp_match<'a>(
1064    &self,
1065    haystack: &'a str,
1066    temp_storage: &'a RowArena,
1067) -> Result<Option<Array<'a>>, EvalError> {
1068    regexp_match_static(haystack, temp_storage, &self.0)
1069}
1070
1071impl fmt::Display for RegexpMatch {
1072    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1073        write!(
1074            f,
1075            "regexp_match[{}, case_insensitive={}]",
1076            self.0.pattern().escaped(),
1077            self.0.case_insensitive
1078        )
1079    }
1080}
1081
1082#[derive(
1083    Ord,
1084    PartialOrd,
1085    Clone,
1086    Debug,
1087    Eq,
1088    PartialEq,
1089    Serialize,
1090    Deserialize,
1091    Hash
1092)]
1093// See `RegexpMatch` above for explanation.
1094#[serde(rename = "RegexpSplitToArrayStatic")]
1095pub struct RegexpSplitToArray(pub Regex);
1096
1097#[sqlfunc(
1098    RegexpSplitToArray,
1099    skip_display = true,
1100    output_type_expr = SqlScalarType::Array(Box::new(SqlScalarType::String))
1101        .nullable(input_type.nullable)
1102)]
1103fn regexp_split_to_array<'a>(
1104    &self,
1105    haystack: &'a str,
1106    temp_storage: &'a RowArena,
1107) -> Result<Array<'a>, EvalError> {
1108    regexp_split_to_array_re(haystack, &self.0, temp_storage)
1109}
1110
1111impl fmt::Display for RegexpSplitToArray {
1112    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1113        write!(
1114            f,
1115            "regexp_split_to_array[{}, case_insensitive={}]",
1116            self.0.pattern().escaped(),
1117            self.0.case_insensitive
1118        )
1119    }
1120}
1121
1122#[sqlfunc(sqlname = "mz_panic")]
1123fn panic<'a>(a: &'a str) -> String {
1124    print!("{}", a);
1125    panic!("{}", a)
1126}
1127
1128#[sqlfunc(sqlname = "quote_ident", preserves_uniqueness = true)]
1129fn quote_ident<'a>(a: &'a str) -> Result<String, EvalError> {
1130    let i = mz_sql_parser::ast::Ident::new(a).map_err(|err| EvalError::InvalidIdentifier {
1131        ident: a.into(),
1132        detail: Some(err.to_string().into()),
1133    })?;
1134    Ok(i.to_string())
1135}
1136
1137#[derive(
1138    Ord,
1139    PartialOrd,
1140    Clone,
1141    Debug,
1142    Eq,
1143    PartialEq,
1144    Serialize,
1145    Deserialize,
1146    Hash
1147)]
1148// See `RegexpMatch` above for explanation.
1149#[serde(rename = "RegexpReplaceStatic")]
1150pub struct RegexpReplace {
1151    pub regex: Regex,
1152    pub limit: usize,
1153}
1154
1155#[sqlfunc(RegexpReplace, skip_display = true)]
1156fn regexp_replace<'a>(&self, source: &'a str, replacement: &'a str) -> Cow<'a, str> {
1157    // WARNING: This function has potential OOM risk if used with an inflationary
1158    // replacement pattern. It is very difficult to calculate the output size ahead
1159    // of time because the replacement pattern may depend on capture groups.
1160    self.regex.replacen(source, self.limit, replacement)
1161}
1162
1163impl fmt::Display for RegexpReplace {
1164    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1165        write!(
1166            f,
1167            "regexp_replace[{}, case_insensitive={}, limit={}]",
1168            self.regex.pattern().escaped(),
1169            self.regex.case_insensitive,
1170            self.limit
1171        )
1172    }
1173}