use proc_macro2::{TokenStream as TokenStream2, TokenTree};
use syn::{BinOp, Expr, ExprBinary, ExprUnary, Ident, Token, Type, UnOp};
use super::super::ast::*;
use super::input::parse_expr_until_fat_arrow_or_semi;
pub(super) fn parse_window_rest(input: syn::parse::ParseStream) -> syn::Result<LinqClause> {
let func_ident: Ident = input.parse()?;
let func = func_ident.to_string();
let takes_column = !matches!(
func.to_uppercase().as_str(),
"ROW_NUMBER" | "RANK" | "DENSE_RANK"
);
let column: Option<Expr> = if takes_column {
if is_window_keyword(input) {
None
} else {
Some(parse_window_field_expr(input)?)
}
} else {
None
};
let mut partition_by: Vec<Expr> = Vec::new();
let mut order_by: Vec<(Expr, bool)> = Vec::new();
loop {
if input.is_empty() {
break;
}
if input.peek(Token![as]) {
break;
}
let cursor = input.cursor();
let (ident, _) = cursor.ident().ok_or_else(|| {
syn::Error::new(
cursor.span(),
"expected `partition_by`, `order_by`, or `as`",
)
})?;
match ident.to_string().as_str() {
"partition_by" => {
let _: Ident = input.parse()?;
partition_by = parse_window_field_list(input)?;
}
"order_by" => {
let _: Ident = input.parse()?;
order_by = parse_window_order_list(input)?;
}
other => {
return Err(syn::Error::new(
ident.span(),
format!("expected `partition_by`, `order_by`, or `as`, got `{other}`"),
));
}
}
}
let _: Token![as] = input.parse()?;
let alias_ident: Ident = input.parse()?;
let alias = alias_ident.to_string();
Ok(LinqClause::Window {
func,
column,
partition_by,
order_by,
alias,
})
}
fn is_window_keyword(input: syn::parse::ParseStream) -> bool {
if input.peek(Token![as]) {
return true;
}
if !input.peek(Ident) {
return false;
}
let cursor = input.cursor();
if let Some((ident, _)) = cursor.ident() {
matches!(ident.to_string().as_str(), "partition_by" | "order_by")
} else {
false
}
}
fn parse_window_field_list(input: syn::parse::ParseStream) -> syn::Result<Vec<Expr>> {
let mut fields = Vec::new();
loop {
if input.is_empty() || is_window_keyword(input) {
break;
}
let expr = parse_window_field_expr(input)?;
fields.push(expr);
if input.is_empty() || is_window_keyword(input) {
break;
}
if input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
} else {
break;
}
}
Ok(fields)
}
fn parse_window_order_list(input: syn::parse::ParseStream) -> syn::Result<Vec<(Expr, bool)>> {
let mut pairs = Vec::new();
loop {
if input.is_empty() || is_window_keyword(input) {
break;
}
let expr = parse_window_field_expr(input)?;
let mut descending = false;
if input.peek(Ident) {
let cursor = input.cursor();
if let Some((ident, _)) = cursor.ident() {
match ident.to_string().as_str() {
"asc" => {
let _: Ident = input.parse()?;
}
"desc" => {
let _: Ident = input.parse()?;
descending = true;
}
_ => {}
}
}
}
pairs.push((expr, descending));
if input.is_empty() || is_window_keyword(input) {
break;
}
if input.peek(Token![,]) {
let _: Token![,] = input.parse()?;
} else {
break;
}
}
Ok(pairs)
}
fn parse_window_field_expr(input: syn::parse::ParseStream) -> syn::Result<Expr> {
let mut tokens = TokenStream2::new();
while !input.is_empty()
&& !input.peek(Token![as])
&& !input.peek(Token![,])
&& !is_window_field_boundary(input)
{
let tt: TokenTree = input.parse()?;
tokens.extend(std::iter::once(tt));
}
if tokens.is_empty() {
return Err(syn::Error::new(
input.span(),
"expected a field expression in window clause",
));
}
syn::parse2(tokens)
}
fn is_window_field_boundary(input: syn::parse::ParseStream) -> bool {
if !input.peek(Ident) {
return false;
}
let cursor = input.cursor();
if let Some((ident, _)) = cursor.ident() {
matches!(
ident.to_string().as_str(),
"partition_by" | "order_by" | "asc" | "desc"
)
} else {
false
}
}
pub(super) fn parse_with_rest(input: syn::parse::ParseStream) -> syn::Result<LinqClause> {
let recursive = if input.peek(Ident) {
let cursor = input.cursor();
if let Some((ident, _)) = cursor.ident() {
if ident == "recursive" {
let _: Ident = input.parse()?;
true
} else {
false
}
} else {
false
}
} else {
false
};
let name: Ident = input.parse()?;
let _: Token![as] = input.parse()?;
let _open: Token![|] = input.parse()?;
let param: Ident = input.parse()?;
let _colon: Token![:] = input.parse()?;
let entity: Type = input.parse()?;
let _close: Token![|] = input.parse()?;
let body = parse_expr_until_fat_arrow_or_semi(input)?;
let link = if recursive && input.peek(Ident) {
let cursor = input.cursor();
if let Some((ident, _)) = cursor.ident() {
if ident == "link" {
let _: Ident = input.parse()?; let fk: Expr = input.parse()?;
let to_kw: Ident = input.parse()?; if to_kw != "to" {
return Err(syn::Error::new(to_kw.span(), "expected `to`"));
}
let pk: Expr = input.parse()?;
Some((fk, pk))
} else {
None
}
} else {
None
}
} else {
None
};
Ok(LinqClause::With {
name: name.to_string(),
entity,
param,
body,
recursive,
link,
})
}
pub(super) fn expr_to_having_ast(expr: &Expr) -> syn::Result<HavingExprAst> {
match expr {
Expr::Binary(b) => match &b.op {
BinOp::And(_) => {
let left = expr_to_having_ast(&b.left)?;
let right = expr_to_having_ast(&b.right)?;
Ok(HavingExprAst::And(Box::new(left), Box::new(right)))
}
BinOp::Or(_) => {
let left = expr_to_having_ast(&b.left)?;
let right = expr_to_having_ast(&b.right)?;
Ok(HavingExprAst::Or(Box::new(left), Box::new(right)))
}
BinOp::Eq(_)
| BinOp::Ne(_)
| BinOp::Gt(_)
| BinOp::Ge(_)
| BinOp::Lt(_)
| BinOp::Le(_) => parse_having_compare_from_binary(b),
_ => Err(syn::Error::new_spanned(
expr,
"having expression supports only `&&`, `||`, `!`, and comparison operators",
)),
},
Expr::Unary(ExprUnary {
op: UnOp::Not(_),
expr: inner,
..
}) => {
let inner_ast = expr_to_having_ast(inner)?;
Ok(HavingExprAst::Not(Box::new(inner_ast)))
}
Expr::Paren(p) => expr_to_having_ast(&p.expr),
_ => Err(syn::Error::new_spanned(
expr,
"having expects a boolean expression of aggregate comparisons",
)),
}
}
fn parse_having_compare_from_binary(b: &ExprBinary) -> syn::Result<HavingExprAst> {
let op = bin_op_to_symbol(&b.op)?;
let (left_agg, left_col) = parse_agg_call(&b.left)?;
match parse_agg_call(&b.right) {
Ok((right_agg, right_col)) => Ok(HavingExprAst::CompareAgg {
left_agg,
left_col,
op: op.to_string(),
right_agg,
right_col,
}),
Err(_) => {
let value: Expr = (*b.right).clone();
Ok(HavingExprAst::Compare {
agg: left_agg,
col: left_col,
op: op.to_string(),
value,
})
}
}
}
fn parse_agg_call(expr: &Expr) -> syn::Result<(String, Expr)> {
let call = match expr {
Expr::Call(c) => c,
_ => {
return Err(syn::Error::new_spanned(
expr,
"expected aggregate function call, e.g. `count(b.id)`",
));
}
};
let agg = match &*call.func {
Expr::Path(p) if p.path.segments.len() == 1 => p.path.segments[0].ident.to_string(),
_ => {
return Err(syn::Error::new_spanned(
&call.func,
"expected aggregate function: count/sum/avg/min/max",
));
}
};
if !matches!(
agg.to_lowercase().as_str(),
"count" | "sum" | "avg" | "min" | "max"
) {
return Err(syn::Error::new_spanned(
&call.func,
"unsupported aggregate; use count/sum/avg/min/max",
));
}
let col = call
.args
.first()
.ok_or_else(|| {
syn::Error::new_spanned(&call.func, "aggregate function requires a column argument")
})?
.clone();
Ok((agg.to_uppercase(), col))
}
fn bin_op_to_symbol(op: &BinOp) -> syn::Result<&'static str> {
match op {
BinOp::Eq(_) => Ok("="),
BinOp::Ne(_) => Ok("!="),
BinOp::Gt(_) => Ok(">"),
BinOp::Ge(_) => Ok(">="),
BinOp::Lt(_) => Ok("<"),
BinOp::Le(_) => Ok("<="),
_ => Err(syn::Error::new_spanned(
op,
"unsupported comparison operator in having",
)),
}
}