Skip to main content

reddb_server/storage/schema/
function_catalog.rs

1//! Function catalog — static table of built-in scalar / aggregate
2//! function signatures used by the Fase 3 expression typer to
3//! resolve `Expr::FunctionCall` nodes to a concrete return type.
4//!
5//! Mirrors PostgreSQL's `pg_proc` catalog with a deliberately
6//! narrow row shape: a single (name, arg_types, return_type, kind)
7//! entry per overload. Multiple entries may share the same name —
8//! the resolver picks the one whose argument types match (after
9//! implicit coercion) using the `func_select_candidate` heuristic
10//! described in the roadmap (parte 4 of the plan file).
11//!
12//! The table is `const &[FunctionEntry]` so it lives in the
13//! read-only segment and lookups stay cache-friendly. Linear
14//! scan is fine for the ~30 entries the catalog covers today.
15//! Future weeks can switch to a `HashMap<&'static str, &[…]>`
16//! grouped by name when the table grows past ~500 entries.
17//!
18//! ## Coverage today
19//!
20//! Aggregates: COUNT, SUM, AVG, MIN, MAX (the five SQL-standard
21//! ones). Each has multiple overloads for the numeric category.
22//!
23//! Scalars covered:
24//!
25//! - String: UPPER, LOWER, LENGTH, COALESCE
26//! - Math:   ABS, ROUND, FLOOR, CEIL
27//! - Time:   NOW, CURRENT_TIMESTAMP, CURRENT_DATE, TIME_BUCKET
28//! - Geo:    GEO_DISTANCE, GEO_BEARING, HAVERSINE
29//! - Misc:   VERIFY_PASSWORD
30//!
31//! Variadic functions (COALESCE, GREATEST, LEAST, CONCAT) are
32//! marked with `variadic: true` and the resolver treats their
33//! `arg_types` slice as a description of the *uniform* element
34//! type — the catalog can't enumerate every arity, so the typer
35//! checks each call-site argument against `arg_types[0]` instead.
36//!
37//! ## What's NOT in this catalog
38//!
39//! - User-defined functions (CREATE FUNCTION) — separate runtime
40//!   table, queried after the static catalog yields no match.
41//! - Polymorphic signatures (anyelement, anyarray) — Fase 3 W4.
42//! - Operator functions backing `+`, `-`, `*` — those go in
43//!   `pg_operator` equivalent which we haven't built yet.
44
45use super::types::DataType;
46
47/// Function classification — affects resolver behavior and
48/// downstream planner cost estimation.
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
50pub enum FunctionKind {
51    /// Pure scalar function: same input → same output, no side
52    /// effects, no row-context dependency. The planner is free
53    /// to constant-fold or push-down through joins.
54    Scalar,
55    /// Aggregate function: consumes a Vec of input values, produces
56    /// a single output. Only valid in projection lists / HAVING /
57    /// window frames.
58    Aggregate,
59    /// Window function: like aggregate but evaluates over an
60    /// ORDER BY frame. Stays in projection lists only.
61    Window,
62    /// Side-effecting / time-dependent function: NOW(), RANDOM().
63    /// The planner cannot cache results across rows.
64    Volatile,
65}
66
67/// One signature in the static function catalog.
68#[derive(Debug, Clone, Copy)]
69pub struct FunctionEntry {
70    pub name: &'static str,
71    pub arg_types: &'static [DataType],
72    pub return_type: DataType,
73    pub kind: FunctionKind,
74    /// When true, the catalog's `arg_types` describes the element
75    /// type of a variadic argument list. Resolver matches each
76    /// call-site argument against `arg_types[0]` and ignores
77    /// arity entirely.
78    pub variadic: bool,
79}
80
81const fn entry(
82    name: &'static str,
83    arg_types: &'static [DataType],
84    return_type: DataType,
85    kind: FunctionKind,
86    variadic: bool,
87) -> FunctionEntry {
88    FunctionEntry {
89        name,
90        arg_types,
91        return_type,
92        kind,
93        variadic,
94    }
95}
96
97// ── Argument-list constants used by multiple entries ──
98// These are static slices so the FunctionEntry::arg_types pointer
99// stays the same across overloads sharing identical signatures.
100// The compiler interns the slice symbols so there's no duplicate
101// storage even if multiple entries reference the same array.
102
103const ARGS_INT: &[DataType] = &[DataType::Integer];
104const ARGS_FLOAT: &[DataType] = &[DataType::Float];
105const ARGS_BIGINT: &[DataType] = &[DataType::BigInt];
106const ARGS_TEXT: &[DataType] = &[DataType::Text];
107const ARGS_TWO_TEXT: &[DataType] = &[DataType::Text, DataType::Text];
108const ARGS_TEXT_INT: &[DataType] = &[DataType::Text, DataType::Integer];
109const ARGS_TEXT_TWO_INT: &[DataType] = &[DataType::Text, DataType::Integer, DataType::Integer];
110const ARGS_NONE: &[DataType] = &[];
111const ARGS_TWO_FLOATS: &[DataType] = &[DataType::Float, DataType::Float];
112const ARGS_GEO_PAIR: &[DataType] = &[DataType::GeoPoint, DataType::GeoPoint];
113const ARGS_FOUR_FLOATS: &[DataType] = &[
114    DataType::Float,
115    DataType::Float,
116    DataType::Float,
117    DataType::Float,
118];
119const ARGS_TIME_BUCKET: &[DataType] = &[DataType::Text, DataType::Timestamp];
120const ARGS_VERIFY_PWD: &[DataType] = &[DataType::Password, DataType::Text];
121const ARGS_MONEY: &[DataType] = &[DataType::Money];
122const ARGS_ONE_TEXT: &[DataType] = &[DataType::Text];
123const ARGS_TWO_TEXT_ANY: &[DataType] = &[DataType::Text, DataType::Text];
124
125// JSON function signatures (Phase 1.4 PG parity).
126//
127// RedDB accepts both Text and Json inputs interchangeably — the evaluator
128// parses text on the fly. We list the Text overload in the catalog so
129// existing text-column calls resolve without a cast; an explicit Json
130// overload is included for schemas that actually use the Json type.
131const ARGS_JSON_TEXT: &[DataType] = &[DataType::Text];
132const ARGS_JSON_JSON: &[DataType] = &[DataType::Json];
133const ARGS_JSON_EXTRACT_TEXT: &[DataType] = &[DataType::Text, DataType::Text];
134const ARGS_JSON_EXTRACT_JSON: &[DataType] = &[DataType::Json, DataType::Text];
135const ARGS_CONTAINS_TEXT: &[DataType] = &[DataType::Text, DataType::Text];
136const ARGS_CONTAINS_JSON: &[DataType] = &[DataType::Json, DataType::Text];
137const ARGS_CONTAINS_ARRAY: &[DataType] = &[DataType::Array, DataType::Text];
138// JSON_SET takes (json, path, value). Value type is Unknown — the evaluator
139// coerces any scalar at runtime (int, float, bool, text, or another json).
140const ARGS_JSON_SET_TEXT: &[DataType] = &[DataType::Text, DataType::Text, DataType::Unknown];
141const ARGS_JSON_SET_JSON: &[DataType] = &[DataType::Json, DataType::Text, DataType::Unknown];
142
143/// The static function catalog. Append-only; removing a row is a
144/// breaking change that may invalidate cached plans referencing
145/// the function. Each block is grouped by category for readability.
146pub const FUNCTION_CATALOG: &[FunctionEntry] = &[
147    // ─────────────────────────────────────────────────────────────
148    // Aggregate functions
149    // ─────────────────────────────────────────────────────────────
150    //
151    // COUNT(*) and COUNT(col) are both modelled as `Integer →
152    // Integer` here; the parser distinguishes the star form via
153    // a separate Projection variant so the catalog doesn't need
154    // a magic asterisk overload.
155    entry(
156        "COUNT",
157        ARGS_INT,
158        DataType::Integer,
159        FunctionKind::Aggregate,
160        false,
161    ),
162    entry(
163        "COUNT",
164        ARGS_TEXT,
165        DataType::Integer,
166        FunctionKind::Aggregate,
167        false,
168    ),
169    entry(
170        "COUNT",
171        ARGS_FLOAT,
172        DataType::Integer,
173        FunctionKind::Aggregate,
174        false,
175    ),
176    entry(
177        "SUM",
178        ARGS_INT,
179        DataType::Integer,
180        FunctionKind::Aggregate,
181        false,
182    ),
183    entry(
184        "SUM",
185        ARGS_BIGINT,
186        DataType::BigInt,
187        FunctionKind::Aggregate,
188        false,
189    ),
190    entry(
191        "SUM",
192        ARGS_FLOAT,
193        DataType::Float,
194        FunctionKind::Aggregate,
195        false,
196    ),
197    entry(
198        "AVG",
199        ARGS_INT,
200        DataType::Float,
201        FunctionKind::Aggregate,
202        false,
203    ),
204    entry(
205        "AVG",
206        ARGS_FLOAT,
207        DataType::Float,
208        FunctionKind::Aggregate,
209        false,
210    ),
211    entry(
212        "MIN",
213        ARGS_INT,
214        DataType::Integer,
215        FunctionKind::Aggregate,
216        false,
217    ),
218    entry(
219        "MIN",
220        ARGS_FLOAT,
221        DataType::Float,
222        FunctionKind::Aggregate,
223        false,
224    ),
225    entry(
226        "MIN",
227        ARGS_TEXT,
228        DataType::Text,
229        FunctionKind::Aggregate,
230        false,
231    ),
232    entry(
233        "MAX",
234        ARGS_INT,
235        DataType::Integer,
236        FunctionKind::Aggregate,
237        false,
238    ),
239    entry(
240        "MAX",
241        ARGS_FLOAT,
242        DataType::Float,
243        FunctionKind::Aggregate,
244        false,
245    ),
246    entry(
247        "MAX",
248        ARGS_TEXT,
249        DataType::Text,
250        FunctionKind::Aggregate,
251        false,
252    ),
253    entry(
254        "STDDEV",
255        ARGS_FLOAT,
256        DataType::Float,
257        FunctionKind::Aggregate,
258        false,
259    ),
260    entry(
261        "VARIANCE",
262        ARGS_FLOAT,
263        DataType::Float,
264        FunctionKind::Aggregate,
265        false,
266    ),
267    entry(
268        "GROUP_CONCAT",
269        ARGS_TWO_TEXT,
270        DataType::Text,
271        FunctionKind::Aggregate,
272        false,
273    ),
274    entry(
275        "STRING_AGG",
276        ARGS_TWO_TEXT,
277        DataType::Text,
278        FunctionKind::Aggregate,
279        false,
280    ),
281    // ─────────────────────────────────────────────────────────────
282    // Scalar — string
283    // ─────────────────────────────────────────────────────────────
284    entry(
285        "UPPER",
286        ARGS_TEXT,
287        DataType::Text,
288        FunctionKind::Scalar,
289        false,
290    ),
291    entry(
292        "LOWER",
293        ARGS_TEXT,
294        DataType::Text,
295        FunctionKind::Scalar,
296        false,
297    ),
298    entry(
299        "LENGTH",
300        ARGS_TEXT,
301        DataType::Integer,
302        FunctionKind::Scalar,
303        false,
304    ),
305    entry(
306        "CHAR_LENGTH",
307        ARGS_TEXT,
308        DataType::Integer,
309        FunctionKind::Scalar,
310        false,
311    ),
312    entry(
313        "CHARACTER_LENGTH",
314        ARGS_TEXT,
315        DataType::Integer,
316        FunctionKind::Scalar,
317        false,
318    ),
319    entry(
320        "OCTET_LENGTH",
321        ARGS_TEXT,
322        DataType::Integer,
323        FunctionKind::Scalar,
324        false,
325    ),
326    entry(
327        "BIT_LENGTH",
328        ARGS_TEXT,
329        DataType::Integer,
330        FunctionKind::Scalar,
331        false,
332    ),
333    entry(
334        "SUBSTRING",
335        ARGS_TWO_TEXT,
336        DataType::Text,
337        FunctionKind::Scalar,
338        false,
339    ),
340    entry(
341        "SUBSTRING",
342        ARGS_TEXT_INT,
343        DataType::Text,
344        FunctionKind::Scalar,
345        false,
346    ),
347    entry(
348        "SUBSTRING",
349        ARGS_TEXT_TWO_INT,
350        DataType::Text,
351        FunctionKind::Scalar,
352        false,
353    ),
354    entry(
355        "SUBSTR",
356        ARGS_TEXT_INT,
357        DataType::Text,
358        FunctionKind::Scalar,
359        false,
360    ),
361    entry(
362        "SUBSTR",
363        ARGS_TEXT_TWO_INT,
364        DataType::Text,
365        FunctionKind::Scalar,
366        false,
367    ),
368    entry(
369        "POSITION",
370        ARGS_TWO_TEXT,
371        DataType::Integer,
372        FunctionKind::Scalar,
373        false,
374    ),
375    entry(
376        "TRIM",
377        ARGS_TEXT,
378        DataType::Text,
379        FunctionKind::Scalar,
380        false,
381    ),
382    entry(
383        "TRIM",
384        ARGS_TWO_TEXT,
385        DataType::Text,
386        FunctionKind::Scalar,
387        false,
388    ),
389    entry(
390        "LTRIM",
391        ARGS_TEXT,
392        DataType::Text,
393        FunctionKind::Scalar,
394        false,
395    ),
396    entry(
397        "LTRIM",
398        ARGS_TWO_TEXT,
399        DataType::Text,
400        FunctionKind::Scalar,
401        false,
402    ),
403    entry(
404        "RTRIM",
405        ARGS_TEXT,
406        DataType::Text,
407        FunctionKind::Scalar,
408        false,
409    ),
410    entry(
411        "RTRIM",
412        ARGS_TWO_TEXT,
413        DataType::Text,
414        FunctionKind::Scalar,
415        false,
416    ),
417    entry(
418        "BTRIM",
419        ARGS_TEXT,
420        DataType::Text,
421        FunctionKind::Scalar,
422        false,
423    ),
424    entry(
425        "BTRIM",
426        ARGS_TWO_TEXT,
427        DataType::Text,
428        FunctionKind::Scalar,
429        false,
430    ),
431    entry(
432        "CONCAT",
433        ARGS_TEXT,
434        DataType::Text,
435        FunctionKind::Scalar,
436        true,
437    ),
438    entry(
439        "CONCAT_WS",
440        ARGS_TEXT,
441        DataType::Text,
442        FunctionKind::Scalar,
443        true,
444    ),
445    entry(
446        "REVERSE",
447        ARGS_TEXT,
448        DataType::Text,
449        FunctionKind::Scalar,
450        false,
451    ),
452    entry(
453        "LEFT",
454        ARGS_TEXT_INT,
455        DataType::Text,
456        FunctionKind::Scalar,
457        false,
458    ),
459    entry(
460        "RIGHT",
461        ARGS_TEXT_INT,
462        DataType::Text,
463        FunctionKind::Scalar,
464        false,
465    ),
466    entry(
467        "QUOTE_LITERAL",
468        ARGS_TEXT,
469        DataType::Text,
470        FunctionKind::Scalar,
471        false,
472    ),
473    // COALESCE is variadic over a uniform element type. The
474    // resolver matches each call-site arg against arg_types[0]
475    // (any concrete type), and the return type is propagated
476    // from the first non-null argument's type at typing time.
477    entry(
478        "COALESCE",
479        ARGS_TEXT,
480        DataType::Text,
481        FunctionKind::Scalar,
482        true,
483    ),
484    // ─────────────────────────────────────────────────────────────
485    // Scalar — math
486    // ─────────────────────────────────────────────────────────────
487    entry(
488        "ABS",
489        ARGS_INT,
490        DataType::Integer,
491        FunctionKind::Scalar,
492        false,
493    ),
494    entry(
495        "ABS",
496        ARGS_FLOAT,
497        DataType::Float,
498        FunctionKind::Scalar,
499        false,
500    ),
501    entry(
502        "ROUND",
503        ARGS_FLOAT,
504        DataType::Float,
505        FunctionKind::Scalar,
506        false,
507    ),
508    entry(
509        "FLOOR",
510        ARGS_FLOAT,
511        DataType::Float,
512        FunctionKind::Scalar,
513        false,
514    ),
515    entry(
516        "CEIL",
517        ARGS_FLOAT,
518        DataType::Float,
519        FunctionKind::Scalar,
520        false,
521    ),
522    entry(
523        "SQRT",
524        ARGS_FLOAT,
525        DataType::Float,
526        FunctionKind::Scalar,
527        false,
528    ),
529    entry(
530        "POWER",
531        ARGS_TWO_FLOATS,
532        DataType::Float,
533        FunctionKind::Scalar,
534        false,
535    ),
536    entry(
537        "POW",
538        ARGS_TWO_FLOATS,
539        DataType::Float,
540        FunctionKind::Scalar,
541        false,
542    ),
543    entry(
544        "EXP",
545        ARGS_FLOAT,
546        DataType::Float,
547        FunctionKind::Scalar,
548        false,
549    ),
550    entry(
551        "LN",
552        ARGS_FLOAT,
553        DataType::Float,
554        FunctionKind::Scalar,
555        false,
556    ),
557    entry(
558        "LOG",
559        ARGS_FLOAT,
560        DataType::Float,
561        FunctionKind::Scalar,
562        false,
563    ),
564    entry(
565        "LOG",
566        ARGS_TWO_FLOATS,
567        DataType::Float,
568        FunctionKind::Scalar,
569        false,
570    ),
571    entry(
572        "LOG10",
573        ARGS_FLOAT,
574        DataType::Float,
575        FunctionKind::Scalar,
576        false,
577    ),
578    entry(
579        "SIN",
580        ARGS_FLOAT,
581        DataType::Float,
582        FunctionKind::Scalar,
583        false,
584    ),
585    entry(
586        "COS",
587        ARGS_FLOAT,
588        DataType::Float,
589        FunctionKind::Scalar,
590        false,
591    ),
592    entry(
593        "TAN",
594        ARGS_FLOAT,
595        DataType::Float,
596        FunctionKind::Scalar,
597        false,
598    ),
599    entry(
600        "ASIN",
601        ARGS_FLOAT,
602        DataType::Float,
603        FunctionKind::Scalar,
604        false,
605    ),
606    entry(
607        "ARCSIN",
608        ARGS_FLOAT,
609        DataType::Float,
610        FunctionKind::Scalar,
611        false,
612    ),
613    entry(
614        "ACOS",
615        ARGS_FLOAT,
616        DataType::Float,
617        FunctionKind::Scalar,
618        false,
619    ),
620    entry(
621        "ARCCOS",
622        ARGS_FLOAT,
623        DataType::Float,
624        FunctionKind::Scalar,
625        false,
626    ),
627    entry(
628        "ATAN",
629        ARGS_FLOAT,
630        DataType::Float,
631        FunctionKind::Scalar,
632        false,
633    ),
634    entry(
635        "ARCTAN",
636        ARGS_FLOAT,
637        DataType::Float,
638        FunctionKind::Scalar,
639        false,
640    ),
641    entry(
642        "ATAN2",
643        ARGS_TWO_FLOATS,
644        DataType::Float,
645        FunctionKind::Scalar,
646        false,
647    ),
648    entry(
649        "COT",
650        ARGS_FLOAT,
651        DataType::Float,
652        FunctionKind::Scalar,
653        false,
654    ),
655    entry(
656        "DEGREES",
657        ARGS_FLOAT,
658        DataType::Float,
659        FunctionKind::Scalar,
660        false,
661    ),
662    entry(
663        "RADIANS",
664        ARGS_FLOAT,
665        DataType::Float,
666        FunctionKind::Scalar,
667        false,
668    ),
669    entry(
670        "PI",
671        ARGS_NONE,
672        DataType::Float,
673        FunctionKind::Scalar,
674        false,
675    ),
676    // ─────────────────────────────────────────────────────────────
677    // Scalar — time
678    // ─────────────────────────────────────────────────────────────
679    //
680    // NOW / CURRENT_TIMESTAMP / CURRENT_DATE are no-arg volatile
681    // scalars that read the wall clock at evaluation time. The
682    // planner must not constant-fold them across rows.
683    entry(
684        "NOW",
685        ARGS_NONE,
686        DataType::TimestampMs,
687        FunctionKind::Volatile,
688        false,
689    ),
690    entry(
691        "CURRENT_TIMESTAMP",
692        ARGS_NONE,
693        DataType::TimestampMs,
694        FunctionKind::Volatile,
695        false,
696    ),
697    entry(
698        "CURRENT_DATE",
699        ARGS_NONE,
700        DataType::Date,
701        FunctionKind::Volatile,
702        false,
703    ),
704    // Phase 2.5.3 multi-tenancy + PG session identity scalars. Marked
705    // Volatile so the planner cannot constant-fold them across rows
706    // — the thread-local value is hot-swappable per statement.
707    entry(
708        "CURRENT_TENANT",
709        ARGS_NONE,
710        DataType::Text,
711        FunctionKind::Volatile,
712        false,
713    ),
714    entry(
715        "CURRENT_USER",
716        ARGS_NONE,
717        DataType::Text,
718        FunctionKind::Volatile,
719        false,
720    ),
721    entry(
722        "SESSION_USER",
723        ARGS_NONE,
724        DataType::Text,
725        FunctionKind::Volatile,
726        false,
727    ),
728    entry(
729        "CURRENT_ROLE",
730        ARGS_NONE,
731        DataType::Text,
732        FunctionKind::Volatile,
733        false,
734    ),
735    entry(
736        "TIME_BUCKET",
737        ARGS_TIME_BUCKET,
738        DataType::TimestampMs,
739        FunctionKind::Scalar,
740        false,
741    ),
742    // ─────────────────────────────────────────────────────────────
743    // Scalar — geo
744    // ─────────────────────────────────────────────────────────────
745    entry(
746        "GEO_DISTANCE",
747        ARGS_GEO_PAIR,
748        DataType::Float,
749        FunctionKind::Scalar,
750        false,
751    ),
752    entry(
753        "GEO_DISTANCE",
754        ARGS_FOUR_FLOATS,
755        DataType::Float,
756        FunctionKind::Scalar,
757        false,
758    ),
759    entry(
760        "GEO_BEARING",
761        ARGS_FOUR_FLOATS,
762        DataType::Float,
763        FunctionKind::Scalar,
764        false,
765    ),
766    entry(
767        "HAVERSINE",
768        ARGS_FOUR_FLOATS,
769        DataType::Float,
770        FunctionKind::Scalar,
771        false,
772    ),
773    entry(
774        "VINCENTY",
775        ARGS_FOUR_FLOATS,
776        DataType::Float,
777        FunctionKind::Scalar,
778        false,
779    ),
780    // ─────────────────────────────────────────────────────────────
781    // Scalar — security
782    // ─────────────────────────────────────────────────────────────
783    //
784    // VERIFY_PASSWORD takes a hashed Password column + a candidate
785    // plaintext Text and returns Boolean. Marked Volatile because
786    // the underlying argon2id verify is intentionally slow and the
787    // planner should not cache results.
788    entry(
789        "VERIFY_PASSWORD",
790        ARGS_VERIFY_PWD,
791        DataType::Boolean,
792        FunctionKind::Volatile,
793        false,
794    ),
795    entry(
796        "MONEY",
797        ARGS_ONE_TEXT,
798        DataType::Money,
799        FunctionKind::Scalar,
800        false,
801    ),
802    entry(
803        "MONEY",
804        ARGS_TWO_TEXT_ANY,
805        DataType::Money,
806        FunctionKind::Scalar,
807        false,
808    ),
809    entry(
810        "MONEY_ASSET",
811        ARGS_MONEY,
812        DataType::AssetCode,
813        FunctionKind::Scalar,
814        false,
815    ),
816    entry(
817        "MONEY_MINOR",
818        ARGS_MONEY,
819        DataType::BigInt,
820        FunctionKind::Scalar,
821        false,
822    ),
823    entry(
824        "MONEY_SCALE",
825        ARGS_MONEY,
826        DataType::Integer,
827        FunctionKind::Scalar,
828        false,
829    ),
830    // ─────────────────────────────────────────────────────────────
831    // Scalar — JSON (Phase 1.4 PG parity)
832    // ─────────────────────────────────────────────────────────────
833    //
834    // JSON_EXTRACT(json_or_text, path) → Text
835    //   Returns the value at `path` serialised as JSON text. Scalar strings
836    //   come back with surrounding quotes; scalar numbers/booleans come back
837    //   as bare tokens. Returns NULL when the path does not resolve.
838    entry(
839        "JSON_EXTRACT",
840        ARGS_JSON_EXTRACT_TEXT,
841        DataType::Text,
842        FunctionKind::Scalar,
843        false,
844    ),
845    entry(
846        "JSON_EXTRACT",
847        ARGS_JSON_EXTRACT_JSON,
848        DataType::Text,
849        FunctionKind::Scalar,
850        false,
851    ),
852    // JSON_EXTRACT_TEXT(json_or_text, path) → Text
853    //   Same as JSON_EXTRACT but strings come back unquoted (PG `->>` style).
854    entry(
855        "JSON_EXTRACT_TEXT",
856        ARGS_JSON_EXTRACT_TEXT,
857        DataType::Text,
858        FunctionKind::Scalar,
859        false,
860    ),
861    entry(
862        "JSON_EXTRACT_TEXT",
863        ARGS_JSON_EXTRACT_JSON,
864        DataType::Text,
865        FunctionKind::Scalar,
866        false,
867    ),
868    entry(
869        "CONTAINS",
870        ARGS_CONTAINS_TEXT,
871        DataType::Boolean,
872        FunctionKind::Scalar,
873        false,
874    ),
875    entry(
876        "CONTAINS",
877        ARGS_CONTAINS_JSON,
878        DataType::Boolean,
879        FunctionKind::Scalar,
880        false,
881    ),
882    entry(
883        "CONTAINS",
884        ARGS_CONTAINS_ARRAY,
885        DataType::Boolean,
886        FunctionKind::Scalar,
887        false,
888    ),
889    // JSON_SET(json_or_text, path, value) → Json
890    //   Immutable update — returns a new JSON blob with `value` written at `path`.
891    //   Creates missing parent objects/arrays on the path.
892    entry(
893        "JSON_SET",
894        ARGS_JSON_SET_TEXT,
895        DataType::Json,
896        FunctionKind::Scalar,
897        false,
898    ),
899    entry(
900        "JSON_SET",
901        ARGS_JSON_SET_JSON,
902        DataType::Json,
903        FunctionKind::Scalar,
904        false,
905    ),
906    // JSON_ARRAY_LENGTH(json) → Integer
907    entry(
908        "JSON_ARRAY_LENGTH",
909        ARGS_JSON_TEXT,
910        DataType::Integer,
911        FunctionKind::Scalar,
912        false,
913    ),
914    entry(
915        "JSON_ARRAY_LENGTH",
916        ARGS_JSON_JSON,
917        DataType::Integer,
918        FunctionKind::Scalar,
919        false,
920    ),
921    // JSON_TYPEOF(json) → Text ("null" | "boolean" | "number" | "string" | "array" | "object")
922    entry(
923        "JSON_TYPEOF",
924        ARGS_JSON_TEXT,
925        DataType::Text,
926        FunctionKind::Scalar,
927        false,
928    ),
929    entry(
930        "JSON_TYPEOF",
931        ARGS_JSON_JSON,
932        DataType::Text,
933        FunctionKind::Scalar,
934        false,
935    ),
936    // JSON_VALID(text) → Boolean — returns true if the argument parses.
937    entry(
938        "JSON_VALID",
939        ARGS_TEXT,
940        DataType::Boolean,
941        FunctionKind::Scalar,
942        false,
943    ),
944    // JSON_ARRAY(any*) → Json — variadic array constructor.
945    // Resolver uses arg_types[0] as the template; Unknown means "any scalar".
946    entry(
947        "JSON_ARRAY",
948        &[DataType::Unknown],
949        DataType::Json,
950        FunctionKind::Scalar,
951        true,
952    ),
953    // JSON_OBJECT(key, value, ...) → Json — variadic k/v pair object constructor.
954    entry(
955        "JSON_OBJECT",
956        &[DataType::Unknown],
957        DataType::Json,
958        FunctionKind::Scalar,
959        true,
960    ),
961];
962
963/// Look up a function by name, returning the slice of overloads
964/// (possibly empty). The resolver walks this slice and picks the
965/// best match using its own coercion logic.
966pub fn lookup(name: &str) -> Vec<&'static FunctionEntry> {
967    FUNCTION_CATALOG
968        .iter()
969        .filter(|e| e.name.eq_ignore_ascii_case(name))
970        .collect()
971}
972
973/// Resolve a function call to the best-matching overload. Returns
974/// `None` when no overload matches the call-site argument types
975/// (after implicit coercion).
976///
977/// **As of issue #82, this function delegates to
978/// `coercion_spine::resolve_function`**. The spine owns the
979/// coercion-aware overload-picking rule that used to live inline
980/// here; the catalog itself is now a pure registry of `pg_proc`-style
981/// rows. Callers that need the per-argument implicit-cast list
982/// (which the runtime evaluator must apply before dispatch) should
983/// call `coercion_spine::resolve_function` directly — this wrapper
984/// only returns the entry to preserve the existing call-site
985/// signature.
986pub fn resolve(name: &str, arg_types: &[DataType]) -> Option<&'static FunctionEntry> {
987    super::coercion_spine::resolve_function(name, arg_types).map(|(entry, _)| entry)
988}