1pub(crate) mod alter_drop_show;
2pub(crate) mod config_parsers;
3pub(crate) mod config_validation;
4pub(crate) mod create;
5pub(crate) mod filter;
6pub(crate) mod formula;
7pub(crate) mod helpers;
8pub(crate) mod point_ops;
9pub(crate) mod query;
10mod syntax;
11pub(crate) mod r#update;
12pub(crate) mod upsert;
13pub(crate) mod with_clause;
14
15use crate::ast::Stmt;
16use crate::error::QqlError;
17use crate::lexer::Lexer;
18use crate::token::{Token, TokenKind};
19use alloc::string::String;
20use alloc::vec::Vec;
21pub use config_validation::{
22 check_deleted_threshold, config_bool, config_float_range, config_has_key,
23 config_max_optimization_threads, config_non_negative_u64, config_positive_u64, config_value,
24 merge_collection_config, validate_hnsw_value, validate_index_options,
25 validate_optimizers_value, validate_params_value, validate_vectors_value,
26};
27
28pub struct Parser;
33
34pub(crate) struct AstLowerer<'a> {
35 pub input: &'a str,
36 tokens: Vec<Token<'a>>,
37 index: usize,
38}
39
40pub const MAX_STATEMENTS: usize = 256;
43
44pub fn ascii_equal(s: &str, upper: &str) -> bool {
45 if s.len() != upper.len() {
46 return false;
47 }
48 s.as_bytes()
49 .iter()
50 .zip(upper.as_bytes().iter())
51 .all(|(a, b)| a.to_ascii_uppercase() == *b)
52}
53
54pub fn ascii_equal_lower(s: &str, lower: &str) -> bool {
55 if s.len() != lower.len() {
56 return false;
57 }
58 s.as_bytes()
59 .iter()
60 .zip(lower.as_bytes().iter())
61 .all(|(a, b)| a.to_ascii_lowercase() == *b)
62}
63
64pub fn is_contextual_field_name(kind: TokenKind) -> bool {
65 matches!(
66 kind,
67 TokenKind::Offset
68 | TokenKind::Score
69 | TokenKind::Threshold
70 | TokenKind::Lookup
71 | TokenKind::Id
72 | TokenKind::Dense
73 | TokenKind::Sparse
74 | TokenKind::Vector
75 | TokenKind::By
76 | TokenKind::Count
77 | TokenKind::Clear
78 | TokenKind::Field
79 | TokenKind::Into
80 )
81}
82
83fn is_contextual_identifier(kind: TokenKind) -> bool {
84 matches!(
85 kind,
86 TokenKind::Offset
87 | TokenKind::Score
88 | TokenKind::Threshold
89 | TokenKind::Lookup
90 | TokenKind::Id
91 | TokenKind::Dense
92 | TokenKind::Sparse
93 | TokenKind::Vector
94 | TokenKind::Count
95 | TokenKind::Clear
96 | TokenKind::Field
97 | TokenKind::Into
98 )
99}
100
101impl Parser {
102 pub fn parse(input: &str) -> Result<Stmt, QqlError> {
103 AstLowerer::lower_statement(input)
104 }
105
106 pub fn parse_all(input: &str) -> Result<Vec<Stmt>, QqlError> {
107 AstLowerer::lower_script(input)
108 }
109}
110
111impl<'a> AstLowerer<'a> {
112 fn new(input: &'a str, tokens: Vec<Token<'a>>) -> Self {
113 Self {
114 input,
115 tokens,
116 index: 0,
117 }
118 }
119
120 fn lower_statement(input: &'a str) -> Result<Stmt, QqlError> {
121 let tokens = Self::lex(input)?;
122 let mut parser = AstLowerer::new(input, tokens);
123 let stmt = parser.parse_stmt()?;
124 if parser.peek()?.kind == TokenKind::Semicolon {
125 parser.advance()?;
126 }
127 parser.expect_end()?;
128 Ok(stmt)
129 }
130
131 fn lower_script(input: &'a str) -> Result<Vec<Stmt>, QqlError> {
132 let tokens = Self::lex(input)?;
133 let mut parser = AstLowerer::new(input, tokens);
134 let mut statements = Vec::new();
135 if parser.peek()?.kind == TokenKind::Semicolon {
136 return Err(QqlError::parse(
137 "QQL-PARSE-EMPTY-STATEMENT",
138 "leading or empty statements are not allowed",
139 parser.peek()?.span,
140 ));
141 }
142
143 while parser.peek()?.kind != TokenKind::Eof {
144 if statements.len() >= MAX_STATEMENTS {
145 return Err(QqlError::parse(
146 "QQL-PARSE-STATEMENT-LIMIT",
147 alloc::format!("a script may contain at most {MAX_STATEMENTS} statements"),
148 parser.peek()?.span,
149 ));
150 }
151 statements.push(parser.parse_stmt()?);
152 match parser.peek()?.kind {
153 TokenKind::Semicolon => {
154 parser.advance()?;
155 if parser.peek()?.kind == TokenKind::Semicolon {
156 return Err(QqlError::parse(
157 "QQL-PARSE-EMPTY-STATEMENT",
158 "repeated semicolons are not allowed",
159 parser.peek()?.span,
160 ));
161 }
162 }
163 TokenKind::Eof => break,
164 _ => {
165 return Err(QqlError::parse(
166 "QQL-PARSE-SEPARATOR",
167 "multiple statements must be separated by a semicolon",
168 parser.peek()?.span,
169 ));
170 }
171 }
172 }
173 Ok(statements)
174 }
175
176 fn lex(input: &'a str) -> Result<Vec<Token<'a>>, QqlError> {
177 let lexer = Lexer::new(input);
178 let mut tokens = Vec::with_capacity(input.len() / 6 + 1);
179 for token_res in lexer {
180 tokens.push(token_res?);
181 }
182 Ok(tokens)
183 }
184
185 fn expect_end(&mut self) -> Result<(), QqlError> {
186 if self.index < self.tokens.len() {
187 let tok = self.tokens[self.index];
188 return Err(QqlError::parse(
189 "QQL-PARSE-TRAILING",
190 alloc::format!("unexpected trailing token '{}'", tok.text),
191 tok.span,
192 ));
193 }
194
195 Ok(())
196 }
197
198 pub fn parse_stmt(&mut self) -> Result<Stmt, QqlError> {
199 let tok = self.peek()?;
200 match tok.kind {
201 TokenKind::Create => self.parse_create(),
202 TokenKind::Alter => self.parse_alter(),
203 TokenKind::Drop => self.parse_drop(),
204 TokenKind::Show => self.parse_show(),
205 TokenKind::Upsert => self.parse_upsert(),
206 TokenKind::Scroll => self.parse_scroll(),
207 TokenKind::Query => self.parse_query(),
208 TokenKind::With => self.parse_query_with_cte(),
209 TokenKind::Delete => self.parse_delete(),
210 TokenKind::Clear => self.parse_clear(),
211 TokenKind::Update => self.parse_update(),
212 TokenKind::Count => self.parse_count(),
213 _ => Err(QqlError::parse(
214 "QQL-PARSE-STATEMENT",
215 alloc::format!("expected a QQL statement keyword, got '{}'", tok.text),
216 tok.span,
217 )),
218 }
219 }
220
221 pub fn peek(&mut self) -> Result<Token<'a>, QqlError> {
224 if self.index < self.tokens.len() {
225 Ok(self.tokens[self.index])
226 } else {
227 Ok(Token::eof(self.input.len()))
228 }
229 }
230
231 pub fn peek_nth(&self, offset: usize) -> Token<'a> {
232 let idx = self.index + offset;
233 if idx < self.tokens.len() {
234 self.tokens[idx]
235 } else {
236 Token::eof(self.input.len())
237 }
238 }
239
240 pub fn advance(&mut self) -> Result<Token<'a>, QqlError> {
241 let tok = self.peek()?;
242 if self.index < self.tokens.len() {
243 self.index += 1;
244 }
245 Ok(tok)
246 }
247
248 pub fn expect(&mut self, kind: TokenKind) -> Result<Token<'a>, QqlError> {
249 let tok = self.peek()?;
250 if tok.kind != kind {
251 return Err(QqlError::parse(
252 "QQL-PARSE-EXPECTED",
253 alloc::format!("expected {} but got '{}'", kind, tok.text),
254 tok.span,
255 ));
256 }
257 self.advance()
258 }
259
260 pub fn parse_identifier_str(&mut self) -> Result<&'a str, QqlError> {
263 let tok = self.peek()?;
264 if tok.kind == TokenKind::Identifier
265 || tok.kind == TokenKind::String
266 || is_contextual_identifier(tok.kind)
267 {
268 self.advance()?;
269 Ok(tok.text)
270 } else {
271 Err(QqlError::parse(
272 "QQL-PARSE-IDENTIFIER",
273 alloc::format!("expected identifier or quoted name, got '{}'", tok.text),
274 tok.span,
275 ))
276 }
277 }
278
279 pub fn parse_identifier(&mut self) -> Result<String, QqlError> {
280 self.parse_identifier_str().map(String::from)
281 }
282
283 pub fn parse_value(&mut self) -> Result<crate::ast::Value, QqlError> {
286 let tok = self.peek()?;
287 match tok.kind {
288 TokenKind::String => {
289 self.advance()?;
290 self.decode_string(tok).map(crate::ast::Value::Str)
291 }
292 TokenKind::Float => {
293 self.advance()?;
294 let v: f64 = tok.text.parse().map_err(|_| {
295 QqlError::parse(
296 "QQL-PARSE-FLOAT",
297 alloc::format!("invalid float literal '{}'", tok.text),
298 tok.span,
299 )
300 })?;
301 Ok(crate::ast::Value::Float(v))
302 }
303 TokenKind::Integer => {
304 self.advance()?;
305 let v: i64 = tok.text.parse().map_err(|_| {
306 QqlError::parse(
307 "QQL-PARSE-INTEGER",
308 alloc::format!("invalid integer literal '{}'", tok.text),
309 tok.span,
310 )
311 })?;
312 Ok(crate::ast::Value::Int(v))
313 }
314 TokenKind::Null => {
315 self.advance()?;
316 Ok(crate::ast::Value::Null)
317 }
318 TokenKind::Identifier => {
319 self.advance()?;
320 if ascii_equal(tok.text, "TRUE") {
321 Ok(crate::ast::Value::Bool(true))
322 } else if ascii_equal(tok.text, "FALSE") {
323 Ok(crate::ast::Value::Bool(false))
324 } else if ascii_equal(tok.text, "NULL") {
325 Ok(crate::ast::Value::Null)
326 } else {
327 Ok(crate::ast::Value::Str(tok.text.to_string()))
328 }
329 }
330 TokenKind::Lbrace => self
331 .parse_payload_dict()
332 .map(|items| crate::ast::Value::Dict(items.into_iter().collect())),
333 TokenKind::Lbracket => self.parse_list().map(crate::ast::Value::List),
334 _ => Err(QqlError::parse(
335 "QQL-PARSE-VALUE",
336 alloc::format!("unexpected value token '{}'", tok.text),
337 tok.span,
338 )),
339 }
340 }
341
342 fn decode_string(&self, token: Token<'a>) -> Result<String, QqlError> {
343 let first_byte = self
344 .input
345 .as_bytes()
346 .get(token.span.start)
347 .copied()
348 .unwrap_or(0);
349 let is_raw_or_backtick = first_byte == b'r' || first_byte == b'`';
350 if is_raw_or_backtick
351 || !(token.text.contains('\\') || first_byte == b'\'' && token.text.contains("''"))
352 {
353 return Ok(token.text.to_string());
354 }
355 let single_quoted = first_byte == b'\'';
356 let mut decoded = String::with_capacity(token.text.len());
357 let mut chars = token.text.chars().peekable();
358 while let Some(ch) = chars.next() {
359 if single_quoted && ch == '\'' && chars.peek() == Some(&'\'') {
360 chars.next();
361 decoded.push('\'');
362 continue;
363 }
364 if ch != '\\' {
365 decoded.push(ch);
366 continue;
367 }
368 let escaped = chars.next().ok_or_else(|| {
369 QqlError::parse(
370 "QQL-PARSE-ESCAPE",
371 "unterminated escape sequence",
372 token.span,
373 )
374 })?;
375 decoded.push(match escaped {
376 'n' => '\n',
377 'r' => '\r',
378 't' => '\t',
379 '\\' => '\\',
380 '\'' => '\'',
381 '"' => '"',
382 '$' => '$',
383 _ => {
384 return Err(QqlError::parse(
385 "QQL-PARSE-ESCAPE",
386 alloc::format!("unsupported escape sequence \\{}", escaped),
387 token.span,
388 ));
389 }
390 });
391 }
392 Ok(decoded)
393 }
394}