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