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