Skip to main content

alopex_sql/scalar/
mod.rs

1//! Shared scalar-function signatures.
2//!
3//! This module is the single source of truth for scalar names, arity, type
4//! contracts, return rules, and optimizer metadata. Evaluation functions are
5//! attached by `executor::evaluator::registry`.
6//!
7//! The v0.7.4 catalog includes the v0.5.3 scalar set, v0.5.1 hash/encoding
8//! functions, and v0.5.2 system functions. `memory_stats`, `io_stats`, and
9//! `clear_cache` are resolved by the executor because they require store
10//! access; their signatures still participate in planner validation here.
11
12use 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    FromTypedArgs(fn(&[TypedExpr]) -> Result<ResolvedType, PlannerError>),
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct FnMeta {
63    pub deterministic: bool,
64    pub volatile: bool,
65    pub side_effecting: bool,
66    pub foldable: bool,
67    pub cacheable: bool,
68    pub reorderable: bool,
69}
70
71pub const PURE_META: FnMeta = FnMeta {
72    deterministic: true,
73    volatile: false,
74    side_effecting: false,
75    foldable: true,
76    cacheable: true,
77    reorderable: true,
78};
79
80pub const RANDOM_META: FnMeta = FnMeta {
81    deterministic: false,
82    volatile: true,
83    side_effecting: false,
84    foldable: false,
85    cacheable: false,
86    reorderable: true,
87};
88
89/// Metadata for values that vary between statements but are fixed within one.
90pub const STATEMENT_STABLE_META: FnMeta = FnMeta {
91    deterministic: false,
92    volatile: false,
93    side_effecting: false,
94    foldable: false,
95    cacheable: false,
96    reorderable: true,
97};
98
99pub const SYSTEM_META: FnMeta = FnMeta {
100    deterministic: false,
101    volatile: true,
102    side_effecting: false,
103    foldable: false,
104    cacheable: false,
105    reorderable: false,
106};
107
108pub const SYSTEM_SIDE_EFFECT_META: FnMeta = FnMeta {
109    deterministic: false,
110    volatile: true,
111    side_effecting: true,
112    foldable: false,
113    cacheable: false,
114    reorderable: false,
115};
116
117#[derive(Debug, Clone)]
118pub struct ScalarSignature {
119    pub name: &'static str,
120    pub arity: Arity,
121    pub check: fn(&[TypedExpr]) -> Result<(), PlannerError>,
122    pub ret: ReturnRule,
123    pub meta: FnMeta,
124}
125
126fn is_numeric(ty: &ResolvedType) -> bool {
127    matches!(
128        ty,
129        ResolvedType::Integer
130            | ResolvedType::BigInt
131            | ResolvedType::Float
132            | ResolvedType::Double
133            | ResolvedType::Null
134    )
135}
136
137pub fn check_numeric(args: &[TypedExpr]) -> Result<(), PlannerError> {
138    for arg in args {
139        if !is_numeric(&arg.resolved_type) {
140            return Err(PlannerError::type_mismatch(
141                "Numeric",
142                arg.resolved_type.type_name(),
143                arg.span,
144            ));
145        }
146    }
147    Ok(())
148}
149
150pub fn check_text(args: &[TypedExpr]) -> Result<(), PlannerError> {
151    for arg in args {
152        if !matches!(arg.resolved_type, ResolvedType::Text | ResolvedType::Null) {
153            return Err(PlannerError::type_mismatch(
154                "Text",
155                arg.resolved_type.type_name(),
156                arg.span,
157            ));
158        }
159    }
160    Ok(())
161}
162
163fn check_json_object(args: &[TypedExpr]) -> Result<(), PlannerError> {
164    if !args.len().is_multiple_of(2) {
165        return Err(PlannerError::invalid_expression(
166            "JSON_OBJECT expects label/value pairs".to_string(),
167        ));
168    }
169    for label in args.iter().step_by(2) {
170        if !matches!(label.resolved_type, ResolvedType::Text | ResolvedType::Null) {
171            return Err(PlannerError::type_mismatch(
172                "Text",
173                label.resolved_type.type_name(),
174                label.span,
175            ));
176        }
177    }
178    Ok(())
179}
180
181fn check_json_input(args: &[TypedExpr]) -> Result<(), PlannerError> {
182    for arg in args {
183        if !matches!(
184            arg.resolved_type,
185            ResolvedType::Text | ResolvedType::Json | ResolvedType::Null
186        ) {
187            return Err(PlannerError::type_mismatch(
188                "JSON or Text",
189                arg.resolved_type.type_name(),
190                arg.span,
191            ));
192        }
193    }
194    Ok(())
195}
196
197fn common_nested_type(
198    types: impl IntoIterator<Item = ResolvedType>,
199) -> Result<ResolvedType, PlannerError> {
200    let mut common = ResolvedType::Null;
201    for data_type in types {
202        common = match (&common, &data_type) {
203            (ResolvedType::Null, _) => data_type,
204            (_, ResolvedType::Null) => common,
205            (left, right) if left == right => common,
206            (ResolvedType::Integer, ResolvedType::BigInt)
207            | (ResolvedType::BigInt, ResolvedType::Integer) => ResolvedType::BigInt,
208            (
209                ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Float,
210                ResolvedType::Double,
211            )
212            | (
213                ResolvedType::Double,
214                ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Float,
215            ) => ResolvedType::Double,
216            (ResolvedType::Integer, ResolvedType::Float)
217            | (ResolvedType::Float, ResolvedType::Integer) => ResolvedType::Float,
218            (ResolvedType::BigInt, ResolvedType::Float)
219            | (ResolvedType::Float, ResolvedType::BigInt) => ResolvedType::Double,
220            _ => {
221                return Err(PlannerError::invalid_expression(
222                    "nested values require a common element type",
223                ));
224            }
225        };
226    }
227    Ok(common)
228}
229
230fn return_array_value(args: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
231    Ok(ResolvedType::Array(Box::new(common_nested_type(
232        args.to_vec(),
233    )?)))
234}
235
236fn return_integer_array(_args: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
237    Ok(ResolvedType::Array(Box::new(ResolvedType::Integer)))
238}
239
240fn return_text_array(_args: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
241    Ok(ResolvedType::Array(Box::new(ResolvedType::Text)))
242}
243
244fn array_element(data_type: &ResolvedType) -> Result<ResolvedType, PlannerError> {
245    match data_type {
246        ResolvedType::Array(element) => Ok((**element).clone()),
247        ResolvedType::Null => Ok(ResolvedType::Null),
248        other => Err(PlannerError::invalid_expression(format!(
249            "expected ARRAY, found {other}"
250        ))),
251    }
252}
253
254fn check_array_first(args: &[TypedExpr]) -> Result<(), PlannerError> {
255    array_element(&args[0].resolved_type).map(|_| ())
256}
257
258fn check_array_append(args: &[TypedExpr]) -> Result<(), PlannerError> {
259    common_nested_type([
260        array_element(&args[0].resolved_type)?,
261        args[1].resolved_type.clone(),
262    ])
263    .map(|_| ())
264}
265
266fn check_array_prepend(args: &[TypedExpr]) -> Result<(), PlannerError> {
267    common_nested_type([
268        args[0].resolved_type.clone(),
269        array_element(&args[1].resolved_type)?,
270    ])
271    .map(|_| ())
272}
273
274fn check_array_replace(args: &[TypedExpr]) -> Result<(), PlannerError> {
275    common_nested_type([
276        array_element(&args[0].resolved_type)?,
277        args[1].resolved_type.clone(),
278        args[2].resolved_type.clone(),
279    ])
280    .map(|_| ())
281}
282
283fn check_array_slice(args: &[TypedExpr]) -> Result<(), PlannerError> {
284    check_array_first(args)?;
285    for arg in &args[1..] {
286        if !matches!(
287            arg.resolved_type,
288            ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Null
289        ) {
290            return Err(PlannerError::invalid_expression(
291                "ARRAY slice bounds must be INTEGER",
292            ));
293        }
294    }
295    Ok(())
296}
297
298fn check_array_pair(args: &[TypedExpr]) -> Result<(), PlannerError> {
299    let left = array_element(&args[0].resolved_type)?;
300    let right = array_element(&args[1].resolved_type)?;
301    common_nested_type([left, right]).map(|_| ())
302}
303
304fn return_array_first(args: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
305    Ok(ResolvedType::Array(Box::new(array_element(&args[0])?)))
306}
307
308fn return_array_append(args: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
309    Ok(ResolvedType::Array(Box::new(common_nested_type([
310        array_element(&args[0])?,
311        args[1].clone(),
312    ])?)))
313}
314
315fn return_array_prepend(args: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
316    Ok(ResolvedType::Array(Box::new(common_nested_type([
317        args[0].clone(),
318        array_element(&args[1])?,
319    ])?)))
320}
321
322fn return_array_cat(args: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
323    Ok(ResolvedType::Array(Box::new(common_nested_type([
324        array_element(&args[0])?,
325        array_element(&args[1])?,
326    ])?)))
327}
328
329fn check_map(args: &[TypedExpr]) -> Result<(), PlannerError> {
330    array_element(&args[0].resolved_type)?;
331    array_element(&args[1].resolved_type)?;
332    Ok(())
333}
334
335fn check_array_text_tail(args: &[TypedExpr]) -> Result<(), PlannerError> {
336    check_array_first(args)?;
337    check_text(&args[1..])
338}
339
340fn return_map(args: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
341    Ok(ResolvedType::Map {
342        key: Box::new(array_element(&args[0])?),
343        value: Box::new(array_element(&args[1])?),
344    })
345}
346
347fn check_struct_pack(args: &[TypedExpr]) -> Result<(), PlannerError> {
348    if !args.len().is_multiple_of(2) {
349        return Err(PlannerError::invalid_expression(
350            "STRUCT_PACK expects name/value pairs",
351        ));
352    }
353    for name in args.iter().step_by(2) {
354        if !matches!(name.kind, TypedExprKind::Literal(Literal::String(_))) {
355            return Err(PlannerError::invalid_expression(
356                "STRUCT_PACK field names must be string literals",
357            ));
358        }
359    }
360    Ok(())
361}
362
363fn return_struct(args: &[TypedExpr]) -> Result<ResolvedType, PlannerError> {
364    Ok(ResolvedType::Struct(
365        args.as_chunks::<2>()
366            .0
367            .iter()
368            .map(|pair| {
369                let TypedExprKind::Literal(Literal::String(name)) = &pair[0].kind else {
370                    unreachable!("checked by check_struct_pack")
371                };
372                (name.clone(), pair[1].resolved_type.clone())
373            })
374            .collect(),
375    ))
376}
377
378fn check_subscript(args: &[TypedExpr]) -> Result<(), PlannerError> {
379    match &args[0].resolved_type {
380        ResolvedType::Array(_) => {
381            if matches!(
382                args[1].resolved_type,
383                ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Null
384            ) {
385                Ok(())
386            } else {
387                Err(PlannerError::invalid_expression(
388                    "ARRAY subscript must be INTEGER",
389                ))
390            }
391        }
392        ResolvedType::Map { key, .. } if args[1].resolved_type.can_cast_to(key) => Ok(()),
393        ResolvedType::Struct(_) if matches!(args[1].resolved_type, ResolvedType::Text) => Ok(()),
394        ResolvedType::Null => Ok(()),
395        other => Err(PlannerError::invalid_expression(format!(
396            "cannot subscript {other}"
397        ))),
398    }
399}
400
401fn return_subscript(args: &[TypedExpr]) -> Result<ResolvedType, PlannerError> {
402    match &args[0].resolved_type {
403        ResolvedType::Array(element) => Ok((**element).clone()),
404        ResolvedType::Map { value, .. } => Ok((**value).clone()),
405        ResolvedType::Struct(fields) => {
406            let TypedExprKind::Literal(Literal::String(name)) = &args[1].kind else {
407                return Ok(ResolvedType::Null);
408            };
409            fields
410                .iter()
411                .find(|(field, _)| field == name)
412                .map(|(_, data_type)| data_type.clone())
413                .ok_or_else(|| {
414                    PlannerError::invalid_expression(format!("unknown struct field '{name}'"))
415                })
416        }
417        ResolvedType::Null => Ok(ResolvedType::Null),
418        _ => unreachable!("checked by check_subscript"),
419    }
420}
421
422fn check_json_selector(args: &[TypedExpr]) -> Result<(), PlannerError> {
423    check_json_input(&args[..1])?;
424    if matches!(
425        args[1].resolved_type,
426        ResolvedType::Text | ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Null
427    ) {
428        Ok(())
429    } else {
430        Err(PlannerError::type_mismatch(
431            "Text or Integer",
432            args[1].resolved_type.type_name(),
433            args[1].span,
434        ))
435    }
436}
437
438fn check_json_path(args: &[TypedExpr]) -> Result<(), PlannerError> {
439    check_json_input(&args[..1])?;
440    check_text(&args[1..])
441}
442
443fn check_jsonb_update(args: &[TypedExpr]) -> Result<(), PlannerError> {
444    if args.len() < 3 || args.len().is_multiple_of(2) {
445        return Err(PlannerError::invalid_expression(
446            "JSON update expects JSON followed by path/value pairs".to_string(),
447        ));
448    }
449    check_json_input(&args[..1])?;
450    for path in args[1..].iter().step_by(2) {
451        check_text(std::slice::from_ref(path))?;
452    }
453    Ok(())
454}
455
456fn check_json_update(args: &[TypedExpr]) -> Result<(), PlannerError> {
457    if args.len() < 3 || args.len().is_multiple_of(2) {
458        return Err(PlannerError::invalid_expression(
459            "JSON update expects JSON followed by path/value pairs".to_string(),
460        ));
461    }
462    check_text(&args[..1])?;
463    for path in args[1..].iter().step_by(2) {
464        check_text(std::slice::from_ref(path))?;
465    }
466    Ok(())
467}
468
469pub fn check_text_or_blob(args: &[TypedExpr]) -> Result<(), PlannerError> {
470    for arg in args {
471        if !matches!(
472            arg.resolved_type,
473            ResolvedType::Text | ResolvedType::Blob | ResolvedType::Null
474        ) {
475            return Err(PlannerError::type_mismatch(
476                "Text or Blob",
477                arg.resolved_type.type_name(),
478                arg.span,
479            ));
480        }
481    }
482    Ok(())
483}
484
485fn check_timestamp(args: &[TypedExpr]) -> Result<(), PlannerError> {
486    for arg in args {
487        if !matches!(
488            arg.resolved_type,
489            ResolvedType::Timestamp | ResolvedType::Null
490        ) {
491            return Err(PlannerError::type_mismatch(
492                "Timestamp",
493                arg.resolved_type.type_name(),
494                arg.span,
495            ));
496        }
497    }
498    Ok(())
499}
500
501fn check_text_timestamp(args: &[TypedExpr]) -> Result<(), PlannerError> {
502    check_text(&args[..1])?;
503    check_timestamp(&args[1..])
504}
505
506fn check_timestamp_text(args: &[TypedExpr]) -> Result<(), PlannerError> {
507    check_timestamp(&args[..1])?;
508    check_text(&args[1..])
509}
510
511fn check_to_timestamp(args: &[TypedExpr]) -> Result<(), PlannerError> {
512    if args.len() == 1 {
513        let arg = &args[0];
514        if is_numeric(&arg.resolved_type)
515            || matches!(arg.resolved_type, ResolvedType::Text | ResolvedType::Null)
516        {
517            return Ok(());
518        }
519        return Err(PlannerError::type_mismatch(
520            "Numeric or Text",
521            arg.resolved_type.type_name(),
522            arg.span,
523        ));
524    }
525    check_text(args)
526}
527
528fn check_temporal_input(args: &[TypedExpr]) -> Result<(), PlannerError> {
529    for arg in args {
530        if !matches!(
531            arg.resolved_type,
532            ResolvedType::Text
533                | ResolvedType::Date
534                | ResolvedType::Time
535                | ResolvedType::Timestamp
536                | ResolvedType::Null
537        ) {
538            return Err(PlannerError::type_mismatch(
539                "Temporal or Text",
540                arg.resolved_type.type_name(),
541                arg.span,
542            ));
543        }
544    }
545    Ok(())
546}
547
548fn check_datetime(args: &[TypedExpr]) -> Result<(), PlannerError> {
549    check_temporal_input(&args[..1])?;
550    check_text(&args[1..])
551}
552
553fn check_temporal_interval(args: &[TypedExpr]) -> Result<(), PlannerError> {
554    let temporal = &args[0];
555    if !matches!(
556        temporal.resolved_type,
557        ResolvedType::Date | ResolvedType::Time | ResolvedType::Timestamp | ResolvedType::Null
558    ) {
559        return Err(PlannerError::type_mismatch(
560            "Date, Time, or Timestamp",
561            temporal.resolved_type.type_name(),
562            temporal.span,
563        ));
564    }
565    let interval = &args[1];
566    if matches!(
567        interval.resolved_type,
568        ResolvedType::Interval | ResolvedType::Null
569    ) {
570        Ok(())
571    } else {
572        Err(PlannerError::type_mismatch(
573            "Interval",
574            interval.resolved_type.type_name(),
575            interval.span,
576        ))
577    }
578}
579
580fn check_age(args: &[TypedExpr]) -> Result<(), PlannerError> {
581    for arg in args {
582        if !matches!(
583            arg.resolved_type,
584            ResolvedType::Date | ResolvedType::Timestamp | ResolvedType::Null
585        ) {
586            return Err(PlannerError::type_mismatch(
587                "Date or Timestamp",
588                arg.resolved_type.type_name(),
589                arg.span,
590            ));
591        }
592    }
593    if args.len() == 2
594        && args[0].resolved_type != ResolvedType::Null
595        && args[1].resolved_type != ResolvedType::Null
596        && args[0].resolved_type != args[1].resolved_type
597    {
598        return Err(PlannerError::type_mismatch(
599            args[0].resolved_type.type_name(),
600            args[1].resolved_type.type_name(),
601            args[1].span,
602        ));
603    }
604    Ok(())
605}
606
607pub fn check_bigint(args: &[TypedExpr]) -> Result<(), PlannerError> {
608    for arg in args {
609        if !matches!(arg.resolved_type, ResolvedType::BigInt | ResolvedType::Null) {
610            return Err(PlannerError::type_mismatch(
611                "BigInt",
612                arg.resolved_type.type_name(),
613                arg.span,
614            ));
615        }
616    }
617    Ok(())
618}
619
620fn check_integer(args: &[TypedExpr]) -> Result<(), PlannerError> {
621    for arg in args {
622        if !matches!(
623            arg.resolved_type,
624            ResolvedType::Integer | ResolvedType::BigInt | ResolvedType::Null
625        ) {
626            return Err(PlannerError::type_mismatch(
627                "Integer",
628                arg.resolved_type.type_name(),
629                arg.span,
630            ));
631        }
632    }
633    Ok(())
634}
635
636pub fn check_blob_text(args: &[TypedExpr]) -> Result<(), PlannerError> {
637    if let Some(first) = args.first()
638        && !matches!(first.resolved_type, ResolvedType::Blob | ResolvedType::Null)
639    {
640        return Err(PlannerError::type_mismatch(
641            "Blob",
642            first.resolved_type.type_name(),
643            first.span,
644        ));
645    }
646    check_text(&args[1..])
647}
648
649pub fn check_any(args: &[TypedExpr]) -> Result<(), PlannerError> {
650    if args.is_empty() {
651        return Err(PlannerError::invalid_expression(
652            "at least one argument is required",
653        ));
654    }
655    Ok(())
656}
657
658pub fn check_no_args(_args: &[TypedExpr]) -> Result<(), PlannerError> {
659    Ok(())
660}
661
662fn check_vector_one(args: &[TypedExpr]) -> Result<(), PlannerError> {
663    match args.first().map(|arg| &arg.resolved_type) {
664        Some(ResolvedType::Vector { .. } | ResolvedType::Null) => Ok(()),
665        Some(ty) => Err(PlannerError::type_mismatch(
666            "Vector",
667            ty.type_name(),
668            args[0].span,
669        )),
670        None => Ok(()),
671    }
672}
673
674fn check_vector_triplet(args: &[TypedExpr]) -> Result<(), PlannerError> {
675    let first = match &args[0].resolved_type {
676        ResolvedType::Vector { dimension, .. } => *dimension,
677        ty => {
678            return Err(PlannerError::type_mismatch(
679                "Vector",
680                ty.type_name(),
681                args[0].span,
682            ));
683        }
684    };
685    let second = match &args[1].resolved_type {
686        ResolvedType::Vector { dimension, .. } => *dimension,
687        ty => {
688            return Err(PlannerError::type_mismatch(
689                "Vector",
690                ty.type_name(),
691                args[1].span,
692            ));
693        }
694    };
695    if first != second {
696        return Err(PlannerError::vector_dimension_mismatch(
697            first,
698            second,
699            args[1].span,
700        ));
701    }
702    match &args[2].resolved_type {
703        ResolvedType::Text => {
704            if let TypedExprKind::Literal(Literal::String(value)) = &args[2].kind
705                && !matches!(
706                    value.to_ascii_lowercase().as_str(),
707                    "cosine" | "l2" | "inner"
708                )
709            {
710                return Err(PlannerError::invalid_metric(value, args[2].span));
711            }
712            Ok(())
713        }
714        ty => Err(PlannerError::type_mismatch(
715            "Text (metric)",
716            ty.type_name(),
717            args[2].span,
718        )),
719    }
720}
721
722fn numeric_return(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
723    let mut result = ResolvedType::Null;
724    for ty in types {
725        if matches!(ty, ResolvedType::Null) {
726            continue;
727        }
728        if !is_numeric(ty) {
729            return Err(PlannerError::type_mismatch(
730                "Numeric",
731                ty.type_name(),
732                Span::default(),
733            ));
734        }
735        result = match (&result, ty) {
736            (ResolvedType::Null, _) => ty.clone(),
737            (ResolvedType::Double, _) | (_, ResolvedType::Double) => ResolvedType::Double,
738            (ResolvedType::Float, ResolvedType::BigInt)
739            | (ResolvedType::BigInt, ResolvedType::Float) => ResolvedType::Double,
740            (ResolvedType::Float, _) | (_, ResolvedType::Float) => ResolvedType::Float,
741            (ResolvedType::BigInt, _) | (_, ResolvedType::BigInt) => ResolvedType::BigInt,
742            _ => ResolvedType::Integer,
743        };
744    }
745    Ok(result)
746}
747
748fn return_arg0(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
749    Ok(types.first().cloned().unwrap_or(ResolvedType::Null))
750}
751
752fn return_first_non_null(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
753    Ok(types
754        .iter()
755        .find(|ty| !matches!(ty, ResolvedType::Null))
756        .cloned()
757        .unwrap_or(ResolvedType::Null))
758}
759
760fn return_numeric(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
761    numeric_return(types)
762}
763
764fn return_arg0_numeric(types: &[ResolvedType]) -> Result<ResolvedType, PlannerError> {
765    return_arg0(types)
766}
767
768const fn sig(
769    name: &'static str,
770    arity: Arity,
771    check: fn(&[TypedExpr]) -> Result<(), PlannerError>,
772    ret: ReturnRule,
773) -> ScalarSignature {
774    ScalarSignature {
775        name,
776        arity,
777        check,
778        ret,
779        meta: PURE_META,
780    }
781}
782
783const fn sig_meta(
784    name: &'static str,
785    arity: Arity,
786    check: fn(&[TypedExpr]) -> Result<(), PlannerError>,
787    ret: ReturnRule,
788    meta: FnMeta,
789) -> ScalarSignature {
790    ScalarSignature {
791        name,
792        arity,
793        check,
794        ret,
795        meta,
796    }
797}
798
799static SIGNATURES: &[ScalarSignature] = &[
800    sig(
801        "to_tsvector",
802        Arity::Range(1, 2),
803        check_text,
804        ReturnRule::Fixed(ResolvedType::Text),
805    ),
806    sig(
807        "to_tsquery",
808        Arity::Range(1, 2),
809        check_text,
810        ReturnRule::Fixed(ResolvedType::Text),
811    ),
812    sig(
813        "plainto_tsquery",
814        Arity::Range(1, 2),
815        check_text,
816        ReturnRule::Fixed(ResolvedType::Text),
817    ),
818    sig(
819        "websearch_to_tsquery",
820        Arity::Range(1, 2),
821        check_text,
822        ReturnRule::Fixed(ResolvedType::Text),
823    ),
824    sig(
825        "ts_rank",
826        Arity::Exact(2),
827        check_text,
828        ReturnRule::Fixed(ResolvedType::Double),
829    ),
830    sig(
831        "ts_headline",
832        Arity::Range(2, 3),
833        check_text,
834        ReturnRule::Fixed(ResolvedType::Text),
835    ),
836    sig(
837        "array_value",
838        Arity::Variadic(0),
839        check_no_args,
840        ReturnRule::FromArgs(return_array_value),
841    ),
842    sig(
843        "list_value",
844        Arity::Variadic(0),
845        check_no_args,
846        ReturnRule::FromArgs(return_array_value),
847    ),
848    sig(
849        "array_append",
850        Arity::Exact(2),
851        check_array_append,
852        ReturnRule::FromArgs(return_array_append),
853    ),
854    sig(
855        "array_prepend",
856        Arity::Exact(2),
857        check_array_prepend,
858        ReturnRule::FromArgs(return_array_prepend),
859    ),
860    sig(
861        "array_cat",
862        Arity::Exact(2),
863        check_array_pair,
864        ReturnRule::FromArgs(return_array_cat),
865    ),
866    sig(
867        "array_remove",
868        Arity::Exact(2),
869        check_array_append,
870        ReturnRule::FromArgs(return_array_first),
871    ),
872    sig(
873        "array_replace",
874        Arity::Exact(3),
875        check_array_replace,
876        ReturnRule::FromArgs(return_array_first),
877    ),
878    sig(
879        "array_length",
880        Arity::Exact(1),
881        check_array_first,
882        ReturnRule::Fixed(ResolvedType::Integer),
883    ),
884    sig(
885        "array_position",
886        Arity::Exact(2),
887        check_array_append,
888        ReturnRule::Fixed(ResolvedType::Integer),
889    ),
890    sig(
891        "array_positions",
892        Arity::Exact(2),
893        check_array_append,
894        ReturnRule::FromArgs(return_integer_array),
895    ),
896    sig(
897        "string_to_array",
898        Arity::Range(2, 3),
899        check_text,
900        ReturnRule::FromArgs(return_text_array),
901    ),
902    sig(
903        "array_to_string",
904        Arity::Range(2, 3),
905        check_array_text_tail,
906        ReturnRule::Fixed(ResolvedType::Text),
907    ),
908    sig(
909        "map",
910        Arity::Exact(2),
911        check_map,
912        ReturnRule::FromArgs(return_map),
913    ),
914    sig(
915        "struct_pack",
916        Arity::Variadic(0),
917        check_struct_pack,
918        ReturnRule::FromTypedArgs(return_struct),
919    ),
920    sig(
921        "array_subscript",
922        Arity::Exact(2),
923        check_subscript,
924        ReturnRule::FromTypedArgs(return_subscript),
925    ),
926    sig(
927        "array_slice",
928        Arity::Exact(3),
929        check_array_slice,
930        ReturnRule::FromArgs(return_array_first),
931    ),
932    sig(
933        "jsonb_extract",
934        Arity::Exact(2),
935        check_json_selector,
936        ReturnRule::Fixed(ResolvedType::Json),
937    ),
938    sig(
939        "jsonb_extract_text",
940        Arity::Exact(2),
941        check_json_selector,
942        ReturnRule::Fixed(ResolvedType::Text),
943    ),
944    sig(
945        "jsonb_extract_path",
946        Arity::Exact(2),
947        check_json_path,
948        ReturnRule::Fixed(ResolvedType::Json),
949    ),
950    sig(
951        "jsonb_extract_path_text",
952        Arity::Exact(2),
953        check_json_path,
954        ReturnRule::Fixed(ResolvedType::Text),
955    ),
956    sig(
957        "jsonb_set",
958        Arity::Variadic(3),
959        check_jsonb_update,
960        ReturnRule::Fixed(ResolvedType::Json),
961    ),
962    sig(
963        "jsonb_insert",
964        Arity::Variadic(3),
965        check_jsonb_update,
966        ReturnRule::Fixed(ResolvedType::Json),
967    ),
968    sig(
969        "jsonb_build_object",
970        Arity::Variadic(0),
971        check_json_object,
972        ReturnRule::Fixed(ResolvedType::Json),
973    ),
974    sig(
975        "jsonb_build_array",
976        Arity::Variadic(0),
977        check_no_args,
978        ReturnRule::Fixed(ResolvedType::Json),
979    ),
980    sig(
981        "json",
982        Arity::Exact(1),
983        check_text,
984        ReturnRule::Fixed(ResolvedType::Text),
985    ),
986    sig(
987        "json_valid",
988        Arity::Exact(1),
989        check_text,
990        ReturnRule::Fixed(ResolvedType::Boolean),
991    ),
992    sig(
993        "json_type",
994        Arity::Range(1, 2),
995        check_text,
996        ReturnRule::Fixed(ResolvedType::Text),
997    ),
998    sig(
999        "json_extract",
1000        Arity::Variadic(2),
1001        check_text,
1002        ReturnRule::Fixed(ResolvedType::Text),
1003    ),
1004    sig(
1005        "json_object",
1006        Arity::Variadic(0),
1007        check_json_object,
1008        ReturnRule::Fixed(ResolvedType::Text),
1009    ),
1010    sig(
1011        "json_array",
1012        Arity::Variadic(0),
1013        check_no_args,
1014        ReturnRule::Fixed(ResolvedType::Text),
1015    ),
1016    sig(
1017        "json_insert",
1018        Arity::Variadic(3),
1019        check_json_update,
1020        ReturnRule::Fixed(ResolvedType::Text),
1021    ),
1022    sig(
1023        "json_replace",
1024        Arity::Variadic(3),
1025        check_json_update,
1026        ReturnRule::Fixed(ResolvedType::Text),
1027    ),
1028    sig(
1029        "json_set",
1030        Arity::Variadic(3),
1031        check_json_update,
1032        ReturnRule::Fixed(ResolvedType::Text),
1033    ),
1034    sig(
1035        "json_remove",
1036        Arity::Variadic(2),
1037        check_text,
1038        ReturnRule::Fixed(ResolvedType::Text),
1039    ),
1040    sig(
1041        "json_array_length",
1042        Arity::Range(1, 2),
1043        check_text,
1044        ReturnRule::Fixed(ResolvedType::Integer),
1045    ),
1046    sig(
1047        "vector_similarity",
1048        Arity::Exact(3),
1049        check_vector_triplet,
1050        ReturnRule::Fixed(ResolvedType::Double),
1051    ),
1052    sig(
1053        "vector_distance",
1054        Arity::Exact(3),
1055        check_vector_triplet,
1056        ReturnRule::Fixed(ResolvedType::Double),
1057    ),
1058    sig(
1059        "vector_dims",
1060        Arity::Exact(1),
1061        check_vector_one,
1062        ReturnRule::Fixed(ResolvedType::Integer),
1063    ),
1064    sig(
1065        "vector_norm",
1066        Arity::Exact(1),
1067        check_vector_one,
1068        ReturnRule::Fixed(ResolvedType::Double),
1069    ),
1070    sig(
1071        "abs",
1072        Arity::Exact(1),
1073        check_numeric,
1074        ReturnRule::FromArgs(return_arg0_numeric),
1075    ),
1076    sig(
1077        "sign",
1078        Arity::Exact(1),
1079        check_numeric,
1080        ReturnRule::Fixed(ResolvedType::Integer),
1081    ),
1082    sig(
1083        "round",
1084        Arity::Range(1, 2),
1085        check_numeric,
1086        ReturnRule::FromArgs(return_arg0_numeric),
1087    ),
1088    sig(
1089        "floor",
1090        Arity::Exact(1),
1091        check_numeric,
1092        ReturnRule::FromArgs(return_arg0_numeric),
1093    ),
1094    sig(
1095        "ceil",
1096        Arity::Exact(1),
1097        check_numeric,
1098        ReturnRule::FromArgs(return_arg0_numeric),
1099    ),
1100    sig(
1101        "ceiling",
1102        Arity::Exact(1),
1103        check_numeric,
1104        ReturnRule::FromArgs(return_arg0_numeric),
1105    ),
1106    sig(
1107        "trunc",
1108        Arity::Range(1, 2),
1109        check_numeric,
1110        ReturnRule::FromArgs(return_arg0_numeric),
1111    ),
1112    sig(
1113        "mod",
1114        Arity::Exact(2),
1115        check_numeric,
1116        ReturnRule::FromArgs(return_numeric),
1117    ),
1118    sig(
1119        "power",
1120        Arity::Exact(2),
1121        check_numeric,
1122        ReturnRule::Fixed(ResolvedType::Double),
1123    ),
1124    sig(
1125        "pow",
1126        Arity::Exact(2),
1127        check_numeric,
1128        ReturnRule::Fixed(ResolvedType::Double),
1129    ),
1130    sig(
1131        "sqrt",
1132        Arity::Exact(1),
1133        check_numeric,
1134        ReturnRule::Fixed(ResolvedType::Double),
1135    ),
1136    sig(
1137        "exp",
1138        Arity::Exact(1),
1139        check_numeric,
1140        ReturnRule::Fixed(ResolvedType::Double),
1141    ),
1142    sig(
1143        "ln",
1144        Arity::Exact(1),
1145        check_numeric,
1146        ReturnRule::Fixed(ResolvedType::Double),
1147    ),
1148    sig(
1149        "log",
1150        Arity::Range(1, 2),
1151        check_numeric,
1152        ReturnRule::Fixed(ResolvedType::Double),
1153    ),
1154    sig(
1155        "log10",
1156        Arity::Exact(1),
1157        check_numeric,
1158        ReturnRule::Fixed(ResolvedType::Double),
1159    ),
1160    sig(
1161        "cbrt",
1162        Arity::Exact(1),
1163        check_numeric,
1164        ReturnRule::Fixed(ResolvedType::Double),
1165    ),
1166    sig(
1167        "cot",
1168        Arity::Exact(1),
1169        check_numeric,
1170        ReturnRule::Fixed(ResolvedType::Double),
1171    ),
1172    sig(
1173        "log2",
1174        Arity::Exact(1),
1175        check_numeric,
1176        ReturnRule::Fixed(ResolvedType::Double),
1177    ),
1178    sig(
1179        "acosh",
1180        Arity::Exact(1),
1181        check_numeric,
1182        ReturnRule::Fixed(ResolvedType::Double),
1183    ),
1184    sig(
1185        "asinh",
1186        Arity::Exact(1),
1187        check_numeric,
1188        ReturnRule::Fixed(ResolvedType::Double),
1189    ),
1190    sig(
1191        "atanh",
1192        Arity::Exact(1),
1193        check_numeric,
1194        ReturnRule::Fixed(ResolvedType::Double),
1195    ),
1196    sig(
1197        "cosh",
1198        Arity::Exact(1),
1199        check_numeric,
1200        ReturnRule::Fixed(ResolvedType::Double),
1201    ),
1202    sig(
1203        "sinh",
1204        Arity::Exact(1),
1205        check_numeric,
1206        ReturnRule::Fixed(ResolvedType::Double),
1207    ),
1208    sig(
1209        "tanh",
1210        Arity::Exact(1),
1211        check_numeric,
1212        ReturnRule::Fixed(ResolvedType::Double),
1213    ),
1214    sig(
1215        "isnan",
1216        Arity::Exact(1),
1217        check_numeric,
1218        ReturnRule::Fixed(ResolvedType::Boolean),
1219    ),
1220    sig_meta(
1221        "random",
1222        Arity::Exact(0),
1223        check_numeric,
1224        ReturnRule::Fixed(ResolvedType::Double),
1225        RANDOM_META,
1226    ),
1227    sig_meta(
1228        "now",
1229        Arity::Range(0, 1),
1230        check_numeric,
1231        ReturnRule::Fixed(ResolvedType::Timestamp),
1232        STATEMENT_STABLE_META,
1233    ),
1234    sig_meta(
1235        "current_timestamp",
1236        Arity::Range(0, 1),
1237        check_numeric,
1238        ReturnRule::Fixed(ResolvedType::Timestamp),
1239        STATEMENT_STABLE_META,
1240    ),
1241    sig_meta(
1242        "current_date",
1243        Arity::Exact(0),
1244        check_numeric,
1245        ReturnRule::Fixed(ResolvedType::Date),
1246        STATEMENT_STABLE_META,
1247    ),
1248    sig_meta(
1249        "current_time",
1250        Arity::Exact(0),
1251        check_numeric,
1252        ReturnRule::Fixed(ResolvedType::Time),
1253        STATEMENT_STABLE_META,
1254    ),
1255    sig(
1256        "make_date",
1257        Arity::Exact(3),
1258        check_numeric,
1259        ReturnRule::Fixed(ResolvedType::Date),
1260    ),
1261    sig(
1262        "make_time",
1263        Arity::Exact(3),
1264        check_numeric,
1265        ReturnRule::Fixed(ResolvedType::Time),
1266    ),
1267    sig(
1268        "make_timestamp",
1269        Arity::Exact(6),
1270        check_numeric,
1271        ReturnRule::Fixed(ResolvedType::Timestamp),
1272    ),
1273    sig(
1274        "make_interval",
1275        Arity::Range(0, 7),
1276        check_numeric,
1277        ReturnRule::Fixed(ResolvedType::Interval),
1278    ),
1279    sig(
1280        "date",
1281        Arity::Exact(1),
1282        check_temporal_input,
1283        ReturnRule::Fixed(ResolvedType::Date),
1284    ),
1285    sig(
1286        "time",
1287        Arity::Exact(1),
1288        check_temporal_input,
1289        ReturnRule::Fixed(ResolvedType::Time),
1290    ),
1291    sig(
1292        "datetime",
1293        Arity::Variadic(1),
1294        check_datetime,
1295        ReturnRule::Fixed(ResolvedType::Timestamp),
1296    ),
1297    sig(
1298        "to_date",
1299        Arity::Exact(2),
1300        check_text,
1301        ReturnRule::Fixed(ResolvedType::Date),
1302    ),
1303    sig(
1304        "age",
1305        Arity::Range(1, 2),
1306        check_age,
1307        ReturnRule::Fixed(ResolvedType::Interval),
1308    ),
1309    sig(
1310        "date_add",
1311        Arity::Exact(2),
1312        check_temporal_interval,
1313        ReturnRule::FromArgs(return_arg0),
1314    ),
1315    sig(
1316        "date_sub",
1317        Arity::Exact(2),
1318        check_temporal_interval,
1319        ReturnRule::FromArgs(return_arg0),
1320    ),
1321    sig(
1322        "extract",
1323        Arity::Exact(2),
1324        check_text_timestamp,
1325        ReturnRule::Fixed(ResolvedType::Double),
1326    ),
1327    sig(
1328        "date_part",
1329        Arity::Exact(2),
1330        check_text_timestamp,
1331        ReturnRule::Fixed(ResolvedType::Double),
1332    ),
1333    sig(
1334        "date_trunc",
1335        Arity::Exact(2),
1336        check_text_timestamp,
1337        ReturnRule::Fixed(ResolvedType::Timestamp),
1338    ),
1339    sig(
1340        "to_char",
1341        Arity::Exact(2),
1342        check_timestamp_text,
1343        ReturnRule::Fixed(ResolvedType::Text),
1344    ),
1345    sig(
1346        "to_timestamp",
1347        Arity::Range(1, 2),
1348        check_to_timestamp,
1349        ReturnRule::Fixed(ResolvedType::Timestamp),
1350    ),
1351    sig(
1352        "strftime",
1353        Arity::Exact(2),
1354        check_text_timestamp,
1355        ReturnRule::Fixed(ResolvedType::Text),
1356    ),
1357    sig(
1358        "julianday",
1359        Arity::Exact(1),
1360        check_timestamp,
1361        ReturnRule::Fixed(ResolvedType::Double),
1362    ),
1363    sig(
1364        "unixepoch",
1365        Arity::Exact(1),
1366        check_timestamp,
1367        ReturnRule::Fixed(ResolvedType::BigInt),
1368    ),
1369    sig(
1370        "sin",
1371        Arity::Exact(1),
1372        check_numeric,
1373        ReturnRule::Fixed(ResolvedType::Double),
1374    ),
1375    sig(
1376        "cos",
1377        Arity::Exact(1),
1378        check_numeric,
1379        ReturnRule::Fixed(ResolvedType::Double),
1380    ),
1381    sig(
1382        "tan",
1383        Arity::Exact(1),
1384        check_numeric,
1385        ReturnRule::Fixed(ResolvedType::Double),
1386    ),
1387    sig(
1388        "asin",
1389        Arity::Exact(1),
1390        check_numeric,
1391        ReturnRule::Fixed(ResolvedType::Double),
1392    ),
1393    sig(
1394        "acos",
1395        Arity::Exact(1),
1396        check_numeric,
1397        ReturnRule::Fixed(ResolvedType::Double),
1398    ),
1399    sig(
1400        "atan",
1401        Arity::Exact(1),
1402        check_numeric,
1403        ReturnRule::Fixed(ResolvedType::Double),
1404    ),
1405    sig(
1406        "atan2",
1407        Arity::Exact(2),
1408        check_numeric,
1409        ReturnRule::Fixed(ResolvedType::Double),
1410    ),
1411    sig(
1412        "degrees",
1413        Arity::Exact(1),
1414        check_numeric,
1415        ReturnRule::Fixed(ResolvedType::Double),
1416    ),
1417    sig(
1418        "radians",
1419        Arity::Exact(1),
1420        check_numeric,
1421        ReturnRule::Fixed(ResolvedType::Double),
1422    ),
1423    sig(
1424        "pi",
1425        Arity::Exact(0),
1426        check_numeric,
1427        ReturnRule::Fixed(ResolvedType::Double),
1428    ),
1429    sig(
1430        "sha256",
1431        Arity::Exact(1),
1432        check_text_or_blob,
1433        ReturnRule::Fixed(ResolvedType::Blob),
1434    ),
1435    sig(
1436        "md5",
1437        Arity::Exact(1),
1438        check_text_or_blob,
1439        ReturnRule::Fixed(ResolvedType::Text),
1440    ),
1441    sig(
1442        "simhash",
1443        Arity::Exact(1),
1444        check_text,
1445        ReturnRule::Fixed(ResolvedType::BigInt),
1446    ),
1447    sig(
1448        "hamming_distance",
1449        Arity::Exact(2),
1450        check_bigint,
1451        ReturnRule::Fixed(ResolvedType::Integer),
1452    ),
1453    sig_meta(
1454        "gen_random_uuid",
1455        Arity::Exact(0),
1456        check_no_args,
1457        ReturnRule::Fixed(ResolvedType::Text),
1458        RANDOM_META,
1459    ),
1460    sig_meta(
1461        "uuidv7",
1462        Arity::Exact(0),
1463        check_no_args,
1464        ReturnRule::Fixed(ResolvedType::Text),
1465        RANDOM_META,
1466    ),
1467    sig(
1468        "hex",
1469        Arity::Exact(1),
1470        check_text_or_blob,
1471        ReturnRule::Fixed(ResolvedType::Text),
1472    ),
1473    sig(
1474        "unhex",
1475        Arity::Exact(1),
1476        check_text,
1477        ReturnRule::Fixed(ResolvedType::Blob),
1478    ),
1479    sig(
1480        "encode",
1481        Arity::Exact(2),
1482        check_blob_text,
1483        ReturnRule::Fixed(ResolvedType::Text),
1484    ),
1485    sig(
1486        "decode",
1487        Arity::Exact(2),
1488        check_text,
1489        ReturnRule::Fixed(ResolvedType::Blob),
1490    ),
1491    sig(
1492        "length",
1493        Arity::Exact(1),
1494        check_text_or_blob,
1495        ReturnRule::Fixed(ResolvedType::Integer),
1496    ),
1497    sig(
1498        "char_length",
1499        Arity::Exact(1),
1500        check_text,
1501        ReturnRule::Fixed(ResolvedType::Integer),
1502    ),
1503    sig(
1504        "octet_length",
1505        Arity::Exact(1),
1506        check_text_or_blob,
1507        ReturnRule::Fixed(ResolvedType::Integer),
1508    ),
1509    sig(
1510        "ascii",
1511        Arity::Exact(1),
1512        check_text,
1513        ReturnRule::Fixed(ResolvedType::Integer),
1514    ),
1515    sig(
1516        "chr",
1517        Arity::Exact(1),
1518        check_integer,
1519        ReturnRule::Fixed(ResolvedType::Text),
1520    ),
1521    sig(
1522        "bit_length",
1523        Arity::Exact(1),
1524        check_text_or_blob,
1525        ReturnRule::Fixed(ResolvedType::Integer),
1526    ),
1527    sig(
1528        "starts_with",
1529        Arity::Exact(2),
1530        check_text,
1531        ReturnRule::Fixed(ResolvedType::Boolean),
1532    ),
1533    sig(
1534        "ends_with",
1535        Arity::Exact(2),
1536        check_text,
1537        ReturnRule::Fixed(ResolvedType::Boolean),
1538    ),
1539    sig(
1540        "translate",
1541        Arity::Exact(3),
1542        check_text,
1543        ReturnRule::Fixed(ResolvedType::Text),
1544    ),
1545    sig(
1546        "levenshtein",
1547        Arity::Exact(2),
1548        check_text,
1549        ReturnRule::Fixed(ResolvedType::Integer),
1550    ),
1551    sig(
1552        "upper",
1553        Arity::Exact(1),
1554        check_text,
1555        ReturnRule::Fixed(ResolvedType::Text),
1556    ),
1557    sig(
1558        "lower",
1559        Arity::Exact(1),
1560        check_text,
1561        ReturnRule::Fixed(ResolvedType::Text),
1562    ),
1563    sig(
1564        "initcap",
1565        Arity::Exact(1),
1566        check_text,
1567        ReturnRule::Fixed(ResolvedType::Text),
1568    ),
1569    sig(
1570        "substr",
1571        Arity::Range(2, 3),
1572        check_numeric_or_text_substr,
1573        ReturnRule::Fixed(ResolvedType::Text),
1574    ),
1575    sig(
1576        "left",
1577        Arity::Exact(2),
1578        check_text_numeric,
1579        ReturnRule::Fixed(ResolvedType::Text),
1580    ),
1581    sig(
1582        "right",
1583        Arity::Exact(2),
1584        check_text_numeric,
1585        ReturnRule::Fixed(ResolvedType::Text),
1586    ),
1587    sig(
1588        "trim",
1589        Arity::Range(1, 2),
1590        check_text,
1591        ReturnRule::Fixed(ResolvedType::Text),
1592    ),
1593    sig(
1594        "ltrim",
1595        Arity::Range(1, 2),
1596        check_text,
1597        ReturnRule::Fixed(ResolvedType::Text),
1598    ),
1599    sig(
1600        "rtrim",
1601        Arity::Range(1, 2),
1602        check_text,
1603        ReturnRule::Fixed(ResolvedType::Text),
1604    ),
1605    sig(
1606        "replace",
1607        Arity::Exact(3),
1608        check_text,
1609        ReturnRule::Fixed(ResolvedType::Text),
1610    ),
1611    sig(
1612        "instr",
1613        Arity::Exact(2),
1614        check_text,
1615        ReturnRule::Fixed(ResolvedType::Integer),
1616    ),
1617    sig(
1618        "strpos",
1619        Arity::Exact(2),
1620        check_text,
1621        ReturnRule::Fixed(ResolvedType::Integer),
1622    ),
1623    sig(
1624        "concat",
1625        Arity::Variadic(0),
1626        check_text,
1627        ReturnRule::Fixed(ResolvedType::Text),
1628    ),
1629    sig(
1630        "concat_ws",
1631        Arity::Variadic(1),
1632        check_text,
1633        ReturnRule::Fixed(ResolvedType::Text),
1634    ),
1635    sig(
1636        "repeat",
1637        Arity::Exact(2),
1638        check_text_numeric,
1639        ReturnRule::Fixed(ResolvedType::Text),
1640    ),
1641    sig(
1642        "reverse",
1643        Arity::Exact(1),
1644        check_text,
1645        ReturnRule::Fixed(ResolvedType::Text),
1646    ),
1647    sig(
1648        "lpad",
1649        Arity::Range(2, 3),
1650        check_pad,
1651        ReturnRule::Fixed(ResolvedType::Text),
1652    ),
1653    sig(
1654        "rpad",
1655        Arity::Range(2, 3),
1656        check_pad,
1657        ReturnRule::Fixed(ResolvedType::Text),
1658    ),
1659    sig(
1660        "split_part",
1661        Arity::Exact(3),
1662        check_text_text_numeric,
1663        ReturnRule::Fixed(ResolvedType::Text),
1664    ),
1665    sig(
1666        "regexp_replace",
1667        Arity::Exact(3),
1668        check_text,
1669        ReturnRule::Fixed(ResolvedType::Text),
1670    ),
1671    sig(
1672        "regexp_match",
1673        Arity::Exact(2),
1674        check_text,
1675        ReturnRule::Fixed(ResolvedType::Text),
1676    ),
1677    sig(
1678        "regexp_matches",
1679        Arity::Range(2, 3),
1680        check_text,
1681        ReturnRule::Fixed(ResolvedType::Text),
1682    ),
1683    sig(
1684        "regexp_like",
1685        Arity::Range(2, 3),
1686        check_text,
1687        ReturnRule::Fixed(ResolvedType::Boolean),
1688    ),
1689    sig(
1690        "coalesce",
1691        Arity::Variadic(1),
1692        check_compatible,
1693        ReturnRule::FromArgs(return_first_non_null),
1694    ),
1695    sig(
1696        "nullif",
1697        Arity::Exact(2),
1698        check_compatible,
1699        ReturnRule::FromArgs(return_first_non_null),
1700    ),
1701    sig(
1702        "ifnull",
1703        Arity::Exact(2),
1704        check_compatible,
1705        ReturnRule::FromArgs(return_first_non_null),
1706    ),
1707    sig(
1708        "iif",
1709        Arity::Exact(3),
1710        check_iif,
1711        ReturnRule::FromArgs(return_first_non_null),
1712    ),
1713    sig(
1714        "greatest",
1715        Arity::Variadic(1),
1716        check_numeric,
1717        ReturnRule::FromArgs(return_numeric),
1718    ),
1719    sig(
1720        "least",
1721        Arity::Variadic(1),
1722        check_numeric,
1723        ReturnRule::FromArgs(return_numeric),
1724    ),
1725    sig(
1726        "typeof",
1727        Arity::Exact(1),
1728        check_any,
1729        ReturnRule::Fixed(ResolvedType::Text),
1730    ),
1731    sig(
1732        "pg_typeof",
1733        Arity::Exact(1),
1734        check_any,
1735        ReturnRule::Fixed(ResolvedType::Text),
1736    ),
1737    sig(
1738        "quote",
1739        Arity::Exact(1),
1740        check_any,
1741        ReturnRule::Fixed(ResolvedType::Text),
1742    ),
1743    sig_meta(
1744        "memory_stats",
1745        Arity::Exact(0),
1746        check_no_args,
1747        ReturnRule::Fixed(ResolvedType::Text),
1748        SYSTEM_META,
1749    ),
1750    sig_meta(
1751        "io_stats",
1752        Arity::Exact(0),
1753        check_no_args,
1754        ReturnRule::Fixed(ResolvedType::Text),
1755        SYSTEM_META,
1756    ),
1757    sig_meta(
1758        "clear_cache",
1759        Arity::Exact(0),
1760        check_no_args,
1761        ReturnRule::Fixed(ResolvedType::BigInt),
1762        SYSTEM_SIDE_EFFECT_META,
1763    ),
1764];
1765
1766fn check_numeric_or_text_substr(args: &[TypedExpr]) -> Result<(), PlannerError> {
1767    if args.is_empty() {
1768        return Ok(());
1769    }
1770    check_text(&args[..1])?;
1771    check_numeric(&args[1..])
1772}
1773
1774fn check_text_numeric(args: &[TypedExpr]) -> Result<(), PlannerError> {
1775    if !args.is_empty() {
1776        check_text(&args[..1])?;
1777    }
1778    if args.len() >= 2 {
1779        check_numeric(&args[1..2])?;
1780    }
1781    Ok(())
1782}
1783
1784fn check_pad(args: &[TypedExpr]) -> Result<(), PlannerError> {
1785    check_text_numeric(&args[..2])?;
1786    if args.len() == 3 {
1787        check_text(&args[2..3])?;
1788    }
1789    Ok(())
1790}
1791
1792fn check_text_text_numeric(args: &[TypedExpr]) -> Result<(), PlannerError> {
1793    check_text(&args[..2])?;
1794    check_numeric(&args[2..3])
1795}
1796
1797fn check_compatible(args: &[TypedExpr]) -> Result<(), PlannerError> {
1798    let mut expected: Option<&ResolvedType> = None;
1799    for arg in args {
1800        if matches!(arg.resolved_type, ResolvedType::Null) {
1801            continue;
1802        }
1803        if let Some(first) = expected {
1804            if !(is_numeric(first) && is_numeric(&arg.resolved_type)) && first != &arg.resolved_type
1805            {
1806                return Err(PlannerError::type_mismatch(
1807                    first.type_name(),
1808                    arg.resolved_type.type_name(),
1809                    arg.span,
1810                ));
1811            }
1812        } else {
1813            expected = Some(&arg.resolved_type);
1814        }
1815    }
1816    Ok(())
1817}
1818
1819fn check_iif(args: &[TypedExpr]) -> Result<(), PlannerError> {
1820    if !matches!(
1821        args.first().map(|a| &a.resolved_type),
1822        Some(ResolvedType::Boolean | ResolvedType::Null)
1823    ) {
1824        let arg = args
1825            .first()
1826            .expect("arity is validated before type checking");
1827        return Err(PlannerError::type_mismatch(
1828            "Boolean",
1829            arg.resolved_type.type_name(),
1830            arg.span,
1831        ));
1832    }
1833    check_compatible(&args[1..])
1834}
1835
1836pub fn signatures() -> &'static [ScalarSignature] {
1837    SIGNATURES
1838}
1839
1840pub fn signature(name: &str) -> Option<&'static ScalarSignature> {
1841    let lower = name.to_ascii_lowercase();
1842    SIGNATURES.iter().find(|sig| sig.name == lower)
1843}
1844
1845pub fn is_numeric_type(ty: &ResolvedType) -> bool {
1846    is_numeric(ty)
1847}
1848
1849#[cfg(test)]
1850mod tests {
1851    use super::*;
1852
1853    #[test]
1854    fn names_are_normalized_and_unique() {
1855        let mut names: Vec<_> = signatures().iter().map(|s| s.name).collect();
1856        names.sort_unstable();
1857        names.dedup();
1858        assert_eq!(names.len(), signatures().len());
1859        assert!(
1860            signatures()
1861                .iter()
1862                .all(|s| s.name == s.name.to_ascii_lowercase())
1863        );
1864    }
1865
1866    #[test]
1867    fn random_metadata_is_volatile() {
1868        let random = signature("RANDOM").unwrap();
1869        assert!(!random.meta.deterministic);
1870        assert!(random.meta.volatile);
1871        assert!(!random.meta.foldable);
1872        assert!(!random.meta.cacheable);
1873    }
1874
1875    #[test]
1876    fn scalar_names_do_not_overlap_aggregate_names() {
1877        let aggregates = [
1878            "count",
1879            "sum",
1880            "total",
1881            "avg",
1882            "min",
1883            "max",
1884            "group_concat",
1885            "string_agg",
1886        ];
1887        assert!(aggregates.iter().all(|name| signature(name).is_none()));
1888    }
1889}