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