mod clauses;
mod model;
mod projection;
mod statement;
#[cfg(test)]
mod tests;
use crate::{
db::sql_shared::{
Keyword, MAX_SQL_EXPR_DEPTH, SqlExpectedToken, SqlIntegerLiteralClause, SqlSyntaxErrorKind,
SqlTokenCursor, TokenKind, sql_expr_depth_limit_error, tokenize_sql,
},
value::Value,
};
use icydb_diagnostic_code::SqlFeatureCode;
pub(crate) use crate::db::sql_shared::SqlParseError;
pub(crate) use model::{
SqlAggregateCall, SqlAggregateKind, SqlAlterColumnAction,
SqlAlterTableAddCheckConstraintStatement, SqlAlterTableAddColumnStatement,
SqlAlterTableAlterColumnStatement, SqlAlterTableDropColumnStatement,
SqlAlterTableDropConstraintStatement, SqlAlterTableRenameColumnStatement,
SqlAlterTableValidateConstraintStatement, SqlAssignment, SqlCaseArm,
SqlCreateIndexExpressionFunction, SqlCreateIndexExpressionKey, SqlCreateIndexKeyItem,
SqlCreateIndexStatement, SqlCreateIndexUniqueness, SqlDdlSchemaVersionContract,
SqlDdlStatement, SqlDeleteStatement, SqlDescribeMode, SqlDescribeStatement,
SqlDropIndexStatement, SqlExpr, SqlExprBinaryOp, SqlExprUnaryOp, SqlInsertSource,
SqlInsertStatement, SqlIntegrityStatement, SqlMembershipValue, SqlOrderDirection, SqlOrderTerm,
SqlProjection, SqlReturningProjection, SqlScalarFunction, SqlScalarFunctionCallShape,
SqlSelectItem, SqlSelectStatement, SqlShowColumnsStatement, SqlShowConstraintsStatement,
SqlShowEntitiesStatement, SqlShowIndexesStatement, SqlShowMemoryStatement,
SqlShowRelationsStatement, SqlShowStoresStatement, SqlStatement, SqlUpdateStatement,
SqlWriteValue,
};
#[cfg(feature = "sql")]
pub(crate) use model::{SqlExplainMode, SqlExplainStatement, SqlExplainTarget};
pub(crate) fn parse_sql(sql: &str) -> Result<SqlStatement, SqlParseError> {
#[cfg(test)]
SQL_PARSE_INVOCATIONS.with(|count| count.set(count.get() + 1));
let tokens = tokenize_sql(sql)?;
if tokens.is_empty() {
return Err(SqlParseError::EmptyInput);
}
let mut parser = Parser::new(SqlTokenCursor::new(tokens));
let statement = parser.parse_statement()?;
if parser.eat_semicolon() && !parser.is_eof() {
return Err(SqlParseError::unsupported_feature(
SqlFeatureCode::MultiStatementSql,
));
}
if !parser.is_eof() {
if let Some(err) = parser.trailing_clause_order_error(&statement) {
return Err(err);
}
if let Some(feature) = parser.peek_unsupported_feature() {
return Err(SqlParseError::unsupported_feature(feature));
}
return Err(SqlParseError::expected_end_of_input(parser.peek_kind()));
}
crate::db::sql::input::validate_sql_statement_input(&statement, &[])
.map_err(|reason| SqlParseError::InputAdmission { reason })?;
Ok(statement)
}
#[cfg(test)]
thread_local! {
static SQL_PARSE_INVOCATIONS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
}
#[cfg(test)]
pub(crate) fn sql_parse_count_for_tests() -> usize {
SQL_PARSE_INVOCATIONS.with(std::cell::Cell::get)
}
pub(super) fn parse_integrity_sql(sql: &str) -> Result<SqlIntegrityStatement, SqlParseError> {
let tokens = tokenize_sql(sql)?;
if tokens.is_empty() {
return Err(SqlParseError::EmptyInput);
}
let mut parser = Parser::new(SqlTokenCursor::new(tokens));
let statement = parser.parse_integrity_statement()?;
if parser.eat_semicolon() && !parser.is_eof() {
return Err(SqlParseError::unsupported_feature(
SqlFeatureCode::MultiStatementSql,
));
}
if !parser.is_eof() {
return Err(SqlParseError::expected_end_of_input(parser.peek_kind()));
}
Ok(statement)
}
struct Parser {
cursor: SqlTokenCursor,
next_param_index: usize,
expr_depth: usize,
}
impl Parser {
const fn new(cursor: SqlTokenCursor) -> Self {
Self {
cursor,
next_param_index: 0,
expr_depth: 0,
}
}
fn parse_literal(&mut self) -> Result<Value, SqlParseError> {
self.cursor.parse_literal()
}
fn expect_string_literal(&mut self) -> Result<String, SqlParseError> {
if !matches!(self.peek_kind(), Some(TokenKind::StringLiteral(_))) {
return Err(SqlParseError::expected(
SqlExpectedToken::StringLiteral,
self.peek_kind(),
));
}
match self.parse_literal()? {
Value::Text(value) => Ok(value),
_ => Err(SqlParseError::expected(
SqlExpectedToken::StringLiteral,
self.peek_kind(),
)),
}
}
fn parse_u32_literal(&mut self, clause: SqlIntegerLiteralClause) -> Result<u32, SqlParseError> {
let Some(TokenKind::Number(value)) = self.peek_kind() else {
return Err(SqlParseError::expected(
SqlExpectedToken::IntegerLiteral { clause },
self.peek_kind(),
));
};
let value = value.as_str();
if value.contains('.') || value.starts_with('-') {
return Err(SqlParseError::invalid_syntax(
SqlSyntaxErrorKind::IntegerLiteralRequiresNonNegative { clause },
));
}
let parsed = value.parse::<u32>().map_err(|_| {
SqlParseError::invalid_syntax(SqlSyntaxErrorKind::IntegerLiteralU32Overflow { clause })
})?;
self.cursor.advance();
Ok(parsed)
}
fn parse_u64_literal(&mut self, clause: SqlIntegerLiteralClause) -> Result<u64, SqlParseError> {
let Some(TokenKind::Number(value)) = self.peek_kind() else {
return Err(SqlParseError::expected(
SqlExpectedToken::IntegerLiteral { clause },
self.peek_kind(),
));
};
let value = value.as_str();
if value.contains('.') || value.starts_with('-') {
return Err(SqlParseError::invalid_syntax(
SqlSyntaxErrorKind::IntegerLiteralRequiresNonNegative { clause },
));
}
let parsed = value.parse::<u64>().map_err(|_| {
SqlParseError::invalid_syntax(SqlSyntaxErrorKind::IntegerLiteralU64Overflow { clause })
})?;
self.cursor.advance();
Ok(parsed)
}
fn expect_keyword(&mut self, keyword: Keyword) -> Result<(), SqlParseError> {
self.cursor.expect_keyword(keyword)
}
fn expect_identifier(&mut self) -> Result<String, SqlParseError> {
self.cursor.expect_identifier()
}
fn expect_lparen(&mut self) -> Result<(), SqlParseError> {
self.cursor.expect_lparen()
}
fn expect_rparen(&mut self) -> Result<(), SqlParseError> {
self.cursor.expect_rparen()
}
fn eat_keyword(&mut self, keyword: Keyword) -> bool {
self.cursor.eat_keyword(keyword)
}
fn eat_identifier_keyword(&mut self, keyword: &str) -> bool {
self.cursor.eat_identifier_keyword(keyword)
}
fn eat_comma(&mut self) -> bool {
self.cursor.eat_comma()
}
fn eat_plus(&mut self) -> bool {
self.cursor.eat_plus()
}
fn eat_question(&mut self) -> bool {
self.cursor.eat_question()
}
fn eat_minus(&mut self) -> bool {
self.cursor.eat_minus()
}
fn eat_slash(&mut self) -> bool {
self.cursor.eat_slash()
}
fn eat_semicolon(&mut self) -> bool {
self.cursor.eat_semicolon()
}
fn eat_star(&mut self) -> bool {
self.cursor.eat_star()
}
const fn take_param_index(&mut self) -> usize {
let index = self.next_param_index;
self.next_param_index = self.next_param_index.saturating_add(1);
index
}
const fn enter_sql_expr_depth(&mut self) -> Result<(), SqlParseError> {
if self.expr_depth >= MAX_SQL_EXPR_DEPTH {
return Err(sql_expr_depth_limit_error());
}
self.expr_depth = self.expr_depth.saturating_add(1);
Ok(())
}
const fn leave_sql_expr_depth(&mut self) {
self.expr_depth = self.expr_depth.saturating_sub(1);
}
fn peek_keyword(&self, keyword: Keyword) -> bool {
self.cursor.peek_keyword(keyword)
}
fn peek_lparen(&self) -> bool {
self.cursor.peek_lparen()
}
fn peek_unsupported_feature(&self) -> Option<SqlFeatureCode> {
SqlParseError::trailing_unsupported_feature(self.cursor.peek_kind())
}
fn peek_kind(&self) -> Option<&TokenKind> {
self.cursor.peek_kind()
}
fn expect_identifier_keyword(&mut self, keyword: &str) -> Result<(), SqlParseError> {
if self.eat_identifier_keyword(keyword) {
return Ok(());
}
Err(SqlParseError::expected(
SqlExpectedToken::identifier_keyword(keyword),
self.peek_kind(),
))
}
const fn is_eof(&self) -> bool {
self.cursor.is_eof()
}
}