Skip to main content

velesdb_core/velesql/parser/
mod.rs

1//! `VelesQL` parser implementation using pest.
2
3mod admin;
4mod condition_vectors;
5mod conditions;
6mod conditions_specialized;
7mod ddl;
8mod ddl_helpers;
9mod dml;
10mod dml_helpers;
11pub(crate) mod helpers;
12mod hints;
13mod introspection;
14mod match_parser;
15mod prescan;
16mod select;
17mod train;
18mod values;
19
20#[allow(dead_code)]
21pub mod match_clause;
22#[cfg(test)]
23mod match_clause_tests;
24mod match_patterns;
25#[cfg(test)]
26mod match_query_tests;
27#[cfg(test)]
28mod robustness_tests;
29#[cfg(test)]
30mod sparse_search_tests;
31#[cfg(test)]
32mod subquery_tests;
33#[cfg(test)]
34mod temporal_tests;
35
36use pest::iterators::Pair;
37use pest::Parser as PestParser;
38use pest_derive::Parser;
39
40use super::ast::Query;
41use super::error::{ParseError, ParseErrorKind};
42use super::{QueryValidator, ValidationConfig};
43
44#[derive(Parser)]
45#[grammar = "velesql/grammar.pest"]
46pub(crate) struct VelesQLParser;
47
48/// EPIC-044 US-005: Extract identifier string from any identifier form.
49/// Handles: regular_identifier, backtick_identifier, doublequote_identifier
50pub(crate) fn extract_identifier(pair: &Pair<'_, Rule>) -> String {
51    match pair.as_rule() {
52        Rule::identifier => {
53            // identifier = { quoted_identifier | regular_identifier }
54            if let Some(inner) = pair.clone().into_inner().next() {
55                extract_identifier(&inner)
56            } else {
57                // Fallback for atomic match
58                pair.as_str().to_string()
59            }
60        }
61        Rule::quoted_identifier => {
62            // quoted_identifier = { backtick_identifier | doublequote_identifier }
63            if let Some(inner) = pair.clone().into_inner().next() {
64                extract_identifier(&inner)
65            } else {
66                pair.as_str().to_string()
67            }
68        }
69        Rule::backtick_identifier => {
70            // Remove surrounding backticks: `name` -> name
71            let s = pair.as_str();
72            s[1..s.len() - 1].to_string()
73        }
74        Rule::doublequote_identifier => {
75            // Remove surrounding quotes and unescape: "col""name" -> col"name
76            let s = pair.as_str();
77            let inner = &s[1..s.len() - 1];
78            inner.replace("\"\"", "\"")
79        }
80        // Rule::regular_identifier and other rules: return as-is
81        _ => pair.as_str().to_string(),
82    }
83}
84
85/// `VelesQL` query parser.
86pub struct Parser;
87
88impl Parser {
89    /// Parses a `VelesQL` query string into an AST.
90    ///
91    /// # Errors
92    ///
93    /// Returns a `ParseError` if the query is invalid.
94    ///
95    /// # Example
96    ///
97    /// ```ignore
98    /// use velesdb_core::velesql::Parser;
99    ///
100    /// let query = Parser::parse("SELECT * FROM documents LIMIT 10")?;
101    /// ```
102    pub fn parse(input: &str) -> Result<Query, ParseError> {
103        let config = ValidationConfig::default();
104        // #896: reject over-length / over-nested queries by a cheap linear
105        // pre-scan of the raw bytes BEFORE pest builds the full parse tree,
106        // so deeply nested ()/[]/(SELECT cannot overflow the native stack.
107        prescan::prescan(input, config.max_query_length)?;
108
109        let pairs = VelesQLParser::parse(Rule::query, input).map_err(|e| {
110            let position = match e.location {
111                pest::error::InputLocation::Pos(p) => p,
112                pest::error::InputLocation::Span((s, _)) => s,
113            };
114            ParseError::new(
115                ParseErrorKind::SyntaxError,
116                position,
117                input.chars().take(50).collect::<String>(),
118                hints::enrich_message(input, position, &e.to_string()),
119            )
120        })?;
121
122        let query_pair = pairs
123            .into_iter()
124            .next()
125            .ok_or_else(|| ParseError::syntax(0, input, "Empty query"))?;
126
127        let query = Self::parse_query(query_pair)?;
128        QueryValidator::enforce_query_complexity(&query, input, &config)?;
129        Ok(query)
130    }
131}