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 args,
40 distinct,
41 order_by,
42 filter,
43 ..
44 } => {
45 let mut rendered_args = args
46 .iter()
47 .map(schema_expr_text)
48 .collect::<Vec<_>>()
49 .join(", ");
50 if *distinct {
51 rendered_args = format!("DISTINCT {rendered_args}");
52 }
53 if !order_by.is_empty() {
54 let order = order_by
55 .iter()
56 .map(|order| {
57 let direction = if order.descending { " DESC" } else { "" };
58 let nulls = match order.nulls {
59 Some(crate::ast::NullsOrder::First) => " NULLS FIRST",
60 Some(crate::ast::NullsOrder::Last) => " NULLS LAST",
61 None => "",
62 };
63 format!("{}{direction}{nulls}", schema_expr_text(&order.expr))
64 })
65 .collect::<Vec<_>>()
66 .join(", ");
67 if !rendered_args.is_empty() {
68 rendered_args.push(' ');
69 }
70 rendered_args.push_str("ORDER BY ");
71 rendered_args.push_str(&order);
72 }
73 let mut rendered = format!("{name}({rendered_args})");
74 if let Some(filter) = filter {
75 write!(
76 &mut rendered,
77 " FILTER (WHERE {})",
78 schema_expr_text(filter)
79 )
80 .expect("writing to a String cannot fail");
81 }
82 rendered
83 }
84 Expr::Array(items) => format!(
85 "ARRAY[{}]",
86 items
87 .iter()
88 .map(schema_expr_text)
89 .collect::<Vec<_>>()
90 .join(", ")
91 ),
92 Expr::Row(items) => format!(
93 "ROW({})",
94 items
95 .iter()
96 .map(schema_expr_text)
97 .collect::<Vec<_>>()
98 .join(", ")
99 ),
100 Expr::Binary { op, lhs, rhs } => format!(
101 "({} {} {})",
102 schema_expr_text(lhs),
103 match op {
104 crate::ast::BinaryOp::Equal => "=",
105 crate::ast::BinaryOp::NotEqual => "<>",
106 crate::ast::BinaryOp::Less => "<",
107 crate::ast::BinaryOp::LessEqual => "<=",
108 crate::ast::BinaryOp::Greater => ">",
109 crate::ast::BinaryOp::GreaterEqual => ">=",
110 crate::ast::BinaryOp::Add => "+",
111 crate::ast::BinaryOp::Subtract => "-",
112 crate::ast::BinaryOp::Multiply => "*",
113 crate::ast::BinaryOp::Divide => "/",
114 },
115 schema_expr_text(rhs)
116 ),
117 Expr::Not(inner) => format!("(NOT {})", schema_expr_text(inner)),
118 Expr::UnaryMinus(inner) => format!("(-{})", schema_expr_text(inner)),
119 Expr::And(items) => format!(
120 "({})",
121 items
122 .iter()
123 .map(schema_expr_text)
124 .collect::<Vec<_>>()
125 .join(" AND ")
126 ),
127 Expr::Or(items) => format!(
128 "({})",
129 items
130 .iter()
131 .map(schema_expr_text)
132 .collect::<Vec<_>>()
133 .join(" OR ")
134 ),
135 Expr::IsNull { expr, negated } => format!(
136 "({} IS {}NULL)",
137 schema_expr_text(expr),
138 if *negated { "NOT " } else { "" }
139 ),
140 Expr::Between { expr, low, high } => format!(
141 "({} BETWEEN {} AND {})",
142 schema_expr_text(expr),
143 schema_expr_text(low),
144 schema_expr_text(high)
145 ),
146 Expr::InList {
147 expr,
148 list,
149 negated,
150 } => format!(
151 "({} {}IN ({}))",
152 schema_expr_text(expr),
153 if *negated { "NOT " } else { "" },
154 list.iter()
155 .map(schema_expr_text)
156 .collect::<Vec<_>>()
157 .join(", ")
158 ),
159 Expr::WindowCall { name, args, .. } => format!(
160 "{}({}) OVER (...)",
161 name,
162 args.iter()
163 .map(schema_expr_text)
164 .collect::<Vec<_>>()
165 .join(", ")
166 ),
167 Expr::Case {
168 base,
169 when,
170 else_branch,
171 } => {
172 let mut rendered = "CASE".to_string();
173 if let Some(base) = base {
174 rendered.push(' ');
175 rendered.push_str(&schema_expr_text(base));
176 }
177 for (condition, result) in when {
178 write!(
179 &mut rendered,
180 " WHEN {} THEN {}",
181 schema_expr_text(condition),
182 schema_expr_text(result)
183 )
184 .expect("writing to a String cannot fail");
185 }
186 if let Some(else_branch) = else_branch {
187 write!(&mut rendered, " ELSE {}", schema_expr_text(else_branch))
188 .expect("writing to a String cannot fail");
189 }
190 rendered.push_str(" END");
191 rendered
192 }
193 Expr::Cast { expr, ty } => format!("({})::{ty}", schema_expr_text(expr)),
194 Expr::ScalarSubquery(body) => format!("({body:?})"),
195 Expr::Exists { body, negated } => {
196 format!("{}EXISTS ({body:?})", if *negated { "NOT " } else { "" })
197 }
198 Expr::InSubquery {
199 expr,
200 body,
201 negated,
202 } => format!(
203 "({} {}IN ({body:?}))",
204 schema_expr_text(expr),
205 if *negated { "NOT " } else { "" }
206 ),
207 }
208}
209
210fn schema_literal_text(value: &Value) -> String {
211 match value {
212 Value::Null => "NULL".into(),
213 Value::Void => "''::void".into(),
214 Value::Bool(value) => if *value { "true" } else { "false" }.into(),
215 Value::Int(value) => value.to_string(),
216 Value::Float(value) if value.is_finite() => value.to_string(),
217 Value::Float(value) => format!("'{value}'::double precision"),
218 Value::Str(value) | Value::FixedChar(value) => {
219 format!("'{}'", value.replace('\'', "''"))
220 }
221 Value::Bytes(value) => {
222 let mut hex = String::new();
223 for byte in value {
224 write!(&mut hex, "{byte:02x}").expect("writing to a String cannot fail");
225 }
226 format!("'\\x{hex}'::bytea")
227 }
228 Value::Temporal(value) => format!("'{value:?}'"),
229 Value::Decimal(value) => format!("{value:?}"),
230 Value::Json(value) => format!("'{}'::json", value.replace('\'', "''")),
231 Value::JsonB(value) => format!("'{}'::jsonb", value.replace('\'', "''")),
232 Value::Array(array)
233 if array
234 .lower_bounds()
235 .iter()
236 .any(|lower_bound| *lower_bound != 1) =>
237 {
238 format!(
239 "'{}'",
240 crate::expr::array_value_to_string(array).replace('\'', "''")
241 )
242 }
243 Value::Array(array) => format!(
244 "ARRAY[{}]",
245 array
246 .elements()
247 .iter()
248 .map(schema_literal_text)
249 .collect::<Vec<_>>()
250 .join(", ")
251 ),
252 Value::List(values) => format!(
253 "ARRAY[{}]",
254 values
255 .iter()
256 .map(schema_literal_text)
257 .collect::<Vec<_>>()
258 .join(", ")
259 ),
260 Value::Row(values) => format!(
261 "ROW({})",
262 values
263 .iter()
264 .map(schema_literal_text)
265 .collect::<Vec<_>>()
266 .join(", ")
267 ),
268 Value::Record(fields) => format!(
269 "ROW({})",
270 fields
271 .iter()
272 .map(|(_, value)| schema_literal_text(value))
273 .collect::<Vec<_>>()
274 .join(", ")
275 ),
276 Value::Map(value) => format!(
277 "'{}'::jsonb",
278 serde_json::to_string(value)
279 .expect("serializing an in-memory Value map cannot fail")
280 .replace('\'', "''")
281 ),
282 }
283}