1use crate::token::Token;
2
3pub const POWQL_KEYWORDS: &[&str] = &[
7 "abs",
8 "add",
9 "alter",
10 "and",
11 "as",
12 "asc",
13 "auto",
14 "avg",
15 "begin",
16 "between",
17 "case",
18 "cast",
19 "ceil",
20 "column",
21 "commit",
22 "concat",
23 "conflict",
24 "count",
25 "cross",
26 "date_add",
27 "date_diff",
28 "default",
29 "delete",
30 "dense_rank",
31 "desc",
32 "distinct",
33 "drop",
34 "else",
35 "end",
36 "exists",
37 "explain",
38 "extract",
39 "false",
40 "filter",
41 "floor",
42 "group",
43 "having",
44 "in",
45 "index",
46 "inner",
47 "insert",
48 "is",
49 "join",
50 "left",
51 "length",
52 "let",
53 "like",
54 "limit",
55 "link",
56 "lower",
57 "match",
58 "materialize",
59 "materialized",
60 "max",
61 "min",
62 "multi",
63 "not",
64 "now",
65 "null",
66 "offset",
67 "on",
68 "or",
69 "order",
70 "outer",
71 "over",
72 "partition",
73 "pow",
74 "rank",
75 "refresh",
76 "required",
77 "returning",
78 "right",
79 "rollback",
80 "round",
81 "row_number",
82 "select",
83 "sqrt",
84 "substring",
85 "sum",
86 "then",
87 "transaction",
88 "trim",
89 "true",
90 "type",
91 "union",
92 "unique",
93 "update",
94 "upper",
95 "upsert",
96 "view",
97 "when",
98];
99
100const MAX_STRING_LITERAL: usize = 16 * 1024 * 1024;
103
104#[derive(Debug)]
105pub struct LexError {
106 pub message: String,
107 pub position: usize,
108}
109
110impl std::fmt::Display for LexError {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 write!(f, "at position {}: {}", self.position, self.message)
113 }
114}
115
116impl std::error::Error for LexError {}
117
118pub fn lex(input: &str) -> Result<Vec<Token>, LexError> {
132 let mut tokens = Vec::new();
133 let chars: Vec<char> = input.chars().collect();
134 let mut pos = 0;
135
136 while pos < chars.len() {
137 if chars[pos].is_whitespace() {
139 pos += 1;
140 continue;
141 }
142
143 if chars[pos] == '#' {
145 while pos < chars.len() && chars[pos] != '\n' {
146 pos += 1;
147 }
148 continue;
149 }
150
151 if chars[pos] == '.'
153 && pos + 1 < chars.len()
154 && (chars[pos + 1].is_alphabetic() || chars[pos + 1] == '_')
155 {
156 pos += 1; let start = pos;
158 while pos < chars.len() && (chars[pos].is_alphanumeric() || chars[pos] == '_') {
159 pos += 1;
160 }
161 let name: String = chars[start..pos].iter().collect();
162 tokens.push(Token::DotIdent(name));
163 continue;
164 }
165
166 if chars[pos] == '$' {
168 pos += 1;
169 let start = pos;
170 while pos < chars.len() && (chars[pos].is_alphanumeric() || chars[pos] == '_') {
171 pos += 1;
172 }
173 let name: String = chars[start..pos].iter().collect();
174 tokens.push(Token::Param(name));
175 continue;
176 }
177
178 if chars[pos] == '"' {
180 pos += 1;
181 let mut s = String::new();
182 while pos < chars.len() && chars[pos] != '"' {
183 if chars[pos] == '\\' && pos + 1 < chars.len() {
184 match chars[pos + 1] {
185 '"' => {
186 s.push('"');
187 pos += 2;
188 }
189 '\\' => {
190 s.push('\\');
191 pos += 2;
192 }
193 'n' => {
194 s.push('\n');
195 pos += 2;
196 }
197 't' => {
198 s.push('\t');
199 pos += 2;
200 }
201 _ => {
202 s.push(chars[pos + 1]);
203 pos += 2;
204 }
205 }
206 } else {
207 s.push(chars[pos]);
208 pos += 1;
209 }
210 }
211 if pos >= chars.len() {
212 return Err(LexError {
213 message: "unterminated string".into(),
214 position: pos,
215 });
216 }
217 pos += 1; if s.len() > MAX_STRING_LITERAL {
219 return Err(LexError {
220 message: format!(
221 "string literal exceeds maximum size of {}MB",
222 MAX_STRING_LITERAL / (1024 * 1024)
223 ),
224 position: pos,
225 });
226 }
227 tokens.push(Token::StringLit(s));
228 continue;
229 }
230
231 if chars[pos].is_ascii_digit()
233 || (chars[pos] == '-' && pos + 1 < chars.len() && chars[pos + 1].is_ascii_digit())
234 {
235 let start = pos;
236 if chars[pos] == '-' {
237 pos += 1;
238 }
239 while pos < chars.len() && chars[pos].is_ascii_digit() {
240 pos += 1;
241 }
242 if pos < chars.len()
243 && chars[pos] == '.'
244 && pos + 1 < chars.len()
245 && chars[pos + 1].is_ascii_digit()
246 {
247 pos += 1;
248 while pos < chars.len() && chars[pos].is_ascii_digit() {
249 pos += 1;
250 }
251 let s: String = chars[start..pos].iter().collect();
252 let value = s.parse::<f64>().map_err(|_| LexError {
253 message: format!("float literal out of range: {s}"),
254 position: start,
255 })?;
256 tokens.push(Token::FloatLit(value));
257 } else {
258 let s: String = chars[start..pos].iter().collect();
259 let value = s.parse::<i64>().map_err(|_| LexError {
260 message: format!("integer literal out of range for i64: {s}"),
261 position: start,
262 })?;
263 tokens.push(Token::IntLit(value));
264 }
265 continue;
266 }
267
268 if chars[pos].is_alphabetic() || chars[pos] == '_' {
270 let start = pos;
271 while pos < chars.len() && (chars[pos].is_alphanumeric() || chars[pos] == '_') {
272 pos += 1;
273 }
274 let word: String = chars[start..pos].iter().collect();
275 let token = match word.as_str() {
276 "type" => Token::Type,
277 "filter" => Token::Filter,
278 "order" => Token::Order,
279 "limit" => Token::Limit,
280 "offset" => Token::Offset,
281 "insert" => Token::Insert,
282 "update" => Token::Update,
283 "delete" => Token::Delete,
284 "default" => Token::Default,
285 "upsert" => Token::Upsert,
286 "returning" => Token::Returning,
287 "conflict" => Token::Conflict,
288 "select" => Token::Select,
289 "required" => Token::Required,
290 "multi" => Token::Multi,
291 "link" => Token::Link,
292 "index" => Token::Index,
293 "unique" => Token::Unique,
294 "on" => Token::On,
295 "asc" => Token::Asc,
296 "auto" => Token::Auto,
297 "desc" => Token::Desc,
298 "and" => Token::And,
299 "or" => Token::Or,
300 "not" => Token::Not,
301 "exists" => Token::Exists,
302 "let" => Token::Let,
303 "as" => Token::As,
304 "match" => Token::Match,
305 "group" => Token::Group,
306 "join" => Token::Join,
307 "inner" => Token::Inner,
308 "left" => Token::LeftKw,
309 "right" => Token::RightKw,
310 "outer" => Token::Outer,
311 "cross" => Token::Cross,
312 "transaction" => Token::Transaction,
313 "begin" => Token::Begin,
314 "commit" => Token::Commit,
315 "rollback" => Token::Rollback,
316 "view" => Token::View,
317 "materialized" => Token::Materialized,
318 "materialize" => Token::Materialized,
319 "refresh" => Token::Refresh,
320 "union" => Token::Union,
321 "having" => Token::Having,
322 "distinct" => Token::Distinct,
323 "in" => Token::In,
324 "between" => Token::Between,
325 "like" => Token::Like,
326 "count" => Token::Count,
327 "avg" => Token::Avg,
328 "sum" => Token::Sum,
329 "min" => Token::Min,
330 "max" => Token::Max,
331 "is" => Token::Is,
332 "null" => Token::Null,
333 "upper" => Token::Upper,
334 "lower" => Token::Lower,
335 "length" => Token::Length,
336 "trim" => Token::Trim,
337 "substring" => Token::Substring,
338 "concat" => Token::Concat,
339 "abs" => Token::Abs,
340 "round" => Token::Round,
341 "ceil" => Token::Ceil,
342 "floor" => Token::Floor,
343 "sqrt" => Token::Sqrt,
344 "pow" => Token::Pow,
345 "now" => Token::Now,
346 "extract" => Token::Extract,
347 "date_add" => Token::DateAdd,
348 "date_diff" => Token::DateDiff,
349 "cast" => Token::Cast,
350 "case" => Token::Case,
351 "when" => Token::When,
352 "then" => Token::Then,
353 "else" => Token::Else,
354 "end" => Token::End,
355 "over" => Token::Over,
356 "partition" => Token::Partition,
357 "row_number" => Token::RowNumber,
358 "rank" => Token::Rank,
359 "dense_rank" => Token::DenseRank,
360 "alter" => Token::Alter,
361 "drop" => Token::Drop,
362 "add" => Token::Add,
363 "column" => Token::Column,
364 "explain" => Token::Explain,
365 "true" => Token::BoolLit(true),
366 "false" => Token::BoolLit(false),
367 _ => Token::Ident(word),
368 };
369 tokens.push(token);
370 continue;
371 }
372
373 if pos + 1 < chars.len() {
375 let two: String = chars[pos..pos + 2].iter().collect();
376 match two.as_str() {
377 ":=" => {
378 tokens.push(Token::Assign);
379 pos += 2;
380 continue;
381 }
382 "->" => {
383 tokens.push(Token::Arrow);
384 pos += 2;
385 continue;
386 }
387 "!=" => {
388 tokens.push(Token::Neq);
389 pos += 2;
390 continue;
391 }
392 "<=" => {
393 tokens.push(Token::Lte);
394 pos += 2;
395 continue;
396 }
397 ">=" => {
398 tokens.push(Token::Gte);
399 pos += 2;
400 continue;
401 }
402 "??" => {
403 tokens.push(Token::Coalesce);
404 pos += 2;
405 continue;
406 }
407 _ => {}
408 }
409 }
410
411 let token = match chars[pos] {
413 '=' => Token::Eq,
414 '<' => Token::Lt,
415 '>' => Token::Gt,
416 '|' => Token::Pipe,
417 '+' => Token::Plus,
418 '-' => Token::Minus,
419 '*' => Token::Star,
420 '/' => Token::Slash,
421 '{' => Token::LBrace,
422 '}' => Token::RBrace,
423 '(' => Token::LParen,
424 ')' => Token::RParen,
425 ',' => Token::Comma,
426 ':' => Token::Colon,
427 '.' => Token::Dot,
428 c => {
429 return Err(LexError {
430 message: format!("unexpected character: {c}"),
431 position: pos,
432 })
433 }
434 };
435 tokens.push(token);
436 pos += 1;
437 }
438
439 tokens.push(Token::Eof);
440 Ok(tokens)
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446 use crate::token::Token;
447
448 #[test]
449 fn test_lex_simple_query() {
450 let tokens = lex("User filter .age > 30").unwrap();
451 assert_eq!(
452 tokens,
453 vec![
454 Token::Ident("User".into()),
455 Token::Filter,
456 Token::DotIdent("age".into()),
457 Token::Gt,
458 Token::IntLit(30),
459 Token::Eof,
460 ]
461 );
462 }
463
464 #[test]
465 fn test_lex_projection() {
466 let tokens = lex("User { name, email }").unwrap();
467 assert_eq!(
468 tokens,
469 vec![
470 Token::Ident("User".into()),
471 Token::LBrace,
472 Token::Ident("name".into()),
473 Token::Comma,
474 Token::Ident("email".into()),
475 Token::RBrace,
476 Token::Eof,
477 ]
478 );
479 }
480
481 #[test]
482 fn test_lex_insert() {
483 let tokens = lex(r#"insert User { name := "Alice", age := 30 }"#).unwrap();
484 assert_eq!(
485 tokens,
486 vec![
487 Token::Insert,
488 Token::Ident("User".into()),
489 Token::LBrace,
490 Token::Ident("name".into()),
491 Token::Assign,
492 Token::StringLit("Alice".into()),
493 Token::Comma,
494 Token::Ident("age".into()),
495 Token::Assign,
496 Token::IntLit(30),
497 Token::RBrace,
498 Token::Eof,
499 ]
500 );
501 }
502
503 #[test]
504 fn test_lex_params() {
505 let tokens = lex("User filter .age > $min_age").unwrap();
506 assert_eq!(
507 tokens,
508 vec![
509 Token::Ident("User".into()),
510 Token::Filter,
511 Token::DotIdent("age".into()),
512 Token::Gt,
513 Token::Param("min_age".into()),
514 Token::Eof,
515 ]
516 );
517 }
518
519 #[test]
520 fn test_lex_string_with_escapes() {
521 let tokens = lex(r#""hello \"world\"""#).unwrap();
522 assert_eq!(
523 tokens,
524 vec![Token::StringLit("hello \"world\"".into()), Token::Eof,]
525 );
526 }
527
528 #[test]
529 fn test_lex_aggregation() {
530 let tokens = lex("count(User)").unwrap();
531 assert_eq!(
532 tokens,
533 vec![
534 Token::Count,
535 Token::LParen,
536 Token::Ident("User".into()),
537 Token::RParen,
538 Token::Eof,
539 ]
540 );
541 }
542
543 #[test]
547 fn test_lex_intlit_overflow_returns_err() {
548 let err = lex("4444444441111111144444").expect_err("must error, not panic");
550 assert!(
551 err.message.contains("integer literal out of range"),
552 "unexpected message: {}",
553 err.message
554 );
555 assert_eq!(err.position, 0);
556 }
557
558 #[test]
562 fn test_lex_fuzz_repro_issue_24() {
563 let input = "as\t\t\t\t\t\t\t\t\t\t\t\t\t44444444411111114444\t\t\t\t\t\t";
564 let err = lex(input).expect_err("fuzz reproducer must now error, not panic");
565 assert!(err.message.contains("integer literal"));
566 }
567}