mod call_arguments;
mod traversal;
pub use call_arguments::{
analyze_expression_call_arguments, scalar_call_argument, scalar_call_arguments,
scalar_call_arguments_with_control, validate_scalar_call_arguments, ScalarCallArgument,
};
use crate::ast::{
BinaryOp, ColumnType, FrameExclusion, FrameMode, FunctionBinding, FunctionOrderSyntax,
InternalColumnRef, NullsOrder, WindowCallModifiers,
};
use uqa_core::Value;
pub type SubqueryId = usize;
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum ScalarExpr {
Star,
QualifiedStar(String),
Default,
Column(String),
Position(usize),
InternalColumn(InternalColumnRef),
QualifiedColumn {
qualifier: String,
column: String,
},
Literal(Value),
TypedLiteral {
value: Value,
ty: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
bound_type: Option<ColumnType>,
#[serde(default, skip_serializing_if = "Option::is_none")]
parameter_index: Option<usize>,
},
Param(usize),
Func {
name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
binding: Option<FunctionBinding>,
args: Vec<Self>,
distinct: bool,
order_by: Vec<ScalarOrder>,
#[serde(default, skip_serializing_if = "FunctionOrderSyntax::is_legacy")]
order_syntax: FunctionOrderSyntax,
filter: Option<Box<Self>>,
},
Array(Vec<Self>),
Row(Vec<Self>),
CompositeRow {
items: Vec<Self>,
binding: crate::ast::CompositeRowBinding,
#[serde(default, skip_serializing_if = "Option::is_none")]
bound_type: Option<ColumnType>,
},
Binary {
op: BinaryOp,
lhs: Box<Self>,
rhs: Box<Self>,
},
UnaryMinus(Box<Self>),
Not(Box<Self>),
And(Vec<Self>),
Or(Vec<Self>),
IsNull {
expr: Box<Self>,
negated: bool,
},
Between {
expr: Box<Self>,
low: Box<Self>,
high: Box<Self>,
},
InList {
expr: Box<Self>,
list: Vec<Self>,
negated: bool,
},
WindowCall {
name: String,
args: Vec<Self>,
spec: ScalarWindowSpec,
#[serde(default, skip_serializing_if = "Option::is_none")]
filter: Option<Box<Self>>,
#[serde(default, skip_serializing_if = "WindowCallModifiers::is_empty")]
modifiers: WindowCallModifiers,
},
Case {
base: Option<Box<Self>>,
when: Vec<(Self, Self)>,
else_branch: Option<Box<Self>>,
},
Cast {
#[serde(default, skip_serializing_if = "is_false")]
implicit: bool,
expr: Box<Self>,
ty: String,
},
ScalarSubquery(SubqueryId),
Exists {
subquery: SubqueryId,
negated: bool,
},
InSubquery {
expr: Box<Self>,
subquery: SubqueryId,
negated: bool,
},
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ScalarOrder {
pub expr: ScalarExpr,
pub descending: bool,
pub nulls: Option<NullsOrder>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ScalarWindowSpec {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub definition: Option<usize>,
pub partition_by: Vec<ScalarExpr>,
pub order_by: Vec<ScalarOrder>,
pub frame: Option<ScalarWindowFrame>,
}
impl ScalarWindowSpec {
pub fn expressions(&self) -> impl Iterator<Item = &ScalarExpr> {
self.partition_by
.iter()
.chain(self.order_by.iter().map(|order| &order.expr))
.chain(
self.frame
.iter()
.flat_map(|frame| [&frame.start, &frame.end])
.filter_map(|bound| match bound {
ScalarFrameBound::Preceding(value) | ScalarFrameBound::Following(value) => {
Some(value.as_ref())
}
_ => None,
}),
)
}
pub fn expressions_mut(&mut self) -> impl Iterator<Item = &mut ScalarExpr> {
self.partition_by
.iter_mut()
.chain(self.order_by.iter_mut().map(|order| &mut order.expr))
.chain(
self.frame
.iter_mut()
.flat_map(|frame| [&mut frame.start, &mut frame.end])
.filter_map(|bound| match bound {
ScalarFrameBound::Preceding(value) | ScalarFrameBound::Following(value) => {
Some(value.as_mut())
}
_ => None,
}),
)
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct ScalarWindowFrame {
pub mode: FrameMode,
pub start: ScalarFrameBound,
pub end: ScalarFrameBound,
#[serde(default = "frame_written_between")]
pub between: bool,
#[serde(default, skip_serializing_if = "FrameExclusion::is_no_others")]
pub exclusion: FrameExclusion,
}
const fn frame_written_between() -> bool {
true
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub enum ScalarFrameBound {
UnboundedPreceding,
UnboundedFollowing,
CurrentRow,
Preceding(Box<ScalarExpr>),
Following(Box<ScalarExpr>),
}
impl ScalarExpr {
#[must_use]
pub fn qualified_column(qualifier: impl Into<String>, column: impl Into<String>) -> Self {
Self::QualifiedColumn {
qualifier: qualifier.into(),
column: column.into(),
}
}
}
#[expect(
clippy::trivially_copy_pass_by_ref,
reason = "serde skip_serializing_if requires a borrowed field"
)]
const fn is_false(value: &bool) -> bool {
!*value
}
#[cfg(test)]
mod tests {
use super::ScalarExpr;
#[test]
fn syntax_cast_origin_survives_owned_and_borrowed_lowering() {
use crate::{ast::Expr, plan::ExpressionPlan};
use uqa_core::{memory::MemoryBudget, CancellationToken};
let legacy = r#"{"Cast":{"expr":{"Column":"value"},"ty":"bigint[]"}}"#;
let explicit: Expr = serde_json::from_str(legacy).unwrap();
assert!(matches!(
explicit,
Expr::Cast {
implicit: false,
..
}
));
assert_eq!(serde_json::to_string(&explicit).unwrap(), legacy);
let mut implicit = explicit;
let Expr::Cast {
implicit: origin, ..
} = &mut implicit
else {
unreachable!()
};
*origin = true;
let stored = serde_json::to_string(&implicit).unwrap();
let restored: Expr = serde_json::from_str(&stored).unwrap();
let expected = ExpressionPlan::lower(restored.clone()).scalar;
assert!(matches!(expected, ScalarExpr::Cast { implicit: true, .. }));
let budget = MemoryBudget::new(1 << 20);
let token = CancellationToken::new();
let borrowed =
ExpressionPlan::lower_column_budgeted(&restored, &budget, &token, &token).unwrap();
assert_eq!(*borrowed, expected);
}
#[test]
fn cast_origin_survives_storage_and_legacy_casts_remain_explicit() {
let legacy = r#"{"Cast":{"expr":{"Column":"value"},"ty":"bigint[]"}}"#;
let explicit: ScalarExpr = serde_json::from_str(legacy).unwrap();
assert!(matches!(
explicit,
ScalarExpr::Cast {
implicit: false,
..
}
));
assert_eq!(serde_json::to_string(&explicit).unwrap(), legacy);
let mut implicit = explicit.clone();
let ScalarExpr::Cast {
implicit: origin, ..
} = &mut implicit
else {
unreachable!()
};
*origin = true;
let stored = serde_json::to_string(&implicit).unwrap();
assert_eq!(
serde_json::from_str::<ScalarExpr>(&stored).unwrap(),
implicit
);
assert_ne!(implicit, explicit);
}
}