use crate::{
db::{
reduced_sql::{Keyword, TokenKind},
sql::parser::{
Parser, SqlAggregateCall, SqlAggregateKind, SqlProjection, SqlSelectItem,
SqlTextFunction, SqlTextFunctionCall,
},
},
value::Value,
};
impl Parser {
pub(super) fn parse_projection(
&mut self,
) -> Result<SqlProjection, crate::db::reduced_sql::SqlParseError> {
if self.eat_star() {
return Ok(SqlProjection::All);
}
let mut items = Vec::new();
loop {
items.push(self.parse_select_item()?);
self.reject_projection_alias_if_present()?;
if self.eat_comma() {
continue;
}
break;
}
if items.is_empty() {
return Err(crate::db::reduced_sql::SqlParseError::expected(
"one projection item",
self.peek_kind(),
));
}
Ok(SqlProjection::Items(items))
}
fn parse_select_item(
&mut self,
) -> Result<SqlSelectItem, crate::db::reduced_sql::SqlParseError> {
if let Some(kind) = self.parse_aggregate_kind() {
return Ok(SqlSelectItem::Aggregate(self.parse_aggregate_call(kind)?));
}
let field = self.expect_identifier()?;
if self.peek_lparen() {
let Some(function) = SqlTextFunction::from_identifier(field.as_str()) else {
return Err(crate::db::reduced_sql::SqlParseError::unsupported_feature(
"SQL function namespace beyond supported aggregate or scalar text projection forms",
));
};
return Ok(SqlSelectItem::TextFunction(
self.parse_text_function_call(function)?,
));
}
Ok(SqlSelectItem::Field(field))
}
pub(super) fn parse_aggregate_kind(&self) -> Option<SqlAggregateKind> {
match self.peek_kind() {
Some(TokenKind::Keyword(Keyword::Count)) => Some(SqlAggregateKind::Count),
Some(TokenKind::Keyword(Keyword::Sum)) => Some(SqlAggregateKind::Sum),
Some(TokenKind::Keyword(Keyword::Avg)) => Some(SqlAggregateKind::Avg),
Some(TokenKind::Keyword(Keyword::Min)) => Some(SqlAggregateKind::Min),
Some(TokenKind::Keyword(Keyword::Max)) => Some(SqlAggregateKind::Max),
_ => None,
}
}
pub(super) fn parse_aggregate_call(
&mut self,
kind: SqlAggregateKind,
) -> Result<SqlAggregateCall, crate::db::reduced_sql::SqlParseError> {
self.bump();
self.expect_lparen()?;
if self.eat_keyword(Keyword::Distinct) {
return Err(crate::db::reduced_sql::SqlParseError::unsupported_feature(
"DISTINCT aggregate qualifiers",
));
}
let field = if kind == SqlAggregateKind::Count && self.eat_star() {
None
} else {
Some(self.expect_identifier()?)
};
self.expect_rparen()?;
Ok(SqlAggregateCall { kind, field })
}
fn parse_text_function_call(
&mut self,
function: SqlTextFunction,
) -> Result<SqlTextFunctionCall, crate::db::reduced_sql::SqlParseError> {
self.expect_lparen()?;
let call = match function {
SqlTextFunction::Trim
| SqlTextFunction::Ltrim
| SqlTextFunction::Rtrim
| SqlTextFunction::Lower
| SqlTextFunction::Upper
| SqlTextFunction::Length => self.parse_unary_text_function_call(function)?,
SqlTextFunction::Left
| SqlTextFunction::Right
| SqlTextFunction::StartsWith
| SqlTextFunction::EndsWith
| SqlTextFunction::Contains => {
self.parse_field_plus_literal_text_function_call(function)?
}
SqlTextFunction::Position => self.parse_position_text_function_call(function)?,
SqlTextFunction::Replace => self.parse_replace_text_function_call(function)?,
SqlTextFunction::Substring => self.parse_substring_text_function_call(function)?,
};
self.expect_rparen()?;
Ok(call)
}
fn reject_projection_alias_if_present(
&mut self,
) -> Result<(), crate::db::reduced_sql::SqlParseError> {
if self.eat_keyword(Keyword::As)
|| matches!(self.peek_kind(), Some(TokenKind::Identifier(_)))
{
return Err(crate::db::reduced_sql::SqlParseError::unsupported_feature(
"column/expression aliases",
));
}
Ok(())
}
fn parse_unary_text_function_call(
&mut self,
function: SqlTextFunction,
) -> Result<SqlTextFunctionCall, crate::db::reduced_sql::SqlParseError> {
let field = self.expect_identifier()?;
Ok(Self::text_function_call(function, field, None, None, None))
}
fn parse_field_plus_literal_text_function_call(
&mut self,
function: SqlTextFunction,
) -> Result<SqlTextFunctionCall, crate::db::reduced_sql::SqlParseError> {
let field = self.expect_identifier()?;
self.expect_text_function_argument_comma()?;
let literal = self.parse_literal()?;
Ok(Self::text_function_call(
function,
field,
Some(literal),
None,
None,
))
}
fn parse_position_text_function_call(
&mut self,
function: SqlTextFunction,
) -> Result<SqlTextFunctionCall, crate::db::reduced_sql::SqlParseError> {
let literal = self.parse_literal()?;
self.expect_text_function_argument_comma()?;
let field = self.expect_identifier()?;
Ok(Self::text_function_call(
function,
field,
Some(literal),
None,
None,
))
}
fn parse_replace_text_function_call(
&mut self,
function: SqlTextFunction,
) -> Result<SqlTextFunctionCall, crate::db::reduced_sql::SqlParseError> {
let field = self.expect_identifier()?;
self.expect_text_function_argument_comma()?;
let from = self.parse_literal()?;
self.expect_text_function_argument_comma()?;
let to = self.parse_literal()?;
Ok(Self::text_function_call(
function,
field,
Some(from),
Some(to),
None,
))
}
fn parse_substring_text_function_call(
&mut self,
function: SqlTextFunction,
) -> Result<SqlTextFunctionCall, crate::db::reduced_sql::SqlParseError> {
let field = self.expect_identifier()?;
self.expect_text_function_argument_comma()?;
let start = self.parse_literal()?;
if !self.eat_comma() {
return Ok(Self::text_function_call(
function,
field,
Some(start),
None,
None,
));
}
let length = self.parse_literal()?;
Ok(Self::text_function_call(
function,
field,
Some(start),
Some(length),
None,
))
}
fn expect_text_function_argument_comma(
&mut self,
) -> Result<(), crate::db::reduced_sql::SqlParseError> {
if self.eat_comma() {
return Ok(());
}
Err(crate::db::reduced_sql::SqlParseError::expected(
"',' between text function arguments",
self.peek_kind(),
))
}
const fn text_function_call(
function: SqlTextFunction,
field: String,
literal: Option<Value>,
literal2: Option<Value>,
literal3: Option<Value>,
) -> SqlTextFunctionCall {
SqlTextFunctionCall {
function,
field,
literal,
literal2,
literal3,
}
}
}