1use std::fmt::Write as _;
10
11use crate::ast::Expr;
12use uqa_core::Value;
13
14pub fn default_expr_text(expr: Option<&Expr>) -> Value {
15 expr.map_or(Value::Null, |expr| Value::Str(schema_expr_text(expr)))
16}
17
18#[expect(
19 clippy::too_many_lines,
20 reason = "preserves catalog column and OID order"
21)]
22pub fn schema_expr_text(expr: &Expr) -> String {
23 match expr {
24 Expr::Star => "*".into(),
25 Expr::QualifiedStar(qualifier) => format!("{qualifier}.*"),
26 Expr::Default => "DEFAULT".into(),
27 Expr::Column(name) => name.clone(),
28 Expr::QualifiedColumn {
29 qualifier, column, ..
30 } => format!("{qualifier}.{column}"),
31 Expr::InternalColumn(column) => {
32 unreachable!("executor-only column {column:?} reached catalog SQL rendering")
33 }
34 Expr::Literal(value) => schema_literal_text(value),
35 Expr::TypedLiteral { value, ty } => format!("({})::{ty}", schema_literal_text(value)),
36 Expr::Param(index) => format!("${index}"),
37 Expr::Func {
38 name,
39 binding,
40 args,
41 distinct,
42 order_by,
43 filter,
44 ..
45 } => {
46 if let Some(crate::ast::FunctionDispatch::NumericOperator(operator)) =
47 binding.as_ref().and_then(|binding| binding.dispatch)
48 {
49 match args.as_slice() {
50 [argument] if operator.arity() == 1 => {
51 return format!("({} {})", operator.symbol(), schema_expr_text(argument))
52 }
53 [left, right] if operator.arity() == 2 => {
54 return format!(
55 "({} {} {})",
56 schema_expr_text(left),
57 operator.symbol(),
58 schema_expr_text(right)
59 )
60 }
61 _ => {}
62 }
63 }
64 let mut rendered_args = args
65 .iter()
66 .map(schema_expr_text)
67 .collect::<Vec<_>>()
68 .join(", ");
69 if *distinct {
70 rendered_args = format!("DISTINCT {rendered_args}");
71 }
72 if !order_by.is_empty() {
73 let order = order_by
74 .iter()
75 .map(|order| {
76 let direction = if order.descending { " DESC" } else { "" };
77 let nulls = match order.nulls {
78 Some(crate::ast::NullsOrder::First) => " NULLS FIRST",
79 Some(crate::ast::NullsOrder::Last) => " NULLS LAST",
80 None => "",
81 };
82 format!("{}{direction}{nulls}", schema_expr_text(&order.expr))
83 })
84 .collect::<Vec<_>>()
85 .join(", ");
86 if !rendered_args.is_empty() {
87 rendered_args.push(' ');
88 }
89 rendered_args.push_str("ORDER BY ");
90 rendered_args.push_str(&order);
91 }
92 let mut rendered = format!("{name}({rendered_args})");
93 if let Some(filter) = filter {
94 write!(
95 &mut rendered,
96 " FILTER (WHERE {})",
97 schema_expr_text(filter)
98 )
99 .expect("writing to a String cannot fail");
100 }
101 rendered
102 }
103 Expr::Array(items) => format!(
104 "ARRAY[{}]",
105 items
106 .iter()
107 .map(schema_expr_text)
108 .collect::<Vec<_>>()
109 .join(", ")
110 ),
111 Expr::Row(items) => format!(
112 "ROW({})",
113 items
114 .iter()
115 .map(schema_expr_text)
116 .collect::<Vec<_>>()
117 .join(", ")
118 ),
119 Expr::Binary { op, lhs, rhs } => format!(
120 "({} {} {})",
121 schema_expr_text(lhs),
122 match op {
123 crate::ast::BinaryOp::Equal => "=",
124 crate::ast::BinaryOp::NotEqual => "<>",
125 crate::ast::BinaryOp::Less => "<",
126 crate::ast::BinaryOp::LessEqual => "<=",
127 crate::ast::BinaryOp::Greater => ">",
128 crate::ast::BinaryOp::GreaterEqual => ">=",
129 crate::ast::BinaryOp::Add => "+",
130 crate::ast::BinaryOp::Subtract => "-",
131 crate::ast::BinaryOp::Multiply => "*",
132 crate::ast::BinaryOp::Divide => "/",
133 },
134 schema_expr_text(rhs)
135 ),
136 Expr::Not(inner) => format!("(NOT {})", schema_expr_text(inner)),
137 Expr::UnaryMinus(inner) => format!("(-{})", schema_expr_text(inner)),
138 Expr::And(items) => format!(
139 "({})",
140 items
141 .iter()
142 .map(schema_expr_text)
143 .collect::<Vec<_>>()
144 .join(" AND ")
145 ),
146 Expr::Or(items) => format!(
147 "({})",
148 items
149 .iter()
150 .map(schema_expr_text)
151 .collect::<Vec<_>>()
152 .join(" OR ")
153 ),
154 Expr::IsNull { expr, negated } => format!(
155 "({} IS {}NULL)",
156 schema_expr_text(expr),
157 if *negated { "NOT " } else { "" }
158 ),
159 Expr::Between { expr, low, high } => format!(
160 "({} BETWEEN {} AND {})",
161 schema_expr_text(expr),
162 schema_expr_text(low),
163 schema_expr_text(high)
164 ),
165 Expr::InList {
166 expr,
167 list,
168 negated,
169 } => format!(
170 "({} {}IN ({}))",
171 schema_expr_text(expr),
172 if *negated { "NOT " } else { "" },
173 list.iter()
174 .map(schema_expr_text)
175 .collect::<Vec<_>>()
176 .join(", ")
177 ),
178 Expr::WindowCall { name, args, .. } => format!(
179 "{}({}) OVER (...)",
180 name,
181 args.iter()
182 .map(schema_expr_text)
183 .collect::<Vec<_>>()
184 .join(", ")
185 ),
186 Expr::Case {
187 base,
188 when,
189 else_branch,
190 } => {
191 let mut rendered = "CASE".to_string();
192 if let Some(base) = base {
193 rendered.push(' ');
194 rendered.push_str(&schema_expr_text(base));
195 }
196 for (condition, result) in when {
197 write!(
198 &mut rendered,
199 " WHEN {} THEN {}",
200 schema_expr_text(condition),
201 schema_expr_text(result)
202 )
203 .expect("writing to a String cannot fail");
204 }
205 if let Some(else_branch) = else_branch {
206 write!(&mut rendered, " ELSE {}", schema_expr_text(else_branch))
207 .expect("writing to a String cannot fail");
208 }
209 rendered.push_str(" END");
210 rendered
211 }
212 Expr::Cast { expr, ty } => format!("({})::{ty}", schema_expr_text(expr)),
213 Expr::ScalarSubquery(body) => format!("({body:?})"),
214 Expr::Exists { body, negated } => {
215 format!("{}EXISTS ({body:?})", if *negated { "NOT " } else { "" })
216 }
217 Expr::InSubquery {
218 expr,
219 body,
220 negated,
221 } => format!(
222 "({} {}IN ({body:?}))",
223 schema_expr_text(expr),
224 if *negated { "NOT " } else { "" }
225 ),
226 }
227}
228
229fn schema_literal_text(value: &Value) -> String {
230 match value {
231 Value::Null => "NULL".into(),
232 Value::Void => "''::void".into(),
233 Value::Bool(value) => if *value { "true" } else { "false" }.into(),
234 Value::Int(value) => value.to_string(),
235 Value::Float(value) if value.is_finite() => value.to_string(),
236 Value::Float(value) => format!("'{value}'::double precision"),
237 Value::Str(value) | Value::FixedChar(value) => {
238 format!("'{}'", value.replace('\'', "''"))
239 }
240 Value::Bytes(value) => {
241 let mut hex = String::new();
242 for byte in value {
243 write!(&mut hex, "{byte:02x}").expect("writing to a String cannot fail");
244 }
245 format!("'\\x{hex}'::bytea")
246 }
247 Value::Temporal(value) => format!("'{value:?}'"),
248 Value::Decimal(value) => format!("{value:?}"),
249 Value::Json(value) => format!("'{}'::json", value.replace('\'', "''")),
250 Value::JsonB(value) => format!("'{}'::jsonb", value.replace('\'', "''")),
251 Value::LegacyVector(vector) => crate::render::legacy_vector_expression(vector)
252 .expect("stored SQL vector has SQL-produced bounds"),
253 Value::Array(array)
254 if array
255 .lower_bounds()
256 .iter()
257 .any(|lower_bound| *lower_bound != 1) =>
258 {
259 format!(
260 "'{}'",
261 crate::expr::array_value_to_string(array)
262 .expect("stored array literal text")
263 .replace('\'', "''")
264 )
265 }
266 Value::Array(array) => format!(
267 "ARRAY[{}]",
268 array
269 .elements()
270 .iter()
271 .map(schema_literal_text)
272 .collect::<Vec<_>>()
273 .join(", ")
274 ),
275 Value::List(values) => format!(
276 "ARRAY[{}]",
277 values
278 .iter()
279 .map(schema_literal_text)
280 .collect::<Vec<_>>()
281 .join(", ")
282 ),
283 Value::Row(values) => format!(
284 "ROW({})",
285 values
286 .iter()
287 .map(schema_literal_text)
288 .collect::<Vec<_>>()
289 .join(", ")
290 ),
291 Value::Record(fields) => format!(
292 "ROW({})",
293 fields
294 .iter()
295 .map(|(_, value)| schema_literal_text(value))
296 .collect::<Vec<_>>()
297 .join(", ")
298 ),
299 Value::Map(value) => format!(
300 "'{}'::jsonb",
301 serde_json::to_string(value)
302 .expect("serializing an in-memory Value map cannot fail")
303 .replace('\'', "''")
304 ),
305 }
306}