Skip to main content

mz_expr/scalar/func/
unary.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//
10// Portions of this file are derived from the PostgreSQL project. The original
11// source code is subject to the terms of the PostgreSQL license, a copy of
12// which can be found in the LICENSE file at the root of this repository.
13
14//! Definition of the [`UnaryFunc`] enum and related machinery.
15
16use std::{fmt, str};
17
18use mz_repr::{Datum, InputDatumType, OutputDatumType, ReprColumnType, RowArena, SqlColumnType};
19
20use crate::Eval;
21use crate::EvalError;
22use crate::scalar::func::RedactSql;
23use crate::scalar::func::impls::*;
24use crate::scalar::func::jq::Jq;
25
26/// A description of an SQL unary function that has the ability to lazy evaluate its arguments
27// This trait will eventually be annotated with #[enum_dispatch] to autogenerate the UnaryFunc enum
28pub trait LazyUnaryFunc {
29    fn eval<'a>(
30        &'a self,
31        datums: &[Datum<'a>],
32        temp_storage: &'a RowArena,
33        a: &'a impl Eval,
34    ) -> Result<Datum<'a>, EvalError>;
35
36    /// The output SqlColumnType of this function.
37    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType;
38
39    fn output_type(&self, input_type: ReprColumnType) -> ReprColumnType {
40        ReprColumnType::from(&self.output_sql_type(SqlColumnType::from_repr(&input_type)))
41    }
42
43    /// Whether this function will produce NULL on NULL input.
44    fn propagates_nulls(&self) -> bool;
45
46    /// Whether this function will produce NULL on non-NULL input.
47    fn introduces_nulls(&self) -> bool;
48
49    /// Whether this function might error on non-error input.
50    fn could_error(&self) -> bool {
51        // NB: override this for functions that never error.
52        true
53    }
54
55    /// Whether this function preserves uniqueness.
56    ///
57    /// Uniqueness is preserved when `if f(x) = f(y) then x = y` is true. This
58    /// is used by the optimizer when a guarantee can be made that a collection
59    /// with unique items will stay unique when mapped by this function.
60    ///
61    /// Note that error results are not covered: Even with `preserves_uniqueness = true`, it can
62    /// happen that two different inputs produce the same error result. (e.g., in case of a
63    /// narrowing cast)
64    ///
65    /// Functions should conservatively return `false` unless they are certain
66    /// the above property is true.
67    fn preserves_uniqueness(&self) -> bool;
68
69    /// The [inverse] of this function, if it has one and we have determined it.
70    ///
71    /// The optimizer _can_ use this information when selecting indexes, e.g. an
72    /// indexed column has a cast applied to it, by moving the right inverse of
73    /// the cast to another value, we can select the indexed column.
74    ///
75    /// Note that a value of `None` does not imply that the inverse does not
76    /// exist; it could also mean we have not yet invested the energy in
77    /// representing it. For example, in the case of complex casts, such as
78    /// between two list types, we could determine the right inverse, but doing
79    /// so is not immediately necessary as this information is only used by the
80    /// optimizer.
81    ///
82    /// ## Right vs. left vs. inverses
83    /// - Right inverses are when the inverse function preserves uniqueness.
84    ///   These are the functions that the optimizer uses to move casts between
85    ///   expressions.
86    /// - Left inverses are when the function itself preserves uniqueness.
87    /// - Inverses are when a function is both a right and a left inverse (e.g.,
88    ///   bit_not_int64 is both a right and left inverse of itself).
89    ///
90    /// We call this function `inverse` for simplicity's sake; it doesn't always
91    /// correspond to the mathematical notion of "inverse." However, in
92    /// conjunction with checks to `preserves_uniqueness` you can determine
93    /// which type of inverse we return.
94    ///
95    /// [inverse]: https://en.wikipedia.org/wiki/Inverse_function
96    fn inverse(&self) -> Option<crate::UnaryFunc>;
97
98    /// Returns true if the function is monotone. (Non-strict; either increasing or decreasing.)
99    /// Monotone functions map ranges to ranges: ie. given a range of possible inputs, we can
100    /// determine the range of possible outputs just by mapping the endpoints.
101    ///
102    /// This property describes the behaviour of the function over ranges where the function is defined:
103    /// ie. the argument and the result are non-error datums.
104    fn is_monotone(&self) -> bool;
105
106    /// Returns true if the function does no actual work, but is merely a type-tracking cast.
107    fn is_eliminable_cast(&self) -> bool;
108}
109
110/// A description of an SQL unary function that operates on eagerly evaluated expressions
111pub trait EagerUnaryFunc {
112    type Input<'a>: InputDatumType<'a, EvalError>;
113    type Output<'a>: OutputDatumType<'a, EvalError>;
114
115    fn call<'a>(&self, input: Self::Input<'a>, temp_storage: &'a RowArena) -> Self::Output<'a>;
116
117    /// The output SqlColumnType of this function
118    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType;
119
120    /// The output of this function as a representation type.
121    fn output_type(&self, input_type: ReprColumnType) -> ReprColumnType {
122        ReprColumnType::from(&self.output_sql_type(SqlColumnType::from_repr(&input_type)))
123    }
124
125    /// Whether this function will produce NULL on NULL input
126    fn propagates_nulls(&self) -> bool {
127        // If the input is not nullable then nulls are propagated
128        !Self::Input::<'_>::nullable()
129    }
130
131    /// Whether this function will produce NULL on non-NULL input
132    fn introduces_nulls(&self) -> bool {
133        // If the output is nullable then nulls can be introduced
134        Self::Output::<'_>::nullable()
135    }
136
137    /// Whether this function could produce an error
138    fn could_error(&self) -> bool {
139        Self::Output::<'_>::fallible()
140    }
141
142    /// Whether this function preserves uniqueness
143    fn preserves_uniqueness(&self) -> bool {
144        false
145    }
146
147    fn inverse(&self) -> Option<crate::UnaryFunc> {
148        None
149    }
150
151    fn is_monotone(&self) -> bool {
152        false
153    }
154
155    fn is_eliminable_cast(&self) -> bool {
156        false
157    }
158}
159
160impl<T: EagerUnaryFunc> LazyUnaryFunc for T {
161    fn eval<'a>(
162        &'a self,
163        datums: &[Datum<'a>],
164        temp_storage: &'a RowArena,
165        a: &'a impl Eval,
166    ) -> Result<Datum<'a>, EvalError> {
167        match T::Input::<'_>::try_from_result(a.eval(datums, temp_storage)) {
168            // If we can convert to the input type then we call the function
169            Ok(input) => self.call(input, temp_storage).into_result(temp_storage),
170            // If we can't and we got a non-null datum something went wrong in the planner
171            Err(Ok(datum)) if !datum.is_null() => {
172                Err(EvalError::Internal("invalid input type".into()))
173            }
174            // Otherwise we just propagate NULLs and errors
175            Err(res) => res,
176        }
177    }
178
179    fn output_sql_type(&self, input_type: SqlColumnType) -> SqlColumnType {
180        self.output_sql_type(input_type)
181    }
182
183    fn propagates_nulls(&self) -> bool {
184        self.propagates_nulls()
185    }
186
187    fn introduces_nulls(&self) -> bool {
188        self.introduces_nulls()
189    }
190
191    fn could_error(&self) -> bool {
192        self.could_error()
193    }
194
195    fn preserves_uniqueness(&self) -> bool {
196        self.preserves_uniqueness()
197    }
198
199    fn inverse(&self) -> Option<crate::UnaryFunc> {
200        self.inverse()
201    }
202
203    fn is_monotone(&self) -> bool {
204        self.is_monotone()
205    }
206
207    fn is_eliminable_cast(&self) -> bool {
208        self.is_eliminable_cast()
209    }
210}
211
212derive_unary!(
213    Not,
214    IsNull,
215    IsTrue,
216    IsFalse,
217    BitNotInt16,
218    BitNotInt32,
219    BitNotInt64,
220    BitNotUint16,
221    BitNotUint32,
222    BitNotUint64,
223    NegInt16,
224    NegInt32,
225    NegInt64,
226    NegFloat32,
227    NegFloat64,
228    NegNumeric,
229    NegInterval,
230    SqrtFloat64,
231    SqrtNumeric,
232    CbrtFloat64,
233    AbsInt16,
234    AbsInt32,
235    AbsInt64,
236    AbsFloat32,
237    AbsFloat64,
238    AbsNumeric,
239    CastBoolToString,
240    CastBoolToStringNonstandard,
241    CastBoolToInt32,
242    CastBoolToInt64,
243    CastInt16ToFloat32,
244    CastInt16ToFloat64,
245    CastInt16ToInt32,
246    CastInt16ToInt64,
247    CastInt16ToUint16,
248    CastInt16ToUint32,
249    CastInt16ToUint64,
250    CastInt16ToString,
251    CastInt2VectorToArray,
252    CastInt32ToBool,
253    CastInt32ToFloat32,
254    CastInt32ToFloat64,
255    CastInt32ToOid,
256    CastInt32ToPgLegacyChar,
257    CastInt32ToInt16,
258    CastInt32ToInt64,
259    CastInt32ToUint16,
260    CastInt32ToUint32,
261    CastInt32ToUint64,
262    CastInt32ToString,
263    CastOidToInt32,
264    CastOidToInt64,
265    CastOidToString,
266    CastOidToRegClass,
267    CastRegClassToOid,
268    CastOidToRegProc,
269    CastRegProcToOid,
270    CastOidToRegType,
271    CastRegTypeToOid,
272    CastInt64ToInt16,
273    CastInt64ToInt32,
274    CastInt64ToUint16,
275    CastInt64ToUint32,
276    CastInt64ToUint64,
277    CastInt16ToNumeric,
278    CastInt32ToNumeric,
279    CastInt64ToBool,
280    CastInt64ToNumeric,
281    CastInt64ToFloat32,
282    CastInt64ToFloat64,
283    CastInt64ToOid,
284    CastInt64ToString,
285    CastUint16ToUint32,
286    CastUint16ToUint64,
287    CastUint16ToInt16,
288    CastUint16ToInt32,
289    CastUint16ToInt64,
290    CastUint16ToNumeric,
291    CastUint16ToFloat32,
292    CastUint16ToFloat64,
293    CastUint16ToString,
294    CastUint32ToUint16,
295    CastUint32ToUint64,
296    CastUint32ToInt16,
297    CastUint32ToInt32,
298    CastUint32ToInt64,
299    CastUint32ToNumeric,
300    CastUint32ToFloat32,
301    CastUint32ToFloat64,
302    CastUint32ToString,
303    CastUint64ToUint16,
304    CastUint64ToUint32,
305    CastUint64ToInt16,
306    CastUint64ToInt32,
307    CastUint64ToInt64,
308    CastUint64ToNumeric,
309    CastUint64ToFloat32,
310    CastUint64ToFloat64,
311    CastUint64ToString,
312    CastFloat32ToInt16,
313    CastFloat32ToInt32,
314    CastFloat32ToInt64,
315    CastFloat32ToUint16,
316    CastFloat32ToUint32,
317    CastFloat32ToUint64,
318    CastFloat32ToFloat64,
319    CastFloat32ToString,
320    CastFloat32ToNumeric,
321    CastFloat64ToNumeric,
322    CastFloat64ToInt16,
323    CastFloat64ToInt32,
324    CastFloat64ToInt64,
325    CastFloat64ToUint16,
326    CastFloat64ToUint32,
327    CastFloat64ToUint64,
328    CastFloat64ToFloat32,
329    CastFloat64ToString,
330    CastNumericToFloat32,
331    CastNumericToFloat64,
332    CastNumericToInt16,
333    CastNumericToInt32,
334    CastNumericToInt64,
335    CastNumericToUint16,
336    CastNumericToUint32,
337    CastNumericToUint64,
338    CastNumericToString,
339    CastMzTimestampToString,
340    CastMzTimestampToTimestamp,
341    CastMzTimestampToTimestampTz,
342    CastStringToMzTimestamp,
343    CastUint64ToMzTimestamp,
344    CastUint32ToMzTimestamp,
345    CastInt64ToMzTimestamp,
346    CastInt32ToMzTimestamp,
347    CastNumericToMzTimestamp,
348    CastTimestampToMzTimestamp,
349    CastTimestampTzToMzTimestamp,
350    CastDateToMzTimestamp,
351    CastStringToBool,
352    CastStringToPgLegacyChar,
353    CastStringToPgLegacyName,
354    CastStringToBytes,
355    CastStringToInt16,
356    CastStringToInt32,
357    CastStringToInt64,
358    CastStringToUint16,
359    CastStringToUint32,
360    CastStringToUint64,
361    CastStringToInt2Vector,
362    CastStringToOid,
363    CastStringToFloat32,
364    CastStringToFloat64,
365    CastStringToDate,
366    CastStringToArray(E),
367    CastStringToList(E),
368    CastStringToMap(E),
369    CastStringToRange(E),
370    CastStringToTime,
371    CastStringToTimestamp,
372    CastStringToTimestampTz,
373    CastStringToInterval,
374    CastStringToNumeric,
375    CastStringToUuid,
376    CastStringToChar,
377    PadChar,
378    CastStringToVarChar,
379    CastCharToString,
380    CastVarCharToString,
381    CastDateToTimestamp,
382    CastDateToTimestampTz,
383    CastDateToString,
384    CastTimeToInterval,
385    CastTimeToString,
386    CastIntervalToString,
387    CastIntervalToTime,
388    CastTimestampToDate,
389    AdjustTimestampPrecision,
390    CastTimestampToTimestampTz,
391    CastTimestampToString,
392    CastTimestampToTime,
393    CastTimestampTzToDate,
394    CastTimestampTzToTimestamp,
395    AdjustTimestampTzPrecision,
396    CastTimestampTzToString,
397    CastTimestampTzToTime,
398    CastPgLegacyCharToString,
399    CastPgLegacyCharToChar,
400    CastPgLegacyCharToVarChar,
401    CastPgLegacyCharToInt32,
402    CastBytesToString,
403    CastStringToJsonb,
404    CastJsonbToString,
405    CastJsonbableToJsonb,
406    CastJsonbToInt16,
407    CastJsonbToInt32,
408    CastJsonbToInt64,
409    CastJsonbToFloat32,
410    CastJsonbToFloat64,
411    CastJsonbToNumeric,
412    CastJsonbToBool,
413    CastUuidToString,
414    CastRecordToString,
415    CastRecord1ToRecord2(E),
416    CastArrayToArray(E),
417    CastArrayToJsonb(E),
418    CastArrayToString,
419    CastListToString,
420    CastListToJsonb(E),
421    CastList1ToList2(E),
422    CastArrayToListOneDim,
423    CastMapToString,
424    CastInt2VectorToString,
425    CastRangeToString,
426    TryCast(E),
427    CeilFloat32,
428    CeilFloat64,
429    CeilNumeric,
430    FloorFloat32,
431    FloorFloat64,
432    FloorNumeric,
433    Ascii,
434    BitCountBytes,
435    BitLengthBytes,
436    BitLengthString,
437    ByteLengthBytes,
438    ByteLengthString,
439    CharLength,
440    Chr,
441    IsLikeMatch,
442    IsRegexpMatch,
443    RegexpMatch,
444    Jq,
445    ExtractInterval,
446    ExtractTime,
447    ExtractTimestamp,
448    ExtractTimestampTz,
449    ExtractDate,
450    DatePartInterval,
451    DatePartTime,
452    DatePartTimestamp,
453    DatePartTimestampTz,
454    DateTruncTimestamp,
455    DateTruncTimestampTz,
456    TimezoneTimestamp,
457    TimezoneTimestampTz,
458    TimezoneTime,
459    ToTimestamp,
460    ToCharTimestamp,
461    ToCharTimestampTz,
462    JustifyDays,
463    JustifyHours,
464    JustifyInterval,
465    JsonbArrayLength,
466    JsonbTypeof,
467    JsonbStripNulls,
468    JsonbPretty,
469    ParseCatalogAclMode,
470    ParseCatalogAuditLogDetails,
471    ParseCatalogCreateSql,
472    ParseCatalogId,
473    ParseCatalogItemReferences,
474    ParseCatalogPrivileges,
475    ParseConnectionDetails,
476    ParseKafkaSourceDetails,
477    ParsePostgresSourceDetails,
478    ParseSourceExportDetails,
479    RedactSql,
480    RoundFloat32,
481    RoundFloat64,
482    RoundNumeric,
483    TruncFloat32,
484    TruncFloat64,
485    TruncNumeric,
486    TrimWhitespace,
487    TrimLeadingWhitespace,
488    TrimTrailingWhitespace,
489    Initcap,
490    RecordGet,
491    ListLength,
492    MapLength,
493    MapBuildFromRecordList,
494    Upper,
495    Lower,
496    Cos,
497    Acos,
498    Cosh,
499    Acosh,
500    Sin,
501    Asin,
502    Sinh,
503    Asinh,
504    Tan,
505    Atan,
506    Tanh,
507    Atanh,
508    Cot,
509    Degrees,
510    Radians,
511    Log10,
512    Log10Numeric,
513    Ln,
514    LnNumeric,
515    Exp,
516    ExpNumeric,
517    Sleep,
518    Panic,
519    AdjustNumericScale,
520    PgColumnSize,
521    MzRowSize,
522    MzTypeName,
523    StepMzTimestamp,
524    RangeLower,
525    RangeUpper,
526    RangeEmpty,
527    RangeLowerInc,
528    RangeUpperInc,
529    RangeLowerInf,
530    RangeUpperInf,
531    MzAclItemGrantor,
532    MzAclItemGrantee,
533    MzAclItemPrivileges,
534    MzFormatPrivileges,
535    MzValidatePrivileges,
536    MzValidateRolePrivilege,
537    AclItemGrantor,
538    AclItemGrantee,
539    AclItemPrivileges,
540    QuoteIdent,
541    TryParseMonotonicIso8601Timestamp,
542    RegexpSplitToArray,
543    PgSizePretty,
544    Crc32Bytes,
545    Crc32String,
546    KafkaMurmur2Bytes,
547    KafkaMurmur2String,
548    SeahashBytes,
549    SeahashString,
550    Reverse
551);
552
553impl<E> UnaryFunc<E> {
554    /// If the unary_func represents "IS X", return X.
555    ///
556    /// A helper method for being able to print Not(IsX) as IS NOT X.
557    pub fn is(&self) -> Option<&'static str> {
558        match self {
559            UnaryFunc::IsNull(_) => Some("NULL"),
560            UnaryFunc::IsTrue(_) => Some("TRUE"),
561            UnaryFunc::IsFalse(_) => Some("FALSE"),
562            _ => None,
563        }
564    }
565}
566
567#[cfg(test)]
568mod test {
569    use itertools::Itertools;
570    use mz_repr::{PropDatum, SqlScalarType};
571
572    use crate::{MirScalarExpr, like_pattern};
573
574    use super::*;
575
576    #[mz_ore::test]
577    fn test_could_error() {
578        let funcs: [UnaryFunc; 5] = [
579            UnaryFunc::IsNull(IsNull),
580            UnaryFunc::CastVarCharToString(CastVarCharToString),
581            UnaryFunc::Not(Not),
582            UnaryFunc::IsLikeMatch(IsLikeMatch(like_pattern::compile("%hi%", false).unwrap())),
583            UnaryFunc::try_cast_if_fallible(CastStringToInt32),
584        ];
585        for func in funcs {
586            assert!(!func.could_error())
587        }
588    }
589
590    #[mz_ore::test]
591    #[cfg_attr(miri, ignore)] // unsupported operation: can't call foreign function `decNumberFromInt32` on OS `linux`
592    fn test_is_monotone() {
593        use proptest::prelude::*;
594
595        /// Asserts that the function is either monotonically increasing or decreasing over
596        /// the given sets of arguments.
597        fn assert_monotone<'a, const N: usize>(
598            expr: &MirScalarExpr,
599            arena: &'a RowArena,
600            datums: &[[Datum<'a>; N]],
601        ) {
602            // TODO: assertions for nulls, errors
603            let Ok(results) = datums
604                .iter()
605                .map(|args| expr.eval(args.as_slice(), arena))
606                .collect::<Result<Vec<_>, _>>()
607            else {
608                return;
609            };
610
611            let forward = results.iter().tuple_windows().all(|(a, b)| a <= b);
612            let reverse = results.iter().tuple_windows().all(|(a, b)| a >= b);
613            assert!(
614                forward || reverse,
615                "expected {expr} to be monotone, but passing {datums:?} returned {results:?}"
616            );
617        }
618
619        fn proptest_unary<'a>(
620            func: UnaryFunc,
621            arena: &'a RowArena,
622            arg: impl Strategy<Value = PropDatum>,
623        ) {
624            let is_monotone = func.is_monotone();
625            let expr = MirScalarExpr::CallUnary {
626                func,
627                expr: Box::new(MirScalarExpr::column(0)),
628            };
629            if is_monotone {
630                proptest!(|(
631                    mut arg in proptest::array::uniform3(arg),
632                )| {
633                    arg.sort();
634                    let args: Vec<_> = arg.iter().map(|a| [Datum::from(a)]).collect();
635                    assert_monotone(&expr, arena, &args);
636                });
637            }
638        }
639
640        let interesting_i32s: Vec<Datum<'static>> =
641            SqlScalarType::Int32.interesting_datums().collect();
642        let i32_datums = proptest::strategy::Union::new([
643            any::<i32>().prop_map(PropDatum::Int32).boxed(),
644            (0..interesting_i32s.len())
645                .prop_map(move |i| {
646                    let Datum::Int32(val) = interesting_i32s[i] else {
647                        unreachable!("interesting int32 has non-i32s")
648                    };
649                    PropDatum::Int32(val)
650                })
651                .boxed(),
652            (-10i32..10).prop_map(PropDatum::Int32).boxed(),
653        ]);
654
655        let arena = RowArena::new();
656
657        // It would be interesting to test all funcs here, but we currently need to hardcode
658        // the generators for the argument types, which makes this tedious. Choose an interesting
659        // subset for now.
660        proptest_unary(
661            UnaryFunc::CastInt32ToNumeric(CastInt32ToNumeric(None)),
662            &arena,
663            &i32_datums,
664        );
665        proptest_unary(
666            UnaryFunc::CastInt32ToUint16(CastInt32ToUint16),
667            &arena,
668            &i32_datums,
669        );
670        proptest_unary(
671            UnaryFunc::CastInt32ToString(CastInt32ToString),
672            &arena,
673            &i32_datums,
674        );
675    }
676}