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