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<E = MirScalarExpr> {
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<E>,
351}
352
353impl<E: Eval> LazyUnaryFunc for CastStringToArray<E> {
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<E> CastStringToArray<E> {
416    /// Rebuilds this function with the element cast expression converted to
417    /// `E2`.
418    pub fn try_map_expr<'a, E2: TryFrom<&'a E>>(
419        &'a self,
420    ) -> Result<CastStringToArray<E2>, E2::Error> {
421        Ok(CastStringToArray {
422            return_ty: self.return_ty.clone(),
423            cast_expr: Box::new(E2::try_from(&*self.cast_expr)?),
424        })
425    }
426
427    /// Rebuilds this function with the element cast expression converted to
428    /// `E2`.
429    pub fn map_expr<'a, E2: From<&'a E>>(&'a self) -> CastStringToArray<E2> {
430        CastStringToArray {
431            return_ty: self.return_ty.clone(),
432            cast_expr: Box::new(E2::from(&*self.cast_expr)),
433        }
434    }
435}
436
437impl<E> fmt::Display for CastStringToArray<E> {
438    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
439        f.write_str("strtoarray")
440    }
441}
442
443#[derive(
444    Ord,
445    PartialOrd,
446    Clone,
447    Debug,
448    Eq,
449    PartialEq,
450    Serialize,
451    Deserialize,
452    Hash
453)]
454pub struct CastStringToList<E = MirScalarExpr> {
455    // Target list's type
456    pub return_ty: SqlScalarType,
457    // The expression to cast the discovered list elements to the list's
458    // element type.
459    pub cast_expr: Box<E>,
460}
461
462impl<E: Eval> LazyUnaryFunc for CastStringToList<E> {
463    fn eval<'a>(
464        &'a self,
465        datums: &[Datum<'a>],
466        temp_storage: &'a RowArena,
467        a: &'a impl Eval,
468    ) -> Result<Datum<'a>, EvalError> {
469        let a = a.eval(datums, temp_storage)?;
470        if a.is_null() {
471            return Ok(Datum::Null);
472        }
473        let parsed_datums = strconv::parse_list(
474            a.unwrap_str(),
475            matches!(
476                self.return_ty.unwrap_list_element_type(),
477                SqlScalarType::List { .. }
478            ),
479            || Datum::Null,
480            |elem_text| {
481                let elem_text = match elem_text {
482                    Cow::Owned(s) => temp_storage.push_string(s),
483                    Cow::Borrowed(s) => s,
484                };
485                self.cast_expr
486                    .eval(&[Datum::String(elem_text)], temp_storage)
487            },
488        )?;
489
490        Ok(temp_storage.make_datum(|packer| packer.push_list(parsed_datums)))
491    }
492
493    /// The output SqlColumnType of this function
494    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
495        self.return_ty
496            .without_modifiers()
497            .nullable(input_type.nullable)
498    }
499
500    /// Whether this function will produce NULL on NULL input
501    fn propagates_nulls(&self) -> bool {
502        true
503    }
504
505    /// Whether this function will produce NULL on non-NULL input
506    fn introduces_nulls(&self) -> bool {
507        false
508    }
509
510    /// Whether this function preserves uniqueness
511    fn preserves_uniqueness(&self) -> bool {
512        false
513    }
514
515    fn inverse(&self) -> Option<crate::UnaryFunc> {
516        to_unary!(super::CastListToString {
517            ty: self.return_ty.clone(),
518        })
519    }
520
521    fn is_monotone(&self) -> bool {
522        false
523    }
524
525    fn is_eliminable_cast(&self) -> bool {
526        false
527    }
528}
529
530impl<E> CastStringToList<E> {
531    /// Rebuilds this function with the element cast expression converted to
532    /// `E2`.
533    pub fn try_map_expr<'a, E2: TryFrom<&'a E>>(
534        &'a self,
535    ) -> Result<CastStringToList<E2>, E2::Error> {
536        Ok(CastStringToList {
537            return_ty: self.return_ty.clone(),
538            cast_expr: Box::new(E2::try_from(&*self.cast_expr)?),
539        })
540    }
541
542    /// Rebuilds this function with the element cast expression converted to
543    /// `E2`.
544    pub fn map_expr<'a, E2: From<&'a E>>(&'a self) -> CastStringToList<E2> {
545        CastStringToList {
546            return_ty: self.return_ty.clone(),
547            cast_expr: Box::new(E2::from(&*self.cast_expr)),
548        }
549    }
550}
551
552impl<E> fmt::Display for CastStringToList<E> {
553    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
554        f.write_str("strtolist")
555    }
556}
557
558#[derive(
559    Ord,
560    PartialOrd,
561    Clone,
562    Debug,
563    Eq,
564    PartialEq,
565    Serialize,
566    Deserialize,
567    Hash
568)]
569pub struct CastStringToMap<E = MirScalarExpr> {
570    // Target map's value type
571    pub return_ty: SqlScalarType,
572    // The expression used to cast the discovered values to the map's value
573    // type.
574    pub cast_expr: Box<E>,
575}
576
577impl<E: Eval> LazyUnaryFunc for CastStringToMap<E> {
578    fn eval<'a>(
579        &'a self,
580        datums: &[Datum<'a>],
581        temp_storage: &'a RowArena,
582        a: &'a impl Eval,
583    ) -> Result<Datum<'a>, EvalError> {
584        let a = a.eval(datums, temp_storage)?;
585        if a.is_null() {
586            return Ok(Datum::Null);
587        }
588        let parsed_map = strconv::parse_map(
589            a.unwrap_str(),
590            matches!(
591                self.return_ty.unwrap_map_value_type(),
592                SqlScalarType::Map { .. }
593            ),
594            |value_text| -> Result<Datum, EvalError> {
595                let value_text = match value_text {
596                    Some(Cow::Owned(s)) => Datum::String(temp_storage.push_string(s)),
597                    Some(Cow::Borrowed(s)) => Datum::String(s),
598                    None => Datum::Null,
599                };
600                self.cast_expr.eval(&[value_text], temp_storage)
601            },
602        )?;
603        let mut pairs: Vec<(String, Datum)> = parsed_map.into_iter().map(|(k, v)| (k, v)).collect();
604        pairs.sort_by(|(k1, _v1), (k2, _v2)| k1.cmp(k2));
605        pairs.dedup_by(|(k1, _v1), (k2, _v2)| k1 == k2);
606        Ok(temp_storage.make_datum(|packer| {
607            packer.push_dict_with(|packer| {
608                for (k, v) in pairs {
609                    packer.push(Datum::String(&k));
610                    packer.push(v);
611                }
612            })
613        }))
614    }
615
616    /// The output SqlColumnType of this function
617    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
618        self.return_ty.clone().nullable(input_type.nullable)
619    }
620
621    /// Whether this function will produce NULL on NULL input
622    fn propagates_nulls(&self) -> bool {
623        true
624    }
625
626    /// Whether this function will produce NULL on non-NULL input
627    fn introduces_nulls(&self) -> bool {
628        false
629    }
630
631    /// Whether this function preserves uniqueness
632    fn preserves_uniqueness(&self) -> bool {
633        false
634    }
635
636    fn inverse(&self) -> Option<crate::UnaryFunc> {
637        to_unary!(super::CastMapToString {
638            ty: self.return_ty.clone(),
639        })
640    }
641
642    fn is_monotone(&self) -> bool {
643        false
644    }
645
646    fn is_eliminable_cast(&self) -> bool {
647        false
648    }
649}
650
651impl<E> CastStringToMap<E> {
652    /// Rebuilds this function with the element cast expression converted to
653    /// `E2`.
654    pub fn try_map_expr<'a, E2: TryFrom<&'a E>>(
655        &'a self,
656    ) -> Result<CastStringToMap<E2>, E2::Error> {
657        Ok(CastStringToMap {
658            return_ty: self.return_ty.clone(),
659            cast_expr: Box::new(E2::try_from(&*self.cast_expr)?),
660        })
661    }
662
663    /// Rebuilds this function with the element cast expression converted to
664    /// `E2`.
665    pub fn map_expr<'a, E2: From<&'a E>>(&'a self) -> CastStringToMap<E2> {
666        CastStringToMap {
667            return_ty: self.return_ty.clone(),
668            cast_expr: Box::new(E2::from(&*self.cast_expr)),
669        }
670    }
671}
672
673impl<E> fmt::Display for CastStringToMap<E> {
674    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
675        f.write_str("strtomap")
676    }
677}
678
679#[derive(
680    Ord,
681    PartialOrd,
682    Clone,
683    Debug,
684    Eq,
685    PartialEq,
686    Serialize,
687    Deserialize,
688    Hash
689)]
690pub struct CastStringToChar {
691    pub length: Option<mz_repr::adt::char::CharLength>,
692    pub fail_on_len: bool,
693}
694
695impl EagerUnaryFunc for CastStringToChar {
696    type Input<'a> = &'a str;
697    type Output<'a> = Result<Char<String>, EvalError>;
698
699    fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
700        let s = format_str_trim(a, self.length, self.fail_on_len).map_err(|_| {
701            assert!(self.fail_on_len);
702            EvalError::StringValueTooLong {
703                target_type: "character".into(),
704                length: usize::cast_from(self.length.unwrap().into_u32()),
705            }
706        })?;
707
708        Ok(Char(s))
709    }
710
711    fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
712        SqlScalarType::Char {
713            length: self.length,
714        }
715        .nullable(input.nullable)
716    }
717
718    fn could_error(&self) -> bool {
719        self.fail_on_len && self.length.is_some()
720    }
721
722    fn inverse(&self) -> Option<crate::UnaryFunc> {
723        to_unary!(super::CastCharToString)
724    }
725
726    fn is_eliminable_cast(&self) -> bool {
727        // even when `length` is `None`, we'll trim whitespace at the end
728        false
729    }
730}
731
732impl fmt::Display for CastStringToChar {
733    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
734        match self.length {
735            Some(length) => {
736                write!(
737                    f,
738                    "text_to_char[len={}, fail_on_len={}]",
739                    length.into_u32(),
740                    self.fail_on_len
741                )
742            }
743            None => f.write_str("text_to_char[len=unbounded]"),
744        }
745    }
746}
747
748#[derive(
749    Ord,
750    PartialOrd,
751    Clone,
752    Debug,
753    Eq,
754    PartialEq,
755    Serialize,
756    Deserialize,
757    Hash
758)]
759pub struct CastStringToRange<E = MirScalarExpr> {
760    // Target range's type
761    pub return_ty: SqlScalarType,
762    // The expression to cast the discovered range elements to the range's
763    // element type.
764    pub cast_expr: Box<E>,
765}
766
767impl<E: Eval> LazyUnaryFunc for CastStringToRange<E> {
768    fn eval<'a>(
769        &'a self,
770        datums: &[Datum<'a>],
771        temp_storage: &'a RowArena,
772        a: &'a impl Eval,
773    ) -> Result<Datum<'a>, EvalError> {
774        let a = a.eval(datums, temp_storage)?;
775        if a.is_null() {
776            return Ok(Datum::Null);
777        }
778        let mut range = strconv::parse_range(a.unwrap_str(), |elem_text| {
779            let elem_text = match elem_text {
780                Cow::Owned(s) => temp_storage.push_string(s),
781                Cow::Borrowed(s) => s,
782            };
783            self.cast_expr
784                .eval(&[Datum::String(elem_text)], temp_storage)
785        })?;
786
787        range.canonicalize()?;
788
789        Ok(temp_storage.make_datum(|packer| {
790            packer
791                .push_range(range)
792                .expect("must have already handled errors")
793        }))
794    }
795
796    /// The output SqlColumnType of this function
797    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
798        self.return_ty
799            .without_modifiers()
800            .nullable(input_type.nullable)
801    }
802
803    /// Whether this function will produce NULL on NULL input
804    fn propagates_nulls(&self) -> bool {
805        true
806    }
807
808    /// Whether this function will produce NULL on non-NULL input
809    fn introduces_nulls(&self) -> bool {
810        false
811    }
812
813    /// Whether this function preserves uniqueness
814    fn preserves_uniqueness(&self) -> bool {
815        false
816    }
817
818    fn inverse(&self) -> Option<crate::UnaryFunc> {
819        to_unary!(super::CastRangeToString {
820            ty: self.return_ty.clone(),
821        })
822    }
823
824    fn is_monotone(&self) -> bool {
825        false
826    }
827
828    fn is_eliminable_cast(&self) -> bool {
829        false
830    }
831}
832
833impl<E> CastStringToRange<E> {
834    /// Rebuilds this function with the element cast expression converted to
835    /// `E2`.
836    pub fn try_map_expr<'a, E2: TryFrom<&'a E>>(
837        &'a self,
838    ) -> Result<CastStringToRange<E2>, E2::Error> {
839        Ok(CastStringToRange {
840            return_ty: self.return_ty.clone(),
841            cast_expr: Box::new(E2::try_from(&*self.cast_expr)?),
842        })
843    }
844
845    /// Rebuilds this function with the element cast expression converted to
846    /// `E2`.
847    pub fn map_expr<'a, E2: From<&'a E>>(&'a self) -> CastStringToRange<E2> {
848        CastStringToRange {
849            return_ty: self.return_ty.clone(),
850            cast_expr: Box::new(E2::from(&*self.cast_expr)),
851        }
852    }
853}
854
855impl<E> fmt::Display for CastStringToRange<E> {
856    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
857        f.write_str("strtorange")
858    }
859}
860
861#[derive(
862    Ord,
863    PartialOrd,
864    Clone,
865    Debug,
866    Eq,
867    PartialEq,
868    Serialize,
869    Deserialize,
870    Hash
871)]
872pub struct CastStringToVarChar {
873    pub length: Option<VarCharMaxLength>,
874    pub fail_on_len: bool,
875}
876
877impl EagerUnaryFunc for CastStringToVarChar {
878    type Input<'a> = &'a str;
879    type Output<'a> = Result<VarChar<&'a str>, EvalError>;
880
881    fn call<'a>(&self, a: Self::Input<'a>) -> Self::Output<'a> {
882        let s =
883            mz_repr::adt::varchar::format_str(a, self.length, self.fail_on_len).map_err(|_| {
884                assert!(self.fail_on_len);
885                EvalError::StringValueTooLong {
886                    target_type: "character varying".into(),
887                    length: usize::cast_from(self.length.unwrap().into_u32()),
888                }
889            })?;
890
891        Ok(VarChar(s))
892    }
893
894    fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
895        SqlScalarType::VarChar {
896            max_length: self.length,
897        }
898        .nullable(input.nullable)
899    }
900
901    fn could_error(&self) -> bool {
902        self.fail_on_len && self.length.is_some()
903    }
904
905    fn preserves_uniqueness(&self) -> bool {
906        self.length.is_none()
907    }
908
909    fn inverse(&self) -> Option<crate::UnaryFunc> {
910        to_unary!(super::CastVarCharToString)
911    }
912
913    fn is_eliminable_cast(&self) -> bool {
914        self.length.is_none()
915    }
916}
917
918impl fmt::Display for CastStringToVarChar {
919    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
920        match self.length {
921            Some(length) => {
922                write!(
923                    f,
924                    "text_to_varchar[len={}, fail_on_len={}]",
925                    length.into_u32(),
926                    self.fail_on_len
927                )
928            }
929            None => f.write_str("text_to_varchar[len=unbounded]"),
930        }
931    }
932}
933
934// If we support another vector type, this should likely get hoisted into a
935// position akin to array parsing.
936static INT2VECTOR_CAST_EXPR: LazyLock<MirScalarExpr> = LazyLock::new(|| MirScalarExpr::CallUnary {
937    func: UnaryFunc::CastStringToInt16(CastStringToInt16),
938    expr: Box::new(MirScalarExpr::column(0)),
939});
940
941#[derive(
942    Ord,
943    PartialOrd,
944    Clone,
945    Debug,
946    Eq,
947    PartialEq,
948    Serialize,
949    Deserialize,
950    Hash
951)]
952pub struct CastStringToInt2Vector;
953
954impl LazyUnaryFunc for CastStringToInt2Vector {
955    fn eval<'a>(
956        &'a self,
957        datums: &[Datum<'a>],
958        temp_storage: &'a RowArena,
959        a: &'a impl Eval,
960    ) -> Result<Datum<'a>, EvalError> {
961        let a = a.eval(datums, temp_storage)?;
962        if a.is_null() {
963            return Ok(Datum::Null);
964        }
965
966        let datums = strconv::parse_legacy_vector(a.unwrap_str(), |elem_text| {
967            let elem_text = match elem_text {
968                Cow::Owned(s) => temp_storage.push_string(s),
969                Cow::Borrowed(s) => s,
970            };
971            INT2VECTOR_CAST_EXPR.eval(&[Datum::String(elem_text)], temp_storage)
972        })?;
973        array_create_scalar(&datums, temp_storage)
974    }
975
976    /// The output SqlColumnType of this function
977    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
978        SqlScalarType::Int2Vector.nullable(input_type.nullable)
979    }
980
981    /// Whether this function will produce NULL on NULL input
982    fn propagates_nulls(&self) -> bool {
983        true
984    }
985
986    /// Whether this function will produce NULL on non-NULL input
987    fn introduces_nulls(&self) -> bool {
988        false
989    }
990
991    /// Whether this function preserves uniqueness
992    fn preserves_uniqueness(&self) -> bool {
993        false
994    }
995
996    fn inverse(&self) -> Option<crate::UnaryFunc> {
997        to_unary!(super::CastInt2VectorToString)
998    }
999
1000    fn is_monotone(&self) -> bool {
1001        false
1002    }
1003
1004    fn is_eliminable_cast(&self) -> bool {
1005        false
1006    }
1007}
1008
1009impl fmt::Display for CastStringToInt2Vector {
1010    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1011        f.write_str("strtoint2vector")
1012    }
1013}
1014
1015#[sqlfunc(
1016    sqlname = "text_to_jsonb",
1017    preserves_uniqueness = false,
1018    inverse = to_unary!(super::CastJsonbToString)
1019)]
1020// TODO(jamii): it would be much more efficient to skip the intermediate repr::jsonb::Jsonb.
1021fn cast_string_to_jsonb<'a>(a: &'a str) -> Result<Jsonb, EvalError> {
1022    Ok(strconv::parse_jsonb(a)?)
1023}
1024
1025#[sqlfunc(sqlname = "btrim")]
1026fn trim_whitespace<'a>(a: &'a str) -> &'a str {
1027    a.trim_matches(' ')
1028}
1029
1030#[sqlfunc(sqlname = "ltrim")]
1031fn trim_leading_whitespace<'a>(a: &'a str) -> &'a str {
1032    a.trim_start_matches(' ')
1033}
1034
1035#[sqlfunc(sqlname = "rtrim")]
1036fn trim_trailing_whitespace<'a>(a: &'a str) -> &'a str {
1037    a.trim_end_matches(' ')
1038}
1039
1040#[sqlfunc(sqlname = "initcap")]
1041fn initcap<'a>(a: &'a str) -> String {
1042    let mut out = String::new();
1043    let mut capitalize_next = true;
1044    for ch in a.chars() {
1045        if capitalize_next {
1046            out.extend(ch.to_uppercase())
1047        } else {
1048            out.extend(ch.to_lowercase())
1049        };
1050        capitalize_next = !ch.is_alphanumeric();
1051    }
1052    out
1053}
1054
1055#[sqlfunc(sqlname = "ascii")]
1056fn ascii<'a>(a: &'a str) -> i32 {
1057    a.chars()
1058        .next()
1059        .and_then(|c| i32::try_from(u32::from(c)).ok())
1060        .unwrap_or(0)
1061}
1062
1063#[sqlfunc(sqlname = "char_length")]
1064fn char_length<'a>(a: &'a str) -> Result<i32, EvalError> {
1065    let length = a.chars().count();
1066    i32::try_from(length).or_else(|_| Err(EvalError::Int32OutOfRange(length.to_string().into())))
1067}
1068
1069#[sqlfunc(sqlname = "bit_length")]
1070fn bit_length_string<'a>(a: &'a str) -> Result<i32, EvalError> {
1071    let length = a.as_bytes().len() * 8;
1072    i32::try_from(length).or_else(|_| Err(EvalError::Int32OutOfRange(length.to_string().into())))
1073}
1074
1075#[sqlfunc(sqlname = "octet_length")]
1076fn byte_length_string<'a>(a: &'a str) -> Result<i32, EvalError> {
1077    let length = a.as_bytes().len();
1078    i32::try_from(length).or_else(|_| Err(EvalError::Int32OutOfRange(length.to_string().into())))
1079}
1080
1081#[sqlfunc]
1082fn upper<'a>(a: &'a str) -> String {
1083    a.to_uppercase()
1084}
1085
1086#[sqlfunc]
1087fn lower<'a>(a: &'a str) -> String {
1088    a.to_lowercase()
1089}
1090
1091#[sqlfunc]
1092fn normalize(text: &str, form_str: &str) -> Result<String, EvalError> {
1093    use unicode_normalization::UnicodeNormalization;
1094
1095    match form_str.to_uppercase().as_str() {
1096        "NFC" => Ok(text.nfc().collect()),
1097        "NFD" => Ok(text.nfd().collect()),
1098        "NFKC" => Ok(text.nfkc().collect()),
1099        "NFKD" => Ok(text.nfkd().collect()),
1100        _ => Err(EvalError::InvalidParameterValue(
1101            format!("invalid normalization form: {}", form_str).into(),
1102        )),
1103    }
1104}
1105
1106#[derive(
1107    Ord,
1108    PartialOrd,
1109    Clone,
1110    Debug,
1111    Eq,
1112    PartialEq,
1113    Serialize,
1114    Deserialize,
1115    Hash
1116)]
1117pub struct IsLikeMatch(pub like_pattern::Matcher);
1118
1119impl EagerUnaryFunc for IsLikeMatch {
1120    type Input<'a> = &'a str;
1121    type Output<'a> = bool;
1122
1123    fn call<'a>(&self, haystack: Self::Input<'a>) -> Self::Output<'a> {
1124        self.0.is_match(haystack)
1125    }
1126
1127    fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
1128        SqlScalarType::Bool.nullable(input.nullable)
1129    }
1130}
1131
1132impl fmt::Display for IsLikeMatch {
1133    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1134        write!(
1135            f,
1136            "{}like[{}]",
1137            if self.0.case_insensitive { "i" } else { "" },
1138            self.0.pattern.escaped()
1139        )
1140    }
1141}
1142
1143#[derive(
1144    Ord,
1145    PartialOrd,
1146    Clone,
1147    Debug,
1148    Eq,
1149    PartialEq,
1150    Serialize,
1151    Deserialize,
1152    Hash
1153)]
1154pub struct IsRegexpMatch(pub Regex);
1155
1156impl EagerUnaryFunc for IsRegexpMatch {
1157    type Input<'a> = &'a str;
1158    type Output<'a> = bool;
1159
1160    fn call<'a>(&self, haystack: Self::Input<'a>) -> Self::Output<'a> {
1161        self.0.is_match(haystack)
1162    }
1163
1164    fn output_sql_type(&self, input: SqlColumnType) -> SqlColumnType {
1165        SqlScalarType::Bool.nullable(input.nullable)
1166    }
1167}
1168
1169impl fmt::Display for IsRegexpMatch {
1170    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1171        write!(
1172            f,
1173            "is_regexp_match[{}, case_insensitive={}]",
1174            self.0.pattern().escaped(),
1175            self.0.case_insensitive
1176        )
1177    }
1178}
1179
1180#[derive(
1181    Ord,
1182    PartialOrd,
1183    Clone,
1184    Debug,
1185    Eq,
1186    PartialEq,
1187    Serialize,
1188    Deserialize,
1189    Hash
1190)]
1191// The serde name distinguishes this precompiled-pattern function from the
1192// generated struct of the dynamic-pattern variadic `regexp_match`, which is
1193// also named `RegexpMatch`. The stable LIR schema registry requires container
1194// names to be unique. Same for `RegexpSplitToArray` and `RegexpReplace` below.
1195#[serde(rename = "RegexpMatchStatic")]
1196pub struct RegexpMatch(pub Regex);
1197
1198impl LazyUnaryFunc for RegexpMatch {
1199    fn eval<'a>(
1200        &'a self,
1201        datums: &[Datum<'a>],
1202        temp_storage: &'a RowArena,
1203        a: &'a impl Eval,
1204    ) -> Result<Datum<'a>, EvalError> {
1205        let haystack = a.eval(datums, temp_storage)?;
1206        if haystack.is_null() {
1207            return Ok(Datum::Null);
1208        }
1209        regexp_match_static(haystack, temp_storage, &self.0)
1210    }
1211
1212    /// The output SqlColumnType of this function
1213    fn output_sql_type(&self, _input_type: SqlColumnType) -> SqlColumnType {
1214        SqlScalarType::Array(Box::new(SqlScalarType::String)).nullable(true)
1215    }
1216
1217    /// Whether this function will produce NULL on NULL input
1218    fn propagates_nulls(&self) -> bool {
1219        true
1220    }
1221
1222    /// Whether this function will produce NULL on non-NULL input
1223    fn introduces_nulls(&self) -> bool {
1224        // Returns null if the regex did not match
1225        true
1226    }
1227
1228    /// Whether this function preserves uniqueness
1229    fn preserves_uniqueness(&self) -> bool {
1230        false
1231    }
1232
1233    fn inverse(&self) -> Option<crate::UnaryFunc> {
1234        None
1235    }
1236
1237    fn is_monotone(&self) -> bool {
1238        false
1239    }
1240
1241    fn is_eliminable_cast(&self) -> bool {
1242        false
1243    }
1244}
1245
1246impl fmt::Display for RegexpMatch {
1247    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1248        write!(
1249            f,
1250            "regexp_match[{}, case_insensitive={}]",
1251            self.0.pattern().escaped(),
1252            self.0.case_insensitive
1253        )
1254    }
1255}
1256
1257#[derive(
1258    Ord,
1259    PartialOrd,
1260    Clone,
1261    Debug,
1262    Eq,
1263    PartialEq,
1264    Serialize,
1265    Deserialize,
1266    Hash
1267)]
1268// See `RegexpMatch` above for explanation.
1269#[serde(rename = "RegexpSplitToArrayStatic")]
1270pub struct RegexpSplitToArray(pub Regex);
1271
1272impl LazyUnaryFunc for RegexpSplitToArray {
1273    fn eval<'a>(
1274        &'a self,
1275        datums: &[Datum<'a>],
1276        temp_storage: &'a RowArena,
1277        a: &'a impl Eval,
1278    ) -> Result<Datum<'a>, EvalError> {
1279        let haystack = a.eval(datums, temp_storage)?;
1280        if haystack.is_null() {
1281            return Ok(Datum::Null);
1282        }
1283        regexp_split_to_array_re(haystack.unwrap_str(), &self.0, temp_storage)
1284    }
1285
1286    /// The output SqlColumnType of this function
1287    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
1288        SqlScalarType::Array(Box::new(SqlScalarType::String)).nullable(input_type.nullable)
1289    }
1290
1291    /// Whether this function will produce NULL on NULL input
1292    fn propagates_nulls(&self) -> bool {
1293        true
1294    }
1295
1296    /// Whether this function will produce NULL on non-NULL input
1297    fn introduces_nulls(&self) -> bool {
1298        false
1299    }
1300
1301    /// Whether this function preserves uniqueness
1302    fn preserves_uniqueness(&self) -> bool {
1303        false
1304    }
1305
1306    fn inverse(&self) -> Option<crate::UnaryFunc> {
1307        None
1308    }
1309
1310    fn is_monotone(&self) -> bool {
1311        false
1312    }
1313
1314    fn is_eliminable_cast(&self) -> bool {
1315        false
1316    }
1317}
1318
1319impl fmt::Display for RegexpSplitToArray {
1320    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1321        write!(
1322            f,
1323            "regexp_split_to_array[{}, case_insensitive={}]",
1324            self.0.pattern().escaped(),
1325            self.0.case_insensitive
1326        )
1327    }
1328}
1329
1330#[sqlfunc(sqlname = "mz_panic")]
1331fn panic<'a>(a: &'a str) -> String {
1332    print!("{}", a);
1333    panic!("{}", a)
1334}
1335
1336#[sqlfunc(sqlname = "quote_ident", preserves_uniqueness = true)]
1337fn quote_ident<'a>(a: &'a str) -> Result<String, EvalError> {
1338    let i = mz_sql_parser::ast::Ident::new(a).map_err(|err| EvalError::InvalidIdentifier {
1339        ident: a.into(),
1340        detail: Some(err.to_string().into()),
1341    })?;
1342    Ok(i.to_string())
1343}
1344
1345#[derive(
1346    Ord,
1347    PartialOrd,
1348    Clone,
1349    Debug,
1350    Eq,
1351    PartialEq,
1352    Serialize,
1353    Deserialize,
1354    Hash
1355)]
1356// See `RegexpMatch` above for explanation.
1357#[serde(rename = "RegexpReplaceStatic")]
1358pub struct RegexpReplace {
1359    pub regex: Regex,
1360    pub limit: usize,
1361}
1362
1363impl binary::EagerBinaryFunc for RegexpReplace {
1364    type Input<'a> = (&'a str, &'a str);
1365    type Output<'a> = Cow<'a, str>;
1366
1367    fn call<'a>(
1368        &self,
1369        (source, replacement): Self::Input<'a>,
1370        _temp_storage: &'a RowArena,
1371    ) -> Self::Output<'a> {
1372        // WARNING: This function has potential OOM risk if used with an inflationary
1373        // replacement pattern. It is very difficult to calculate the output size ahead
1374        // of time because the replacement pattern may depend on capture groups.
1375        self.regex.replacen(source, self.limit, replacement)
1376    }
1377
1378    fn output_sql_type(&self, input_types: &[SqlColumnType]) -> SqlColumnType {
1379        use mz_repr::AsColumnType;
1380        let output = <Self::Output<'_> as AsColumnType>::as_column_type();
1381        let propagates_nulls = binary::EagerBinaryFunc::propagates_nulls(self);
1382        let nullable = output.nullable;
1383        let input_nullable = input_types.iter().any(|t| t.nullable);
1384        output.nullable(nullable || (propagates_nulls && input_nullable))
1385    }
1386}
1387
1388impl fmt::Display for RegexpReplace {
1389    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1390        write!(
1391            f,
1392            "regexp_replace[{}, case_insensitive={}, limit={}]",
1393            self.regex.pattern().escaped(),
1394            self.regex.case_insensitive,
1395            self.limit
1396        )
1397    }
1398}