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