use wasm_dbms_api::prelude::SqlError;
use super::{MAX_CONDITION_TERMS, Parser};
use crate::ast::{
Aggregate, AggregateKind, ColumnRef, CompareOp, Condition, Literal, Operand, ValueExpr,
};
use crate::lexer::{Keyword, Token};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Aggregates {
Allowed,
Forbidden,
}
impl Parser {
pub(super) fn condition(&mut self, aggregates: Aggregates) -> Result<Condition, SqlError> {
let mut left = self.and_condition(aggregates)?;
while self.at_keyword(Keyword::Or) {
self.count_condition_term()?;
self.advance();
let right = self.and_condition(aggregates)?;
left = Condition::Or(Box::new(left), Box::new(right));
}
Ok(left)
}
fn and_condition(&mut self, aggregates: Aggregates) -> Result<Condition, SqlError> {
let mut left = self.not_condition(aggregates)?;
while self.at_keyword(Keyword::And) {
self.count_condition_term()?;
self.advance();
let right = self.not_condition(aggregates)?;
left = Condition::And(Box::new(left), Box::new(right));
}
Ok(left)
}
fn not_condition(&mut self, aggregates: Aggregates) -> Result<Condition, SqlError> {
self.count_condition_term()?;
if self.eat_keyword(Keyword::Not) {
let inner = self.not_condition(aggregates)?;
return Ok(Condition::Not(Box::new(inner)));
}
if self.eat(&Token::LeftParen) {
let inner = self.condition(aggregates)?;
self.expect(&Token::RightParen, "`)`")?;
return Ok(inner);
}
self.predicate(aggregates)
}
fn count_condition_term(&mut self) -> Result<(), SqlError> {
self.condition_terms += 1;
if self.condition_terms > MAX_CONDITION_TERMS {
return Err(self.error(format!(
"condition is too complex: more than {MAX_CONDITION_TERMS} terms"
)));
}
Ok(())
}
fn predicate(&mut self, aggregates: Aggregates) -> Result<Condition, SqlError> {
let operand = self.operand(aggregates)?;
let op = match self.peek() {
Token::Eq => CompareOp::Eq,
Token::NotEq => CompareOp::NotEq,
Token::Lt => CompareOp::Lt,
Token::LtEq => CompareOp::LtEq,
Token::Gt => CompareOp::Gt,
Token::GtEq => CompareOp::GtEq,
Token::Keyword(Keyword::Is) => {
self.advance();
let negated = self.eat_keyword(Keyword::Not);
self.expect_keyword(Keyword::Null)?;
return Ok(Condition::IsNull { operand, negated });
}
Token::Keyword(Keyword::In) => {
self.advance();
return self.in_list(operand, false);
}
Token::Keyword(Keyword::Like) => {
self.advance();
return self.like(operand, false);
}
Token::Keyword(Keyword::Not) => {
self.advance();
if self.eat_keyword(Keyword::In) {
return self.in_list(operand, true);
}
if self.eat_keyword(Keyword::Like) {
return self.like(operand, true);
}
return Err(self.unexpected("`IN` or `LIKE` after `NOT`"));
}
_ => return Err(self.unexpected("a comparison operator, `IN`, `LIKE` or `IS`")),
};
self.advance();
let position = self.position();
let value = self.value()?;
if value == ValueExpr::Literal(Literal::Null) {
return Err(
position.error("cannot compare with `NULL`; use `IS NULL` or `IS NOT NULL`")
);
}
Ok(Condition::Compare { operand, op, value })
}
fn in_list(&mut self, operand: Operand, negated: bool) -> Result<Condition, SqlError> {
self.expect(&Token::LeftParen, "`(`")?;
let mut values = Vec::new();
loop {
let position = self.position();
let value = self.value()?;
if value == ValueExpr::Literal(Literal::Null) {
return Err(position.error("`NULL` is not allowed in an `IN` list"));
}
values.push(value);
if !self.eat(&Token::Comma) {
break;
}
}
self.expect(&Token::RightParen, "`,` or `)`")?;
Ok(Condition::In {
operand,
values,
negated,
})
}
fn like(&mut self, operand: Operand, negated: bool) -> Result<Condition, SqlError> {
let position = self.position();
let pattern = self.value()?;
if !matches!(
pattern,
ValueExpr::Parameter(_) | ValueExpr::Literal(Literal::String(_))
) {
return Err(position.error("`LIKE` requires a string literal or `?` pattern"));
}
Ok(Condition::Like {
operand,
pattern,
negated,
})
}
pub(super) fn operand(&mut self, aggregates: Aggregates) -> Result<Operand, SqlError> {
let is_call =
matches!(self.peek(), Token::Identifier(_)) && *self.peek_at(1) == Token::LeftParen;
if is_call {
self.aggregate(aggregates).map(Operand::Aggregate)
} else {
self.column_ref().map(Operand::Column)
}
}
fn aggregate(&mut self, aggregates: Aggregates) -> Result<Aggregate, SqlError> {
let position = self.position();
let name = self.identifier("a function name")?;
let function = match name.to_ascii_uppercase().as_str() {
"COUNT" => AggregateKind::Count,
"SUM" => AggregateKind::Sum,
"AVG" => AggregateKind::Avg,
"MIN" => AggregateKind::Min,
"MAX" => AggregateKind::Max,
_ => {
return Err(position.error(format!(
"unknown function `{name}`; only COUNT, SUM, AVG, MIN and MAX are supported"
)));
}
};
if aggregates == Aggregates::Forbidden {
return Err(position.error("aggregate functions are not allowed in `WHERE`"));
}
self.expect(&Token::LeftParen, "`(`")?;
if self.at_keyword(Keyword::Distinct) {
return Err(self.error("`DISTINCT` inside an aggregate function is not supported"));
}
let column = if function == AggregateKind::Count && self.eat(&Token::Star) {
None
} else {
Some(self.column_ref()?)
};
self.expect(&Token::RightParen, "`)`")?;
Ok(Aggregate { function, column })
}
pub(super) fn column_ref(&mut self) -> Result<ColumnRef, SqlError> {
let first = self.identifier("a column name")?;
if !self.eat(&Token::Dot) {
return Ok(ColumnRef {
table: None,
column: first,
});
}
if *self.peek() == Token::Star {
return Err(self.error("qualified wildcard `table.*` is not supported"));
}
let column = self.identifier("a column name")?;
Ok(ColumnRef {
table: Some(first),
column,
})
}
pub(super) fn value(&mut self) -> Result<ValueExpr, SqlError> {
let negative = self.eat(&Token::Minus);
let literal = match (self.peek().clone(), negative) {
(Token::Integer(value), false) => Literal::Integer(i128::from(value)),
(Token::Integer(value), true) => Literal::Integer(-i128::from(value)),
(Token::Float(value), false) => Literal::Float(value),
(Token::Float(value), true) => Literal::Float(format!("-{value}")),
(_, true) => return Err(self.unexpected("a number after `-`")),
(Token::String(value), false) => Literal::String(value),
(Token::Keyword(Keyword::True), false) => Literal::Boolean(true),
(Token::Keyword(Keyword::False), false) => Literal::Boolean(false),
(Token::Keyword(Keyword::Null), false) => Literal::Null,
(Token::Placeholder, false) => {
self.advance();
return Ok(ValueExpr::Parameter(self.next_parameter()));
}
(_, false) => return Err(self.unexpected("a literal or `?`")),
};
self.advance();
Ok(ValueExpr::Literal(literal))
}
}