Skip to main content

mz_expr/scalar/func/
binary.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//! Utilities for binary functions.
11
12use mz_repr::{Datum, InputDatumType, OutputDatumType, ReprColumnType, RowArena, SqlColumnType};
13
14use crate::{Eval, EvalError};
15
16/// A description of an SQL binary function that has the ability to lazy evaluate its arguments
17// This trait will eventually be annotated with #[enum_dispatch] to autogenerate the UnaryFunc enum
18pub(crate) trait LazyBinaryFunc {
19    fn eval<'a>(
20        &'a self,
21        datums: &[Datum<'a>],
22        temp_storage: &'a RowArena,
23        exprs: &[&'a impl Eval],
24    ) -> Result<Datum<'a>, EvalError>;
25
26    /// The output SqlColumnType of this function.
27    fn output_sql_type(&self, input_types: &[SqlColumnType]) -> SqlColumnType;
28
29    /// A wrapper around [`Self::output_sql_type`] that works with representation types.
30    fn output_type(&self, input_types: &[ReprColumnType]) -> ReprColumnType {
31        ReprColumnType::from(
32            &self.output_sql_type(
33                &input_types
34                    .iter()
35                    .map(SqlColumnType::from_repr)
36                    .collect::<Vec<_>>(),
37            ),
38        )
39    }
40
41    /// Whether this function will produce NULL on NULL input.
42    fn propagates_nulls(&self) -> bool;
43
44    /// Whether this function will produce NULL on non-NULL input.
45    fn introduces_nulls(&self) -> bool;
46
47    /// Whether this function might error on non-error input.
48    fn could_error(&self) -> bool {
49        // NB: override this for functions that never error.
50        true
51    }
52
53    /// Returns the negation of the function, if one exists.
54    fn negate(&self) -> Option<crate::BinaryFunc>;
55
56    /// Returns true if the function is monotone. (Non-strict; either increasing or decreasing.)
57    /// Monotone functions map ranges to ranges: ie. given a range of possible inputs, we can
58    /// determine the range of possible outputs just by mapping the endpoints.
59    ///
60    /// This describes the *pointwise* behaviour of the function:
61    /// ie. the behaviour of any specific argument as the others are held constant. (For example, `a - b` is
62    /// monotone in the first argument because for any particular value of `b`, increasing `a` will
63    /// always cause the result to increase... and in the second argument because for any specific `a`,
64    /// increasing `b` will always cause the result to _decrease_.)
65    ///
66    /// This property describes the behaviour of the function over ranges where the function is defined:
67    /// ie. the arguments and the result are non-error datums.
68    fn is_monotone(&self) -> (bool, bool);
69
70    /// Whether [`Self::is_monotone`]'s "map ranges to ranges by sampling the
71    /// endpoints" guarantee still holds when an operand may be infinite.
72    ///
73    /// False for multiplication and division: their indeterminate forms
74    /// (`0 * inf`, `inf / inf`) and magnitude collapse (`finite / inf = 0`)
75    /// produce results the range endpoints do not bound, so an abstract
76    /// interpreter must not narrow their output range when an operand may be
77    /// infinite.
78    fn is_infinity_monotone(&self) -> bool {
79        true
80    }
81
82    /// Yep, I guess this returns true for infix operators.
83    fn is_infix_op(&self) -> bool;
84}
85
86pub(crate) trait EagerBinaryFunc {
87    type Input<'a>: InputDatumType<'a, EvalError>;
88    type Output<'a>: OutputDatumType<'a, EvalError>;
89
90    fn call<'a>(&self, input: Self::Input<'a>, temp_storage: &'a RowArena) -> Self::Output<'a>;
91
92    /// The output SqlColumnType of this function
93    fn output_sql_type(&self, input_types: &[SqlColumnType]) -> SqlColumnType;
94
95    /// The output of this function as a representation type.
96    #[allow(dead_code)]
97    fn output_type(&self, input_types: &[ReprColumnType]) -> ReprColumnType {
98        ReprColumnType::from(
99            &self.output_sql_type(
100                &input_types
101                    .iter()
102                    .map(SqlColumnType::from_repr)
103                    .collect::<Vec<_>>(),
104            ),
105        )
106    }
107
108    /// Whether this function will produce NULL on NULL input
109    fn propagates_nulls(&self) -> bool {
110        // If the inputs are not nullable then nulls are propagated
111        !Self::Input::nullable()
112    }
113
114    /// Whether this function will produce NULL on non-NULL input
115    fn introduces_nulls(&self) -> bool {
116        // If the output is nullable then nulls can be introduced
117        Self::Output::nullable()
118    }
119
120    /// Whether this function could produce an error
121    fn could_error(&self) -> bool {
122        Self::Output::fallible()
123    }
124
125    /// Returns the negation of the given binary function, if it exists.
126    fn negate(&self) -> Option<crate::BinaryFunc> {
127        None
128    }
129
130    fn is_monotone(&self) -> (bool, bool) {
131        (false, false)
132    }
133
134    fn is_infinity_monotone(&self) -> bool {
135        true
136    }
137
138    fn is_infix_op(&self) -> bool {
139        false
140    }
141}
142
143/// Evaluates the arguments of a binary function one at a time, in order.
144struct EvalArgs<'a, 'd, E> {
145    exprs: std::array::IntoIter<&'a E, 2>,
146    datums: &'d [Datum<'a>],
147    temp_storage: &'a RowArena,
148}
149
150impl<'a, 'd, E: Eval> Iterator for EvalArgs<'a, 'd, E> {
151    type Item = Result<Datum<'a>, EvalError>;
152    #[inline]
153    fn next(&mut self) -> Option<Self::Item> {
154        self.exprs
155            .next()
156            .map(|expr| expr.eval(self.datums, self.temp_storage))
157    }
158}
159
160impl<T: EagerBinaryFunc> LazyBinaryFunc for T {
161    fn eval<'a>(
162        &'a self,
163        datums: &[Datum<'a>],
164        temp_storage: &'a RowArena,
165        exprs: &[&'a impl Eval],
166    ) -> Result<Datum<'a>, EvalError> {
167        let [expr_a, expr_b] = exprs else {
168            return Err(EvalError::Internal(
169                format!("binary function called with {} arguments", exprs.len()).into(),
170            ));
171        };
172        // `try_from_iter` is generic over the iterator, so a closure over `exprs` gives
173        // it a fresh type per function and per `Eval` implementor, and the unwrapping is
174        // emitted once for each. A named iterator type is shared by all functions, so it
175        // is emitted once per input type and `Eval` implementor. Evaluating both
176        // arguments into an array first has the same instantiation count but measured
177        // about 12% slower on `case_literal/if_chain`.
178        let mut datums = EvalArgs {
179            exprs: [*expr_a, *expr_b].into_iter(),
180            datums,
181            temp_storage,
182        };
183        let input = match T::Input::try_from_iter(&mut datums) {
184            // If we can convert to the input type then we call the function
185            Ok(input) => input,
186            // If we can't and we got a non-null datum something went wrong in the planner
187            Err(Ok(Some(datum))) if !datum.is_null() => {
188                return Err(EvalError::Internal("invalid input type".into()));
189            }
190            Err(Ok(None)) => {
191                return Err(EvalError::Internal("unexpectedly missing parameter".into()));
192            }
193            // Otherwise we just propagate NULLs and errors
194            Err(Ok(Some(datum))) => return Ok(datum),
195            Err(Err(res)) => return Err(res),
196        };
197        if datums.next().is_some() {
198            return Err(EvalError::Internal("unconsumed input argument".into()));
199        }
200        self.call(input, temp_storage).into_result(temp_storage)
201    }
202
203    fn output_sql_type(&self, input_types: &[SqlColumnType]) -> SqlColumnType {
204        self.output_sql_type(input_types)
205    }
206
207    fn propagates_nulls(&self) -> bool {
208        self.propagates_nulls()
209    }
210
211    fn introduces_nulls(&self) -> bool {
212        self.introduces_nulls()
213    }
214
215    fn could_error(&self) -> bool {
216        self.could_error()
217    }
218
219    fn negate(&self) -> Option<crate::BinaryFunc> {
220        self.negate()
221    }
222
223    fn is_monotone(&self) -> (bool, bool) {
224        self.is_monotone()
225    }
226
227    fn is_infinity_monotone(&self) -> bool {
228        self.is_infinity_monotone()
229    }
230
231    fn is_infix_op(&self) -> bool {
232        self.is_infix_op()
233    }
234}
235
236pub use derive::BinaryFunc;
237
238mod derive {
239    use std::fmt;
240
241    use mz_repr::{Datum, ReprColumnType, RowArena, SqlColumnType};
242
243    use crate::scalar::func::binary::LazyBinaryFunc;
244    use crate::scalar::func::*;
245    use crate::{Eval, EvalError};
246
247    derive_binary! {
248        AddInt16(AddInt16),
249        AddInt32(AddInt32),
250        AddInt64(AddInt64),
251        AddUint16(AddUint16),
252        AddUint32(AddUint32),
253        AddUint64(AddUint64),
254        AddFloat32(AddFloat32),
255        AddFloat64(AddFloat64),
256        AddInterval(AddInterval),
257        AddTimestampInterval(AddTimestampInterval),
258        AddTimestampTzInterval(AddTimestampTzInterval),
259        AddDateInterval(AddDateInterval),
260        AddDateTime(AddDateTime),
261        AddTimeInterval(AddTimeInterval),
262        AddNumeric(AddNumeric),
263        AgeTimestamp(AgeTimestamp),
264        AgeTimestampTz(AgeTimestampTz),
265        BitAndInt16(BitAndInt16),
266        BitAndInt32(BitAndInt32),
267        BitAndInt64(BitAndInt64),
268        BitAndUint16(BitAndUint16),
269        BitAndUint32(BitAndUint32),
270        BitAndUint64(BitAndUint64),
271        BitOrInt16(BitOrInt16),
272        BitOrInt32(BitOrInt32),
273        BitOrInt64(BitOrInt64),
274        BitOrUint16(BitOrUint16),
275        BitOrUint32(BitOrUint32),
276        BitOrUint64(BitOrUint64),
277        BitXorInt16(BitXorInt16),
278        BitXorInt32(BitXorInt32),
279        BitXorInt64(BitXorInt64),
280        BitXorUint16(BitXorUint16),
281        BitXorUint32(BitXorUint32),
282        BitXorUint64(BitXorUint64),
283        BitShiftLeftInt16(BitShiftLeftInt16),
284        BitShiftLeftInt32(BitShiftLeftInt32),
285        BitShiftLeftInt64(BitShiftLeftInt64),
286        BitShiftLeftUint16(BitShiftLeftUint16),
287        BitShiftLeftUint32(BitShiftLeftUint32),
288        BitShiftLeftUint64(BitShiftLeftUint64),
289        BitShiftRightInt16(BitShiftRightInt16),
290        BitShiftRightInt32(BitShiftRightInt32),
291        BitShiftRightInt64(BitShiftRightInt64),
292        BitShiftRightUint16(BitShiftRightUint16),
293        BitShiftRightUint32(BitShiftRightUint32),
294        BitShiftRightUint64(BitShiftRightUint64),
295        SubInt16(SubInt16),
296        SubInt32(SubInt32),
297        SubInt64(SubInt64),
298        SubUint16(SubUint16),
299        SubUint32(SubUint32),
300        SubUint64(SubUint64),
301        SubFloat32(SubFloat32),
302        SubFloat64(SubFloat64),
303        SubInterval(SubInterval),
304        SubTimestamp(SubTimestamp),
305        SubTimestampTz(SubTimestampTz),
306        SubTimestampInterval(SubTimestampInterval),
307        SubTimestampTzInterval(SubTimestampTzInterval),
308        SubDate(SubDate),
309        SubDateInterval(SubDateInterval),
310        SubTime(SubTime),
311        SubTimeInterval(SubTimeInterval),
312        SubNumeric(SubNumeric),
313        MulInt16(MulInt16),
314        MulInt32(MulInt32),
315        MulInt64(MulInt64),
316        MulUint16(MulUint16),
317        MulUint32(MulUint32),
318        MulUint64(MulUint64),
319        MulFloat32(MulFloat32),
320        MulFloat64(MulFloat64),
321        MulNumeric(MulNumeric),
322        MulInterval(MulInterval),
323        DivInt16(DivInt16),
324        DivInt32(DivInt32),
325        DivInt64(DivInt64),
326        DivUint16(DivUint16),
327        DivUint32(DivUint32),
328        DivUint64(DivUint64),
329        DivFloat32(DivFloat32),
330        DivFloat64(DivFloat64),
331        DivNumeric(DivNumeric),
332        DivInterval(DivInterval),
333        ModInt16(ModInt16),
334        ModInt32(ModInt32),
335        ModInt64(ModInt64),
336        ModUint16(ModUint16),
337        ModUint32(ModUint32),
338        ModUint64(ModUint64),
339        ModFloat32(ModFloat32),
340        ModFloat64(ModFloat64),
341        ModNumeric(ModNumeric),
342        RoundNumeric(RoundNumericBinary),
343        Eq(Eq),
344        NotEq(NotEq),
345        Lt(Lt),
346        Lte(Lte),
347        Gt(Gt),
348        Gte(Gte),
349        LikeEscape(LikeEscape),
350        IsLikeMatchCaseInsensitive(IsLikeMatchCaseInsensitive),
351        IsLikeMatchCaseSensitive(IsLikeMatchCaseSensitive),
352        IsRegexpMatchCaseSensitive(IsRegexpMatchCaseSensitive),
353        IsRegexpMatchCaseInsensitive(IsRegexpMatchCaseInsensitive),
354        ToCharTimestamp(ToCharTimestampFormat),
355        ToCharTimestampTz(ToCharTimestampTzFormat),
356        DateBinTimestamp(DateBinTimestamp),
357        DateBinTimestampTz(DateBinTimestampTz),
358        ExtractInterval(DatePartIntervalNumeric),
359        ExtractTime(DatePartTimeNumeric),
360        ExtractTimestamp(DatePartTimestampTimestampNumeric),
361        ExtractTimestampTz(DatePartTimestampTimestampTzNumeric),
362        ExtractDate(ExtractDateUnits),
363        DatePartInterval(DatePartIntervalF64),
364        DatePartTime(DatePartTimeF64),
365        DatePartTimestamp(DatePartTimestampTimestampF64),
366        DatePartTimestampTz(DatePartTimestampTimestampTzF64),
367        DateTruncTimestamp(DateTruncUnitsTimestamp),
368        DateTruncTimestampTz(DateTruncUnitsTimestampTz),
369        DateTruncInterval(DateTruncInterval),
370        TimezoneTimestampBinary(TimezoneTimestampBinary),
371        TimezoneTimestampTzBinary(TimezoneTimestampTzBinary),
372        TimezoneIntervalTimestampBinary(TimezoneIntervalTimestampBinary),
373        TimezoneIntervalTimestampTzBinary(TimezoneIntervalTimestampTzBinary),
374        TimezoneIntervalTimeBinary(TimezoneIntervalTimeBinary),
375        TimezoneOffset(TimezoneOffset),
376        TextConcat(TextConcatBinary),
377        JsonbGetInt64(JsonbGetInt64),
378        JsonbGetInt64Stringify(JsonbGetInt64Stringify),
379        JsonbGetString(JsonbGetString),
380        JsonbGetStringStringify(JsonbGetStringStringify),
381        JsonbGetPath(JsonbGetPath),
382        JsonbGetPathStringify(JsonbGetPathStringify),
383        JsonbContainsString(JsonbContainsString),
384        JsonbConcat(JsonbConcat),
385        JsonbContainsJsonb(JsonbContainsJsonb),
386        JsonbDeleteInt64(JsonbDeleteInt64),
387        JsonbDeleteString(JsonbDeleteString),
388        MapContainsKey(MapContainsKey),
389        MapGetValue(MapGetValue),
390        MapContainsAllKeys(MapContainsAllKeys),
391        MapContainsAnyKeys(MapContainsAnyKeys),
392        MapContainsMap(MapContainsMap),
393        ConvertFrom(ConvertFrom),
394        Left(Left),
395        Position(Position),
396        Strpos(Strpos),
397        Right(Right),
398        RepeatString(RepeatString),
399        Normalize(Normalize),
400        Trim(Trim),
401        TrimLeading(TrimLeading),
402        TrimTrailing(TrimTrailing),
403        EncodedBytesCharLength(EncodedBytesCharLength),
404        ListLengthMax(ListLengthMax),
405        ArrayContains(ArrayContains),
406        ArrayContainsArray(ArrayContainsArray),
407        ArrayContainsArrayRev(ArrayContainsArrayRev),
408        ArrayLength(ArrayLength),
409        ArrayLower(ArrayLower),
410        ArrayRemove(ArrayRemove),
411        ArrayUpper(ArrayUpper),
412        ArrayArrayConcat(ArrayArrayConcat),
413        ListListConcat(ListListConcat),
414        ListElementConcat(ListElementConcat),
415        ElementListConcat(ElementListConcat),
416        ListRemove(ListRemove),
417        ListContainsList(ListContainsList),
418        ListContainsListRev(ListContainsListRev),
419        DigestString(DigestString),
420        DigestBytes(DigestBytes),
421        MzRenderTypmod(MzRenderTypmod),
422        Encode(Encode),
423        Decode(Decode),
424        LogNumeric(LogBaseNumeric),
425        Power(Power),
426        PowerNumeric(PowerNumeric),
427        GetBit(GetBit),
428        GetByte(GetByte),
429        ConstantTimeEqBytes(ConstantTimeEqBytes),
430        ConstantTimeEqString(ConstantTimeEqString),
431        RangeContainsDate(RangeContainsDate),
432        RangeContainsDateRev(RangeContainsDateRev),
433        RangeContainsI32(RangeContainsI32),
434        RangeContainsI32Rev(RangeContainsI32Rev),
435        RangeContainsI64(RangeContainsI64),
436        RangeContainsI64Rev(RangeContainsI64Rev),
437        RangeContainsNumeric(RangeContainsNumeric),
438        RangeContainsNumericRev(RangeContainsNumericRev),
439        RangeContainsRange(RangeContainsRange),
440        RangeContainsRangeRev(RangeContainsRangeRev),
441        RangeContainsTimestamp(RangeContainsTimestamp),
442        RangeContainsTimestampRev(RangeContainsTimestampRev),
443        RangeContainsTimestampTz(RangeContainsTimestampTz),
444        RangeContainsTimestampTzRev(RangeContainsTimestampTzRev),
445        RangeOverlaps(RangeOverlaps),
446        RangeAfter(RangeAfter),
447        RangeBefore(RangeBefore),
448        RangeOverleft(RangeOverleft),
449        RangeOverright(RangeOverright),
450        RangeAdjacent(RangeAdjacent),
451        RangeUnion(RangeUnion),
452        RangeIntersection(RangeIntersection),
453        RangeDifference(RangeDifference),
454        UuidGenerateV5(UuidGenerateV5),
455        MzAclItemContainsPrivilege(MzAclItemContainsPrivilege),
456        ParseIdent(ParseIdent),
457        PrettySql(PrettySql),
458        RegexpReplace(RegexpReplace),
459        StartsWith(StartsWith),
460    }
461}
462
463#[cfg(test)]
464mod test {
465    use mz_expr_derive::sqlfunc;
466    use mz_repr::SqlScalarType;
467
468    use crate::EvalError;
469    use crate::scalar::func::binary::LazyBinaryFunc;
470
471    #[sqlfunc(sqlname = "INFALLIBLE", is_infix_op = true, test = true)]
472    #[allow(dead_code)]
473    fn infallible1(a: f32, b: f32) -> f32 {
474        a + b
475    }
476
477    #[sqlfunc(test = true)]
478    #[allow(dead_code)]
479    fn infallible2(a: Option<f32>, b: Option<f32>) -> f32 {
480        a.unwrap_or_default() + b.unwrap_or_default()
481    }
482
483    #[sqlfunc(test = true)]
484    #[allow(dead_code)]
485    fn infallible3(a: f32, b: f32) -> Option<f32> {
486        Some(a + b)
487    }
488
489    #[mz_ore::test]
490    fn elision_rules_infallible() {
491        assert_eq!(format!("{}", Infallible1), "INFALLIBLE");
492        assert!(Infallible1.propagates_nulls());
493        assert!(!Infallible1.introduces_nulls());
494
495        assert!(!Infallible2.propagates_nulls());
496        assert!(!Infallible2.introduces_nulls());
497
498        assert!(Infallible3.propagates_nulls());
499        assert!(Infallible3.introduces_nulls());
500    }
501
502    #[mz_ore::test]
503    fn output_types_infallible() {
504        assert_eq!(
505            Infallible1.output_sql_type(&[
506                SqlScalarType::Float32.nullable(true),
507                SqlScalarType::Float32.nullable(true)
508            ]),
509            SqlScalarType::Float32.nullable(true)
510        );
511        assert_eq!(
512            Infallible1.output_sql_type(&[
513                SqlScalarType::Float32.nullable(true),
514                SqlScalarType::Float32.nullable(false)
515            ]),
516            SqlScalarType::Float32.nullable(true)
517        );
518        assert_eq!(
519            Infallible1.output_sql_type(&[
520                SqlScalarType::Float32.nullable(false),
521                SqlScalarType::Float32.nullable(true)
522            ]),
523            SqlScalarType::Float32.nullable(true)
524        );
525        assert_eq!(
526            Infallible1.output_sql_type(&[
527                SqlScalarType::Float32.nullable(false),
528                SqlScalarType::Float32.nullable(false)
529            ]),
530            SqlScalarType::Float32.nullable(false)
531        );
532
533        assert_eq!(
534            Infallible2.output_sql_type(&[
535                SqlScalarType::Float32.nullable(true),
536                SqlScalarType::Float32.nullable(true)
537            ]),
538            SqlScalarType::Float32.nullable(false)
539        );
540        assert_eq!(
541            Infallible2.output_sql_type(&[
542                SqlScalarType::Float32.nullable(true),
543                SqlScalarType::Float32.nullable(false)
544            ]),
545            SqlScalarType::Float32.nullable(false)
546        );
547        assert_eq!(
548            Infallible2.output_sql_type(&[
549                SqlScalarType::Float32.nullable(false),
550                SqlScalarType::Float32.nullable(true)
551            ]),
552            SqlScalarType::Float32.nullable(false)
553        );
554        assert_eq!(
555            Infallible2.output_sql_type(&[
556                SqlScalarType::Float32.nullable(false),
557                SqlScalarType::Float32.nullable(false)
558            ]),
559            SqlScalarType::Float32.nullable(false)
560        );
561
562        assert_eq!(
563            Infallible3.output_sql_type(&[
564                SqlScalarType::Float32.nullable(true),
565                SqlScalarType::Float32.nullable(true)
566            ]),
567            SqlScalarType::Float32.nullable(true)
568        );
569        assert_eq!(
570            Infallible3.output_sql_type(&[
571                SqlScalarType::Float32.nullable(true),
572                SqlScalarType::Float32.nullable(false)
573            ]),
574            SqlScalarType::Float32.nullable(true)
575        );
576        assert_eq!(
577            Infallible3.output_sql_type(&[
578                SqlScalarType::Float32.nullable(false),
579                SqlScalarType::Float32.nullable(true)
580            ]),
581            SqlScalarType::Float32.nullable(true)
582        );
583        assert_eq!(
584            Infallible3.output_sql_type(&[
585                SqlScalarType::Float32.nullable(false),
586                SqlScalarType::Float32.nullable(false)
587            ]),
588            SqlScalarType::Float32.nullable(true)
589        );
590    }
591
592    #[sqlfunc(test = true)]
593    #[allow(dead_code)]
594    fn fallible1(a: f32, b: f32) -> Result<f32, EvalError> {
595        Ok(a + b)
596    }
597
598    #[sqlfunc(test = true)]
599    #[allow(dead_code)]
600    fn fallible2(a: Option<f32>, b: Option<f32>) -> Result<f32, EvalError> {
601        Ok(a.unwrap_or_default() + b.unwrap_or_default())
602    }
603
604    #[sqlfunc(test = true)]
605    #[allow(dead_code)]
606    fn fallible3(a: f32, b: f32) -> Result<Option<f32>, EvalError> {
607        Ok(Some(a + b))
608    }
609
610    #[mz_ore::test]
611    fn elision_rules_fallible() {
612        assert!(Fallible1.propagates_nulls());
613        assert!(!Fallible1.introduces_nulls());
614
615        assert!(!Fallible2.propagates_nulls());
616        assert!(!Fallible2.introduces_nulls());
617
618        assert!(Fallible3.propagates_nulls());
619        assert!(Fallible3.introduces_nulls());
620    }
621
622    #[mz_ore::test]
623    fn output_types_fallible() {
624        assert_eq!(
625            Fallible1.output_sql_type(&[
626                SqlScalarType::Float32.nullable(true),
627                SqlScalarType::Float32.nullable(true)
628            ]),
629            SqlScalarType::Float32.nullable(true)
630        );
631        assert_eq!(
632            Fallible1.output_sql_type(&[
633                SqlScalarType::Float32.nullable(true),
634                SqlScalarType::Float32.nullable(false)
635            ]),
636            SqlScalarType::Float32.nullable(true)
637        );
638        assert_eq!(
639            Fallible1.output_sql_type(&[
640                SqlScalarType::Float32.nullable(false),
641                SqlScalarType::Float32.nullable(true)
642            ]),
643            SqlScalarType::Float32.nullable(true)
644        );
645        assert_eq!(
646            Fallible1.output_sql_type(&[
647                SqlScalarType::Float32.nullable(false),
648                SqlScalarType::Float32.nullable(false)
649            ]),
650            SqlScalarType::Float32.nullable(false)
651        );
652
653        assert_eq!(
654            Fallible2.output_sql_type(&[
655                SqlScalarType::Float32.nullable(true),
656                SqlScalarType::Float32.nullable(true)
657            ]),
658            SqlScalarType::Float32.nullable(false)
659        );
660        assert_eq!(
661            Fallible2.output_sql_type(&[
662                SqlScalarType::Float32.nullable(true),
663                SqlScalarType::Float32.nullable(false)
664            ]),
665            SqlScalarType::Float32.nullable(false)
666        );
667        assert_eq!(
668            Fallible2.output_sql_type(&[
669                SqlScalarType::Float32.nullable(false),
670                SqlScalarType::Float32.nullable(true)
671            ]),
672            SqlScalarType::Float32.nullable(false)
673        );
674        assert_eq!(
675            Fallible2.output_sql_type(&[
676                SqlScalarType::Float32.nullable(false),
677                SqlScalarType::Float32.nullable(false)
678            ]),
679            SqlScalarType::Float32.nullable(false)
680        );
681
682        assert_eq!(
683            Fallible3.output_sql_type(&[
684                SqlScalarType::Float32.nullable(true),
685                SqlScalarType::Float32.nullable(true)
686            ]),
687            SqlScalarType::Float32.nullable(true)
688        );
689        assert_eq!(
690            Fallible3.output_sql_type(&[
691                SqlScalarType::Float32.nullable(true),
692                SqlScalarType::Float32.nullable(false)
693            ]),
694            SqlScalarType::Float32.nullable(true)
695        );
696        assert_eq!(
697            Fallible3.output_sql_type(&[
698                SqlScalarType::Float32.nullable(false),
699                SqlScalarType::Float32.nullable(true)
700            ]),
701            SqlScalarType::Float32.nullable(true)
702        );
703        assert_eq!(
704            Fallible3.output_sql_type(&[
705                SqlScalarType::Float32.nullable(false),
706                SqlScalarType::Float32.nullable(false)
707            ]),
708            SqlScalarType::Float32.nullable(true)
709        );
710    }
711}