1use std::collections::HashSet;
10
11use crate::ast::ColumnType;
12use crate::{RowSchema, ScalarExpr, ScalarFrameBound};
13use uqa_core::Value;
14
15use crate::{plan::QueryBlockPlan, FunctionTypeResolver, SQLError, SQLParam};
16
17mod expressions;
18mod names;
19pub(crate) use names::resolve_grouping_expression_reference;
20pub use names::{bind_grouping_names, resolve_grouping_expression};
21mod validation;
22pub use validation::validate_grouped_expressions;
23#[cfg(test)]
24mod tests;
25
26pub fn prepare_grouping_sets(
28 engine: &dyn crate::routines::RoutineResolution,
29 statement: &QueryBlockPlan,
30 schema: &RowSchema,
31 params: &[SQLParam],
32) -> Result<Option<QueryBlockPlan>, SQLError> {
33 if statement.group_by.is_empty() && statement.grouping_sets.is_empty() {
34 return Ok(None);
35 }
36
37 let mut prepared = statement.clone();
38 let mut changed = bind_grouping_names(engine, &mut prepared, schema, None, params)?;
40 changed |= expressions::bind_grouping_expressions(engine, &mut prepared, schema, params)?;
41 if !prepared.group_distinct {
42 return Ok(changed.then_some(prepared));
43 }
44 prepared.group_distinct = false;
45 let mut seen = HashSet::with_capacity(prepared.grouping_sets.len());
46 let mut distinct = Vec::with_capacity(prepared.grouping_sets.len());
47 for grouping_set in std::mem::take(&mut prepared.grouping_sets) {
48 let identity = grouping_set_identity(engine, &grouping_set, schema, params)?;
49 if seen.insert(identity) {
50 distinct.push(grouping_set);
51 }
52 }
53 prepared.grouping_sets = distinct;
54 Ok(Some(prepared))
55}
56
57fn grouping_set_identity(
58 engine: &dyn FunctionTypeResolver,
59 grouping_set: &[ScalarExpr],
60 schema: &RowSchema,
61 params: &[SQLParam],
62) -> Result<Vec<Vec<u8>>, SQLError> {
63 let mut identity = grouping_set
64 .iter()
65 .map(|expression| expression_identity(engine, expression, schema, params))
66 .collect::<Result<Vec<_>, _>>()?;
67 identity.sort_unstable();
68 identity.dedup();
69 Ok(identity)
70}
71
72pub(crate) fn expression_identity(
73 engine: &dyn FunctionTypeResolver,
74 expression: &ScalarExpr,
75 schema: &RowSchema,
76 params: &[SQLParam],
77) -> Result<Vec<u8>, SQLError> {
78 let expression =
79 crate::bind_type_introspection_with_resolver(expression.clone(), schema, params, engine);
80 let expression = normalize_expression(engine, expression, schema, params)?;
81 serde_json::to_vec(&expression).map_err(|error| {
82 SQLError::Internal(format!(
83 "serialize GROUP BY DISTINCT expression identity: {error}"
84 ))
85 })
86}
87
88#[expect(
89 clippy::too_many_lines,
90 reason = "preserves SELECT schema and row identity"
91)]
92fn normalize_expression(
93 engine: &dyn FunctionTypeResolver,
94 expression: ScalarExpr,
95 schema: &RowSchema,
96 params: &[SQLParam],
97) -> Result<ScalarExpr, SQLError> {
98 Ok(match expression {
99 ScalarExpr::Column(column) => schema
100 .unqualified_position(&column)
101 .map_or(ScalarExpr::Column(column), ScalarExpr::Position),
102 ScalarExpr::QualifiedColumn { qualifier, column } => {
103 schema.qualified_position(&qualifier, &column).map_or(
104 ScalarExpr::QualifiedColumn { qualifier, column },
105 ScalarExpr::Position,
106 )
107 }
108 ScalarExpr::Func {
109 order_syntax,
110 name,
111 binding,
112 args,
113 distinct,
114 order_by,
115 filter,
116 } => {
117 let name = canonical_function_name(name);
118 let argument_types = args
119 .iter()
120 .map(|argument| expression_type(engine, argument, schema, params))
121 .collect::<Result<Vec<_>, _>>()?;
122 let targets = crate::builtin_function_argument_targets(&name, &argument_types);
123 ScalarExpr::Func {
124 order_syntax,
125 name,
126 binding,
127 args: args
128 .into_iter()
129 .zip(targets)
130 .map(|(argument, target)| {
131 normalize_unknown_literal(engine, argument, target.as_ref(), schema, params)
132 })
133 .collect::<Result<Vec<_>, _>>()?,
134 distinct,
135 order_by: order_by
136 .into_iter()
137 .map(|mut order| {
138 order.expr = normalize_expression(engine, order.expr, schema, params)?;
139 Ok(order)
140 })
141 .collect::<Result<Vec<_>, SQLError>>()?,
142 filter: filter
143 .map(|expression| {
144 normalize_expression(engine, *expression, schema, params).map(Box::new)
145 })
146 .transpose()?,
147 }
148 }
149 ScalarExpr::Array(items) => {
150 ScalarExpr::Array(normalize_items(engine, items, schema, params)?)
151 }
152 ScalarExpr::Row(items) => ScalarExpr::Row(normalize_items(engine, items, schema, params)?),
153 ScalarExpr::CompositeRow {
154 items,
155 binding,
156 bound_type,
157 } => ScalarExpr::CompositeRow {
158 items: normalize_items(engine, items, schema, params)?,
159 binding: binding.clone(),
160 bound_type: bound_type.clone(),
161 },
162 ScalarExpr::Binary { op, lhs, rhs } => {
163 let left_type = expression_type(engine, &lhs, schema, params)?;
164 let right_type = expression_type(engine, &rhs, schema, params)?;
165 ScalarExpr::Binary {
166 op,
167 lhs: Box::new(normalize_unknown_literal(
168 engine,
169 *lhs,
170 left_type.is_none().then_some(right_type.as_ref()).flatten(),
171 schema,
172 params,
173 )?),
174 rhs: Box::new(normalize_unknown_literal(
175 engine,
176 *rhs,
177 right_type.is_none().then_some(left_type.as_ref()).flatten(),
178 schema,
179 params,
180 )?),
181 }
182 }
183 ScalarExpr::UnaryMinus(expression) => {
184 let expression = normalize_expression(engine, *expression, schema, params)?;
185 if let ScalarExpr::Literal(
186 value @ (Value::Int(_) | Value::Float(_) | Value::Decimal(_)),
187 ) = &expression
188 {
189 ScalarExpr::Literal(crate::expr::negate_value(value, None)?)
190 } else {
191 ScalarExpr::UnaryMinus(Box::new(expression))
192 }
193 }
194 ScalarExpr::Not(expression) => ScalarExpr::Not(Box::new(normalize_expression(
195 engine,
196 *expression,
197 schema,
198 params,
199 )?)),
200 ScalarExpr::And(items) => ScalarExpr::And(normalize_items(engine, items, schema, params)?),
201 ScalarExpr::Or(items) => ScalarExpr::Or(normalize_items(engine, items, schema, params)?),
202 ScalarExpr::IsNull { expr, negated } => ScalarExpr::IsNull {
203 expr: Box::new(normalize_expression(engine, *expr, schema, params)?),
204 negated,
205 },
206 ScalarExpr::Between { expr, low, high } => {
207 let target = expression_type(engine, &expr, schema, params)?;
208 ScalarExpr::Between {
209 expr: Box::new(normalize_expression(engine, *expr, schema, params)?),
210 low: Box::new(normalize_unknown_literal(
211 engine,
212 *low,
213 target.as_ref(),
214 schema,
215 params,
216 )?),
217 high: Box::new(normalize_unknown_literal(
218 engine,
219 *high,
220 target.as_ref(),
221 schema,
222 params,
223 )?),
224 }
225 }
226 ScalarExpr::InList {
227 expr,
228 list,
229 negated,
230 } => {
231 let target = expression_type(engine, &expr, schema, params)?;
232 ScalarExpr::InList {
233 expr: Box::new(normalize_expression(engine, *expr, schema, params)?),
234 list: list
235 .into_iter()
236 .map(|item| {
237 normalize_unknown_literal(engine, item, target.as_ref(), schema, params)
238 })
239 .collect::<Result<Vec<_>, _>>()?,
240 negated,
241 }
242 }
243 ScalarExpr::WindowCall {
244 name,
245 args,
246 mut spec,
247 filter,
248 modifiers,
249 } => {
250 spec.partition_by = normalize_items(engine, spec.partition_by, schema, params)?;
251 for order in &mut spec.order_by {
252 order.expr = normalize_expression(engine, order.expr.clone(), schema, params)?;
253 }
254 if let Some(frame) = &mut spec.frame {
255 normalize_frame_bound(engine, &mut frame.start, schema, params)?;
256 normalize_frame_bound(engine, &mut frame.end, schema, params)?;
257 }
258 ScalarExpr::WindowCall {
259 modifiers,
260 name: canonical_function_name(name),
261 args: normalize_items(engine, args, schema, params)?,
262 spec,
263 filter: filter
264 .map(|expression| {
265 normalize_expression(engine, *expression, schema, params).map(Box::new)
266 })
267 .transpose()?,
268 }
269 }
270 ScalarExpr::Case {
271 base,
272 when,
273 else_branch,
274 } => ScalarExpr::Case {
275 base: base
276 .map(|expression| {
277 normalize_expression(engine, *expression, schema, params).map(Box::new)
278 })
279 .transpose()?,
280 when: when
281 .into_iter()
282 .map(|(condition, result)| {
283 Ok((
284 normalize_expression(engine, condition, schema, params)?,
285 normalize_expression(engine, result, schema, params)?,
286 ))
287 })
288 .collect::<Result<Vec<_>, SQLError>>()?,
289 else_branch: else_branch
290 .map(|expression| {
291 normalize_expression(engine, *expression, schema, params).map(Box::new)
292 })
293 .transpose()?,
294 },
295 ScalarExpr::Collated {
296 expr,
297 ty,
298 collation,
299 } => {
300 let mut expression = normalize_expression(engine, *expr, schema, params)?;
301 match (&mut expression, collation.implicit_oid()) {
302 (
303 ScalarExpr::TypedLiteral {
304 bound_type,
305 collation_oid,
306 ..
307 },
308 Some(oid),
309 ) if bound_type.as_ref() == Some(&ty) => {
310 *collation_oid = Some(oid);
311 normalize_expression(engine, expression, schema, params)?
312 }
313 _ => ScalarExpr::Collated {
314 expr: Box::new(expression),
315 ty,
316 collation,
317 },
318 }
319 }
320 ScalarExpr::Cast { expr, ty, .. } => {
321 let source_type = expression_type(engine, &expr, schema, params)?;
322 let target_type = crate::type_resolution::resolve_declared_column_type(
323 engine,
324 &ColumnType::Named(ty),
325 )?;
326 let expression = normalize_expression(engine, *expr, schema, params)?;
327 if source_type.as_ref() == Some(&target_type) {
328 expression
329 } else if input_requires_catalog(&target_type) {
330 ScalarExpr::Cast {
331 implicit: false,
332 expr: Box::new(expression),
333 ty: target_type.catalog_name(),
334 }
335 } else if let ScalarExpr::Literal(value @ Value::Str(_)) = &expression {
336 let input_type = if matches!(
337 target_type.without_temporal_modifiers(),
338 ColumnType::Interval
339 ) {
340 target_type.clone()
341 } else {
342 target_type.without_type_modifiers()
343 };
344 let value = crate::expr::cast_value(value, &input_type.sql_name())?;
345 let input = normalize_expression(
346 engine,
347 ScalarExpr::TypedLiteral {
348 collation_oid: None,
349 composite_source: None,
350 value,
351 ty: input_type.catalog_name(),
352 bound_type: Some(input_type.clone()),
353 parameter_index: None,
354 },
355 schema,
356 params,
357 )?;
358 if input_type == target_type {
359 input
360 } else {
361 ScalarExpr::Cast {
362 implicit: false,
363 expr: Box::new(input),
364 ty: target_type.catalog_name(),
365 }
366 }
367 } else if let ScalarExpr::Literal(Value::Null) = expression {
368 ScalarExpr::TypedLiteral {
369 collation_oid: None,
370 composite_source: None,
371 value: Value::Null,
372 ty: target_type.catalog_name(),
373 bound_type: Some(target_type),
374 parameter_index: None,
375 }
376 } else {
377 ScalarExpr::Cast {
378 implicit: false,
379 expr: Box::new(expression),
380 ty: target_type.catalog_name(),
381 }
382 }
383 }
384 ScalarExpr::InSubquery {
385 expr,
386 subquery,
387 negated,
388 } => ScalarExpr::InSubquery {
389 expr: Box::new(normalize_expression(engine, *expr, schema, params)?),
390 subquery,
391 negated,
392 },
393 ScalarExpr::TypedLiteral {
394 value,
395 ty,
396 bound_type,
397 parameter_index,
398 collation_oid,
399 composite_source,
400 } => {
401 let literal = ScalarExpr::Literal(value.clone());
402 let declared = match bound_type {
403 Some(ty) => ty,
404 None => crate::type_resolution::resolve_declared_column_type(
405 engine,
406 &ColumnType::Named(ty),
407 )?,
408 };
409 let collation_oid = collation_oid.filter(|oid| {
410 i64::from(*oid) != crate::catalog::type_metadata::pg_type_collation_oid(&declared)
411 });
412 if parameter_index.is_none()
413 && composite_source.is_none()
414 && collation_oid.is_none()
415 && expression_type(engine, &literal, schema, params)?.as_ref() == Some(&declared)
416 {
417 literal
418 } else {
419 ScalarExpr::TypedLiteral {
420 collation_oid,
421 composite_source,
422 value,
423 ty: declared.catalog_name(),
424 bound_type: Some(declared),
425 parameter_index,
426 }
427 }
428 }
429 expression @ (ScalarExpr::Star
430 | ScalarExpr::QualifiedStar(_)
431 | ScalarExpr::Default
432 | ScalarExpr::Position(_)
433 | ScalarExpr::InternalColumn(_)
434 | ScalarExpr::Literal(_)
435 | ScalarExpr::Param(_)
436 | ScalarExpr::ScalarSubquery(_)
437 | ScalarExpr::Exists { .. }) => expression,
438 })
439}
440
441fn input_requires_catalog(ty: &ColumnType) -> bool {
442 match ty {
443 ColumnType::Named(_)
444 | ColumnType::Domain { .. }
445 | ColumnType::Enum(_)
446 | ColumnType::Composite(_)
447 | ColumnType::Regproc
448 | ColumnType::Regprocedure
449 | ColumnType::Regclass
450 | ColumnType::Regcollation
451 | ColumnType::Regnamespace
452 | ColumnType::Regrole
453 | ColumnType::Regtype
454 | ColumnType::Record
455 | ColumnType::AnyArray => true,
456 ColumnType::Array(element) => input_requires_catalog(element),
457 _ => false,
458 }
459}
460
461fn normalize_items(
462 engine: &dyn FunctionTypeResolver,
463 items: Vec<ScalarExpr>,
464 schema: &RowSchema,
465 params: &[SQLParam],
466) -> Result<Vec<ScalarExpr>, SQLError> {
467 items
468 .into_iter()
469 .map(|item| normalize_expression(engine, item, schema, params))
470 .collect()
471}
472
473fn normalize_unknown_literal(
474 engine: &dyn FunctionTypeResolver,
475 expression: ScalarExpr,
476 target: Option<&ColumnType>,
477 schema: &RowSchema,
478 params: &[SQLParam],
479) -> Result<ScalarExpr, SQLError> {
480 if matches!(expression, ScalarExpr::Literal(Value::Null)) {
481 if let Some(target) = target {
482 return normalize_expression(
483 engine,
484 ScalarExpr::Cast {
485 implicit: true,
486 expr: Box::new(expression),
487 ty: target.catalog_name(),
488 },
489 schema,
490 params,
491 );
492 }
493 }
494 normalize_expression(engine, expression, schema, params)
495}
496
497fn normalize_frame_bound(
498 engine: &dyn FunctionTypeResolver,
499 bound: &mut ScalarFrameBound,
500 schema: &RowSchema,
501 params: &[SQLParam],
502) -> Result<(), SQLError> {
503 match bound {
504 ScalarFrameBound::Preceding(expression) | ScalarFrameBound::Following(expression) => {
505 **expression = normalize_expression(engine, (**expression).clone(), schema, params)?;
506 }
507 ScalarFrameBound::UnboundedPreceding
508 | ScalarFrameBound::UnboundedFollowing
509 | ScalarFrameBound::CurrentRow => {}
510 }
511 Ok(())
512}
513
514fn expression_type(
515 engine: &dyn FunctionTypeResolver,
516 expression: &ScalarExpr,
517 schema: &RowSchema,
518 params: &[SQLParam],
519) -> Result<Option<ColumnType>, SQLError> {
520 crate::scalar_type_with_resolver(expression, schema, params, engine)
521}
522
523fn canonical_function_name(name: String) -> String {
524 let lower = name.to_ascii_lowercase();
525 match lower.strip_prefix("pg_catalog.") {
526 Some(unqualified) => unqualified.to_owned(),
527 None => lower,
528 }
529}