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 STATEMENT_STABLE_META: FnMeta = FnMeta {
90 deterministic: false,
91 volatile: false,
92 side_effecting: false,
93 foldable: false,
94 cacheable: false,
95 reorderable: true,
96};
97
98pub const SYSTEM_META: FnMeta = FnMeta {
99 deterministic: false,
100 volatile: true,
101 side_effecting: false,
102 foldable: false,
103 cacheable: false,
104 reorderable: false,
105};
106
107pub const SYSTEM_SIDE_EFFECT_META: FnMeta = FnMeta {
108 deterministic: false,
109 volatile: true,
110 side_effecting: true,
111 foldable: false,
112 cacheable: false,
113 reorderable: false,
114};
115
116#[derive(Debug, Clone)]
117pub struct ScalarSignature {
118 pub name: &'static str,
119 pub arity: Arity,
120 pub check: fn(&[TypedExpr]) -> Result<(), PlannerError>,
121 pub ret: ReturnRule,
122 pub meta: FnMeta,
123}
124
125fn is_numeric(ty: &ResolvedType) -> bool {
126 matches!(
127 ty,
128 ResolvedType::Integer
129 | ResolvedType::BigInt
130 | ResolvedType::Float
131 | ResolvedType::Double
132 | ResolvedType::Null
133 )
134}
135
136pub fn check_numeric(args: &[TypedExpr]) -> Result<(), PlannerError> {
137 for arg in args {
138 if !is_numeric(&arg.resolved_type) {
139 return Err(PlannerError::type_mismatch(
140 "Numeric",
141 arg.resolved_type.type_name(),
142 arg.span,
143 ));
144 }
145 }
146 Ok(())
147}
148
149pub fn check_text(args: &[TypedExpr]) -> Result<(), PlannerError> {
150 for arg in args {
151 if !matches!(arg.resolved_type, ResolvedType::Text | ResolvedType::Null) {
152 return Err(PlannerError::type_mismatch(
153 "Text",
154 arg.resolved_type.type_name(),
155 arg.span,
156 ));
157 }
158 }
159 Ok(())
160}
161
162pub fn check_text_or_blob(args: &[TypedExpr]) -> Result<(), PlannerError> {
163 for arg in args {
164 if !matches!(
165 arg.resolved_type,
166 ResolvedType::Text | ResolvedType::Blob | ResolvedType::Null
167 ) {
168 return Err(PlannerError::type_mismatch(
169 "Text or Blob",
170 arg.resolved_type.type_name(),
171 arg.span,
172 ));
173 }
174 }
175 Ok(())
176}
177
178fn check_timestamp(args: &[TypedExpr]) -> Result<(), PlannerError> {
179 for arg in args {
180 if !matches!(
181 arg.resolved_type,
182 ResolvedType::Timestamp | ResolvedType::Null
183 ) {
184 return Err(PlannerError::type_mismatch(
185 "Timestamp",
186 arg.resolved_type.type_name(),
187 arg.span,
188 ));
189 }
190 }
191 Ok(())
192}
193
194fn check_text_timestamp(args: &[TypedExpr]) -> Result<(), PlannerError> {
195 check_text(&args[..1])?;
196 check_timestamp(&args[1..])
197}
198
199fn check_timestamp_text(args: &[TypedExpr]) -> Result<(), PlannerError> {
200 check_timestamp(&args[..1])?;
201 check_text(&args[1..])
202}
203
204fn check_to_timestamp(args: &[TypedExpr]) -> Result<(), PlannerError> {
205 if args.len() == 1 {
206 let arg = &args[0];
207 if is_numeric(&arg.resolved_type)
208 || matches!(arg.resolved_type, ResolvedType::Text | ResolvedType::Null)
209 {
210 return Ok(());
211 }
212 return Err(PlannerError::type_mismatch(
213 "Numeric or Text",
214 arg.resolved_type.type_name(),
215 arg.span,
216 ));
217 }
218 check_text(args)
219}
220
221pub fn check_bigint(args: &[TypedExpr]) -> Result<(), PlannerError> {
222 for arg in args {
223 if !matches!(arg.resolved_type, ResolvedType::BigInt | ResolvedType::Null) {
224 return Err(PlannerError::type_mismatch(
225 "BigInt",
226 arg.resolved_type.type_name(),
227 arg.span,
228 ));
229 }
230 }
231 Ok(())
232}
233
234fn check_integer(args: &[TypedExpr]) -> Result<(), PlannerError> {
235 for arg in args {
236 if !matches!(
237 arg.resolved_type,
238 ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Null
239 ) {
240 return Err(PlannerError::type_mismatch(
241 "Integer",
242 arg.resolved_type.type_name(),
243 arg.span,
244 ));
245 }
246 }
247 Ok(())
248}
249
250pub fn check_blob_text(args: &[TypedExpr]) -> Result<(), PlannerError> {
251 if let Some(first) = args.first()
252 && !matches!(first.resolved_type, ResolvedType::Blob | ResolvedType::Null)
253 {
254 return Err(PlannerError::type_mismatch(
255 "Blob",
256 first.resolved_type.type_name(),
257 first.span,
258 ));
259 }
260 check_text(&args[1..])
261}
262
263pub fn check_any(args: &[TypedExpr]) -> Result<(), PlannerError> {
264 if args.is_empty() {
265 return Err(PlannerError::invalid_expression(
266 "at least one argument is required",
267 ));
268 }
269 Ok(())
270}
271
272pub fn check_no_args(_args: &[TypedExpr]) -> Result<(), PlannerError> {
273 Ok(())
274}
275
276fn check_vector_one(args: &[TypedExpr]) -> Result<(), PlannerError> {
277 match args.first().map(|arg| &arg.resolved_type) {
278 Some(ResolvedType::Vector { .. } | ResolvedType::Null) => Ok(()),
279 Some(ty) => Err(PlannerError::type_mismatch(
280 "Vector",
281 ty.type_name(),
282 args[0].span,
283 )),
284 None => Ok(()),
285 }
286}
287
288fn check_vector_triplet(args: &[TypedExpr]) -> Result<(), PlannerError> {
289 let first = match &args[0].resolved_type {
290 ResolvedType::Vector { dimension, .. } => *dimension,
291 ty => {
292 return Err(PlannerError::type_mismatch(
293 "Vector",
294 ty.type_name(),
295 args[0].span,
296 ));
297 }
298 };
299 let second = match &args[1].resolved_type {
300 ResolvedType::Vector { dimension, .. } => *dimension,
301 ty => {
302 return Err(PlannerError::type_mismatch(
303 "Vector",
304 ty.type_name(),
305 args[1].span,
306 ));
307 }
308 };
309 if first != second {
310 return Err(PlannerError::vector_dimension_mismatch(
311 first,
312 second,
313 args[1].span,
314 ));
315 }
316 match &args[2].resolved_type {
317 ResolvedType::Text => {
318 if let TypedExprKind::Literal(Literal::String(value)) = &args[2].kind
319 && !matches!(
320 value.to_ascii_lowercase().as_str(),
321 "cosine" | "l2" | "inner"
322 )
323 {
324 return Err(PlannerError::invalid_metric(value, args[2].span));
325 }
326 Ok(())
327 }
328 ty => Err(PlannerError::type_mismatch(
329 "Text (metric)",
330 ty.type_name(),
331 args[2].span,
332 )),
333 }
334}
335
336fn numeric_return(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
337 let mut result = ResolvedType::Null;
338 for ty in types {
339 if matches!(ty, ResolvedType::Null) {
340 continue;
341 }
342 if !is_numeric(ty) {
343 return Err(PlannerError::type_mismatch(
344 "Numeric",
345 ty.type_name(),
346 Span::default(),
347 ));
348 }
349 result = match (&result, ty) {
350 (ResolvedType::Null, _) => ty.clone(),
351 (ResolvedType::Double, _) | (_, ResolvedType::Double) => ResolvedType::Double,
352 (ResolvedType::Float, ResolvedType::BigInt)
353 | (ResolvedType::BigInt, ResolvedType::Float) => ResolvedType::Double,
354 (ResolvedType::Float, _) | (_, ResolvedType::Float) => ResolvedType::Float,
355 (ResolvedType::BigInt, _) | (_, ResolvedType::BigInt) => ResolvedType::BigInt,
356 _ => ResolvedType::Integer,
357 };
358 }
359 Ok(result)
360}
361
362fn return_arg0(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
363 Ok(types.first().cloned().unwrap_or(ResolvedType::Null))
364}
365
366fn return_first_non_null(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
367 Ok(types
368 .iter()
369 .find(|ty| !matches!(ty, ResolvedType::Null))
370 .cloned()
371 .unwrap_or(ResolvedType::Null))
372}
373
374fn return_numeric(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
375 numeric_return(types)
376}
377
378fn return_arg0_numeric(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
379 return_arg0(types)
380}
381
382const fn sig(
383 name: &'static str,
384 arity: Arity,
385 check: fn(&[TypedExpr]) -> Result<(), PlannerError>,
386 ret: ReturnRule,
387) -> ScalarSignature {
388 ScalarSignature {
389 name,
390 arity,
391 check,
392 ret,
393 meta: PURE_META,
394 }
395}
396
397const fn sig_meta(
398 name: &'static str,
399 arity: Arity,
400 check: fn(&[TypedExpr]) -> Result<(), PlannerError>,
401 ret: ReturnRule,
402 meta: FnMeta,
403) -> ScalarSignature {
404 ScalarSignature {
405 name,
406 arity,
407 check,
408 ret,
409 meta,
410 }
411}
412
413static SIGNATURES: &[ScalarSignature] = &[
414 sig(
415 "vector_similarity",
416 Arity::Exact(3),
417 check_vector_triplet,
418 ReturnRule::Fixed(ResolvedType::Double),
419 ),
420 sig(
421 "vector_distance",
422 Arity::Exact(3),
423 check_vector_triplet,
424 ReturnRule::Fixed(ResolvedType::Double),
425 ),
426 sig(
427 "vector_dims",
428 Arity::Exact(1),
429 check_vector_one,
430 ReturnRule::Fixed(ResolvedType::Integer),
431 ),
432 sig(
433 "vector_norm",
434 Arity::Exact(1),
435 check_vector_one,
436 ReturnRule::Fixed(ResolvedType::Double),
437 ),
438 sig(
439 "abs",
440 Arity::Exact(1),
441 check_numeric,
442 ReturnRule::FromArgs(return_arg0_numeric),
443 ),
444 sig(
445 "sign",
446 Arity::Exact(1),
447 check_numeric,
448 ReturnRule::Fixed(ResolvedType::Integer),
449 ),
450 sig(
451 "round",
452 Arity::Range(1, 2),
453 check_numeric,
454 ReturnRule::FromArgs(return_arg0_numeric),
455 ),
456 sig(
457 "floor",
458 Arity::Exact(1),
459 check_numeric,
460 ReturnRule::FromArgs(return_arg0_numeric),
461 ),
462 sig(
463 "ceil",
464 Arity::Exact(1),
465 check_numeric,
466 ReturnRule::FromArgs(return_arg0_numeric),
467 ),
468 sig(
469 "ceiling",
470 Arity::Exact(1),
471 check_numeric,
472 ReturnRule::FromArgs(return_arg0_numeric),
473 ),
474 sig(
475 "trunc",
476 Arity::Range(1, 2),
477 check_numeric,
478 ReturnRule::FromArgs(return_arg0_numeric),
479 ),
480 sig(
481 "mod",
482 Arity::Exact(2),
483 check_numeric,
484 ReturnRule::FromArgs(return_numeric),
485 ),
486 sig(
487 "power",
488 Arity::Exact(2),
489 check_numeric,
490 ReturnRule::Fixed(ResolvedType::Double),
491 ),
492 sig(
493 "pow",
494 Arity::Exact(2),
495 check_numeric,
496 ReturnRule::Fixed(ResolvedType::Double),
497 ),
498 sig(
499 "sqrt",
500 Arity::Exact(1),
501 check_numeric,
502 ReturnRule::Fixed(ResolvedType::Double),
503 ),
504 sig(
505 "exp",
506 Arity::Exact(1),
507 check_numeric,
508 ReturnRule::Fixed(ResolvedType::Double),
509 ),
510 sig(
511 "ln",
512 Arity::Exact(1),
513 check_numeric,
514 ReturnRule::Fixed(ResolvedType::Double),
515 ),
516 sig(
517 "log",
518 Arity::Range(1, 2),
519 check_numeric,
520 ReturnRule::Fixed(ResolvedType::Double),
521 ),
522 sig(
523 "log10",
524 Arity::Exact(1),
525 check_numeric,
526 ReturnRule::Fixed(ResolvedType::Double),
527 ),
528 sig(
529 "cbrt",
530 Arity::Exact(1),
531 check_numeric,
532 ReturnRule::Fixed(ResolvedType::Double),
533 ),
534 sig(
535 "cot",
536 Arity::Exact(1),
537 check_numeric,
538 ReturnRule::Fixed(ResolvedType::Double),
539 ),
540 sig(
541 "log2",
542 Arity::Exact(1),
543 check_numeric,
544 ReturnRule::Fixed(ResolvedType::Double),
545 ),
546 sig(
547 "acosh",
548 Arity::Exact(1),
549 check_numeric,
550 ReturnRule::Fixed(ResolvedType::Double),
551 ),
552 sig(
553 "asinh",
554 Arity::Exact(1),
555 check_numeric,
556 ReturnRule::Fixed(ResolvedType::Double),
557 ),
558 sig(
559 "atanh",
560 Arity::Exact(1),
561 check_numeric,
562 ReturnRule::Fixed(ResolvedType::Double),
563 ),
564 sig(
565 "cosh",
566 Arity::Exact(1),
567 check_numeric,
568 ReturnRule::Fixed(ResolvedType::Double),
569 ),
570 sig(
571 "sinh",
572 Arity::Exact(1),
573 check_numeric,
574 ReturnRule::Fixed(ResolvedType::Double),
575 ),
576 sig(
577 "tanh",
578 Arity::Exact(1),
579 check_numeric,
580 ReturnRule::Fixed(ResolvedType::Double),
581 ),
582 sig(
583 "isnan",
584 Arity::Exact(1),
585 check_numeric,
586 ReturnRule::Fixed(ResolvedType::Boolean),
587 ),
588 sig_meta(
589 "random",
590 Arity::Exact(0),
591 check_numeric,
592 ReturnRule::Fixed(ResolvedType::Double),
593 RANDOM_META,
594 ),
595 sig_meta(
596 "now",
597 Arity::Range(0, 1),
598 check_numeric,
599 ReturnRule::Fixed(ResolvedType::Timestamp),
600 STATEMENT_STABLE_META,
601 ),
602 sig_meta(
603 "current_timestamp",
604 Arity::Range(0, 1),
605 check_numeric,
606 ReturnRule::Fixed(ResolvedType::Timestamp),
607 STATEMENT_STABLE_META,
608 ),
609 sig(
610 "extract",
611 Arity::Exact(2),
612 check_text_timestamp,
613 ReturnRule::Fixed(ResolvedType::Double),
614 ),
615 sig(
616 "date_part",
617 Arity::Exact(2),
618 check_text_timestamp,
619 ReturnRule::Fixed(ResolvedType::Double),
620 ),
621 sig(
622 "date_trunc",
623 Arity::Exact(2),
624 check_text_timestamp,
625 ReturnRule::Fixed(ResolvedType::Timestamp),
626 ),
627 sig(
628 "to_char",
629 Arity::Exact(2),
630 check_timestamp_text,
631 ReturnRule::Fixed(ResolvedType::Text),
632 ),
633 sig(
634 "to_timestamp",
635 Arity::Range(1, 2),
636 check_to_timestamp,
637 ReturnRule::Fixed(ResolvedType::Timestamp),
638 ),
639 sig(
640 "strftime",
641 Arity::Exact(2),
642 check_text_timestamp,
643 ReturnRule::Fixed(ResolvedType::Text),
644 ),
645 sig(
646 "julianday",
647 Arity::Exact(1),
648 check_timestamp,
649 ReturnRule::Fixed(ResolvedType::Double),
650 ),
651 sig(
652 "unixepoch",
653 Arity::Exact(1),
654 check_timestamp,
655 ReturnRule::Fixed(ResolvedType::BigInt),
656 ),
657 sig(
658 "sin",
659 Arity::Exact(1),
660 check_numeric,
661 ReturnRule::Fixed(ResolvedType::Double),
662 ),
663 sig(
664 "cos",
665 Arity::Exact(1),
666 check_numeric,
667 ReturnRule::Fixed(ResolvedType::Double),
668 ),
669 sig(
670 "tan",
671 Arity::Exact(1),
672 check_numeric,
673 ReturnRule::Fixed(ResolvedType::Double),
674 ),
675 sig(
676 "asin",
677 Arity::Exact(1),
678 check_numeric,
679 ReturnRule::Fixed(ResolvedType::Double),
680 ),
681 sig(
682 "acos",
683 Arity::Exact(1),
684 check_numeric,
685 ReturnRule::Fixed(ResolvedType::Double),
686 ),
687 sig(
688 "atan",
689 Arity::Exact(1),
690 check_numeric,
691 ReturnRule::Fixed(ResolvedType::Double),
692 ),
693 sig(
694 "atan2",
695 Arity::Exact(2),
696 check_numeric,
697 ReturnRule::Fixed(ResolvedType::Double),
698 ),
699 sig(
700 "degrees",
701 Arity::Exact(1),
702 check_numeric,
703 ReturnRule::Fixed(ResolvedType::Double),
704 ),
705 sig(
706 "radians",
707 Arity::Exact(1),
708 check_numeric,
709 ReturnRule::Fixed(ResolvedType::Double),
710 ),
711 sig(
712 "pi",
713 Arity::Exact(0),
714 check_numeric,
715 ReturnRule::Fixed(ResolvedType::Double),
716 ),
717 sig(
718 "sha256",
719 Arity::Exact(1),
720 check_text_or_blob,
721 ReturnRule::Fixed(ResolvedType::Blob),
722 ),
723 sig(
724 "md5",
725 Arity::Exact(1),
726 check_text_or_blob,
727 ReturnRule::Fixed(ResolvedType::Text),
728 ),
729 sig(
730 "simhash",
731 Arity::Exact(1),
732 check_text,
733 ReturnRule::Fixed(ResolvedType::BigInt),
734 ),
735 sig(
736 "hamming_distance",
737 Arity::Exact(2),
738 check_bigint,
739 ReturnRule::Fixed(ResolvedType::Integer),
740 ),
741 sig_meta(
742 "gen_random_uuid",
743 Arity::Exact(0),
744 check_no_args,
745 ReturnRule::Fixed(ResolvedType::Text),
746 RANDOM_META,
747 ),
748 sig_meta(
749 "uuidv7",
750 Arity::Exact(0),
751 check_no_args,
752 ReturnRule::Fixed(ResolvedType::Text),
753 RANDOM_META,
754 ),
755 sig(
756 "hex",
757 Arity::Exact(1),
758 check_text_or_blob,
759 ReturnRule::Fixed(ResolvedType::Text),
760 ),
761 sig(
762 "unhex",
763 Arity::Exact(1),
764 check_text,
765 ReturnRule::Fixed(ResolvedType::Blob),
766 ),
767 sig(
768 "encode",
769 Arity::Exact(2),
770 check_blob_text,
771 ReturnRule::Fixed(ResolvedType::Text),
772 ),
773 sig(
774 "decode",
775 Arity::Exact(2),
776 check_text,
777 ReturnRule::Fixed(ResolvedType::Blob),
778 ),
779 sig(
780 "length",
781 Arity::Exact(1),
782 check_text_or_blob,
783 ReturnRule::Fixed(ResolvedType::Integer),
784 ),
785 sig(
786 "char_length",
787 Arity::Exact(1),
788 check_text,
789 ReturnRule::Fixed(ResolvedType::Integer),
790 ),
791 sig(
792 "octet_length",
793 Arity::Exact(1),
794 check_text_or_blob,
795 ReturnRule::Fixed(ResolvedType::Integer),
796 ),
797 sig(
798 "ascii",
799 Arity::Exact(1),
800 check_text,
801 ReturnRule::Fixed(ResolvedType::Integer),
802 ),
803 sig(
804 "chr",
805 Arity::Exact(1),
806 check_integer,
807 ReturnRule::Fixed(ResolvedType::Text),
808 ),
809 sig(
810 "bit_length",
811 Arity::Exact(1),
812 check_text_or_blob,
813 ReturnRule::Fixed(ResolvedType::Integer),
814 ),
815 sig(
816 "starts_with",
817 Arity::Exact(2),
818 check_text,
819 ReturnRule::Fixed(ResolvedType::Boolean),
820 ),
821 sig(
822 "ends_with",
823 Arity::Exact(2),
824 check_text,
825 ReturnRule::Fixed(ResolvedType::Boolean),
826 ),
827 sig(
828 "translate",
829 Arity::Exact(3),
830 check_text,
831 ReturnRule::Fixed(ResolvedType::Text),
832 ),
833 sig(
834 "levenshtein",
835 Arity::Exact(2),
836 check_text,
837 ReturnRule::Fixed(ResolvedType::Integer),
838 ),
839 sig(
840 "upper",
841 Arity::Exact(1),
842 check_text,
843 ReturnRule::Fixed(ResolvedType::Text),
844 ),
845 sig(
846 "lower",
847 Arity::Exact(1),
848 check_text,
849 ReturnRule::Fixed(ResolvedType::Text),
850 ),
851 sig(
852 "initcap",
853 Arity::Exact(1),
854 check_text,
855 ReturnRule::Fixed(ResolvedType::Text),
856 ),
857 sig(
858 "substr",
859 Arity::Range(2, 3),
860 check_numeric_or_text_substr,
861 ReturnRule::Fixed(ResolvedType::Text),
862 ),
863 sig(
864 "left",
865 Arity::Exact(2),
866 check_text_numeric,
867 ReturnRule::Fixed(ResolvedType::Text),
868 ),
869 sig(
870 "right",
871 Arity::Exact(2),
872 check_text_numeric,
873 ReturnRule::Fixed(ResolvedType::Text),
874 ),
875 sig(
876 "trim",
877 Arity::Range(1, 2),
878 check_text,
879 ReturnRule::Fixed(ResolvedType::Text),
880 ),
881 sig(
882 "ltrim",
883 Arity::Range(1, 2),
884 check_text,
885 ReturnRule::Fixed(ResolvedType::Text),
886 ),
887 sig(
888 "rtrim",
889 Arity::Range(1, 2),
890 check_text,
891 ReturnRule::Fixed(ResolvedType::Text),
892 ),
893 sig(
894 "replace",
895 Arity::Exact(3),
896 check_text,
897 ReturnRule::Fixed(ResolvedType::Text),
898 ),
899 sig(
900 "instr",
901 Arity::Exact(2),
902 check_text,
903 ReturnRule::Fixed(ResolvedType::Integer),
904 ),
905 sig(
906 "strpos",
907 Arity::Exact(2),
908 check_text,
909 ReturnRule::Fixed(ResolvedType::Integer),
910 ),
911 sig(
912 "concat",
913 Arity::Variadic(0),
914 check_text,
915 ReturnRule::Fixed(ResolvedType::Text),
916 ),
917 sig(
918 "concat_ws",
919 Arity::Variadic(1),
920 check_text,
921 ReturnRule::Fixed(ResolvedType::Text),
922 ),
923 sig(
924 "repeat",
925 Arity::Exact(2),
926 check_text_numeric,
927 ReturnRule::Fixed(ResolvedType::Text),
928 ),
929 sig(
930 "reverse",
931 Arity::Exact(1),
932 check_text,
933 ReturnRule::Fixed(ResolvedType::Text),
934 ),
935 sig(
936 "lpad",
937 Arity::Range(2, 3),
938 check_pad,
939 ReturnRule::Fixed(ResolvedType::Text),
940 ),
941 sig(
942 "rpad",
943 Arity::Range(2, 3),
944 check_pad,
945 ReturnRule::Fixed(ResolvedType::Text),
946 ),
947 sig(
948 "split_part",
949 Arity::Exact(3),
950 check_text_text_numeric,
951 ReturnRule::Fixed(ResolvedType::Text),
952 ),
953 sig(
954 "regexp_replace",
955 Arity::Exact(3),
956 check_text,
957 ReturnRule::Fixed(ResolvedType::Text),
958 ),
959 sig(
960 "regexp_match",
961 Arity::Exact(2),
962 check_text,
963 ReturnRule::Fixed(ResolvedType::Text),
964 ),
965 sig(
966 "regexp_matches",
967 Arity::Range(2, 3),
968 check_text,
969 ReturnRule::Fixed(ResolvedType::Text),
970 ),
971 sig(
972 "regexp_like",
973 Arity::Range(2, 3),
974 check_text,
975 ReturnRule::Fixed(ResolvedType::Boolean),
976 ),
977 sig(
978 "coalesce",
979 Arity::Variadic(1),
980 check_compatible,
981 ReturnRule::FromArgs(return_first_non_null),
982 ),
983 sig(
984 "nullif",
985 Arity::Exact(2),
986 check_compatible,
987 ReturnRule::FromArgs(return_first_non_null),
988 ),
989 sig(
990 "ifnull",
991 Arity::Exact(2),
992 check_compatible,
993 ReturnRule::FromArgs(return_first_non_null),
994 ),
995 sig(
996 "iif",
997 Arity::Exact(3),
998 check_iif,
999 ReturnRule::FromArgs(return_first_non_null),
1000 ),
1001 sig(
1002 "greatest",
1003 Arity::Variadic(1),
1004 check_numeric,
1005 ReturnRule::FromArgs(return_numeric),
1006 ),
1007 sig(
1008 "least",
1009 Arity::Variadic(1),
1010 check_numeric,
1011 ReturnRule::FromArgs(return_numeric),
1012 ),
1013 sig(
1014 "typeof",
1015 Arity::Exact(1),
1016 check_any,
1017 ReturnRule::Fixed(ResolvedType::Text),
1018 ),
1019 sig(
1020 "pg_typeof",
1021 Arity::Exact(1),
1022 check_any,
1023 ReturnRule::Fixed(ResolvedType::Text),
1024 ),
1025 sig(
1026 "quote",
1027 Arity::Exact(1),
1028 check_any,
1029 ReturnRule::Fixed(ResolvedType::Text),
1030 ),
1031 sig_meta(
1032 "memory_stats",
1033 Arity::Exact(0),
1034 check_no_args,
1035 ReturnRule::Fixed(ResolvedType::Text),
1036 SYSTEM_META,
1037 ),
1038 sig_meta(
1039 "io_stats",
1040 Arity::Exact(0),
1041 check_no_args,
1042 ReturnRule::Fixed(ResolvedType::Text),
1043 SYSTEM_META,
1044 ),
1045 sig_meta(
1046 "clear_cache",
1047 Arity::Exact(0),
1048 check_no_args,
1049 ReturnRule::Fixed(ResolvedType::BigInt),
1050 SYSTEM_SIDE_EFFECT_META,
1051 ),
1052];
1053
1054fn check_numeric_or_text_substr(args: &[TypedExpr]) -> Result<(), PlannerError> {
1055 if args.is_empty() {
1056 return Ok(());
1057 }
1058 check_text(&args[..1])?;
1059 check_numeric(&args[1..])
1060}
1061
1062fn check_text_numeric(args: &[TypedExpr]) -> Result<(), PlannerError> {
1063 if !args.is_empty() {
1064 check_text(&args[..1])?;
1065 }
1066 if args.len() >= 2 {
1067 check_numeric(&args[1..2])?;
1068 }
1069 Ok(())
1070}
1071
1072fn check_pad(args: &[TypedExpr]) -> Result<(), PlannerError> {
1073 check_text_numeric(&args[..2])?;
1074 if args.len() == 3 {
1075 check_text(&args[2..3])?;
1076 }
1077 Ok(())
1078}
1079
1080fn check_text_text_numeric(args: &[TypedExpr]) -> Result<(), PlannerError> {
1081 check_text(&args[..2])?;
1082 check_numeric(&args[2..3])
1083}
1084
1085fn check_compatible(args: &[TypedExpr]) -> Result<(), PlannerError> {
1086 let mut expected: Option<&ResolvedType> = None;
1087 for arg in args {
1088 if matches!(arg.resolved_type, ResolvedType::Null) {
1089 continue;
1090 }
1091 if let Some(first) = expected {
1092 if !(is_numeric(first) && is_numeric(&arg.resolved_type)) && first != &arg.resolved_type
1093 {
1094 return Err(PlannerError::type_mismatch(
1095 first.type_name(),
1096 arg.resolved_type.type_name(),
1097 arg.span,
1098 ));
1099 }
1100 } else {
1101 expected = Some(&arg.resolved_type);
1102 }
1103 }
1104 Ok(())
1105}
1106
1107fn check_iif(args: &[TypedExpr]) -> Result<(), PlannerError> {
1108 if !matches!(
1109 args.first().map(|a| &a.resolved_type),
1110 Some(ResolvedType::Boolean | ResolvedType::Null)
1111 ) {
1112 let arg = args
1113 .first()
1114 .expect("arity is validated before type checking");
1115 return Err(PlannerError::type_mismatch(
1116 "Boolean",
1117 arg.resolved_type.type_name(),
1118 arg.span,
1119 ));
1120 }
1121 check_compatible(&args[1..])
1122}
1123
1124pub fn signatures() -> &'static [ScalarSignature] {
1125 SIGNATURES
1126}
1127
1128pub fn signature(name: &str) -> Option<&'static ScalarSignature> {
1129 let lower = name.to_ascii_lowercase();
1130 SIGNATURES.iter().find(|sig| sig.name == lower)
1131}
1132
1133pub fn is_numeric_type(ty: &ResolvedType) -> bool {
1134 is_numeric(ty)
1135}
1136
1137#[cfg(test)]
1138mod tests {
1139 use super::*;
1140
1141 #[test]
1142 fn names_are_normalized_and_unique() {
1143 let mut names: Vec<_> = signatures().iter().map(|s| s.name).collect();
1144 names.sort_unstable();
1145 names.dedup();
1146 assert_eq!(names.len(), signatures().len());
1147 assert!(
1148 signatures()
1149 .iter()
1150 .all(|s| s.name == s.name.to_ascii_lowercase())
1151 );
1152 }
1153
1154 #[test]
1155 fn random_metadata_is_volatile() {
1156 let random = signature("RANDOM").unwrap();
1157 assert!(!random.meta.deterministic);
1158 assert!(random.meta.volatile);
1159 assert!(!random.meta.foldable);
1160 assert!(!random.meta.cacheable);
1161 }
1162
1163 #[test]
1164 fn scalar_names_do_not_overlap_aggregate_names() {
1165 let aggregates = [
1166 "count",
1167 "sum",
1168 "total",
1169 "avg",
1170 "min",
1171 "max",
1172 "group_concat",
1173 "string_agg",
1174 ];
1175 assert!(aggregates.iter().all(|name| signature(name).is_none()));
1176 }
1177}