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::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    // Not injective: only the first byte is kept (e.g. 'a' and 'abc' both
51    // collapse to 'a'::"char"), so inverse-cast canonicalization of
52    // `c::text = lit` would silently change results.
53    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    // Not injective: `parse_bytes` accepts both hex (`\x..`) and the
68    // traditional textual encoding for the same bytes, so distinct text
69    // literals can map to the same bytea. Inverse-cast canonicalization of
70    // `b::text = lit` would otherwise rewrite to a comparison that ignores
71    // the actual textual form.
72    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")]
264// TODO: Pretty sure this preserves uniqueness, but not 100%.
265//
266// Ironically, even though this has "monotonic" in the name, it's not quite
267// eligible for `#[is_monotone = true]` because any input could also be
268// mapped to null. So, handle it via SpecialUnary in the interpreter.
269fn 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    // Target array's type.
347    pub return_ty: SqlScalarType,
348    // The expression to cast the discovered array elements to the array's
349    // element type.
350    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    /// The output SqlColumnType of this function
381    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
382        self.return_ty.clone().nullable(input_type.nullable)
383    }
384
385    /// Whether this function will produce NULL on NULL input
386    fn propagates_nulls(&self) -> bool {
387        true
388    }
389
390    /// Whether this function will produce NULL on non-NULL input
391    fn introduces_nulls(&self) -> bool {
392        false
393    }
394
395    /// Whether this function preserves uniqueness
396    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    // Target list's type
434    pub return_ty: SqlScalarType,
435    // The expression to cast the discovered list elements to the list's
436    // element type.
437    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    /// The output SqlColumnType of this function
472    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
473        self.return_ty
474            .without_modifiers()
475            .nullable(input_type.nullable)
476    }
477
478    /// Whether this function will produce NULL on NULL input
479    fn propagates_nulls(&self) -> bool {
480        true
481    }
482
483    /// Whether this function will produce NULL on non-NULL input
484    fn introduces_nulls(&self) -> bool {
485        false
486    }
487
488    /// Whether this function preserves uniqueness
489    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    // Target map's value type
527    pub return_ty: SqlScalarType,
528    // The expression used to cast the discovered values to the map's value
529    // type.
530    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    /// The output SqlColumnType of this function
573    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
574        self.return_ty.clone().nullable(input_type.nullable)
575    }
576
577    /// Whether this function will produce NULL on NULL input
578    fn propagates_nulls(&self) -> bool {
579        true
580    }
581
582    /// Whether this function will produce NULL on non-NULL input
583    fn introduces_nulls(&self) -> bool {
584        false
585    }
586
587    /// Whether this function preserves uniqueness
588    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        // even when `length` is `None`, we'll trim whitespace at the end
662        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    // Target range's type
695    pub return_ty: SqlScalarType,
696    // The expression to cast the discovered range elements to the range's
697    // element type.
698    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    /// The output SqlColumnType of this function
731    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
732        self.return_ty
733            .without_modifiers()
734            .nullable(input_type.nullable)
735    }
736
737    /// Whether this function will produce NULL on NULL input
738    fn propagates_nulls(&self) -> bool {
739        true
740    }
741
742    /// Whether this function will produce NULL on non-NULL input
743    fn introduces_nulls(&self) -> bool {
744        false
745    }
746
747    /// Whether this function preserves uniqueness
748    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
846// If we support another vector type, this should likely get hoisted into a
847// position akin to array parsing.
848static 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    /// The output SqlColumnType of this function
889    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
890        SqlScalarType::Int2Vector.nullable(input_type.nullable)
891    }
892
893    /// Whether this function will produce NULL on NULL input
894    fn propagates_nulls(&self) -> bool {
895        true
896    }
897
898    /// Whether this function will produce NULL on non-NULL input
899    fn introduces_nulls(&self) -> bool {
900        false
901    }
902
903    /// Whether this function preserves uniqueness
904    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)]
932// TODO(jamii): it would be much more efficient to skip the intermediate repr::jsonb::Jsonb.
933fn 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    /// The output SqlColumnType of this function
1120    fn output_sql_type(&self, _input_type: SqlColumnType) -> SqlColumnType {
1121        SqlScalarType::Array(Box::new(SqlScalarType::String)).nullable(true)
1122    }
1123
1124    /// Whether this function will produce NULL on NULL input
1125    fn propagates_nulls(&self) -> bool {
1126        true
1127    }
1128
1129    /// Whether this function will produce NULL on non-NULL input
1130    fn introduces_nulls(&self) -> bool {
1131        // Returns null if the regex did not match
1132        true
1133    }
1134
1135    /// Whether this function preserves uniqueness
1136    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    /// The output SqlColumnType of this function
1192    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
1193        SqlScalarType::Array(Box::new(SqlScalarType::String)).nullable(input_type.nullable)
1194    }
1195
1196    /// Whether this function will produce NULL on NULL input
1197    fn propagates_nulls(&self) -> bool {
1198        true
1199    }
1200
1201    /// Whether this function will produce NULL on non-NULL input
1202    fn introduces_nulls(&self) -> bool {
1203        false
1204    }
1205
1206    /// Whether this function preserves uniqueness
1207    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        // WARNING: This function has potential OOM risk if used with an inflationary
1276        // replacement pattern. It is very difficult to calculate the output size ahead
1277        // of time because the replacement pattern may depend on capture groups.
1278        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}