Skip to main content

uqa_sql/catalog/node_tree/
deparse.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Reconstruct SQL from typed catalog nodes using the caller's catalog names.
8
9use super::{invalid, values, Field, Node};
10use crate::SQLError;
11
12mod bodies;
13mod control;
14mod operators;
15
16pub use bodies::return_body;
17
18#[cfg(test)]
19mod tests;
20
21pub trait ExpressionNames {
22    fn column(&self, attribute: i64) -> Result<String, SQLError>;
23    fn routine(&self, oid: i64) -> Result<Vec<String>, SQLError>;
24    fn type_name(&self, oid: i64, modifier: i64) -> Result<String, SQLError>;
25
26    /// SQL spelling of an external parameter after the renderer validates its positive number.
27    fn parameter(&self, number: i64) -> Result<String, SQLError> {
28        Ok(format!("${number}"))
29    }
30}
31
32pub fn expression(
33    value: &Field,
34    names: &dyn ExpressionNames,
35    pretty: bool,
36) -> Result<String, SQLError> {
37    Renderer {
38        names,
39        pretty,
40        indent: 0,
41    }
42    .field(value, pretty)
43}
44
45struct Renderer<'a> {
46    names: &'a dyn ExpressionNames,
47    pretty: bool,
48    indent: usize,
49}
50
51impl Renderer<'_> {
52    fn field(&self, value: &Field, outer: bool) -> Result<String, SQLError> {
53        match value {
54            Field::Node(node) => self.node(node, outer),
55            Field::List(values) => values
56                .iter()
57                .map(|value| self.field(value, outer))
58                .collect::<Result<Vec<_>, _>>()
59                .map(|values| values.join(", ")),
60            _ => Err(invalid("expected an expression node")),
61        }
62    }
63
64    fn node(&self, node: &Node, outer: bool) -> Result<String, SQLError> {
65        match node.kind.as_str() {
66            "COERCETODOMAINVALUE" => Ok("VALUE".into()),
67            "VAR" => {
68                if node.integer("varno")? != 1 || node.integer("varlevelsup")? != 0 {
69                    return Err(invalid("invalid varno in stored expression"));
70                }
71                self.names
72                    .column(node.integer("varattno")?)
73                    .map(|name| crate::expr::quote_ident(&name))
74            }
75            "PARAM" => self.parameter(node),
76            "CONST" => self.constant(node),
77            "OPEXPR" | "DISTINCTEXPR" => self.operator(node, outer),
78            "FUNCEXPR" => self.function(node),
79            "RELABELTYPE" => self.cast(
80                node.field("arg")?,
81                node.integer("resulttype")?,
82                node.integer("resulttypmod")?,
83            ),
84            "COERCEVIAIO" => self.cast(node.field("arg")?, node.integer("resulttype")?, -1),
85            "COERCETODOMAIN" => self.cast(
86                node.field("arg")?,
87                node.integer("resulttype")?,
88                node.integer("resulttypmod")?,
89            ),
90            "BOOLEXPR" => self.boolean(node, outer),
91            "NULLTEST" => {
92                let operator = match node.integer("nulltesttype")? {
93                    0 => "IS NULL",
94                    1 => "IS NOT NULL",
95                    _ => return Err(invalid("invalid null test")),
96                };
97                Ok(parentheses(
98                    format!("{} {operator}", self.field(node.field("arg")?, false)?),
99                    !outer,
100                ))
101            }
102            "COALESCEEXPR" | "MINMAXEXPR" => {
103                let name = match node.kind.as_str() {
104                    "COALESCEEXPR" => "COALESCE",
105                    _ => match node.integer("op")? {
106                        0 => "GREATEST",
107                        1 => "LEAST",
108                        _ => return Err(invalid("invalid minimum/maximum expression")),
109                    },
110                };
111                let arguments = list(node, "args")?
112                    .iter()
113                    .map(|arg| self.field(arg, false))
114                    .collect::<Result<Vec<_>, _>>()?;
115                Ok(format!("{name}({})", arguments.join(", ")))
116            }
117            "ARRAYEXPR" => {
118                let elements = list(node, "elements")?
119                    .iter()
120                    .map(|arg| self.field(arg, self.pretty))
121                    .collect::<Result<Vec<_>, _>>()?;
122                Ok(format!("ARRAY[{}]", elements.join(", ")))
123            }
124            "CASEEXPR" => self.case(node),
125            "SCALARARRAYOPEXPR" => {
126                let operator =
127                    crate::type_resolution::binary_operator_by_oid(node.integer("opno")?)
128                        .ok_or_else(|| invalid("unknown scalar-array operator"))?;
129                let [left, right] = list(node, "args")? else {
130                    return Err(invalid("invalid scalar-array operands"));
131                };
132                let quantifier = if node.boolean("useOr")? { "ANY" } else { "ALL" };
133                Ok(parentheses(
134                    format!(
135                        "{} {} {quantifier} ({})",
136                        self.field(left, false)?,
137                        operator.name,
138                        self.field(right, false)?
139                    ),
140                    !outer,
141                ))
142            }
143            _ => Err(SQLError::Unsupported(format!(
144                "catalog expression deparser for {}",
145                node.kind
146            ))),
147        }
148    }
149
150    fn parameter(&self, node: &Node) -> Result<String, SQLError> {
151        let number = node.integer("paramid")?;
152        if node.integer("paramkind")? != 0 || number <= 0 {
153            return Err(invalid("invalid external parameter in stored expression"));
154        }
155        self.names.parameter(number)
156    }
157
158    fn cast(&self, argument: &Field, oid: i64, modifier: i64) -> Result<String, SQLError> {
159        let text = self.field(argument, false)?;
160        let atomic = matches!(argument, Field::Node(node) if matches!(node.kind.as_str(), "VAR" | "PARAM" | "COERCETODOMAINVALUE" | "CONST" | "FUNCEXPR"));
161        Ok(format!(
162            "{}::{}",
163            parentheses(text, !self.pretty || !atomic),
164            self.names.type_name(oid, modifier)?
165        ))
166    }
167
168    fn constant(&self, node: &Node) -> Result<String, SQLError> {
169        let oid = node.integer("consttype")?;
170        let modifier = node.integer("consttypmod")?;
171        let type_name = || self.names.type_name(oid, modifier);
172        if node.boolean("constisnull")? {
173            return Ok(format!("NULL::{}", type_name()?));
174        }
175        let Field::Datum { length, bytes } = node.field("constvalue")? else {
176            return Err(invalid("constant has no Datum"));
177        };
178        if node.boolean("constbyval")? && (bytes.len() != 8 || !matches!(*length, 1 | 2 | 4 | 8)) {
179            return Err(invalid("invalid by-value Datum length"));
180        }
181        let quoted = |value: &str| -> Result<String, SQLError> {
182            Ok(format!("{}::{}", literal(value), type_name()?))
183        };
184        match oid {
185            16 => match bytes.first() {
186                Some(0) => Ok("false".into()),
187                Some(1) => Ok("true".into()),
188                _ => Err(invalid("invalid boolean Datum")),
189            },
190            20 | 21 | 23 | 26 | 28 => {
191                let value = match oid {
192                    21 => i64::from(i16::from_le_bytes(prefix(bytes)?)),
193                    23 => i64::from(i32::from_le_bytes(prefix(bytes)?)),
194                    26 | 28 => i64::from(u32::from_le_bytes(prefix(bytes)?)),
195                    _ => i64::from_le_bytes(prefix(bytes)?),
196                };
197                if oid == 23 && value >= 0 {
198                    Ok(value.to_string())
199                } else {
200                    quoted(&value.to_string())
201                }
202            }
203            700 | 701 => {
204                let value = if oid == 700 {
205                    f64::from(f32::from_le_bytes(prefix(bytes)?))
206                } else {
207                    f64::from_le_bytes(prefix(bytes)?)
208                };
209                quoted(&uqa_core::format_float_pg(value))
210            }
211            18 => quoted(
212                std::str::from_utf8(
213                    bytes
214                        .get(..1)
215                        .ok_or_else(|| invalid("empty character Datum"))?,
216                )
217                .map_err(|_| invalid("invalid character Datum"))?,
218            ),
219            19 => {
220                let end = bytes
221                    .iter()
222                    .position(|byte| *byte == 0)
223                    .unwrap_or(bytes.len());
224                quoted(
225                    std::str::from_utf8(&bytes[..end])
226                        .map_err(|_| invalid("invalid name Datum"))?,
227                )
228            }
229            25 | 1042 | 1043 | 1790 => quoted(
230                std::str::from_utf8(values::varlena_payload(*length, bytes)?)
231                    .map_err(|_| invalid("invalid string Datum"))?,
232            ),
233            1700 => {
234                let text = values::numeric::decode(values::varlena_payload(*length, bytes)?)?;
235                if text.as_bytes().first().is_some_and(u8::is_ascii_digit) && text.contains('.') {
236                    if modifier < 0 {
237                        Ok(text)
238                    } else {
239                        Ok(format!("{text}::{}", type_name()?))
240                    }
241                } else {
242                    quoted(&text)
243                }
244            }
245            1082 | 1083 | 1114 | 1184 | 1186 | 1266 => {
246                quoted(&values::temporal::decode(bytes, oid)?.to_sql_string())
247            }
248            _ => Err(SQLError::Unsupported(format!(
249                "catalog expression Datum deparser for type {oid}"
250            ))),
251        }
252    }
253}
254
255fn list<'a>(node: &'a Node, name: &str) -> Result<&'a [Field], SQLError> {
256    match node.field(name)? {
257        Field::List(values) => Ok(values),
258        Field::Null => Ok(&[]),
259        _ => Err(invalid(format!("expected node list in {name}"))),
260    }
261}
262
263fn atom<'a>(node: &'a Node, name: &str) -> Result<&'a str, SQLError> {
264    match node.field(name)? {
265        Field::Atom(value) | Field::String(value) => Ok(value),
266        _ => Err(invalid(format!("expected token in {name}"))),
267    }
268}
269
270fn prefix<const N: usize>(bytes: &[u8]) -> Result<[u8; N], SQLError> {
271    bytes
272        .get(..N)
273        .and_then(|prefix| prefix.try_into().ok())
274        .ok_or_else(|| invalid("truncated constant Datum"))
275}
276
277fn parentheses(text: String, required: bool) -> String {
278    if required {
279        format!("({text})")
280    } else {
281        text
282    }
283}
284
285fn literal(value: &str) -> String {
286    let value = value.replace('\'', "''");
287    if value.contains('\\') {
288        format!("E'{}'", value.replace('\\', "\\\\"))
289    } else {
290        format!("'{value}'")
291    }
292}