Skip to main content

uqa_sql/catalog/
json_path.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! `PostgreSQL` identities shared by JSON path overload selection and catalog projection.
8
9use crate::{ast::FunctionDispatch, ColumnType};
10
11#[derive(Debug, Clone, Copy)]
12pub struct JsonPathFunction {
13    pub name: &'static str,
14    pub oid: i64,
15    pub argument_oids: &'static [i64],
16    pub result_oid: i64,
17    pub jsonb: bool,
18    pub as_text: bool,
19}
20
21pub const JSON_PATH_FUNCTIONS: [JsonPathFunction; 4] = [
22    JsonPathFunction {
23        name: "json_extract_path",
24        oid: 3951,
25        argument_oids: &[114, 1009],
26        result_oid: 114,
27        jsonb: false,
28        as_text: false,
29    },
30    JsonPathFunction {
31        name: "json_extract_path_text",
32        oid: 3953,
33        argument_oids: &[114, 1009],
34        result_oid: 25,
35        jsonb: false,
36        as_text: true,
37    },
38    JsonPathFunction {
39        name: "jsonb_extract_path",
40        oid: 3217,
41        argument_oids: &[3802, 1009],
42        result_oid: 3802,
43        jsonb: true,
44        as_text: false,
45    },
46    JsonPathFunction {
47        name: "jsonb_extract_path_text",
48        oid: 3940,
49        argument_oids: &[3802, 1009],
50        result_oid: 25,
51        jsonb: true,
52        as_text: true,
53    },
54];
55
56impl JsonPathFunction {
57    #[must_use]
58    pub const fn by_oid(oid: i64) -> Option<Self> {
59        let mut index = 0;
60        while index < JSON_PATH_FUNCTIONS.len() {
61            if JSON_PATH_FUNCTIONS[index].oid == oid {
62                return Some(JSON_PATH_FUNCTIONS[index]);
63            }
64            index += 1;
65        }
66        None
67    }
68
69    #[must_use]
70    pub fn lookup(name: &str) -> Option<Self> {
71        JSON_PATH_FUNCTIONS.into_iter().find(|entry| {
72            name.eq_ignore_ascii_case(entry.name)
73                || name
74                    .get(..11)
75                    .is_some_and(|prefix| prefix.eq_ignore_ascii_case("pg_catalog."))
76                    && name[11..].eq_ignore_ascii_case(entry.name)
77        })
78    }
79
80    #[must_use]
81    pub const fn input_type(self) -> ColumnType {
82        if self.jsonb {
83            ColumnType::JsonB
84        } else {
85            ColumnType::Json
86        }
87    }
88
89    #[must_use]
90    pub const fn result_type(self) -> ColumnType {
91        if self.as_text {
92            ColumnType::Text
93        } else {
94            self.input_type()
95        }
96    }
97
98    #[must_use]
99    pub const fn dispatch(self) -> FunctionDispatch {
100        FunctionDispatch::JsonPath {
101            jsonb: self.jsonb,
102            as_text: self.as_text,
103        }
104    }
105}