1use std::collections::{BTreeMap, BTreeSet};
37use std::fmt;
38
39use mz_repr::adt::char::CharLength;
40use mz_repr::adt::datetime::DateTimeUnits;
41use mz_repr::adt::numeric::NumericMaxScale;
42use mz_repr::adt::regex::Regex;
43use mz_repr::adt::timestamp::TimestampPrecision;
44use mz_repr::adt::varchar::VarCharMaxLength;
45use mz_repr::{ColumnName, Datum, Row, SqlColumnType, SqlScalarType, StableRow};
46use serde::Serialize;
47
48use crate::func::format::DateTimeFormat;
49use crate::func::variadic::{
50 And, ArrayCreate, ArrayFill, ArrayIndex, ArrayToString, Coalesce, ErrorIfNull, Greatest, Least,
51 ListCreate, MapBuild, Or, RangeCreate, RecordCreate,
52};
53use crate::func::*;
54use crate::{BinaryFunc, MirScalarExpr, UnaryFunc, VariadicFunc, like_pattern};
55
56#[derive(Debug)]
68pub struct Sample<F> {
69 pub func: F,
70 pub input_types: Vec<SqlColumnType>,
71 pub label: &'static str,
72}
73
74impl<F> Sample<F> {
75 pub fn labeled(self, label: &'static str) -> Self {
78 assert!(!label.is_empty(), "sample labels must be non-empty");
79 Sample { label, ..self }
80 }
81}
82
83#[derive(Debug)]
86pub struct FuncRegistry {
87 pub unary: BTreeMap<String, Record<UnaryFuncProperties>>,
88 pub binary: BTreeMap<String, Record<BinaryFuncProperties>>,
89 pub variadic: BTreeMap<String, Record<VariadicFuncProperties>>,
90}
91
92#[derive(Debug)]
99pub struct Record<P> {
100 pub properties: P,
101 pub source: FuncSource,
102}
103
104#[derive(Debug, Serialize)]
111pub struct FuncSource {
112 pub display: String,
113 pub sqlfunc_decl: Option<&'static str>,
114 pub body_fingerprint: Option<String>,
115}
116
117impl FuncSource {
118 fn of(display: String, source: Option<SqlFuncSource>) -> Self {
119 FuncSource {
120 display,
121 sqlfunc_decl: source.map(|s| s.decl),
122 body_fingerprint: source.map(|s| format!("{:016x}", s.body_fingerprint)),
123 }
124 }
125}
126
127#[derive(Debug)]
133pub struct ColumnTypeProbe<T>(pub std::marker::PhantomData<T>);
134
135pub trait ProbeColumnType {
137 fn column_type(&self) -> Option<SqlColumnType>;
138}
139
140impl<T: mz_repr::AsColumnType> ProbeColumnType for ColumnTypeProbe<T> {
141 fn column_type(&self) -> Option<SqlColumnType> {
142 Some(T::as_column_type())
143 }
144}
145
146pub trait ProbeColumnTypeFallback {
149 fn column_type(&self) -> Option<SqlColumnType>;
150}
151
152impl<T> ProbeColumnTypeFallback for &ColumnTypeProbe<T> {
153 fn column_type(&self) -> Option<SqlColumnType> {
154 None
155 }
156}
157
158#[derive(Debug, Serialize)]
159pub struct UnaryFuncProperties {
160 pub variant: String,
161 pub propagates_nulls: bool,
162 pub introduces_nulls: bool,
163 pub could_error: bool,
164 pub preserves_uniqueness: bool,
165 pub is_monotone: bool,
166 pub is_eliminable_cast: bool,
167 pub inverse: Option<&'static str>,
169 pub input_types: Vec<String>,
170 pub output_type: Option<String>,
171 pub sqlfunc_signature: Option<&'static str>,
174}
175
176#[derive(Debug, Serialize)]
177pub struct BinaryFuncProperties {
178 pub variant: String,
179 pub propagates_nulls: bool,
180 pub introduces_nulls: bool,
181 pub could_error: bool,
182 pub is_monotone: (bool, bool),
183 pub is_infinity_monotone: bool,
184 pub is_infix_op: bool,
185 pub negate: Option<&'static str>,
187 pub input_types: Vec<String>,
188 pub output_type: Option<String>,
189 pub sqlfunc_signature: Option<&'static str>,
192}
193
194#[derive(Debug, Serialize)]
195pub struct VariadicFuncProperties {
196 pub variant: String,
197 pub propagates_nulls: bool,
198 pub introduces_nulls: bool,
199 pub could_error: bool,
200 pub is_monotone: bool,
201 pub is_associative: bool,
202 pub is_infix_op: bool,
203 pub input_types: Vec<String>,
204 pub output_type: Option<String>,
205 pub sqlfunc_signature: Option<&'static str>,
208}
209
210impl FuncRegistry {
211 pub fn build() -> FuncRegistry {
217 FuncRegistry {
218 unary: collect(
219 "UnaryFunc",
220 UnaryFunc::variant_names(),
221 UnaryFunc::from_variant_name,
222 UnaryFunc::sqlfunc_input_types,
223 unary_samples(),
224 UnaryFunc::variant_name,
225 UnaryFuncProperties::of,
226 ),
227 binary: collect(
228 "BinaryFunc",
229 BinaryFunc::variant_names(),
230 BinaryFunc::from_variant_name,
231 BinaryFunc::sqlfunc_input_types,
232 binary_samples(),
233 BinaryFunc::variant_name,
234 BinaryFuncProperties::of,
235 ),
236 variadic: collect(
237 "VariadicFunc",
238 VariadicFunc::variant_names(),
239 VariadicFunc::from_variant_name,
240 VariadicFunc::sqlfunc_input_types,
241 variadic_samples(),
242 VariadicFunc::variant_name,
243 VariadicFuncProperties::of,
244 ),
245 }
246 }
247}
248
249fn collect<F: fmt::Debug, P>(
268 enum_name: &str,
269 names: impl Iterator<Item = &'static str>,
270 construct: fn(&str) -> Option<F>,
271 natural_inputs: fn(&F) -> Option<Vec<SqlColumnType>>,
272 samples: Vec<Sample<F>>,
273 variant_name: fn(&F) -> &'static str,
274 properties: fn(&Sample<F>) -> P,
275) -> BTreeMap<String, P> {
276 let samples_fn = format!(
277 "{}_samples() in src/expr/src/scalar/func/registry.rs",
278 enum_name.trim_end_matches("Func").to_lowercase()
279 );
280 let mut by_name: BTreeMap<(&'static str, &'static str), Sample<F>> = BTreeMap::new();
281 let mut hand_written = BTreeSet::new();
282 for sample in samples {
283 let name = variant_name(&sample.func);
284 let label = sample.label;
285 hand_written.insert(name);
286 let duplicate = by_name.insert((name, label), sample).is_some();
287 assert!(
288 !duplicate,
289 "duplicate {enum_name} sample for `{name}` with label `{label}`"
290 );
291 }
292
293 let mut records = BTreeMap::new();
294 for name in names {
295 let primary = by_name.remove(&(name, "")).or_else(|| {
296 construct(name).map(|func| Sample {
297 input_types: natural_inputs(&func).unwrap_or_default(),
298 func,
299 label: "",
300 })
301 });
302 let primary = primary.unwrap_or_else(|| {
303 panic!(
304 "{enum_name} variant `{name}` cannot be constructed from its name because \
305 its payload needs data. Add a representative Sample for it to {samples_fn}."
306 )
307 });
308 assert!(
309 hand_written.contains(name) || !payload_has_fields(&primary.func),
310 "{enum_name} variant `{name}` has a payload with fields but only its defaulted \
311 instance is recorded. Add a Sample with a non-default payload to {samples_fn}, \
312 labeled if the defaulted instance should stay the primary record."
313 );
314 records.insert(
315 name.to_string(),
316 probe(enum_name, name, &primary, properties),
317 );
318 }
319 for ((name, label), sample) in by_name {
320 assert!(
321 records.contains_key(name),
322 "{enum_name} sample `{name}[{label}]` names an unknown variant"
323 );
324 records.insert(
325 format!("{name}[{label}]"),
326 probe(enum_name, &format!("{name}[{label}]"), &sample, properties),
327 );
328 }
329 records
330}
331
332fn payload_has_fields<F: fmt::Debug>(func: &F) -> bool {
338 let rendered = format!("{func:?}");
339 rendered.matches('(').count() > 1 || rendered.contains('{')
340}
341
342fn probe<F, P>(
345 enum_name: &str,
346 key: &str,
347 sample: &Sample<F>,
348 properties: fn(&Sample<F>) -> P,
349) -> P {
350 mz_ore::panic::catch_unwind_str(std::panic::AssertUnwindSafe(|| properties(sample)))
351 .unwrap_or_else(|message| {
352 panic!(
353 "probing {enum_name} sample `{key}` panicked: {message}\n\
354 Check the sample's input types in src/expr/src/scalar/func/registry.rs."
355 )
356 })
357}
358
359fn variant_ident<F: Serialize>(func: &F) -> String {
361 let value = serde_json::to_value(func).expect("function variants serialize");
362 match value {
363 serde_json::Value::Object(map) if map.len() == 1 => {
364 map.into_iter().next().expect("one entry").0
365 }
366 serde_json::Value::String(name) => name,
367 other => panic!("unexpected function variant encoding: {other}"),
368 }
369}
370
371fn type_strings(types: &[SqlColumnType]) -> Vec<String> {
372 types.iter().map(ToString::to_string).collect()
373}
374
375impl UnaryFuncProperties {
376 fn of(sample: &Sample<UnaryFunc>) -> Record<Self> {
377 let func = &sample.func;
378 let output_type = match sample.input_types.as_slice() {
379 [] => None,
380 [input] => Some(func.output_sql_type(input.clone()).to_string()),
381 _ => panic!("unary sample `{}` needs exactly one input type", func),
382 };
383 let source = func.sqlfunc_source();
384 let properties = UnaryFuncProperties {
385 variant: variant_ident(func),
386 propagates_nulls: func.propagates_nulls(),
387 introduces_nulls: func.introduces_nulls(),
388 could_error: func.could_error(),
389 preserves_uniqueness: func.preserves_uniqueness(),
390 is_monotone: func.is_monotone(),
391 is_eliminable_cast: func.is_eliminable_cast(),
392 inverse: func.inverse().map(|f| f.variant_name()),
393 input_types: type_strings(&sample.input_types),
394 output_type,
395 sqlfunc_signature: source.map(|s| s.signature),
396 };
397 Record {
398 properties,
399 source: FuncSource::of(func.to_string(), source),
400 }
401 }
402}
403
404impl BinaryFuncProperties {
405 fn of(sample: &Sample<BinaryFunc>) -> Record<Self> {
406 let func = &sample.func;
407 let output_type = match sample.input_types.as_slice() {
408 [] => None,
409 [_, _] => Some(func.output_sql_type(&sample.input_types).to_string()),
410 _ => panic!("binary sample `{}` needs exactly two input types", func),
411 };
412 let source = func.sqlfunc_source();
413 let properties = BinaryFuncProperties {
414 variant: variant_ident(func),
415 propagates_nulls: func.propagates_nulls(),
416 introduces_nulls: func.introduces_nulls(),
417 could_error: func.could_error(),
418 is_monotone: func.is_monotone(),
419 is_infinity_monotone: func.is_infinity_monotone(),
420 is_infix_op: func.is_infix_op(),
421 negate: func.negate().map(|f| f.variant_name()),
422 input_types: type_strings(&sample.input_types),
423 output_type,
424 sqlfunc_signature: source.map(|s| s.signature),
425 };
426 Record {
427 properties,
428 source: FuncSource::of(func.to_string(), source),
429 }
430 }
431}
432
433impl VariadicFuncProperties {
434 fn of(sample: &Sample<VariadicFunc>) -> Record<Self> {
435 let func = &sample.func;
436 let output_type = (!sample.input_types.is_empty())
437 .then(|| func.output_sql_type(sample.input_types.clone()).to_string());
438 let source = func.sqlfunc_source();
439 let properties = VariadicFuncProperties {
440 variant: variant_ident(func),
441 propagates_nulls: func.propagates_nulls(),
442 introduces_nulls: func.introduces_nulls(),
443 could_error: func.could_error(),
444 is_monotone: func.is_monotone(),
445 is_associative: func.is_associative(),
446 is_infix_op: func.is_infix_op(),
447 input_types: type_strings(&sample.input_types),
448 output_type,
449 sqlfunc_signature: source.map(|s| s.signature),
450 };
451 Record {
452 properties,
453 source: FuncSource::of(func.to_string(), source),
454 }
455 }
456}
457
458fn column(i: usize) -> Box<MirScalarExpr> {
459 Box::new(MirScalarExpr::column(i))
460}
461
462fn record_type() -> SqlScalarType {
463 SqlScalarType::Record {
464 fields: [
465 ("a".into(), SqlScalarType::Int32.nullable(false)),
466 ("b".into(), SqlScalarType::String.nullable(true)),
467 ]
468 .into(),
469 custom_id: None,
470 }
471}
472
473fn time_record_type() -> SqlScalarType {
474 SqlScalarType::Record {
475 fields: [("a".into(), SqlScalarType::Time.nullable(false))].into(),
476 custom_id: None,
477 }
478}
479
480fn list_type(element: SqlScalarType) -> SqlScalarType {
481 SqlScalarType::List {
482 element_type: Box::new(element),
483 custom_id: None,
484 }
485}
486
487fn map_type(value: SqlScalarType) -> SqlScalarType {
488 SqlScalarType::Map {
489 value_type: Box::new(value),
490 custom_id: None,
491 }
492}
493
494fn range_type(element: SqlScalarType) -> SqlScalarType {
495 SqlScalarType::Range {
496 element_type: Box::new(element),
497 }
498}
499
500fn array_type(element: SqlScalarType) -> SqlScalarType {
501 SqlScalarType::Array(Box::new(element))
502}
503
504fn regex() -> Regex {
505 Regex::new("a+", false).expect("valid regex")
506}
507
508fn unary<F: Into<UnaryFunc>>(func: F, input: SqlScalarType) -> Sample<UnaryFunc> {
509 Sample {
510 func: func.into(),
511 input_types: vec![input.nullable(false)],
512 label: "",
513 }
514}
515
516fn unary_samples() -> Vec<Sample<UnaryFunc>> {
517 let timestamp_precision = Some(TimestampPrecision::try_from(3i64).expect("valid precision"));
518 let numeric_scale = NumericMaxScale::try_from(2i64).expect("valid scale");
519 let char_length = Some(CharLength::try_from(5i64).expect("valid length"));
520 let varchar_length = Some(VarCharMaxLength::try_from(5i64).expect("valid length"));
521 let tz = mz_pgtz::timezone::Timezone::Tz(chrono_tz::Tz::UTC);
522 vec![
523 unary(
525 CastArrayToString {
526 ty: array_type(SqlScalarType::Int32),
527 },
528 array_type(SqlScalarType::Int32),
529 ),
530 unary(
531 CastArrayToJsonb {
532 cast_element: column(0),
533 },
534 array_type(SqlScalarType::Int32),
535 ),
536 unary(
537 CastArrayToArray {
538 return_ty: array_type(SqlScalarType::Int64),
539 cast_expr: column(0),
540 },
541 array_type(SqlScalarType::Int32),
542 ),
543 unary(
544 CastListToString {
545 ty: list_type(SqlScalarType::Int32),
546 },
547 list_type(SqlScalarType::Int32),
548 ),
549 unary(
550 CastListToJsonb {
551 cast_element: column(0),
552 },
553 list_type(SqlScalarType::Int32),
554 ),
555 unary(
556 CastList1ToList2 {
557 return_ty: list_type(SqlScalarType::Int64),
558 cast_expr: column(0),
559 },
560 list_type(SqlScalarType::Int32),
561 ),
562 unary(
563 CastMapToString {
564 ty: map_type(SqlScalarType::Int32),
565 },
566 map_type(SqlScalarType::Int32),
567 ),
568 unary(
569 MapBuildFromRecordList {
570 value_type: SqlScalarType::Int32,
571 },
572 list_type(record_type()),
573 ),
574 unary(
575 CastRangeToString {
576 ty: range_type(SqlScalarType::Int32),
577 },
578 range_type(SqlScalarType::Int32),
579 ),
580 unary(CastRecordToString { ty: record_type() }, record_type()),
581 unary(
582 CastRecord1ToRecord2 {
583 return_ty: record_type(),
584 cast_exprs: vec![*column(0), *column(1)].into(),
585 },
586 record_type(),
587 ),
588 unary(RecordGet(1), record_type()),
589 unary(
595 UnaryFunc::try_cast_if_fallible(CastStringToInt32),
596 SqlScalarType::String,
597 ),
598 unary(
599 UnaryFunc::try_cast_if_fallible(CastInt64ToInt32),
600 SqlScalarType::Int64,
601 )
602 .labeled("narrowing"),
603 unary(
604 UnaryFunc::try_cast_if_fallible(CastJsonbToNumeric(None)),
605 SqlScalarType::Jsonb,
606 )
607 .labeled("jsonb"),
608 unary(
609 UnaryFunc::try_cast_if_fallible(CastList1ToList2 {
610 return_ty: list_type(SqlScalarType::Int32),
611 cast_expr: column(0),
612 }),
613 list_type(SqlScalarType::Int64),
614 )
615 .labeled("list"),
616 unary(
617 CastStringToArray {
618 return_ty: array_type(SqlScalarType::Int32),
619 cast_expr: column(0),
620 },
621 SqlScalarType::String,
622 ),
623 unary(
624 CastStringToList {
625 return_ty: list_type(SqlScalarType::Int32),
626 cast_expr: column(0),
627 },
628 SqlScalarType::String,
629 ),
630 unary(
631 CastStringToMap {
632 return_ty: map_type(SqlScalarType::Int32),
633 cast_expr: column(0),
634 },
635 SqlScalarType::String,
636 ),
637 unary(
638 CastStringToRange {
639 return_ty: range_type(SqlScalarType::Int32),
640 cast_expr: column(0),
641 },
642 SqlScalarType::String,
643 ),
644 unary(
646 CastStringToChar {
647 length: char_length,
648 fail_on_len: true,
649 },
650 SqlScalarType::String,
651 ),
652 unary(
653 PadChar {
654 length: char_length,
655 },
656 SqlScalarType::String,
657 ),
658 unary(
659 CastStringToVarChar {
660 length: varchar_length,
661 fail_on_len: true,
662 },
663 SqlScalarType::String,
664 ),
665 unary(
667 CastTimestampToTimestampTz {
668 from: None,
669 to: timestamp_precision,
670 },
671 SqlScalarType::Timestamp { precision: None },
672 ),
673 unary(
674 CastTimestampTzToTimestamp {
675 from: None,
676 to: timestamp_precision,
677 },
678 SqlScalarType::TimestampTz { precision: None },
679 ),
680 unary(
681 AdjustTimestampPrecision {
682 from: None,
683 to: timestamp_precision,
684 },
685 SqlScalarType::Timestamp { precision: None },
686 ),
687 unary(
688 AdjustTimestampTzPrecision {
689 from: None,
690 to: timestamp_precision,
691 },
692 SqlScalarType::TimestampTz { precision: None },
693 ),
694 unary(
695 AdjustNumericScale(numeric_scale),
696 SqlScalarType::Numeric { max_scale: None },
697 ),
698 unary(
700 IsLikeMatch(like_pattern::compile("%a%", false).expect("valid pattern")),
701 SqlScalarType::String,
702 ),
703 unary(IsRegexpMatch(regex()), SqlScalarType::String),
704 unary(RegexpMatch(regex()), SqlScalarType::String),
705 unary(
706 Jq(JqProgram::new(".a").expect("valid program")),
707 SqlScalarType::Jsonb,
708 ),
709 unary(RegexpSplitToArray(regex()), SqlScalarType::String),
710 unary(
712 ExtractInterval(DateTimeUnits::Epoch),
713 SqlScalarType::Interval,
714 ),
715 unary(ExtractTime(DateTimeUnits::Epoch), SqlScalarType::Time),
716 unary(
717 ExtractTimestamp(DateTimeUnits::Epoch),
718 SqlScalarType::Timestamp { precision: None },
719 ),
720 unary(
721 ExtractTimestampTz(DateTimeUnits::Epoch),
722 SqlScalarType::TimestampTz { precision: None },
723 ),
724 unary(ExtractDate(DateTimeUnits::Epoch), SqlScalarType::Date),
725 unary(
726 DatePartInterval(DateTimeUnits::Epoch),
727 SqlScalarType::Interval,
728 ),
729 unary(DatePartTime(DateTimeUnits::Epoch), SqlScalarType::Time),
730 unary(
731 DatePartTimestamp(DateTimeUnits::Epoch),
732 SqlScalarType::Timestamp { precision: None },
733 ),
734 unary(
735 DatePartTimestampTz(DateTimeUnits::Epoch),
736 SqlScalarType::TimestampTz { precision: None },
737 ),
738 unary(
739 DateTruncTimestamp(DateTimeUnits::Day),
740 SqlScalarType::Timestamp { precision: None },
741 ),
742 unary(
743 DateTruncTimestampTz(DateTimeUnits::Day),
744 SqlScalarType::TimestampTz { precision: None },
745 ),
746 unary(
747 TimezoneTimestamp(tz.clone()),
748 SqlScalarType::Timestamp { precision: None },
749 ),
750 unary(
751 TimezoneTimestampTz(tz.clone()),
752 SqlScalarType::TimestampTz { precision: None },
753 ),
754 unary(
755 TimezoneTime {
756 tz,
757 wall_time: chrono::NaiveDateTime::default(),
758 },
759 SqlScalarType::Time,
760 ),
761 unary(
762 ToCharTimestamp {
763 format_string: "YYYY".into(),
764 format: DateTimeFormat::compile("YYYY"),
765 },
766 SqlScalarType::Timestamp { precision: None },
767 ),
768 unary(
769 ToCharTimestampTz {
770 format_string: "YYYY".into(),
771 format: DateTimeFormat::compile("YYYY"),
772 },
773 SqlScalarType::TimestampTz { precision: None },
774 ),
775 unary(CastStringToInt2Vector, SqlScalarType::String),
777 unary(CastDateToTimestamp(None), SqlScalarType::Date),
778 unary(CastDateToTimestampTz(None), SqlScalarType::Date),
779 unary(CastStringToTimestamp(None), SqlScalarType::String),
780 unary(CastStringToTimestampTz(None), SqlScalarType::String),
781 unary(
782 CastDateToTimestamp(timestamp_precision),
783 SqlScalarType::Date,
784 )
785 .labeled("precision"),
786 unary(
787 CastDateToTimestampTz(timestamp_precision),
788 SqlScalarType::Date,
789 )
790 .labeled("precision"),
791 unary(
792 CastStringToTimestamp(timestamp_precision),
793 SqlScalarType::String,
794 )
795 .labeled("precision"),
796 unary(
797 CastStringToTimestampTz(timestamp_precision),
798 SqlScalarType::String,
799 )
800 .labeled("precision"),
801 unary(
803 CastStringToChar {
804 length: None,
805 fail_on_len: false,
806 },
807 SqlScalarType::String,
808 )
809 .labeled("unbounded"),
810 unary(PadChar { length: None }, SqlScalarType::String).labeled("unbounded"),
811 unary(
812 CastStringToVarChar {
813 length: None,
814 fail_on_len: false,
815 },
816 SqlScalarType::String,
817 )
818 .labeled("unbounded"),
819 unary(
821 CastTimestampToTimestampTz {
822 from: timestamp_precision,
823 to: None,
824 },
825 SqlScalarType::Timestamp {
826 precision: timestamp_precision,
827 },
828 )
829 .labeled("widening"),
830 unary(
831 CastTimestampTzToTimestamp {
832 from: timestamp_precision,
833 to: None,
834 },
835 SqlScalarType::TimestampTz {
836 precision: timestamp_precision,
837 },
838 )
839 .labeled("widening"),
840 unary(
841 AdjustTimestampPrecision {
842 from: timestamp_precision,
843 to: None,
844 },
845 SqlScalarType::Timestamp {
846 precision: timestamp_precision,
847 },
848 )
849 .labeled("widening"),
850 unary(
851 AdjustTimestampTzPrecision {
852 from: timestamp_precision,
853 to: None,
854 },
855 SqlScalarType::TimestampTz {
856 precision: timestamp_precision,
857 },
858 )
859 .labeled("widening"),
860 unary(
863 CastArrayToString {
864 ty: array_type(SqlScalarType::Time),
865 },
866 array_type(SqlScalarType::Time),
867 )
868 .labeled("time"),
869 unary(
870 CastListToString {
871 ty: list_type(SqlScalarType::Time),
872 },
873 list_type(SqlScalarType::Time),
874 )
875 .labeled("time"),
876 unary(
877 CastMapToString {
878 ty: map_type(SqlScalarType::Time),
879 },
880 map_type(SqlScalarType::Time),
881 )
882 .labeled("time"),
883 unary(
884 CastRangeToString {
885 ty: range_type(SqlScalarType::Time),
886 },
887 range_type(SqlScalarType::Time),
888 )
889 .labeled("time"),
890 unary(
891 CastRecordToString {
892 ty: time_record_type(),
893 },
894 time_record_type(),
895 )
896 .labeled("time"),
897 unary(
899 ExtractInterval(DateTimeUnits::Month),
900 SqlScalarType::Interval,
901 )
902 .labeled("month"),
903 unary(ExtractTime(DateTimeUnits::Minute), SqlScalarType::Time).labeled("minute"),
904 unary(
905 ExtractTimestamp(DateTimeUnits::Month),
906 SqlScalarType::Timestamp { precision: None },
907 )
908 .labeled("month"),
909 unary(
910 ExtractTimestampTz(DateTimeUnits::Month),
911 SqlScalarType::TimestampTz { precision: None },
912 )
913 .labeled("month"),
914 unary(ExtractDate(DateTimeUnits::Month), SqlScalarType::Date).labeled("month"),
915 unary(
916 DatePartInterval(DateTimeUnits::Month),
917 SqlScalarType::Interval,
918 )
919 .labeled("month"),
920 unary(DatePartTime(DateTimeUnits::Minute), SqlScalarType::Time).labeled("minute"),
921 unary(
922 DatePartTimestamp(DateTimeUnits::Month),
923 SqlScalarType::Timestamp { precision: None },
924 )
925 .labeled("month"),
926 unary(
927 DatePartTimestampTz(DateTimeUnits::Month),
928 SqlScalarType::TimestampTz { precision: None },
929 )
930 .labeled("month"),
931 ]
932 .into_iter()
933 .chain(numeric_cast_samples(numeric_scale))
934 .collect()
935}
936
937fn numeric_cast_samples(scale: NumericMaxScale) -> Vec<Sample<UnaryFunc>> {
940 let casts: [(fn(Option<NumericMaxScale>) -> UnaryFunc, SqlScalarType); 10] = [
941 (|s| CastInt16ToNumeric(s).into(), SqlScalarType::Int16),
942 (|s| CastInt32ToNumeric(s).into(), SqlScalarType::Int32),
943 (|s| CastInt64ToNumeric(s).into(), SqlScalarType::Int64),
944 (|s| CastUint16ToNumeric(s).into(), SqlScalarType::UInt16),
945 (|s| CastUint32ToNumeric(s).into(), SqlScalarType::UInt32),
946 (|s| CastUint64ToNumeric(s).into(), SqlScalarType::UInt64),
947 (|s| CastFloat32ToNumeric(s).into(), SqlScalarType::Float32),
948 (|s| CastFloat64ToNumeric(s).into(), SqlScalarType::Float64),
949 (|s| CastStringToNumeric(s).into(), SqlScalarType::String),
950 (|s| CastJsonbToNumeric(s).into(), SqlScalarType::Jsonb),
951 ];
952 casts
953 .into_iter()
954 .flat_map(|(cast, input)| {
955 [
956 unary(cast(None), input.clone()),
957 unary(cast(Some(scale)), input).labeled("scale"),
958 ]
959 })
960 .collect()
961}
962
963fn binary<F: Into<BinaryFunc>>(
964 func: F,
965 left: SqlScalarType,
966 right: SqlScalarType,
967) -> Sample<BinaryFunc> {
968 Sample {
969 func: func.into(),
970 input_types: vec![left.nullable(false), right.nullable(false)],
971 label: "",
972 }
973}
974
975fn binary_samples() -> Vec<Sample<BinaryFunc>> {
976 vec![
977 binary(
978 ListLengthMax { max_layer: 1 },
979 list_type(SqlScalarType::Int32),
980 SqlScalarType::Int64,
981 ),
982 binary(
983 RegexpReplace {
984 regex: regex(),
985 limit: 1,
986 },
987 SqlScalarType::String,
988 SqlScalarType::String,
989 ),
990 ]
991}
992
993fn variadic<F: Into<VariadicFunc>>(func: F, inputs: Vec<SqlScalarType>) -> Sample<VariadicFunc> {
994 Sample {
995 func: func.into(),
996 input_types: inputs.into_iter().map(|ty| ty.nullable(false)).collect(),
997 label: "",
998 }
999}
1000
1001fn variadic_samples() -> Vec<Sample<VariadicFunc>> {
1002 vec![
1003 variadic(
1004 ArrayCreate {
1005 elem_type: SqlScalarType::Int32,
1006 },
1007 vec![SqlScalarType::Int32, SqlScalarType::Int32],
1008 ),
1009 variadic(
1010 ArrayFill {
1011 elem_type: SqlScalarType::Int32,
1012 },
1013 vec![SqlScalarType::Int32, array_type(SqlScalarType::Int32)],
1014 ),
1015 variadic(
1016 ArrayIndex { offset: 1 },
1017 vec![array_type(SqlScalarType::Int32), SqlScalarType::Int64],
1018 ),
1019 variadic(
1020 ArrayToString {
1021 elem_type: SqlScalarType::Int32,
1022 },
1023 vec![array_type(SqlScalarType::Int32), SqlScalarType::String],
1024 ),
1025 variadic(
1026 ListCreate {
1027 elem_type: SqlScalarType::Int32,
1028 },
1029 vec![SqlScalarType::Int32, SqlScalarType::Int32],
1030 ),
1031 variadic(
1032 RangeCreate {
1033 elem_type: SqlScalarType::Int32,
1034 },
1035 vec![
1036 SqlScalarType::Int32,
1037 SqlScalarType::Int32,
1038 SqlScalarType::String,
1039 ],
1040 ),
1041 variadic(
1042 RecordCreate {
1043 field_names: vec![ColumnName::from("a".to_string())],
1044 },
1045 vec![SqlScalarType::Int32],
1046 ),
1047 variadic(
1048 MapBuild {
1049 value_type: SqlScalarType::Int32,
1050 },
1051 vec![SqlScalarType::String, SqlScalarType::Int32],
1052 ),
1053 variadic(
1054 CaseLiteral {
1055 lookup: vec![CaseLiteralEntry {
1056 literal: StableRow::from(Row::pack_slice(&[Datum::Int32(1)])),
1057 expr_index: 0,
1058 }],
1059 return_type: SqlScalarType::String.nullable(true),
1060 },
1061 vec![SqlScalarType::Int32, SqlScalarType::String],
1062 ),
1063 variadic(And, vec![SqlScalarType::Bool, SqlScalarType::Bool]),
1065 variadic(Or, vec![SqlScalarType::Bool, SqlScalarType::Bool]),
1066 variadic(Coalesce, vec![SqlScalarType::Int32, SqlScalarType::Int32]),
1067 variadic(Greatest, vec![SqlScalarType::Int32, SqlScalarType::Int32]),
1068 variadic(Least, vec![SqlScalarType::Int32, SqlScalarType::Int32]),
1069 variadic(
1070 ErrorIfNull,
1071 vec![SqlScalarType::Int32, SqlScalarType::String],
1072 ),
1073 ]
1074}
1075
1076#[cfg(test)]
1077mod tests {
1078 use super::*;
1079
1080 #[mz_ore::test]
1081 fn payload_field_detection() {
1082 fn unary(func: impl Into<UnaryFunc>) -> UnaryFunc {
1083 func.into()
1084 }
1085 assert!(!payload_has_fields(&unary(Not)));
1086 assert!(payload_has_fields(&unary(CastInt32ToNumeric(None))));
1087 assert!(payload_has_fields(&unary(PadChar { length: None })));
1088 assert!(!payload_has_fields(&VariadicFunc::from(And)));
1089 assert!(payload_has_fields(&VariadicFunc::from(ArrayIndex {
1090 offset: 0
1091 })));
1092 }
1093}