use wasm_dbms_api::prelude::SqlError;
use super::Parser;
use super::condition::Aggregates;
use crate::ast::{
ColumnRef, JoinClause, JoinKind, OrderItem, Projection, RowCount, Select, SelectItem, TableRef,
};
use crate::lexer::{Keyword, Token};
impl Parser {
pub(super) fn select(&mut self) -> Result<Select, SqlError> {
self.advance();
let distinct = self.eat_keyword(Keyword::Distinct);
let projection = self.projection()?;
self.expect_keyword(Keyword::From)?;
let from = self.table_ref()?;
let mut joins = Vec::new();
while let Some(kind) = self.join_kind()? {
joins.push(self.join(kind)?);
}
let filter = if self.eat_keyword(Keyword::Where) {
Some(self.condition(Aggregates::Forbidden)?)
} else {
None
};
let group_by = if self.eat_keyword(Keyword::Group) {
self.expect_keyword(Keyword::By)?;
self.group_by_columns()?
} else {
Vec::new()
};
let having = if self.eat_keyword(Keyword::Having) {
Some(self.condition(Aggregates::Allowed)?)
} else {
None
};
let order_by = if self.eat_keyword(Keyword::Order) {
self.expect_keyword(Keyword::By)?;
self.order_items()?
} else {
Vec::new()
};
let mut limit = None;
let mut offset = None;
loop {
if limit.is_none() && self.eat_keyword(Keyword::Limit) {
limit = Some(self.row_count("`LIMIT`")?);
} else if offset.is_none() && self.eat_keyword(Keyword::Offset) {
offset = Some(self.row_count("`OFFSET`")?);
} else {
break;
}
}
Ok(Select {
distinct,
projection,
from,
joins,
filter,
group_by,
having,
order_by,
limit,
offset,
})
}
fn projection(&mut self) -> Result<Projection, SqlError> {
if self.eat(&Token::Star) {
if *self.peek() == Token::Comma {
return Err(self.error("`*` cannot be combined with other select items"));
}
return Ok(Projection::All);
}
let mut items = Vec::new();
loop {
let operand = self.operand(Aggregates::Allowed)?;
let alias = if self.eat_keyword(Keyword::As) {
Some(self.identifier("an alias")?)
} else {
None
};
items.push(SelectItem { operand, alias });
if !self.eat(&Token::Comma) {
break;
}
}
Ok(Projection::Items(items))
}
fn table_ref(&mut self) -> Result<TableRef, SqlError> {
let name = self.identifier("a table name")?;
let has_alias =
self.eat_keyword(Keyword::As) || matches!(self.peek(), Token::Identifier(_));
let alias = if has_alias {
Some(self.identifier("an alias")?)
} else {
None
};
Ok(TableRef { name, alias })
}
fn join_kind(&mut self) -> Result<Option<JoinKind>, SqlError> {
let (kind, outer_allowed) = match self.peek() {
Token::Keyword(Keyword::Join) => (JoinKind::Inner, false),
Token::Keyword(Keyword::Inner) => {
self.advance();
(JoinKind::Inner, false)
}
Token::Keyword(Keyword::Left) => {
self.advance();
(JoinKind::Left, true)
}
Token::Keyword(Keyword::Right) => {
self.advance();
(JoinKind::Right, true)
}
Token::Keyword(Keyword::Full) => {
self.advance();
(JoinKind::Full, true)
}
_ => return Ok(None),
};
if outer_allowed {
self.eat_keyword(Keyword::Outer);
}
self.expect_keyword(Keyword::Join)?;
Ok(Some(kind))
}
fn join(&mut self, kind: JoinKind) -> Result<JoinClause, SqlError> {
let table = self.table_ref()?;
self.expect_keyword(Keyword::On)?;
let left = self.column_ref()?;
self.expect(&Token::Eq, "`=`")?;
let right = self.column_ref()?;
if self.at_keyword(Keyword::And) || self.at_keyword(Keyword::Or) {
return Err(self.error("a join condition must be a single `left = right` comparison"));
}
Ok(JoinClause {
kind,
table,
left,
right,
})
}
fn group_by_columns(&mut self) -> Result<Vec<ColumnRef>, SqlError> {
let mut columns = Vec::new();
loop {
columns.push(self.column_ref()?);
if !self.eat(&Token::Comma) {
return Ok(columns);
}
}
}
fn order_items(&mut self) -> Result<Vec<OrderItem>, SqlError> {
let mut items = Vec::new();
loop {
let operand = self.operand(Aggregates::Allowed)?;
let descending = self.eat_keyword(Keyword::Desc);
if !descending {
self.eat_keyword(Keyword::Asc);
}
items.push(OrderItem {
operand,
descending,
});
if !self.eat(&Token::Comma) {
return Ok(items);
}
}
}
fn row_count(&mut self, clause: &str) -> Result<RowCount, SqlError> {
let count = match self.peek() {
Token::Integer(value) => RowCount::Value(*value),
Token::Placeholder => {
self.advance();
return Ok(RowCount::Parameter(self.next_parameter()));
}
_ => {
return Err(
self.unexpected(&format!("a non-negative integer or `?` after {clause}"))
);
}
};
self.advance();
Ok(count)
}
}