uqa_sql/catalog/node_tree/
deparse.rs1use 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 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}