1use serde::{Deserialize, Serialize};
8use std::sync::atomic::{AtomicU64, Ordering};
9use uqa_core::Value;
10
11use super::{FunctionBinding, SelectStmt};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
15#[doc(hidden)]
16pub struct InternalRelationId(u64);
17
18impl InternalRelationId {
19 #[must_use]
21 pub fn allocate() -> Self {
22 static NEXT_ID: AtomicU64 = AtomicU64::new(1);
23 let id = uqa_core::atomic::try_update_u64(
24 &NEXT_ID,
25 Ordering::Relaxed,
26 Ordering::Relaxed,
27 |current| current.checked_add(1),
28 )
29 .expect("internal relation identity space exhausted");
30 Self(id)
31 }
32
33 #[must_use]
35 pub fn column(self, attribute: usize) -> InternalColumnRef {
36 InternalColumnRef {
37 relation: self,
38 attribute: u32::try_from(attribute).expect("internal relation attribute exceeds u32"),
39 }
40 }
41
42 #[must_use]
43 pub const fn raw(self) -> u64 {
44 self.0
45 }
46
47 #[must_use]
48 pub const fn from_raw(raw: u64) -> Self {
49 Self(raw)
50 }
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
55#[doc(hidden)]
56pub struct InternalColumnRef {
57 relation: InternalRelationId,
58 attribute: u32,
59}
60
61impl InternalColumnRef {
62 #[must_use]
63 pub const fn relation(self) -> InternalRelationId {
64 self.relation
65 }
66
67 #[must_use]
68 pub const fn attribute(self) -> usize {
69 self.attribute as usize
70 }
71
72 #[must_use]
73 pub const fn from_raw(relation: u64, attribute: u32) -> Self {
74 Self {
75 relation: InternalRelationId::from_raw(relation),
76 attribute,
77 }
78 }
79}
80
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
82pub struct Projection {
83 pub expr: Expr,
84 pub alias: Option<String>,
85}
86
87#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88pub struct OrderBy {
89 pub expr: Expr,
90 pub descending: bool,
91 pub nulls: Option<NullsOrder>,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
98pub enum NullsOrder {
99 First,
100 Last,
101}
102
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct WindowDefinition<S = WindowSpec> {
106 pub name: Option<String>,
107 pub inherited: Option<usize>,
108 pub spec: S,
109}
110
111#[derive(Debug, Clone, PartialEq)]
113pub struct WindowDefinitionSyntax(pub(crate) serde_json::Value);
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
116pub struct WindowSpec {
117 #[serde(skip)]
119 pub raw_definition: Option<WindowDefinitionSyntax>,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
122 pub definition: Option<usize>,
123 #[serde(default, skip_serializing_if = "Option::is_none")]
125 pub reference: Option<WindowReference>,
126 pub partition_by: Vec<Expr>,
127 pub order_by: Vec<OrderBy>,
128 pub frame: Option<WindowFrame>,
131}
132
133impl WindowSpec {
134 pub fn expressions_mut(&mut self) -> impl Iterator<Item = &mut Expr> {
136 self.partition_by
137 .iter_mut()
138 .chain(self.order_by.iter_mut().map(|order| &mut order.expr))
139 .chain(
140 self.frame
141 .iter_mut()
142 .flat_map(|frame| [&mut frame.start, &mut frame.end])
143 .filter_map(|bound| match bound {
144 FrameBound::Preceding(value) | FrameBound::Following(value) => {
145 Some(value.as_mut())
146 }
147 _ => None,
148 }),
149 )
150 }
151}
152
153#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
154pub struct WindowReference {
155 pub name: String,
156 pub kind: WindowReferenceKind,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
160pub enum WindowReferenceKind {
161 Direct,
163 Copy,
165}
166
167#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
168pub struct WindowFrame {
169 pub mode: FrameMode,
170 pub start: FrameBound,
171 pub end: FrameBound,
172 #[serde(default = "super::default_true")]
174 pub between: bool,
175 #[serde(default, skip_serializing_if = "FrameExclusion::is_no_others")]
176 pub exclusion: FrameExclusion,
177}
178
179#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
180pub enum FrameMode {
181 Rows,
182 Range,
183 Groups,
184}
185
186#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
188pub enum FunctionCallSyntax {
189 #[default]
190 Legacy,
191 Ordinary,
192 WithinGroup,
193 Extract,
194}
195
196pub type FunctionOrderSyntax = FunctionCallSyntax;
198
199impl FunctionCallSyntax {
200 #[must_use]
201 pub const fn is_legacy(&self) -> bool {
202 matches!(self, Self::Legacy)
203 }
204}
205
206#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
208pub struct WindowCallModifiers {
209 pub distinct: bool,
210 pub ordered: bool,
212 pub within_group: bool,
213}
214
215impl WindowCallModifiers {
216 #[must_use]
217 pub const fn is_empty(&self) -> bool {
218 !self.distinct && !self.ordered && !self.within_group
219 }
220}
221
222#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
224pub enum FrameExclusion {
225 #[default]
226 NoOthers,
227 CurrentRow,
228 Group,
230 Ties,
232}
233
234impl FrameExclusion {
235 #[must_use]
236 pub const fn is_no_others(&self) -> bool {
237 matches!(self, Self::NoOthers)
238 }
239
240 #[must_use]
242 pub const fn sql(self) -> Option<&'static str> {
243 match self {
244 Self::NoOthers => None,
245 Self::CurrentRow => Some("EXCLUDE CURRENT ROW"),
246 Self::Group => Some("EXCLUDE GROUP"),
247 Self::Ties => Some("EXCLUDE TIES"),
248 }
249 }
250}
251
252#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
253pub enum FrameBound {
254 UnboundedPreceding,
255 UnboundedFollowing,
256 CurrentRow,
257 Preceding(Box<Expr>),
258 Following(Box<Expr>),
259}
260
261#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
263pub enum Expr {
264 Star,
265 QualifiedStar(String),
267 Default,
269 Column(String),
271 QualifiedColumn {
273 qualifier: String,
274 column: String,
275 },
276 #[doc(hidden)]
278 InternalColumn(InternalColumnRef),
279 Literal(Value),
280 #[doc(hidden)]
282 TypedLiteral {
283 value: Value,
284 ty: String,
285 },
286 Param(usize),
288 Func {
290 name: String,
291 #[serde(default, skip_serializing_if = "Option::is_none")]
292 binding: Option<FunctionBinding>,
293 args: Vec<Expr>,
294 distinct: bool,
297 order_by: Vec<OrderBy>,
299 #[serde(default, skip_serializing_if = "FunctionOrderSyntax::is_legacy")]
300 order_syntax: FunctionOrderSyntax,
301 filter: Option<Box<Expr>>,
303 },
304 Array(Vec<Expr>),
306 Row(Vec<Expr>),
308 CompositeRow {
310 items: Vec<Expr>,
311 binding: super::CompositeRowBinding,
312 },
313 Binary {
315 op: BinaryOp,
316 lhs: Box<Expr>,
317 rhs: Box<Expr>,
318 },
319 UnaryMinus(Box<Expr>),
322 Not(Box<Expr>),
324 And(Vec<Expr>),
326 Or(Vec<Expr>),
328 IsNull {
330 expr: Box<Expr>,
331 negated: bool,
332 },
333 Between {
335 expr: Box<Expr>,
336 low: Box<Expr>,
337 high: Box<Expr>,
338 },
339 InList {
341 expr: Box<Expr>,
342 list: Vec<Expr>,
343 negated: bool,
344 },
345 WindowCall {
347 name: String,
348 args: Vec<Expr>,
349 spec: Box<WindowSpec>,
350 #[serde(default, skip_serializing_if = "Option::is_none")]
351 filter: Option<Box<Expr>>,
352 #[serde(default, skip_serializing_if = "WindowCallModifiers::is_empty")]
353 modifiers: WindowCallModifiers,
354 },
355 Case {
360 base: Option<Box<Expr>>,
361 when: Vec<(Expr, Expr)>,
362 else_branch: Option<Box<Expr>>,
363 },
364 Cast {
367 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
369 implicit: bool,
370 expr: Box<Expr>,
371 ty: String,
372 },
373 ScalarSubquery(Box<SelectStmt>),
376 Exists {
379 body: Box<SelectStmt>,
380 negated: bool,
381 },
382 InSubquery {
386 expr: Box<Expr>,
387 body: Box<SelectStmt>,
388 negated: bool,
389 },
390}
391
392impl Expr {
393 pub fn qualified_column(qualifier: impl Into<String>, column: impl Into<String>) -> Self {
394 Self::QualifiedColumn {
395 qualifier: qualifier.into(),
396 column: column.into(),
397 }
398 }
399
400 #[doc(hidden)]
403 #[expect(
404 clippy::too_many_lines,
405 reason = "exhaustive AST migration preserves every serialized variant"
406 )]
407 pub fn upgrade_legacy_serialized_dispatches(&mut self) -> bool {
408 let mut changed = false;
409 match self {
410 Self::Func {
411 name,
412 binding,
413 args,
414 order_by,
415 filter,
416 ..
417 } => {
418 for argument in args {
419 changed |= argument.upgrade_legacy_serialized_dispatches();
420 }
421 for order in order_by {
422 changed |= order.expr.upgrade_legacy_serialized_dispatches();
423 }
424 if let Some(filter) = filter {
425 changed |= filter.upgrade_legacy_serialized_dispatches();
426 }
427 changed |=
428 super::FunctionBinding::upgrade_legacy_serialized_dispatch(name, binding);
429 }
430 Self::Array(items)
431 | Self::Row(items)
432 | Self::CompositeRow { items, .. }
433 | Self::And(items)
434 | Self::Or(items) => {
435 for item in items {
436 changed |= item.upgrade_legacy_serialized_dispatches();
437 }
438 }
439 Self::Binary { lhs, rhs, .. } => {
440 changed |= lhs.upgrade_legacy_serialized_dispatches();
441 changed |= rhs.upgrade_legacy_serialized_dispatches();
442 }
443 Self::UnaryMinus(inner)
444 | Self::Not(inner)
445 | Self::IsNull { expr: inner, .. }
446 | Self::Cast { expr: inner, .. } => {
447 changed |= inner.upgrade_legacy_serialized_dispatches();
448 }
449 Self::Between { expr, low, high } => {
450 changed |= expr.upgrade_legacy_serialized_dispatches();
451 changed |= low.upgrade_legacy_serialized_dispatches();
452 changed |= high.upgrade_legacy_serialized_dispatches();
453 }
454 Self::InList { expr, list, .. } => {
455 changed |= expr.upgrade_legacy_serialized_dispatches();
456 for item in list {
457 changed |= item.upgrade_legacy_serialized_dispatches();
458 }
459 }
460 Self::WindowCall {
461 args, spec, filter, ..
462 } => {
463 for argument in args {
464 changed |= argument.upgrade_legacy_serialized_dispatches();
465 }
466 if let Some(filter) = filter {
467 changed |= filter.upgrade_legacy_serialized_dispatches();
468 }
469 for partition in &mut spec.partition_by {
470 changed |= partition.upgrade_legacy_serialized_dispatches();
471 }
472 for order in &mut spec.order_by {
473 changed |= order.expr.upgrade_legacy_serialized_dispatches();
474 }
475 if let Some(frame) = &mut spec.frame {
476 for bound in [&mut frame.start, &mut frame.end] {
477 match bound {
478 FrameBound::Preceding(expression)
479 | FrameBound::Following(expression) => {
480 changed |= expression.upgrade_legacy_serialized_dispatches();
481 }
482 FrameBound::UnboundedPreceding
483 | FrameBound::UnboundedFollowing
484 | FrameBound::CurrentRow => {}
485 }
486 }
487 }
488 }
489 Self::Case {
490 base,
491 when,
492 else_branch,
493 } => {
494 if let Some(base) = base {
495 changed |= base.upgrade_legacy_serialized_dispatches();
496 }
497 for (condition, result) in when {
498 changed |= condition.upgrade_legacy_serialized_dispatches();
499 changed |= result.upgrade_legacy_serialized_dispatches();
500 }
501 if let Some(branch) = else_branch {
502 changed |= branch.upgrade_legacy_serialized_dispatches();
503 }
504 }
505 Self::InSubquery { expr, body, .. } => {
506 changed |= expr.upgrade_legacy_serialized_dispatches();
507 changed |= body.upgrade_legacy_serialized_dispatches();
508 }
509 Self::ScalarSubquery(body) | Self::Exists { body, .. } => {
510 changed |= body.upgrade_legacy_serialized_dispatches();
511 }
512 Self::Default
513 | Self::Star
514 | Self::QualifiedStar(_)
515 | Self::Column(_)
516 | Self::QualifiedColumn { .. }
517 | Self::InternalColumn(_)
518 | Self::Literal(_)
519 | Self::TypedLiteral { .. }
520 | Self::Param(_) => {}
521 }
522 changed
523 }
524
525 #[must_use]
527 pub fn contains_window(&self) -> bool {
528 self.any_node(&|node| matches!(node, Self::WindowCall { .. }))
529 }
530
531 #[must_use]
533 pub fn contains_aggregate(&self) -> bool {
534 self.any_node(
535 &|node| matches!(node, Self::Func { name, .. } if is_builtin_aggregate_function(name)),
536 )
537 }
538
539 #[must_use]
541 pub fn contains_unqualified_column(&self) -> bool {
542 self.any_node(&|node| matches!(node, Self::Column(_)))
543 }
544
545 #[must_use]
547 pub fn contains_function_with_unknown_strictness(&self) -> bool {
548 self.any_node(&|node| {
549 matches!(
550 node,
551 Self::Func {
552 name,
553 args,
554 binding,
555 ..
556 } if crate::expr::bound_scalar_function_strictness(
557 name,
558 binding.as_ref(),
559 args.len(),
560 )
561 .is_none()
562 )
563 })
564 }
565
566 #[must_use]
568 pub fn any_node(&self, hit: &dyn Fn(&Self) -> bool) -> bool {
569 if hit(self) {
570 return true;
571 }
572 match self {
573 Self::Func {
574 args,
575 order_by,
576 filter,
577 ..
578 } => {
579 args.iter().any(|arg| arg.any_node(hit))
580 || order_by.iter().any(|order| order.expr.any_node(hit))
581 || filter.as_deref().is_some_and(|filter| filter.any_node(hit))
582 }
583 Self::Array(items)
584 | Self::Row(items)
585 | Self::CompositeRow { items, .. }
586 | Self::And(items)
587 | Self::Or(items) => items.iter().any(|item| item.any_node(hit)),
588 Self::UnaryMinus(expr) | Self::Not(expr) | Self::Cast { expr, .. } => {
589 expr.any_node(hit)
590 }
591 Self::Binary { lhs, rhs, .. } => lhs.any_node(hit) || rhs.any_node(hit),
592 Self::IsNull { expr, .. } | Self::InSubquery { expr, .. } => expr.any_node(hit),
593 Self::Between { expr, low, high } => {
594 expr.any_node(hit) || low.any_node(hit) || high.any_node(hit)
595 }
596 Self::InList { expr, list, .. } => {
597 expr.any_node(hit) || list.iter().any(|item| item.any_node(hit))
598 }
599 Self::Case {
600 base,
601 when,
602 else_branch,
603 } => {
604 base.as_deref().is_some_and(|base| base.any_node(hit))
605 || when
606 .iter()
607 .any(|(condition, result)| condition.any_node(hit) || result.any_node(hit))
608 || else_branch
609 .as_deref()
610 .is_some_and(|branch| branch.any_node(hit))
611 }
612 Self::WindowCall { .. }
613 | Self::Star
614 | Self::QualifiedStar(_)
615 | Self::Default
616 | Self::Column(_)
617 | Self::QualifiedColumn { .. }
618 | Self::InternalColumn(_)
619 | Self::Literal(_)
620 | Self::TypedLiteral { .. }
621 | Self::Param(_)
622 | Self::ScalarSubquery(_)
623 | Self::Exists { .. } => false,
624 }
625 }
626}
627
628#[must_use]
630pub fn is_builtin_aggregate_function(name: &str) -> bool {
631 matches!(
632 name.to_ascii_lowercase().as_str(),
633 "count"
634 | "sum"
635 | "avg"
636 | "min"
637 | "max"
638 | "string_agg"
639 | "array_agg"
640 | "bool_and"
641 | "bool_or"
642 | "stddev"
643 | "stddev_samp"
644 | "stddev_pop"
645 | "variance"
646 | "var_samp"
647 | "var_pop"
648 | "percentile_cont"
649 | "percentile_disc"
650 | "mode"
651 | "json_agg"
652 | "jsonb_agg"
653 | "json_object_agg"
654 | "jsonb_object_agg"
655 )
656}
657
658#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
659pub enum BinaryOp {
660 Equal,
661 NotEqual,
662 Less,
663 LessEqual,
664 Greater,
665 GreaterEqual,
666 Add,
667 Subtract,
668 Multiply,
669 Divide,
670}
671
672pub type ValueExpr = Expr;