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