#![allow(dead_code)]
use std::ops::Range;
#[derive(Debug, Clone, PartialEq)]
pub enum FieldPath {
Simple(String),
Nested { base: Box<FieldPath>, field: String },
ArrayIndex {
base: Box<FieldPath>,
index: ArrayIndex,
},
ArraySlice {
base: Box<FieldPath>,
slice: ArraySlice,
},
}
impl FieldPath {
pub fn simple(name: String) -> Self {
FieldPath::Simple(name)
}
pub fn nested(base: FieldPath, field: String) -> Self {
FieldPath::Nested {
base: Box::new(base),
field,
}
}
pub fn index(base: FieldPath, index: ArrayIndex) -> Self {
FieldPath::ArrayIndex {
base: Box::new(base),
index,
}
}
pub fn slice(base: FieldPath, slice: ArraySlice) -> Self {
FieldPath::ArraySlice {
base: Box::new(base),
slice,
}
}
pub fn to_mongodb_path(&self) -> Option<String> {
match self {
FieldPath::Simple(name) => Some(name.clone()),
FieldPath::Nested { base, field } => {
base.to_mongodb_path().map(|b| format!("{}.{}", b, field))
}
FieldPath::ArrayIndex { .. } | FieldPath::ArraySlice { .. } => None,
}
}
pub fn requires_aggregation(&self) -> bool {
match self {
FieldPath::Simple(_) => false,
FieldPath::Nested { base, .. } => base.requires_aggregation(),
FieldPath::ArrayIndex { .. } | FieldPath::ArraySlice { .. } => true,
}
}
pub fn base_field(&self) -> String {
match self {
FieldPath::Simple(name) => name.clone(),
FieldPath::Nested { base, .. }
| FieldPath::ArrayIndex { base, .. }
| FieldPath::ArraySlice { base, .. } => base.base_field(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ArrayIndex {
Positive(i64),
Negative(i64),
}
impl ArrayIndex {
pub fn positive(index: i64) -> Self {
ArrayIndex::Positive(index)
}
pub fn negative(index: i64) -> Self {
ArrayIndex::Negative(index.abs())
}
pub fn resolve(&self, array_len: Option<usize>) -> Option<usize> {
match (self, array_len) {
(ArrayIndex::Positive(idx), _) if *idx >= 0 => Some(*idx as usize),
(ArrayIndex::Negative(idx), Some(len)) if *idx <= len as i64 => {
Some(len - (*idx as usize))
}
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ArraySlice {
pub start: Option<SliceIndex>,
pub end: Option<SliceIndex>,
pub step: Option<i64>,
}
impl ArraySlice {
pub fn new(start: Option<SliceIndex>, end: Option<SliceIndex>, step: Option<i64>) -> Self {
Self { start, end, step }
}
pub fn full() -> Self {
Self::new(None, None, None)
}
pub fn to(end: SliceIndex) -> Self {
Self::new(None, Some(end), None)
}
pub fn from(start: SliceIndex) -> Self {
Self::new(Some(start), None, None)
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SliceIndex {
Positive(i64),
Negative(i64),
}
#[derive(Debug, Clone, PartialEq)]
pub struct SqlContext {
pub clause: SqlClause,
pub position: usize,
pub expected: Vec<Expected>,
pub partial_input: String,
}
impl SqlContext {
pub fn new(clause: SqlClause, position: usize, expected: Vec<Expected>) -> Self {
Self {
clause,
position,
expected,
partial_input: String::new(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SqlClause {
Select,
From,
Where,
GroupBy,
OrderBy,
Limit,
Offset,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Expected {
Keyword(&'static str),
TableName,
ColumnName,
Expression,
Operator,
AggregateFunction,
OrderDirection,
Number,
String,
Star,
EndOfStatement,
}
impl Expected {
pub fn description(&self) -> &str {
match self {
Expected::Keyword(kw) => kw,
Expected::TableName => "table name",
Expected::ColumnName => "column name",
Expected::Expression => "expression",
Expected::Operator => "operator",
Expected::AggregateFunction => "aggregate function",
Expected::OrderDirection => "ASC or DESC",
Expected::Number => "number",
Expected::String => "string",
Expected::Star => "*",
Expected::EndOfStatement => "end of statement",
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ParseResult<T> {
Ok(T),
Partial(T, Vec<Expected>),
Error(ParseError),
}
impl<T> ParseResult<T> {
pub fn is_ok(&self) -> bool {
matches!(self, ParseResult::Ok(_))
}
pub fn is_partial(&self) -> bool {
matches!(self, ParseResult::Partial(_, _))
}
pub fn is_error(&self) -> bool {
matches!(self, ParseResult::Error(_))
}
pub fn value(self) -> Option<T> {
match self {
ParseResult::Ok(v) | ParseResult::Partial(v, _) => Some(v),
ParseResult::Error(_) => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ParseError {
pub message: String,
pub span: Range<usize>,
pub expected: Vec<Expected>,
pub hint: Option<String>,
}
impl ParseError {
pub fn new(message: String, span: Range<usize>) -> Self {
Self {
message,
span,
expected: Vec::new(),
hint: None,
}
}
pub fn with_expected(message: String, span: Range<usize>, expected: Vec<Expected>) -> Self {
Self {
message,
span,
expected,
hint: None,
}
}
pub fn with_hint(mut self, hint: String) -> Self {
self.hint = Some(hint);
self
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SqlSelect {
pub columns: Vec<SqlColumn>,
pub table: Option<String>,
pub where_clause: Option<SqlExpr>,
pub group_by: Option<Vec<String>>,
pub order_by: Option<Vec<SqlOrderBy>>,
pub limit: Option<usize>,
pub offset: Option<usize>,
}
impl SqlSelect {
pub fn new() -> Self {
Self {
columns: Vec::new(),
table: None,
where_clause: None,
group_by: None,
order_by: None,
limit: None,
offset: None,
}
}
pub fn needs_aggregate(&self) -> bool {
self.group_by.is_some()
|| self.columns.iter().any(|c| match c {
SqlColumn::Aggregate { .. } => true,
SqlColumn::Field { alias, .. } => alias.is_some(),
_ => false,
})
}
}
impl Default for SqlSelect {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SqlColumn {
Star,
Field {
path: FieldPath,
alias: Option<String>,
},
Aggregate {
func: String,
field: Option<FieldPath>,
alias: Option<String>,
distinct: bool,
},
}
impl SqlColumn {
pub fn field(path: FieldPath) -> Self {
SqlColumn::Field { path, alias: None }
}
pub fn aggregate(func: String, field: Option<FieldPath>) -> Self {
SqlColumn::Aggregate {
func,
field,
alias: None,
distinct: false,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SqlExpr {
Literal(SqlLiteral),
FieldPath(FieldPath),
BinaryOp {
left: Box<SqlExpr>,
op: SqlOperator,
right: Box<SqlExpr>,
},
LogicalOp {
left: Box<SqlExpr>,
op: SqlLogicalOperator,
right: Box<SqlExpr>,
},
Function { name: String, args: Vec<SqlExpr> },
In {
expr: Box<SqlExpr>,
values: Vec<SqlExpr>,
},
Like { expr: Box<SqlExpr>, pattern: String },
IsNull { expr: Box<SqlExpr>, negated: bool },
}
#[derive(Debug, Clone, PartialEq)]
pub enum SqlLiteral {
String(String),
Number(f64),
Boolean(bool),
Null,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SqlOperator {
Eq,
Ne,
Gt,
Lt,
Ge,
Le,
}
impl SqlOperator {
pub fn binding_power(&self) -> (u8, u8) {
match self {
SqlOperator::Eq | SqlOperator::Ne => (3, 4),
SqlOperator::Gt | SqlOperator::Lt | SqlOperator::Ge | SqlOperator::Le => (5, 6),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum SqlLogicalOperator {
And,
Or,
Not,
}
impl SqlLogicalOperator {
pub fn binding_power(&self) -> (u8, u8) {
match self {
SqlLogicalOperator::Or => (1, 2),
SqlLogicalOperator::And => (3, 4),
SqlLogicalOperator::Not => (7, 8),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SqlOrderBy {
pub path: FieldPath,
pub asc: bool,
}
impl SqlOrderBy {
pub fn new(path: FieldPath, asc: bool) -> Self {
Self { path, asc }
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ArrayAccessError {
EmptyIndex,
InvalidIndexType(String),
MissingCloseBracket,
InvalidSliceSyntax(String),
ZeroStepSize,
UnsupportedFeature(String),
}
impl ArrayAccessError {
pub fn to_user_message(&self) -> String {
match self {
ArrayAccessError::EmptyIndex => {
"Empty array index. Use arr[0] for first element or arr[-1] for last element."
.to_string()
}
ArrayAccessError::InvalidIndexType(val) => {
format!("Invalid array index '{}'. Index must be a number.", val)
}
ArrayAccessError::MissingCloseBracket => {
"Missing closing bracket ']' for array access.".to_string()
}
ArrayAccessError::InvalidSliceSyntax(msg) => {
format!("Invalid array slice syntax: {}", msg)
}
ArrayAccessError::ZeroStepSize => "Array slice step cannot be zero.".to_string(),
ArrayAccessError::UnsupportedFeature(feature) => {
format!("Unsupported feature: {}", feature)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sql_select_needs_aggregate() {
let mut select = SqlSelect::new();
assert!(!select.needs_aggregate());
select.group_by = Some(vec!["category".to_string()]);
assert!(select.needs_aggregate());
let mut select2 = SqlSelect::new();
select2.columns.push(SqlColumn::Aggregate {
func: "COUNT".to_string(),
field: None,
alias: None,
distinct: false,
});
assert!(select2.needs_aggregate());
}
#[test]
fn test_expected_description() {
assert_eq!(Expected::Keyword("SELECT").description(), "SELECT");
assert_eq!(Expected::TableName.description(), "table name");
assert_eq!(Expected::ColumnName.description(), "column name");
}
#[test]
fn test_parse_result_checks() {
let ok_result: ParseResult<i32> = ParseResult::Ok(42);
assert!(ok_result.is_ok());
assert!(!ok_result.is_partial());
assert!(!ok_result.is_error());
let partial_result: ParseResult<i32> = ParseResult::Partial(42, vec![]);
assert!(!partial_result.is_ok());
assert!(partial_result.is_partial());
assert!(!partial_result.is_error());
let error_result: ParseResult<i32> =
ParseResult::Error(ParseError::new("error".to_string(), 0..1));
assert!(!error_result.is_ok());
assert!(!error_result.is_partial());
assert!(error_result.is_error());
}
}