1use crate::PlannerError;
13use crate::ast::expr::Literal;
14use crate::ast::span::Span;
15use crate::planner::typed_expr::{TypedExpr, TypedExprKind};
16use crate::planner::types::ResolvedType;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Arity {
20 Exact(usize),
21 Range(usize, usize),
22 Variadic(usize),
23}
24
25impl Arity {
26 pub fn accepts(self, count: usize) -> bool {
27 match self {
28 Self::Exact(n) => count == n,
29 Self::Range(min, max) => (min..=max).contains(&count),
30 Self::Variadic(min) => count >= min,
31 }
32 }
33
34 pub fn describe(self) -> String {
35 match self {
36 Self::Exact(n) => n.to_string(),
37 Self::Range(min, max) => format!("{min}..={max}"),
38 Self::Variadic(min) => format!("{min} or more"),
39 }
40 }
41
42 pub fn validate(self, name: &str, count: usize, _span: Span) -> Result<(), PlannerError> {
43 if self.accepts(count) {
44 Ok(())
45 } else {
46 Err(PlannerError::invalid_expression(format!(
47 "function '{name}' expects {} argument(s), got {count}",
48 self.describe()
49 )))
50 }
51 }
52}
53
54#[derive(Debug, Clone)]
55pub enum ReturnRule {
56 Fixed(ResolvedType),
57 FromArgs(fn(&[ResolvedType]) -> Result<ResolvedType, PlannerError>),
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub struct FnMeta {
62 pub deterministic: bool,
63 pub volatile: bool,
64 pub side_effecting: bool,
65 pub foldable: bool,
66 pub cacheable: bool,
67 pub reorderable: bool,
68}
69
70pub const PURE_META: FnMeta = FnMeta {
71 deterministic: true,
72 volatile: false,
73 side_effecting: false,
74 foldable: true,
75 cacheable: true,
76 reorderable: true,
77};
78
79pub const RANDOM_META: FnMeta = FnMeta {
80 deterministic: false,
81 volatile: true,
82 side_effecting: false,
83 foldable: false,
84 cacheable: false,
85 reorderable: true,
86};
87
88pub const SYSTEM_META: FnMeta = FnMeta {
89 deterministic: false,
90 volatile: true,
91 side_effecting: false,
92 foldable: false,
93 cacheable: false,
94 reorderable: false,
95};
96
97pub const SYSTEM_SIDE_EFFECT_META: FnMeta = FnMeta {
98 deterministic: false,
99 volatile: true,
100 side_effecting: true,
101 foldable: false,
102 cacheable: false,
103 reorderable: false,
104};
105
106#[derive(Debug, Clone)]
107pub struct ScalarSignature {
108 pub name: &'static str,
109 pub arity: Arity,
110 pub check: fn(&[TypedExpr]) -> Result<(), PlannerError>,
111 pub ret: ReturnRule,
112 pub meta: FnMeta,
113}
114
115fn is_numeric(ty: &ResolvedType) -> bool {
116 matches!(
117 ty,
118 ResolvedType::Integer
119 | ResolvedType::BigInt
120 | ResolvedType::Float
121 | ResolvedType::Double
122 | ResolvedType::Null
123 )
124}
125
126pub fn check_numeric(args: &[TypedExpr]) -> Result<(), PlannerError> {
127 for arg in args {
128 if !is_numeric(&arg.resolved_type) {
129 return Err(PlannerError::type_mismatch(
130 "Numeric",
131 arg.resolved_type.type_name(),
132 arg.span,
133 ));
134 }
135 }
136 Ok(())
137}
138
139pub fn check_text(args: &[TypedExpr]) -> Result<(), PlannerError> {
140 for arg in args {
141 if !matches!(arg.resolved_type, ResolvedType::Text | ResolvedType::Null) {
142 return Err(PlannerError::type_mismatch(
143 "Text",
144 arg.resolved_type.type_name(),
145 arg.span,
146 ));
147 }
148 }
149 Ok(())
150}
151
152pub fn check_text_or_blob(args: &[TypedExpr]) -> Result<(), PlannerError> {
153 for arg in args {
154 if !matches!(
155 arg.resolved_type,
156 ResolvedType::Text | ResolvedType::Blob | ResolvedType::Null
157 ) {
158 return Err(PlannerError::type_mismatch(
159 "Text or Blob",
160 arg.resolved_type.type_name(),
161 arg.span,
162 ));
163 }
164 }
165 Ok(())
166}
167
168pub fn check_bigint(args: &[TypedExpr]) -> Result<(), PlannerError> {
169 for arg in args {
170 if !matches!(arg.resolved_type, ResolvedType::BigInt | ResolvedType::Null) {
171 return Err(PlannerError::type_mismatch(
172 "BigInt",
173 arg.resolved_type.type_name(),
174 arg.span,
175 ));
176 }
177 }
178 Ok(())
179}
180
181pub fn check_blob_text(args: &[TypedExpr]) -> Result<(), PlannerError> {
182 if let Some(first) = args.first()
183 && !matches!(first.resolved_type, ResolvedType::Blob | ResolvedType::Null)
184 {
185 return Err(PlannerError::type_mismatch(
186 "Blob",
187 first.resolved_type.type_name(),
188 first.span,
189 ));
190 }
191 check_text(&args[1..])
192}
193
194pub fn check_any(args: &[TypedExpr]) -> Result<(), PlannerError> {
195 if args.is_empty() {
196 return Err(PlannerError::invalid_expression(
197 "at least one argument is required",
198 ));
199 }
200 Ok(())
201}
202
203pub fn check_no_args(_args: &[TypedExpr]) -> Result<(), PlannerError> {
204 Ok(())
205}
206
207fn check_vector_one(args: &[TypedExpr]) -> Result<(), PlannerError> {
208 match args.first().map(|arg| &arg.resolved_type) {
209 Some(ResolvedType::Vector { .. } | ResolvedType::Null) => Ok(()),
210 Some(ty) => Err(PlannerError::type_mismatch(
211 "Vector",
212 ty.type_name(),
213 args[0].span,
214 )),
215 None => Ok(()),
216 }
217}
218
219fn check_vector_triplet(args: &[TypedExpr]) -> Result<(), PlannerError> {
220 let first = match &args[0].resolved_type {
221 ResolvedType::Vector { dimension, .. } => *dimension,
222 ty => {
223 return Err(PlannerError::type_mismatch(
224 "Vector",
225 ty.type_name(),
226 args[0].span,
227 ));
228 }
229 };
230 let second = match &args[1].resolved_type {
231 ResolvedType::Vector { dimension, .. } => *dimension,
232 ty => {
233 return Err(PlannerError::type_mismatch(
234 "Vector",
235 ty.type_name(),
236 args[1].span,
237 ));
238 }
239 };
240 if first != second {
241 return Err(PlannerError::vector_dimension_mismatch(
242 first,
243 second,
244 args[1].span,
245 ));
246 }
247 match &args[2].resolved_type {
248 ResolvedType::Text => {
249 if let TypedExprKind::Literal(Literal::String(value)) = &args[2].kind
250 && !matches!(
251 value.to_ascii_lowercase().as_str(),
252 "cosine" | "l2" | "inner"
253 )
254 {
255 return Err(PlannerError::invalid_metric(value, args[2].span));
256 }
257 Ok(())
258 }
259 ty => Err(PlannerError::type_mismatch(
260 "Text (metric)",
261 ty.type_name(),
262 args[2].span,
263 )),
264 }
265}
266
267fn numeric_return(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
268 let mut result = ResolvedType::Null;
269 for ty in types {
270 if matches!(ty, ResolvedType::Null) {
271 continue;
272 }
273 if !is_numeric(ty) {
274 return Err(PlannerError::type_mismatch(
275 "Numeric",
276 ty.type_name(),
277 Span::default(),
278 ));
279 }
280 result = match (&result, ty) {
281 (ResolvedType::Null, _) => ty.clone(),
282 (ResolvedType::Double, _) | (_, ResolvedType::Double) => ResolvedType::Double,
283 (ResolvedType::Float, ResolvedType::BigInt)
284 | (ResolvedType::BigInt, ResolvedType::Float) => ResolvedType::Double,
285 (ResolvedType::Float, _) | (_, ResolvedType::Float) => ResolvedType::Float,
286 (ResolvedType::BigInt, _) | (_, ResolvedType::BigInt) => ResolvedType::BigInt,
287 _ => ResolvedType::Integer,
288 };
289 }
290 Ok(result)
291}
292
293fn return_arg0(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
294 Ok(types.first().cloned().unwrap_or(ResolvedType::Null))
295}
296
297fn return_first_non_null(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
298 Ok(types
299 .iter()
300 .find(|ty| !matches!(ty, ResolvedType::Null))
301 .cloned()
302 .unwrap_or(ResolvedType::Null))
303}
304
305fn return_numeric(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
306 numeric_return(types)
307}
308
309fn return_arg0_numeric(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
310 return_arg0(types)
311}
312
313const fn sig(
314 name: &'static str,
315 arity: Arity,
316 check: fn(&[TypedExpr]) -> Result<(), PlannerError>,
317 ret: ReturnRule,
318) -> ScalarSignature {
319 ScalarSignature {
320 name,
321 arity,
322 check,
323 ret,
324 meta: PURE_META,
325 }
326}
327
328const fn sig_meta(
329 name: &'static str,
330 arity: Arity,
331 check: fn(&[TypedExpr]) -> Result<(), PlannerError>,
332 ret: ReturnRule,
333 meta: FnMeta,
334) -> ScalarSignature {
335 ScalarSignature {
336 name,
337 arity,
338 check,
339 ret,
340 meta,
341 }
342}
343
344static SIGNATURES: &[ScalarSignature] = &[
345 sig(
346 "vector_similarity",
347 Arity::Exact(3),
348 check_vector_triplet,
349 ReturnRule::Fixed(ResolvedType::Double),
350 ),
351 sig(
352 "vector_distance",
353 Arity::Exact(3),
354 check_vector_triplet,
355 ReturnRule::Fixed(ResolvedType::Double),
356 ),
357 sig(
358 "vector_dims",
359 Arity::Exact(1),
360 check_vector_one,
361 ReturnRule::Fixed(ResolvedType::Integer),
362 ),
363 sig(
364 "vector_norm",
365 Arity::Exact(1),
366 check_vector_one,
367 ReturnRule::Fixed(ResolvedType::Double),
368 ),
369 sig(
370 "abs",
371 Arity::Exact(1),
372 check_numeric,
373 ReturnRule::FromArgs(return_arg0_numeric),
374 ),
375 sig(
376 "sign",
377 Arity::Exact(1),
378 check_numeric,
379 ReturnRule::Fixed(ResolvedType::Integer),
380 ),
381 sig(
382 "round",
383 Arity::Range(1, 2),
384 check_numeric,
385 ReturnRule::FromArgs(return_arg0_numeric),
386 ),
387 sig(
388 "floor",
389 Arity::Exact(1),
390 check_numeric,
391 ReturnRule::FromArgs(return_arg0_numeric),
392 ),
393 sig(
394 "ceil",
395 Arity::Exact(1),
396 check_numeric,
397 ReturnRule::FromArgs(return_arg0_numeric),
398 ),
399 sig(
400 "ceiling",
401 Arity::Exact(1),
402 check_numeric,
403 ReturnRule::FromArgs(return_arg0_numeric),
404 ),
405 sig(
406 "trunc",
407 Arity::Range(1, 2),
408 check_numeric,
409 ReturnRule::FromArgs(return_arg0_numeric),
410 ),
411 sig(
412 "mod",
413 Arity::Exact(2),
414 check_numeric,
415 ReturnRule::FromArgs(return_numeric),
416 ),
417 sig(
418 "power",
419 Arity::Exact(2),
420 check_numeric,
421 ReturnRule::Fixed(ResolvedType::Double),
422 ),
423 sig(
424 "pow",
425 Arity::Exact(2),
426 check_numeric,
427 ReturnRule::Fixed(ResolvedType::Double),
428 ),
429 sig(
430 "sqrt",
431 Arity::Exact(1),
432 check_numeric,
433 ReturnRule::Fixed(ResolvedType::Double),
434 ),
435 sig(
436 "exp",
437 Arity::Exact(1),
438 check_numeric,
439 ReturnRule::Fixed(ResolvedType::Double),
440 ),
441 sig(
442 "ln",
443 Arity::Exact(1),
444 check_numeric,
445 ReturnRule::Fixed(ResolvedType::Double),
446 ),
447 sig(
448 "log",
449 Arity::Range(1, 2),
450 check_numeric,
451 ReturnRule::Fixed(ResolvedType::Double),
452 ),
453 sig(
454 "log10",
455 Arity::Exact(1),
456 check_numeric,
457 ReturnRule::Fixed(ResolvedType::Double),
458 ),
459 sig_meta(
460 "random",
461 Arity::Exact(0),
462 check_numeric,
463 ReturnRule::Fixed(ResolvedType::Double),
464 RANDOM_META,
465 ),
466 sig(
467 "sin",
468 Arity::Exact(1),
469 check_numeric,
470 ReturnRule::Fixed(ResolvedType::Double),
471 ),
472 sig(
473 "cos",
474 Arity::Exact(1),
475 check_numeric,
476 ReturnRule::Fixed(ResolvedType::Double),
477 ),
478 sig(
479 "tan",
480 Arity::Exact(1),
481 check_numeric,
482 ReturnRule::Fixed(ResolvedType::Double),
483 ),
484 sig(
485 "asin",
486 Arity::Exact(1),
487 check_numeric,
488 ReturnRule::Fixed(ResolvedType::Double),
489 ),
490 sig(
491 "acos",
492 Arity::Exact(1),
493 check_numeric,
494 ReturnRule::Fixed(ResolvedType::Double),
495 ),
496 sig(
497 "atan",
498 Arity::Exact(1),
499 check_numeric,
500 ReturnRule::Fixed(ResolvedType::Double),
501 ),
502 sig(
503 "atan2",
504 Arity::Exact(2),
505 check_numeric,
506 ReturnRule::Fixed(ResolvedType::Double),
507 ),
508 sig(
509 "degrees",
510 Arity::Exact(1),
511 check_numeric,
512 ReturnRule::Fixed(ResolvedType::Double),
513 ),
514 sig(
515 "radians",
516 Arity::Exact(1),
517 check_numeric,
518 ReturnRule::Fixed(ResolvedType::Double),
519 ),
520 sig(
521 "pi",
522 Arity::Exact(0),
523 check_numeric,
524 ReturnRule::Fixed(ResolvedType::Double),
525 ),
526 sig(
527 "sha256",
528 Arity::Exact(1),
529 check_text_or_blob,
530 ReturnRule::Fixed(ResolvedType::Blob),
531 ),
532 sig(
533 "md5",
534 Arity::Exact(1),
535 check_text_or_blob,
536 ReturnRule::Fixed(ResolvedType::Text),
537 ),
538 sig(
539 "simhash",
540 Arity::Exact(1),
541 check_text,
542 ReturnRule::Fixed(ResolvedType::BigInt),
543 ),
544 sig(
545 "hamming_distance",
546 Arity::Exact(2),
547 check_bigint,
548 ReturnRule::Fixed(ResolvedType::Integer),
549 ),
550 sig_meta(
551 "gen_random_uuid",
552 Arity::Exact(0),
553 check_no_args,
554 ReturnRule::Fixed(ResolvedType::Text),
555 RANDOM_META,
556 ),
557 sig_meta(
558 "uuidv7",
559 Arity::Exact(0),
560 check_no_args,
561 ReturnRule::Fixed(ResolvedType::Text),
562 RANDOM_META,
563 ),
564 sig(
565 "hex",
566 Arity::Exact(1),
567 check_text_or_blob,
568 ReturnRule::Fixed(ResolvedType::Text),
569 ),
570 sig(
571 "unhex",
572 Arity::Exact(1),
573 check_text,
574 ReturnRule::Fixed(ResolvedType::Blob),
575 ),
576 sig(
577 "encode",
578 Arity::Exact(2),
579 check_blob_text,
580 ReturnRule::Fixed(ResolvedType::Text),
581 ),
582 sig(
583 "decode",
584 Arity::Exact(2),
585 check_text,
586 ReturnRule::Fixed(ResolvedType::Blob),
587 ),
588 sig(
589 "length",
590 Arity::Exact(1),
591 check_text_or_blob,
592 ReturnRule::Fixed(ResolvedType::Integer),
593 ),
594 sig(
595 "char_length",
596 Arity::Exact(1),
597 check_text,
598 ReturnRule::Fixed(ResolvedType::Integer),
599 ),
600 sig(
601 "octet_length",
602 Arity::Exact(1),
603 check_text_or_blob,
604 ReturnRule::Fixed(ResolvedType::Integer),
605 ),
606 sig(
607 "upper",
608 Arity::Exact(1),
609 check_text,
610 ReturnRule::Fixed(ResolvedType::Text),
611 ),
612 sig(
613 "lower",
614 Arity::Exact(1),
615 check_text,
616 ReturnRule::Fixed(ResolvedType::Text),
617 ),
618 sig(
619 "initcap",
620 Arity::Exact(1),
621 check_text,
622 ReturnRule::Fixed(ResolvedType::Text),
623 ),
624 sig(
625 "substr",
626 Arity::Range(2, 3),
627 check_numeric_or_text_substr,
628 ReturnRule::Fixed(ResolvedType::Text),
629 ),
630 sig(
631 "left",
632 Arity::Exact(2),
633 check_text_numeric,
634 ReturnRule::Fixed(ResolvedType::Text),
635 ),
636 sig(
637 "right",
638 Arity::Exact(2),
639 check_text_numeric,
640 ReturnRule::Fixed(ResolvedType::Text),
641 ),
642 sig(
643 "trim",
644 Arity::Range(1, 2),
645 check_text,
646 ReturnRule::Fixed(ResolvedType::Text),
647 ),
648 sig(
649 "ltrim",
650 Arity::Range(1, 2),
651 check_text,
652 ReturnRule::Fixed(ResolvedType::Text),
653 ),
654 sig(
655 "rtrim",
656 Arity::Range(1, 2),
657 check_text,
658 ReturnRule::Fixed(ResolvedType::Text),
659 ),
660 sig(
661 "replace",
662 Arity::Exact(3),
663 check_text,
664 ReturnRule::Fixed(ResolvedType::Text),
665 ),
666 sig(
667 "instr",
668 Arity::Exact(2),
669 check_text,
670 ReturnRule::Fixed(ResolvedType::Integer),
671 ),
672 sig(
673 "strpos",
674 Arity::Exact(2),
675 check_text,
676 ReturnRule::Fixed(ResolvedType::Integer),
677 ),
678 sig(
679 "concat",
680 Arity::Variadic(0),
681 check_text,
682 ReturnRule::Fixed(ResolvedType::Text),
683 ),
684 sig(
685 "concat_ws",
686 Arity::Variadic(1),
687 check_text,
688 ReturnRule::Fixed(ResolvedType::Text),
689 ),
690 sig(
691 "repeat",
692 Arity::Exact(2),
693 check_text_numeric,
694 ReturnRule::Fixed(ResolvedType::Text),
695 ),
696 sig(
697 "reverse",
698 Arity::Exact(1),
699 check_text,
700 ReturnRule::Fixed(ResolvedType::Text),
701 ),
702 sig(
703 "lpad",
704 Arity::Range(2, 3),
705 check_pad,
706 ReturnRule::Fixed(ResolvedType::Text),
707 ),
708 sig(
709 "rpad",
710 Arity::Range(2, 3),
711 check_pad,
712 ReturnRule::Fixed(ResolvedType::Text),
713 ),
714 sig(
715 "split_part",
716 Arity::Exact(3),
717 check_text_text_numeric,
718 ReturnRule::Fixed(ResolvedType::Text),
719 ),
720 sig(
721 "regexp_replace",
722 Arity::Exact(3),
723 check_text,
724 ReturnRule::Fixed(ResolvedType::Text),
725 ),
726 sig(
727 "regexp_match",
728 Arity::Exact(2),
729 check_text,
730 ReturnRule::Fixed(ResolvedType::Text),
731 ),
732 sig(
733 "regexp_matches",
734 Arity::Range(2, 3),
735 check_text,
736 ReturnRule::Fixed(ResolvedType::Text),
737 ),
738 sig(
739 "coalesce",
740 Arity::Variadic(1),
741 check_compatible,
742 ReturnRule::FromArgs(return_first_non_null),
743 ),
744 sig(
745 "nullif",
746 Arity::Exact(2),
747 check_compatible,
748 ReturnRule::FromArgs(return_first_non_null),
749 ),
750 sig(
751 "ifnull",
752 Arity::Exact(2),
753 check_compatible,
754 ReturnRule::FromArgs(return_first_non_null),
755 ),
756 sig(
757 "iif",
758 Arity::Exact(3),
759 check_iif,
760 ReturnRule::FromArgs(return_first_non_null),
761 ),
762 sig(
763 "greatest",
764 Arity::Variadic(1),
765 check_numeric,
766 ReturnRule::FromArgs(return_numeric),
767 ),
768 sig(
769 "least",
770 Arity::Variadic(1),
771 check_numeric,
772 ReturnRule::FromArgs(return_numeric),
773 ),
774 sig(
775 "typeof",
776 Arity::Exact(1),
777 check_any,
778 ReturnRule::Fixed(ResolvedType::Text),
779 ),
780 sig(
781 "pg_typeof",
782 Arity::Exact(1),
783 check_any,
784 ReturnRule::Fixed(ResolvedType::Text),
785 ),
786 sig(
787 "quote",
788 Arity::Exact(1),
789 check_any,
790 ReturnRule::Fixed(ResolvedType::Text),
791 ),
792 sig_meta(
793 "memory_stats",
794 Arity::Exact(0),
795 check_no_args,
796 ReturnRule::Fixed(ResolvedType::Text),
797 SYSTEM_META,
798 ),
799 sig_meta(
800 "io_stats",
801 Arity::Exact(0),
802 check_no_args,
803 ReturnRule::Fixed(ResolvedType::Text),
804 SYSTEM_META,
805 ),
806 sig_meta(
807 "clear_cache",
808 Arity::Exact(0),
809 check_no_args,
810 ReturnRule::Fixed(ResolvedType::BigInt),
811 SYSTEM_SIDE_EFFECT_META,
812 ),
813];
814
815fn check_numeric_or_text_substr(args: &[TypedExpr]) -> Result<(), PlannerError> {
816 if args.is_empty() {
817 return Ok(());
818 }
819 check_text(&args[..1])?;
820 check_numeric(&args[1..])
821}
822
823fn check_text_numeric(args: &[TypedExpr]) -> Result<(), PlannerError> {
824 if !args.is_empty() {
825 check_text(&args[..1])?;
826 }
827 if args.len() >= 2 {
828 check_numeric(&args[1..2])?;
829 }
830 Ok(())
831}
832
833fn check_pad(args: &[TypedExpr]) -> Result<(), PlannerError> {
834 check_text_numeric(&args[..2])?;
835 if args.len() == 3 {
836 check_text(&args[2..3])?;
837 }
838 Ok(())
839}
840
841fn check_text_text_numeric(args: &[TypedExpr]) -> Result<(), PlannerError> {
842 check_text(&args[..2])?;
843 check_numeric(&args[2..3])
844}
845
846fn check_compatible(args: &[TypedExpr]) -> Result<(), PlannerError> {
847 let mut expected: Option<&ResolvedType> = None;
848 for arg in args {
849 if matches!(arg.resolved_type, ResolvedType::Null) {
850 continue;
851 }
852 if let Some(first) = expected {
853 if !(is_numeric(first) && is_numeric(&arg.resolved_type)) && first != &arg.resolved_type
854 {
855 return Err(PlannerError::type_mismatch(
856 first.type_name(),
857 arg.resolved_type.type_name(),
858 arg.span,
859 ));
860 }
861 } else {
862 expected = Some(&arg.resolved_type);
863 }
864 }
865 Ok(())
866}
867
868fn check_iif(args: &[TypedExpr]) -> Result<(), PlannerError> {
869 if !matches!(
870 args.first().map(|a| &a.resolved_type),
871 Some(ResolvedType::Boolean | ResolvedType::Null)
872 ) {
873 let arg = args
874 .first()
875 .expect("arity is validated before type checking");
876 return Err(PlannerError::type_mismatch(
877 "Boolean",
878 arg.resolved_type.type_name(),
879 arg.span,
880 ));
881 }
882 check_compatible(&args[1..])
883}
884
885pub fn signatures() -> &'static [ScalarSignature] {
886 SIGNATURES
887}
888
889pub fn signature(name: &str) -> Option<&'static ScalarSignature> {
890 let lower = name.to_ascii_lowercase();
891 SIGNATURES.iter().find(|sig| sig.name == lower)
892}
893
894pub fn is_numeric_type(ty: &ResolvedType) -> bool {
895 is_numeric(ty)
896}
897
898#[cfg(test)]
899mod tests {
900 use super::*;
901
902 #[test]
903 fn names_are_normalized_and_unique() {
904 let mut names: Vec<_> = signatures().iter().map(|s| s.name).collect();
905 names.sort_unstable();
906 names.dedup();
907 assert_eq!(names.len(), signatures().len());
908 assert!(
909 signatures()
910 .iter()
911 .all(|s| s.name == s.name.to_ascii_lowercase())
912 );
913 }
914
915 #[test]
916 fn random_metadata_is_volatile() {
917 let random = signature("RANDOM").unwrap();
918 assert!(!random.meta.deterministic);
919 assert!(random.meta.volatile);
920 assert!(!random.meta.foldable);
921 assert!(!random.meta.cacheable);
922 }
923
924 #[test]
925 fn scalar_names_do_not_overlap_aggregate_names() {
926 let aggregates = [
927 "count",
928 "sum",
929 "total",
930 "avg",
931 "min",
932 "max",
933 "group_concat",
934 "string_agg",
935 ];
936 assert!(aggregates.iter().all(|name| signature(name).is_none()));
937 }
938}