1use fsqlite_ast::Span;
7use fsqlite_types::limits::MAX_VARIABLE_NUMBER;
8use fsqlite_types::sync_primitives::Instant;
9use hashbrown::HashSet;
10use memchr::memchr;
11use std::sync::Arc;
12use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
13use tracing::Level;
14
15use crate::token::{Token, TokenKind};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub struct TokenizeDurationSecondsHistogram {
20 pub le_100us: u64,
22 pub le_250us: u64,
24 pub le_500us: u64,
26 pub le_1ms: u64,
28 pub le_5ms: u64,
30 pub gt_5ms: u64,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
36pub struct TokenizeMetricsSnapshot {
37 pub fsqlite_tokenize_tokens_total: u64,
39 pub fsqlite_tokenize_duration_seconds: TokenizeDurationSecondsHistogram,
41 pub fsqlite_tokenize_duration_seconds_count: u64,
43 pub fsqlite_tokenize_duration_seconds_sum_micros: u64,
45}
46
47static FSQLITE_TOKENIZE_TOKENS_TOTAL: AtomicU64 = AtomicU64::new(0);
48static FSQLITE_TOKENIZE_DURATION_SECONDS_LE_100US: AtomicU64 = AtomicU64::new(0);
49static FSQLITE_TOKENIZE_DURATION_SECONDS_LE_250US: AtomicU64 = AtomicU64::new(0);
50static FSQLITE_TOKENIZE_DURATION_SECONDS_LE_500US: AtomicU64 = AtomicU64::new(0);
51static FSQLITE_TOKENIZE_DURATION_SECONDS_LE_1MS: AtomicU64 = AtomicU64::new(0);
52static FSQLITE_TOKENIZE_DURATION_SECONDS_LE_5MS: AtomicU64 = AtomicU64::new(0);
53static FSQLITE_TOKENIZE_DURATION_SECONDS_GT_5MS: AtomicU64 = AtomicU64::new(0);
54static FSQLITE_TOKENIZE_DURATION_SECONDS_COUNT: AtomicU64 = AtomicU64::new(0);
55static FSQLITE_TOKENIZE_DURATION_SECONDS_SUM_MICROS: AtomicU64 = AtomicU64::new(0);
56static FSQLITE_TOKENIZE_METRICS_ENABLED: AtomicBool = AtomicBool::new(false);
57
58fn saturating_u64_from_usize(value: usize) -> u64 {
59 u64::try_from(value).unwrap_or(u64::MAX)
60}
61
62fn saturating_u64_from_u128(value: u128) -> u64 {
63 u64::try_from(value).unwrap_or(u64::MAX)
64}
65
66fn record_tokenize_metrics(token_count: usize, elapsed_micros: u64) {
67 FSQLITE_TOKENIZE_TOKENS_TOTAL
68 .fetch_add(saturating_u64_from_usize(token_count), Ordering::Relaxed);
69 FSQLITE_TOKENIZE_DURATION_SECONDS_COUNT.fetch_add(1, Ordering::Relaxed);
70 FSQLITE_TOKENIZE_DURATION_SECONDS_SUM_MICROS.fetch_add(elapsed_micros, Ordering::Relaxed);
71
72 let bucket = match elapsed_micros {
73 0..=100 => &FSQLITE_TOKENIZE_DURATION_SECONDS_LE_100US,
74 101..=250 => &FSQLITE_TOKENIZE_DURATION_SECONDS_LE_250US,
75 251..=500 => &FSQLITE_TOKENIZE_DURATION_SECONDS_LE_500US,
76 501..=1_000 => &FSQLITE_TOKENIZE_DURATION_SECONDS_LE_1MS,
77 1_001..=5_000 => &FSQLITE_TOKENIZE_DURATION_SECONDS_LE_5MS,
78 _ => &FSQLITE_TOKENIZE_DURATION_SECONDS_GT_5MS,
79 };
80 bucket.fetch_add(1, Ordering::Relaxed);
81}
82
83#[must_use]
85pub fn tokenize_metrics_snapshot() -> TokenizeMetricsSnapshot {
86 TokenizeMetricsSnapshot {
87 fsqlite_tokenize_tokens_total: FSQLITE_TOKENIZE_TOKENS_TOTAL.load(Ordering::Relaxed),
88 fsqlite_tokenize_duration_seconds: TokenizeDurationSecondsHistogram {
89 le_100us: FSQLITE_TOKENIZE_DURATION_SECONDS_LE_100US.load(Ordering::Relaxed),
90 le_250us: FSQLITE_TOKENIZE_DURATION_SECONDS_LE_250US.load(Ordering::Relaxed),
91 le_500us: FSQLITE_TOKENIZE_DURATION_SECONDS_LE_500US.load(Ordering::Relaxed),
92 le_1ms: FSQLITE_TOKENIZE_DURATION_SECONDS_LE_1MS.load(Ordering::Relaxed),
93 le_5ms: FSQLITE_TOKENIZE_DURATION_SECONDS_LE_5MS.load(Ordering::Relaxed),
94 gt_5ms: FSQLITE_TOKENIZE_DURATION_SECONDS_GT_5MS.load(Ordering::Relaxed),
95 },
96 fsqlite_tokenize_duration_seconds_count: FSQLITE_TOKENIZE_DURATION_SECONDS_COUNT
97 .load(Ordering::Relaxed),
98 fsqlite_tokenize_duration_seconds_sum_micros: FSQLITE_TOKENIZE_DURATION_SECONDS_SUM_MICROS
99 .load(Ordering::Relaxed),
100 }
101}
102
103pub fn set_tokenize_metrics_enabled(enabled: bool) {
105 FSQLITE_TOKENIZE_METRICS_ENABLED.store(enabled, Ordering::Relaxed);
106}
107
108#[must_use]
110pub fn tokenize_metrics_enabled() -> bool {
111 FSQLITE_TOKENIZE_METRICS_ENABLED.load(Ordering::Relaxed)
112}
113
114pub fn reset_tokenize_metrics() {
116 FSQLITE_TOKENIZE_TOKENS_TOTAL.store(0, Ordering::Relaxed);
117 FSQLITE_TOKENIZE_DURATION_SECONDS_LE_100US.store(0, Ordering::Relaxed);
118 FSQLITE_TOKENIZE_DURATION_SECONDS_LE_250US.store(0, Ordering::Relaxed);
119 FSQLITE_TOKENIZE_DURATION_SECONDS_LE_500US.store(0, Ordering::Relaxed);
120 FSQLITE_TOKENIZE_DURATION_SECONDS_LE_1MS.store(0, Ordering::Relaxed);
121 FSQLITE_TOKENIZE_DURATION_SECONDS_LE_5MS.store(0, Ordering::Relaxed);
122 FSQLITE_TOKENIZE_DURATION_SECONDS_GT_5MS.store(0, Ordering::Relaxed);
123 FSQLITE_TOKENIZE_DURATION_SECONDS_COUNT.store(0, Ordering::Relaxed);
124 FSQLITE_TOKENIZE_DURATION_SECONDS_SUM_MICROS.store(0, Ordering::Relaxed);
125}
126
127const MAX_RETAINED_IDENTIFIER_INTERNER_ENTRIES: usize = 256;
129const MAX_RETAINED_IDENTIFIER_INTERNER_BYTES: usize = 16 * 1024;
130
131thread_local! {
132 static FSQLITE_FORCE_INTERNER_SCAN_BENCH: std::cell::Cell<bool> =
136 const { std::cell::Cell::new(false) };
137}
138
139pub fn set_force_interner_scan_bench(enabled: bool) {
144 FSQLITE_FORCE_INTERNER_SCAN_BENCH.with(|c| c.set(enabled));
145}
146
147#[derive(Debug, Default)]
149pub(crate) struct IdentifierInterner {
150 values: HashSet<Arc<str>>,
151 interned_bytes: usize,
156}
157
158impl IdentifierInterner {
159 fn intern(&mut self, value: &str) -> Arc<str> {
160 if let Some(existing) = self.values.get(value) {
161 return Arc::clone(existing);
162 }
163
164 let interned: Arc<str> = Arc::from(value);
165 let inserted = Arc::clone(&interned);
166 self.values.insert(interned);
167 self.interned_bytes = self.interned_bytes.saturating_add(value.len());
168 inserted
169 }
170
171 pub(crate) fn reset(&mut self) {
172 self.values = HashSet::new();
173 self.interned_bytes = 0;
174 }
175
176 pub(crate) fn retained_bytes(&self) -> usize {
177 let interned_value_bytes = if FSQLITE_FORCE_INTERNER_SCAN_BENCH.with(std::cell::Cell::get) {
181 self.values
182 .iter()
183 .fold(0usize, |sum, value| sum.saturating_add(value.len()))
184 } else {
185 self.interned_bytes
186 };
187 self.values
188 .capacity()
189 .saturating_mul(std::mem::size_of::<Arc<str>>())
190 .saturating_add(interned_value_bytes)
191 }
192
193 pub(crate) fn prepare_for_next_parse(&mut self) {
194 if self.values.len() > MAX_RETAINED_IDENTIFIER_INTERNER_ENTRIES
195 || self.retained_bytes() > MAX_RETAINED_IDENTIFIER_INTERNER_BYTES
196 {
197 self.reset();
198 }
199 }
200
201 #[cfg(test)]
202 pub(crate) fn is_empty(&self) -> bool {
203 self.values.is_empty()
204 }
205
206 #[cfg(test)]
207 pub(crate) fn len(&self) -> usize {
208 self.values.len()
209 }
210}
211
212pub struct Lexer<'a> {
214 src: &'a [u8],
216 pos: usize,
218 line: u32,
220 col: u32,
222 trace_chars: bool,
224 interner: IdentifierInterner,
226}
227
228impl<'a> Lexer<'a> {
229 fn log_token(token: &Token) {
230 tracing::debug!(
231 target: "fsqlite.parse",
232 token = ?token.kind,
233 start = token.span.start,
234 end = token.span.end,
235 line = token.line,
236 col = token.col,
237 "tokenized token"
238 );
239 }
240
241 #[must_use]
243 pub fn new(source: &'a str) -> Self {
244 Self {
245 src: source.as_bytes(),
246 pos: 0,
247 line: 1,
248 col: 1,
249 trace_chars: tracing::enabled!(target: "fsqlite.parse", Level::TRACE),
250 interner: IdentifierInterner::default(),
251 }
252 }
253
254 #[must_use]
256 pub fn tokenize(source: &'a str) -> Vec<Token> {
257 let mut tokens = Vec::new();
258 Self::tokenize_into(source, &mut tokens);
259 tokens
260 }
261
262 fn new_with_interner(source: &'a str, interner: IdentifierInterner) -> Self {
263 Self {
264 src: source.as_bytes(),
265 pos: 0,
266 line: 1,
267 col: 1,
268 trace_chars: tracing::enabled!(target: "fsqlite.parse", Level::TRACE),
269 interner,
270 }
271 }
272
273 pub fn tokenize_into(source: &'a str, tokens: &mut Vec<Token>) {
279 let mut interner = IdentifierInterner::default();
280 Self::tokenize_into_with_interner(source, tokens, &mut interner);
281 }
282
283 pub(crate) fn tokenize_into_with_interner(
284 source: &'a str,
285 tokens: &mut Vec<Token>,
286 interner: &mut IdentifierInterner,
287 ) {
288 let input_bytes = source.len();
289 let collect_tokenize_metrics = tokenize_metrics_enabled();
290 let trace_tokenize = tracing::enabled!(target: "fsqlite.parse", Level::TRACE);
291 let span = trace_tokenize.then(|| {
292 tracing::span!(
293 target: "fsqlite.parse",
294 Level::TRACE,
295 "tokenize",
296 token_count = tracing::field::Empty,
297 input_bytes,
298 elapsed_us = tracing::field::Empty,
299 )
300 });
301 let _guard = span.as_ref().map(|span| span.enter());
302 let started = (collect_tokenize_metrics || trace_tokenize).then(Instant::now);
303
304 let mut lexer = Self::new_with_interner(source, std::mem::take(interner));
305 let target_capacity = input_bytes / 4 + 1;
306 tokens.clear();
307 if target_capacity > tokens.capacity() {
308 tokens.reserve(target_capacity - tokens.capacity());
309 }
310 loop {
311 let tok = lexer.next_token();
312 let is_eof = tok.kind == TokenKind::Eof;
313 tokens.push(tok);
314 if is_eof {
315 break;
316 }
317 }
318
319 *interner = lexer.interner;
320
321 if let Some(started) = started {
322 let elapsed_us = saturating_u64_from_u128(started.elapsed().as_micros());
323 if let Some(span) = span.as_ref() {
324 span.record("token_count", saturating_u64_from_usize(tokens.len()));
325 span.record("elapsed_us", elapsed_us);
326 }
327 if collect_tokenize_metrics {
328 record_tokenize_metrics(tokens.len(), elapsed_us);
329 }
330 }
331 }
332
333 #[must_use]
335 pub fn metrics_snapshot() -> TokenizeMetricsSnapshot {
336 tokenize_metrics_snapshot()
337 }
338
339 pub fn reset_metrics() {
341 reset_tokenize_metrics();
342 }
343
344 pub fn next_token(&mut self) -> Token {
346 self.skip_whitespace_and_comments();
347
348 if self.pos >= self.src.len() {
349 let token = self.make_token(TokenKind::Eof, self.pos, self.pos);
350 Self::log_token(&token);
351 return token;
352 }
353
354 let start = self.pos;
355 let start_line = self.line;
356 let start_col = self.col;
357 let ch = self.src[self.pos];
358
359 let kind = match ch {
360 b'\'' => self.lex_string(),
362
363 b'"' => self.lex_double_quoted_id(),
365
366 b'`' => self.lex_backtick_id(),
368
369 b'[' => self.lex_bracket_id(),
371
372 b'X' | b'x' if self.peek_at(1) == Some(b'\'') => self.lex_blob(),
374
375 b'0'..=b'9' => self.lex_number(),
377 b'.' if self.peek_at(1).is_some_and(|c| c.is_ascii_digit()) => self.lex_number(),
378
379 b'a'..=b'z' | b'A'..=b'Z' | b'_' | 0x80..=0xFF => self.lex_identifier(),
381
382 b'?' => self.lex_question(),
384 b':' => self.lex_colon_param(),
385 b'@' => self.lex_at_param(),
386 b'$' => self.lex_dollar_param(),
387
388 b'+' => {
390 self.advance();
391 TokenKind::Plus
392 }
393 b'*' => {
394 self.advance();
395 TokenKind::Star
396 }
397 b'/' => {
398 self.advance();
399 TokenKind::Slash
400 }
401 b'%' => {
402 self.advance();
403 TokenKind::Percent
404 }
405 b'&' => {
406 self.advance();
407 TokenKind::Ampersand
408 }
409 b'~' => {
410 self.advance();
411 TokenKind::Tilde
412 }
413 b',' => {
414 self.advance();
415 TokenKind::Comma
416 }
417 b';' => {
418 self.advance();
419 TokenKind::Semicolon
420 }
421 b'(' => {
422 self.advance();
423 TokenKind::LeftParen
424 }
425 b')' => {
426 self.advance();
427 TokenKind::RightParen
428 }
429 b'.' => {
430 self.advance();
431 TokenKind::Dot
432 }
433
434 b'-' => self.lex_minus_or_arrow(),
436 b'<' => self.lex_lt(),
437 b'>' => self.lex_gt(),
438 b'=' => self.lex_eq(),
439 b'!' => self.lex_bang(),
440 b'|' => self.lex_pipe(),
441
442 _ => {
443 self.advance();
444 let s = String::from_utf8_lossy(&self.src[start..self.pos]).into_owned();
445 TokenKind::Error(format!("unrecognized token: \"{s}\""))
449 }
450 };
451
452 let token = Token {
453 kind,
454 #[allow(clippy::cast_possible_truncation)]
455 span: Span::new(start as u32, self.pos as u32),
456 line: start_line,
457 col: start_col,
458 };
459
460 Self::log_token(&token);
461 token
462 }
463
464 #[allow(clippy::cast_possible_truncation)]
469 fn advance_by(&mut self, n: usize) {
470 if n == 0 {
471 return;
472 }
473 let end = self.pos + n;
474 let slice = &self.src[self.pos..end];
475 #[allow(clippy::naive_bytecount)]
476 let newlines = slice.iter().filter(|&&b| b == b'\n').count();
477 if newlines > 0 {
478 self.line += newlines as u32;
479 let last_nl = slice.iter().rposition(|&b| b == b'\n').unwrap_or(0);
480 self.col = (n - last_nl) as u32;
481 } else {
482 self.col += n as u32;
483 }
484 self.pos = end;
485 }
486
487 fn advance(&mut self) -> u8 {
488 let pos = self.pos;
489 let line = self.line;
490 let col = self.col;
491 let ch = self.src[self.pos];
492 self.pos += 1;
493 if ch == b'\n' {
494 self.line += 1;
495 self.col = 1;
496 } else {
497 self.col += 1;
498 }
499 if self.trace_chars {
500 tracing::trace!(
501 target: "fsqlite.parse",
502 byte = ch,
503 pos,
504 line,
505 col,
506 "tokenize char"
507 );
508 }
509 ch
510 }
511
512 fn peek(&self) -> Option<u8> {
513 self.src.get(self.pos).copied()
514 }
515
516 fn peek_at(&self, offset: usize) -> Option<u8> {
517 self.src.get(self.pos + offset).copied()
518 }
519
520 #[allow(clippy::cast_possible_truncation)]
521 fn make_token(&self, kind: TokenKind, start: usize, end: usize) -> Token {
522 Token {
523 kind,
524 span: Span::new(start as u32, end as u32),
525 line: self.line,
526 col: self.col,
527 }
528 }
529
530 fn skip_whitespace_and_comments(&mut self) {
532 loop {
533 let mut ws_len = 0;
535 while self.pos + ws_len < self.src.len()
536 && self.src[self.pos + ws_len].is_ascii_whitespace()
537 {
538 ws_len += 1;
539 }
540 if ws_len > 0 {
541 self.advance_by(ws_len);
542 }
543
544 if self.pos >= self.src.len() {
545 break;
546 }
547
548 if self.src[self.pos] == b'-' && self.peek_at(1) == Some(b'-') {
550 self.advance(); self.advance(); while self.pos < self.src.len() && self.src[self.pos] != b'\n' {
553 self.advance();
554 }
555 continue;
556 }
557
558 if self.src[self.pos] == b'/' && self.peek_at(1) == Some(b'*') {
560 self.advance(); self.advance(); let closed = loop {
563 if self.pos >= self.src.len() {
564 break false;
565 }
566 if self.src[self.pos] == b'*' && self.peek_at(1) == Some(b'/') {
567 self.advance();
568 self.advance();
569 break true;
570 }
571 self.advance();
572 };
573 if !closed {
574 self.pos = self.src.len();
576 }
577 continue;
578 }
579
580 break;
581 }
582 }
583
584 fn lex_string(&mut self) -> TokenKind {
589 let start = self.pos;
590 self.advance(); let mut value = String::new();
593 loop {
594 let remaining = &self.src[self.pos..];
596 if let Some(offset) = memchr(b'\'', remaining) {
597 value.push_str(&String::from_utf8_lossy(
599 &self.src[self.pos..self.pos + offset],
600 ));
601 self.advance_by(offset);
603 self.advance(); if self.peek() == Some(b'\'') {
607 value.push('\'');
608 self.advance();
609 } else {
610 return TokenKind::String(value);
611 }
612 } else {
613 self.pos = self.src.len();
615 return TokenKind::Error(format!(
616 "unrecognized token: \"{}\"",
617 String::from_utf8_lossy(&self.src[start..self.pos])
618 ));
619 }
620 }
621 }
622
623 fn lex_double_quoted_id(&mut self) -> TokenKind {
625 let start = self.pos;
626 self.advance(); let mut value = String::new();
629 loop {
630 let remaining = &self.src[self.pos..];
631 if let Some(offset) = memchr(b'"', remaining) {
632 value.push_str(&String::from_utf8_lossy(
633 &self.src[self.pos..self.pos + offset],
634 ));
635 self.advance_by(offset);
636 self.advance(); if self.peek() == Some(b'"') {
640 value.push('"');
641 self.advance();
642 } else {
643 return TokenKind::QuotedId(self.interner.intern(&value), true);
644 }
645 } else {
646 self.pos = self.src.len();
647 return TokenKind::Error(format!(
648 "unrecognized token: \"{}\"",
649 String::from_utf8_lossy(&self.src[start..self.pos])
650 ));
651 }
652 }
653 }
654
655 fn lex_backtick_id(&mut self) -> TokenKind {
657 let start = self.pos;
658 self.advance(); let mut value = String::new();
661 loop {
662 let remaining = &self.src[self.pos..];
663 if let Some(offset) = memchr(b'`', remaining) {
664 value.push_str(&String::from_utf8_lossy(
665 &self.src[self.pos..self.pos + offset],
666 ));
667 self.advance_by(offset);
668 self.advance(); if self.peek() == Some(b'`') {
671 value.push('`');
672 self.advance();
673 } else {
674 return TokenKind::QuotedId(self.interner.intern(&value), false);
675 }
676 } else {
677 self.pos = self.src.len();
678 return TokenKind::Error(format!(
679 "unrecognized token: \"{}\"",
680 String::from_utf8_lossy(&self.src[start..self.pos])
681 ));
682 }
683 }
684 }
685
686 fn lex_bracket_id(&mut self) -> TokenKind {
688 let start = self.pos;
689 self.advance(); let mut value = String::new();
692 let remaining = &self.src[self.pos..];
693 if let Some(offset) = memchr(b']', remaining) {
694 value.push_str(&String::from_utf8_lossy(
695 &self.src[self.pos..self.pos + offset],
696 ));
697 self.advance_by(offset);
698 self.advance(); TokenKind::QuotedId(self.interner.intern(&value), false)
700 } else {
701 self.pos = self.src.len();
702 TokenKind::Error(format!(
703 "unrecognized token: \"{}\"",
704 String::from_utf8_lossy(&self.src[start..self.pos])
705 ))
706 }
707 }
708
709 fn lex_blob(&mut self) -> TokenKind {
711 let start = self.pos;
712 self.advance(); self.advance(); let hex_start = self.pos;
716 let remaining = &self.src[self.pos..];
717 if let Some(offset) = memchr(b'\'', remaining) {
718 let hex_bytes = &self.src[hex_start..hex_start + offset];
719 self.advance_by(offset);
720 self.advance(); if !hex_bytes.len().is_multiple_of(2) {
724 return TokenKind::Error(format!(
729 "unrecognized token: \"{}\"",
730 String::from_utf8_lossy(&self.src[start..self.pos])
731 ));
732 }
733
734 let mut bytes = Vec::with_capacity(hex_bytes.len() / 2);
737 for &[hi, lo] in hex_bytes.as_chunks::<2>().0 {
738 let hi = hex_digit(hi);
739 let lo = hex_digit(lo);
740 match (hi, lo) {
741 (Some(h), Some(l)) => bytes.push((h << 4) | l),
742 _ => {
743 return TokenKind::Error(format!(
746 "unrecognized token: \"{}\"",
747 String::from_utf8_lossy(&self.src[start..self.pos])
748 ));
749 }
750 }
751 }
752 TokenKind::Blob(bytes)
753 } else {
754 self.pos = self.src.len();
755 TokenKind::Error(format!(
756 "unrecognized token: \"{}\"",
757 String::from_utf8_lossy(&self.src[start..self.pos])
758 ))
759 }
760 }
761
762 fn lex_number(&mut self) -> TokenKind {
764 let start = self.pos;
765
766 if self.src[self.pos] == b'0' && self.peek_at(1).is_some_and(|c| c == b'x' || c == b'X') {
768 self.advance(); self.advance(); let hex_start = self.pos;
771 while self.pos < self.src.len() {
776 let c = self.src[self.pos];
777 let is_separator = c == b'_'
778 && self.pos > hex_start
779 && self.src[self.pos - 1].is_ascii_hexdigit()
780 && self.peek_at(1).is_some_and(|n| n.is_ascii_hexdigit());
781 if c.is_ascii_hexdigit() || is_separator {
782 self.advance();
783 } else {
784 break;
785 }
786 }
787 if self.pos == hex_start {
788 while self.pos < self.src.len()
794 && (self.src[self.pos].is_ascii_alphanumeric() || self.src[self.pos] == b'_')
795 {
796 self.advance();
797 }
798 let err_text = String::from_utf8_lossy(&self.src[start..self.pos]);
799 return TokenKind::Error(format!("unrecognized token: \"{err_text}\""));
800 }
801 if self
805 .peek()
806 .is_some_and(|n| n.is_ascii_alphanumeric() || n == b'_')
807 {
808 let err_start = start;
809 while self.pos < self.src.len()
810 && (self.src[self.pos].is_ascii_alphanumeric() || self.src[self.pos] == b'_')
811 {
812 self.advance();
813 }
814 let err_text = String::from_utf8_lossy(&self.src[err_start..self.pos]);
815 return TokenKind::Error(format!("unrecognized token: \"{err_text}\""));
816 }
817 let hex_str = String::from_utf8_lossy(&self.src[hex_start..self.pos]).replace('_', "");
818 let significant = hex_str.trim_start_matches('0');
822 if significant.len() > 16 {
823 return TokenKind::Error(format!("hex literal out of range at byte {start}"));
824 }
825 let parse_str = if significant.is_empty() {
826 "0"
827 } else {
828 significant
829 };
830 return match u64::from_str_radix(parse_str, 16) {
833 Ok(v) => {
834 #[allow(clippy::cast_possible_wrap)]
835 let i = v as i64;
836 TokenKind::Integer(i)
837 }
838 Err(_) => TokenKind::Error(format!("hex literal out of range at byte {start}")),
839 };
840 }
841
842 let mut is_float = false;
844
845 self.consume_decimal_digit_run();
848
849 let is_valid_exponent = |lexer: &Self, mut offset: usize| -> bool {
851 if let Some(c) = lexer.peek_at(offset)
852 && (c == b'e' || c == b'E')
853 {
854 offset += 1;
855 if let Some(s) = lexer.peek_at(offset)
856 && (s == b'+' || s == b'-')
857 {
858 offset += 1;
859 }
860 if let Some(d) = lexer.peek_at(offset) {
861 return d.is_ascii_digit();
862 }
863 }
864 false
865 };
866
867 if self.pos < self.src.len()
869 && self.src[self.pos] == b'.'
870 && (self.peek_at(1).is_some_and(|c| c.is_ascii_digit()) || is_valid_exponent(self, 1))
871 {
872 is_float = true;
873 self.advance(); self.consume_decimal_digit_run();
875 } else if self.pos < self.src.len()
876 && self.src[self.pos] == b'.'
877 && start < self.pos && !self.peek_at(1).is_some_and(|c| c.is_ascii_alphanumeric() || c == b'_')
879 {
880 is_float = true;
882 self.advance(); }
884
885 if self.src[start] == b'.' {
887 is_float = true;
888 }
889
890 if is_valid_exponent(self, 0) {
892 is_float = true;
893 self.advance(); if self.pos < self.src.len()
895 && (self.src[self.pos] == b'+' || self.src[self.pos] == b'-')
896 {
897 self.advance();
898 }
899 self.consume_decimal_digit_run();
900 }
901
902 if let Some(c) = self.peek()
905 && (c.is_ascii_alphabetic()
906 || c == b'_'
907 || (c == b'.'
908 && self
909 .peek_at(1)
910 .is_some_and(|n| n.is_ascii_alphabetic() || n == b'_')))
911 {
912 let err_start = start;
913 while self.pos < self.src.len() {
914 let ch = self.src[self.pos];
915 if ch.is_ascii_alphanumeric() || ch == b'_' || ch == b'.' {
916 self.advance();
917 } else {
918 break;
919 }
920 }
921 let err_text = String::from_utf8_lossy(&self.src[err_start..self.pos]);
922 return TokenKind::Error(format!("unrecognized token: \"{err_text}\""));
923 }
924
925 let text_raw = String::from_utf8_lossy(&self.src[start..self.pos]);
926 let text: std::borrow::Cow<'_, str> = if text_raw.as_ref().contains('_') {
929 std::borrow::Cow::Owned(text_raw.replace('_', ""))
930 } else {
931 text_raw
932 };
933 if is_float {
934 match text.parse::<f64>() {
937 Ok(v) => TokenKind::Float(v),
938 Err(_) => {
939 let mut text_fixed = text.clone().into_owned();
941 if text_fixed.starts_with(".e") || text_fixed.starts_with(".E") {
942 text_fixed.insert(0, '0');
943 }
944 match text_fixed.parse::<f64>() {
945 Ok(v) => TokenKind::Float(v),
946 Err(_) => TokenKind::Error(format!("invalid float: {text}")),
947 }
948 }
949 }
950 } else {
951 match text.parse::<i64>() {
952 Ok(v) => TokenKind::Integer(v),
953 Err(_) => {
954 TokenKind::OversizedInt(text.into_owned())
957 }
958 }
959 }
960 }
961
962 fn consume_decimal_digit_run(&mut self) {
968 while self.pos < self.src.len() {
969 let c = self.src[self.pos];
970 let is_separator = c == b'_'
971 && self.pos > 0
972 && self.src[self.pos - 1].is_ascii_digit()
973 && self.peek_at(1).is_some_and(|n| n.is_ascii_digit());
974 if c.is_ascii_digit() || is_separator {
975 self.advance();
976 } else {
977 break;
978 }
979 }
980 }
981
982 fn lex_identifier(&mut self) -> TokenKind {
984 let start = self.pos;
985 self.advance(); while self.pos < self.src.len() {
988 let ch = self.src[self.pos];
989 if ch.is_ascii_alphanumeric() || ch == b'_' || ch >= 0x80 {
990 self.advance();
991 } else {
992 break;
993 }
994 }
995
996 let ident_bytes = &self.src[start..self.pos];
997
998 if let Some(kw) = TokenKind::lookup_keyword_bytes(ident_bytes) {
1000 kw
1001 } else {
1002 let text = String::from_utf8_lossy(ident_bytes);
1003 TokenKind::Id(self.interner.intern(&text))
1004 }
1005 }
1006
1007 fn lex_question(&mut self) -> TokenKind {
1009 self.advance(); if self.pos < self.src.len() && self.src[self.pos].is_ascii_digit() {
1011 let num_start = self.pos;
1012 while self.pos < self.src.len() && self.src[self.pos].is_ascii_digit() {
1013 self.advance();
1014 }
1015 let text = String::from_utf8_lossy(&self.src[num_start..self.pos]);
1016 match text.parse::<u32>() {
1017 Ok(n) if (1..=MAX_VARIABLE_NUMBER).contains(&n) => TokenKind::QuestionNum(n),
1018 Ok(_) | Err(_) => TokenKind::Error(format!(
1021 "variable number must be between ?1 and ?{MAX_VARIABLE_NUMBER}"
1022 )),
1023 }
1024 } else {
1025 TokenKind::Question
1026 }
1027 }
1028
1029 fn lex_alpha_param(&mut self, prefix: char, constructor: fn(String) -> TokenKind) -> TokenKind {
1030 self.advance(); let name_start = self.pos;
1032 while self.pos < self.src.len() {
1033 let ch = self.src[self.pos];
1034 if ch.is_ascii_alphanumeric() || ch == b'_' || ch >= 0x80 {
1035 self.advance();
1036 } else if ch == b':' && self.peek_at(1) == Some(b':') {
1037 self.advance();
1038 self.advance();
1039 } else if ch == b'(' {
1040 self.advance();
1041 while self.pos < self.src.len() && self.src[self.pos] != b')' {
1042 self.advance();
1043 }
1044 if self.pos >= self.src.len() || self.src[self.pos] != b')' {
1045 let name = String::from_utf8_lossy(&self.src[name_start..self.pos]);
1046 return TokenKind::Error(format!("unrecognized token: \"{prefix}{name}\""));
1047 }
1048 self.advance();
1049 break; } else {
1051 break;
1052 }
1053 }
1054 if self.pos == name_start {
1055 return TokenKind::Error(format!("unrecognized token: \"{prefix}\""));
1060 }
1061 let name = String::from_utf8_lossy(&self.src[name_start..self.pos]).into_owned();
1062 constructor(name)
1063 }
1064
1065 fn lex_colon_param(&mut self) -> TokenKind {
1067 self.lex_alpha_param(':', TokenKind::ColonParam)
1068 }
1069
1070 fn lex_at_param(&mut self) -> TokenKind {
1072 self.lex_alpha_param('@', TokenKind::AtParam)
1073 }
1074
1075 fn lex_dollar_param(&mut self) -> TokenKind {
1077 self.lex_alpha_param('$', TokenKind::DollarParam)
1078 }
1079
1080 fn lex_minus_or_arrow(&mut self) -> TokenKind {
1086 self.advance(); if self.peek() == Some(b'>') {
1088 self.advance(); if self.peek() == Some(b'>') {
1090 self.advance(); TokenKind::DoubleArrow
1092 } else {
1093 TokenKind::Arrow
1094 }
1095 } else {
1096 TokenKind::Minus
1097 }
1098 }
1099
1100 fn lex_lt(&mut self) -> TokenKind {
1102 self.advance(); match self.peek() {
1104 Some(b'=') => {
1105 self.advance();
1106 TokenKind::Le
1107 }
1108 Some(b'>') => {
1109 self.advance();
1110 TokenKind::LtGt
1111 }
1112 Some(b'<') => {
1113 self.advance();
1114 TokenKind::ShiftLeft
1115 }
1116 _ => TokenKind::Lt,
1117 }
1118 }
1119
1120 fn lex_gt(&mut self) -> TokenKind {
1122 self.advance(); match self.peek() {
1124 Some(b'=') => {
1125 self.advance();
1126 TokenKind::Ge
1127 }
1128 Some(b'>') => {
1129 self.advance();
1130 TokenKind::ShiftRight
1131 }
1132 _ => TokenKind::Gt,
1133 }
1134 }
1135
1136 fn lex_eq(&mut self) -> TokenKind {
1138 self.advance(); if self.peek() == Some(b'=') {
1140 self.advance();
1141 TokenKind::EqEq
1142 } else {
1143 TokenKind::Eq
1144 }
1145 }
1146
1147 fn lex_bang(&mut self) -> TokenKind {
1149 self.advance(); if self.peek() == Some(b'=') {
1151 self.advance();
1152 TokenKind::Ne
1153 } else {
1154 TokenKind::Error("unexpected '!', did you mean '!='?".to_owned())
1155 }
1156 }
1157
1158 fn lex_pipe(&mut self) -> TokenKind {
1160 self.advance(); if self.peek() == Some(b'|') {
1162 self.advance();
1163 TokenKind::Concat
1164 } else {
1165 TokenKind::Pipe
1166 }
1167 }
1168}
1169
1170const fn hex_digit(b: u8) -> Option<u8> {
1173 match b {
1174 b'0'..=b'9' => Some(b - b'0'),
1175 b'a'..=b'f' => Some(b - b'a' + 10),
1176 b'A'..=b'F' => Some(b - b'A' + 10),
1177 _ => None,
1178 }
1179}
1180
1181#[cfg(test)]
1182mod tests {
1183 use super::*;
1184
1185 #[test]
1186 fn interner_incremental_bytes_equals_fold() {
1187 let mut interner = IdentifierInterner::default();
1191 for v in ["alpha", "beta", "gamma", "alpha", "delta", "beta"] {
1192 interner.intern(v);
1193 }
1194 assert_eq!(interner.interned_bytes, 19);
1196 set_force_interner_scan_bench(false);
1197 let incremental = interner.retained_bytes();
1198 set_force_interner_scan_bench(true);
1199 let folded = interner.retained_bytes();
1200 set_force_interner_scan_bench(false);
1201 assert_eq!(
1202 incremental, folded,
1203 "incremental retained_bytes must equal the O(N) fold"
1204 );
1205 interner.reset();
1206 assert_eq!(interner.interned_bytes, 0);
1207 assert_eq!(interner.retained_bytes(), 0);
1208 }
1209
1210 fn lex(src: &str) -> Vec<Token> {
1211 Lexer::tokenize(src)
1212 }
1213
1214 fn kinds(src: &str) -> Vec<TokenKind> {
1215 lex(src).into_iter().map(|t| t.kind).collect()
1216 }
1217
1218 #[test]
1219 fn test_lex_integer_literals() {
1220 let tokens = kinds("42 0 0xFF");
1221 assert_eq!(
1222 tokens,
1223 vec![
1224 TokenKind::Integer(42),
1225 TokenKind::Integer(0),
1226 TokenKind::Integer(255),
1227 TokenKind::Eof,
1228 ]
1229 );
1230 }
1231
1232 #[test]
1233 fn test_tokenize_into_reuses_caller_owned_capacity() {
1234 let mut scratch = Vec::new();
1235 Lexer::tokenize_into(
1236 "SELECT 'abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz';",
1237 &mut scratch,
1238 );
1239 let warmed_capacity = scratch.capacity();
1240 assert!(
1241 warmed_capacity > 0,
1242 "warm parse should allocate token scratch"
1243 );
1244
1245 Lexer::tokenize_into("SELECT 1;", &mut scratch);
1246 assert_eq!(
1247 scratch.capacity(),
1248 warmed_capacity,
1249 "smaller follow-up parse should reuse the warmed token buffer",
1250 );
1251 assert_eq!(
1252 scratch.last().map(|token| &token.kind),
1253 Some(&TokenKind::Eof),
1254 "tokenize_into should still terminate with EOF in reused scratch",
1255 );
1256 }
1257
1258 #[test]
1259 fn test_lex_float_literals() {
1260 let tokens = kinds("3.14 1e10 .5 1.0e-3 0.0");
1261 let expected = 3.0 + 0.14;
1264 assert!(matches!(
1265 tokens[0],
1266 TokenKind::Float(v) if (v - expected).abs() < 1e-10
1267 ));
1268 assert!(matches!(tokens[1], TokenKind::Float(v) if (v - 1e10).abs() < 1.0));
1269 assert!(matches!(tokens[2], TokenKind::Float(v) if (v - 0.5).abs() < 1e-10));
1270 assert!(matches!(tokens[3], TokenKind::Float(v) if (v - 0.001).abs() < 1e-10));
1271 assert!(matches!(tokens[4], TokenKind::Float(v) if v.abs() < 1e-10));
1272 assert_eq!(tokens[5], TokenKind::Eof);
1273 }
1274
1275 #[test]
1276 fn test_lex_string_literals() {
1277 let tokens = kinds("'hello' 'it''s' ''");
1278 assert_eq!(tokens[0], TokenKind::String("hello".to_owned()));
1279 assert_eq!(tokens[1], TokenKind::String("it's".to_owned()));
1280 assert_eq!(tokens[2], TokenKind::String(String::new()));
1281 assert_eq!(tokens[3], TokenKind::Eof);
1282 }
1283
1284 #[test]
1285 fn test_lex_blob_literals() {
1286 let tokens = kinds("X'CAFE' x'00ff' X''");
1287 assert_eq!(tokens[0], TokenKind::Blob(vec![0xCA, 0xFE]));
1288 assert_eq!(tokens[1], TokenKind::Blob(vec![0x00, 0xFF]));
1289 assert_eq!(tokens[2], TokenKind::Blob(vec![]));
1290 assert_eq!(tokens[3], TokenKind::Eof);
1291 }
1292
1293 #[test]
1294 fn test_lex_blob_odd_hex_error() {
1295 let tokens = kinds("X'CAF'");
1296 assert!(matches!(tokens[0], TokenKind::Error(_)));
1297 }
1298
1299 #[test]
1300 fn test_lex_blob_non_ascii_no_panic() {
1301 let tokens = kinds("X'U\u{05fc} '");
1304 assert!(matches!(tokens[0], TokenKind::Error(_)));
1305
1306 let tokens2 = kinds("X'GG'");
1308 assert!(matches!(tokens2[0], TokenKind::Error(_)));
1309 }
1310
1311 #[test]
1312 fn test_lex_variables() {
1313 let tokens = kinds("?1 :name @param $var ?");
1314 assert_eq!(tokens[0], TokenKind::QuestionNum(1));
1315 assert_eq!(tokens[1], TokenKind::ColonParam("name".to_owned()));
1316 assert_eq!(tokens[2], TokenKind::AtParam("param".to_owned()));
1317 assert_eq!(tokens[3], TokenKind::DollarParam("var".to_owned()));
1318 assert_eq!(tokens[4], TokenKind::Question);
1319 assert_eq!(tokens[5], TokenKind::Eof);
1320 }
1321
1322 #[test]
1323 fn test_lex_quoted_identifiers() {
1324 let tokens = kinds("\"table_name\" [column] `backtick`");
1325 assert_eq!(tokens[0], TokenKind::QuotedId("table_name".into(), true));
1326 assert_eq!(tokens[1], TokenKind::QuotedId("column".into(), false));
1327 assert_eq!(tokens[2], TokenKind::QuotedId("backtick".into(), false));
1328 }
1329
1330 #[test]
1331 fn test_lex_dqs_flag() {
1332 let tokens = kinds("\"hello\"");
1333 assert_eq!(tokens[0], TokenKind::QuotedId("hello".into(), true));
1335 }
1336
1337 #[test]
1338 fn test_lex_keywords() {
1339 let tokens = kinds("SELECT FROM WHERE INSERT CREATE TABLE CONCURRENT");
1340 assert_eq!(tokens[0], TokenKind::KwSelect);
1341 assert_eq!(tokens[1], TokenKind::KwFrom);
1342 assert_eq!(tokens[2], TokenKind::KwWhere);
1343 assert_eq!(tokens[3], TokenKind::KwInsert);
1344 assert_eq!(tokens[4], TokenKind::KwCreate);
1345 assert_eq!(tokens[5], TokenKind::KwTable);
1346 assert_eq!(tokens[6], TokenKind::KwConcurrent);
1347
1348 let tokens2 = kinds("select from where");
1350 assert_eq!(tokens2[0], TokenKind::KwSelect);
1351 assert_eq!(tokens2[1], TokenKind::KwFrom);
1352 assert_eq!(tokens2[2], TokenKind::KwWhere);
1353 }
1354
1355 #[test]
1356 fn test_lex_operators() {
1357 let tokens = kinds("+ - * / % & | ~ << >> = < <= > >= == != <> || -> ->>");
1358 let expected = vec![
1359 TokenKind::Plus,
1360 TokenKind::Minus,
1361 TokenKind::Star,
1362 TokenKind::Slash,
1363 TokenKind::Percent,
1364 TokenKind::Ampersand,
1365 TokenKind::Pipe,
1366 TokenKind::Tilde,
1367 TokenKind::ShiftLeft,
1368 TokenKind::ShiftRight,
1369 TokenKind::Eq,
1370 TokenKind::Lt,
1371 TokenKind::Le,
1372 TokenKind::Gt,
1373 TokenKind::Ge,
1374 TokenKind::EqEq,
1375 TokenKind::Ne,
1376 TokenKind::LtGt,
1377 TokenKind::Concat,
1378 TokenKind::Arrow,
1379 TokenKind::DoubleArrow,
1380 TokenKind::Eof,
1381 ];
1382 assert_eq!(tokens, expected);
1383 }
1384
1385 #[test]
1386 fn test_lex_eq_vs_eqeq() {
1387 let tokens = kinds("= ==");
1388 assert_eq!(tokens[0], TokenKind::Eq);
1389 assert_eq!(tokens[1], TokenKind::EqEq);
1390 }
1391
1392 #[test]
1393 fn test_lex_ne_vs_ltgt() {
1394 let tokens = kinds("!= <>");
1395 assert_eq!(tokens[0], TokenKind::Ne);
1396 assert_eq!(tokens[1], TokenKind::LtGt);
1397 }
1398
1399 #[test]
1400 fn test_lex_error_unterminated_string() {
1401 for input in ["'hello", "\"abc", "`abc", "[abc", "x'12"] {
1406 let tokens = kinds(input);
1407 let expected = format!("unrecognized token: \"{input}\"");
1408 assert!(
1409 matches!(tokens[0], TokenKind::Error(ref e) if e.contains(&expected)),
1410 "expected {input:?} -> {expected:?}, got {:?}",
1411 tokens[0]
1412 );
1413 }
1414 }
1415
1416 #[test]
1417 fn test_lex_line_column_tracking() {
1418 let tokens = lex("SELECT\n a,\n b");
1419 assert_eq!(tokens[0].line, 1);
1420 assert_eq!(tokens[0].col, 1);
1421 assert_eq!(tokens[1].line, 2);
1423 assert_eq!(tokens[1].col, 3);
1424 assert_eq!(tokens[2].line, 2);
1426 assert_eq!(tokens[2].col, 4);
1427 assert_eq!(tokens[3].line, 3);
1429 assert_eq!(tokens[3].col, 3);
1430 }
1431
1432 #[test]
1433 fn test_lex_whitespace_and_comments_skipped() {
1434 let tokens = kinds("SELECT -- this is a comment\n a /* block */ FROM b");
1435 assert_eq!(tokens[0], TokenKind::KwSelect);
1436 assert_eq!(tokens[1], TokenKind::Id("a".into()));
1437 assert_eq!(tokens[2], TokenKind::KwFrom);
1438 assert_eq!(tokens[3], TokenKind::Id("b".into()));
1439 assert_eq!(tokens[4], TokenKind::Eof);
1440 }
1441
1442 #[test]
1443 fn test_lex_hex_large_values() {
1444 let tokens = kinds("0xFFFFFFFFFFFFFFFF");
1447 assert_eq!(tokens[0], TokenKind::Integer(-1));
1448
1449 let tokens = kinds("0x8000000000000000");
1451 assert_eq!(tokens[0], TokenKind::Integer(i64::MIN));
1452
1453 let tokens = kinds("0x7FFFFFFFFFFFFFFF");
1455 assert_eq!(tokens[0], TokenKind::Integer(i64::MAX));
1456 }
1457
1458 #[test]
1459 fn test_lex_hex_overflow_17_digits_rejects() {
1460 let tokens = kinds("0x10000000000000000");
1463 assert!(
1464 matches!(&tokens[0], TokenKind::Error(msg) if msg.contains("out of range")),
1465 "expected error for 17-digit hex, got {:?}",
1466 tokens[0]
1467 );
1468 }
1469
1470 #[test]
1471 fn test_lex_hex_leading_zeros_accepted() {
1472 let tokens = kinds("0x00000000000000001");
1475 assert_eq!(tokens[0], TokenKind::Integer(1));
1476 }
1477
1478 #[test]
1479 fn test_lex_number_hex() {
1480 let tokens = kinds("0x1A 0Xff 0x0");
1481 assert_eq!(tokens[0], TokenKind::Integer(26));
1482 assert_eq!(tokens[1], TokenKind::Integer(255));
1483 assert_eq!(tokens[2], TokenKind::Integer(0));
1484 assert_eq!(tokens[3], TokenKind::Eof);
1485 }
1486
1487 #[test]
1488 fn test_lex_number_unrecognized() {
1489 let tokens = kinds("123a 123.a");
1490 assert!(
1491 matches!(tokens[0], TokenKind::Error(ref e) if e.contains("unrecognized token: \"123a\""))
1492 );
1493 assert!(
1494 matches!(tokens[1], TokenKind::Error(ref e) if e.contains("unrecognized token: \"123.a\""))
1495 );
1496 }
1497
1498 #[test]
1499 fn test_lex_number_hex_invalid() {
1500 let tokens = kinds("0x");
1504 assert!(
1505 matches!(tokens[0], TokenKind::Error(ref e) if e.contains("unrecognized token: \"0x\"")),
1506 "expected 0x -> unrecognized token, got {:?}",
1507 tokens[0]
1508 );
1509 let tokens = kinds("0xGG");
1510 assert!(
1511 matches!(tokens[0], TokenKind::Error(ref e) if e.contains("unrecognized token: \"0xGG\"")),
1512 "expected 0xGG -> unrecognized token, got {:?}",
1513 tokens[0]
1514 );
1515 }
1516
1517 #[test]
1518 fn test_lex_blob_invalid_is_unrecognized() {
1519 let tokens = kinds("x'123'"); assert!(
1524 matches!(tokens[0], TokenKind::Error(ref e) if e.contains("unrecognized token: \"x'123'\"")),
1525 "expected x'123' -> unrecognized token, got {:?}",
1526 tokens[0]
1527 );
1528 let tokens = kinds("x'1G'"); assert!(
1530 matches!(tokens[0], TokenKind::Error(ref e) if e.contains("unrecognized token: \"x'1G'\"")),
1531 "expected x'1G' -> unrecognized token, got {:?}",
1532 tokens[0]
1533 );
1534 }
1535
1536 #[test]
1537 fn test_lex_positional_params() {
1538 let tokens = kinds("? ?123");
1539 assert_eq!(tokens[0], TokenKind::Question);
1540 assert_eq!(tokens[1], TokenKind::QuestionNum(123));
1541 assert_eq!(tokens[2], TokenKind::Eof);
1542 }
1543
1544 #[test]
1545 fn test_lex_positional_params_reject_zero_and_out_of_range() {
1546 let tokens = kinds("?0 ?32767");
1547 assert!(
1548 matches!(tokens[0], TokenKind::Error(ref e) if e.contains("between ?1 and ?32766")),
1549 "expected ?0 to be rejected, got {:?}",
1550 tokens[0]
1551 );
1552 assert!(
1553 matches!(tokens[1], TokenKind::Error(ref e) if e.contains("between ?1 and ?32766")),
1554 "expected ?32767 to be rejected, got {:?}",
1555 tokens[1]
1556 );
1557 assert_eq!(tokens[2], TokenKind::Eof);
1558 }
1559
1560 #[test]
1561 fn test_lex_named_params() {
1562 let tokens = kinds(":foo @bar $baz_123");
1563 assert_eq!(tokens[0], TokenKind::ColonParam("foo".to_owned()));
1564 assert_eq!(tokens[1], TokenKind::AtParam("bar".to_owned()));
1565 assert_eq!(tokens[2], TokenKind::DollarParam("baz_123".to_owned()));
1566 assert_eq!(tokens[3], TokenKind::Eof);
1567 }
1568
1569 #[test]
1570 fn test_lex_named_params_with_tcl_syntax() {
1571 let tokens = kinds("$::foo(bar) :a::b");
1572 assert_eq!(tokens[0], TokenKind::DollarParam("::foo(bar)".to_owned()));
1573 assert_eq!(tokens[1], TokenKind::ColonParam("a::b".to_owned()));
1574 assert_eq!(tokens[2], TokenKind::Eof);
1575 }
1576
1577 #[test]
1578 fn test_lex_named_params_with_unclosed_tcl_array_syntax() {
1579 let tokens = kinds("$::foo(bar");
1580 assert!(
1581 matches!(tokens[0], TokenKind::Error(ref e) if e.contains("unrecognized token")),
1582 "expected unterminated Tcl-style parameter to be rejected, got {:?}",
1583 tokens[0]
1584 );
1585 assert_eq!(tokens[1], TokenKind::Eof);
1586 }
1587
1588 #[test]
1589 fn test_lex_unexpected_char_is_unrecognized_token() {
1590 for input in ["#", "\\", "^", "{", "}", "]"] {
1594 let tokens = kinds(input);
1595 let expected = format!("unrecognized token: \"{input}\"");
1596 assert!(
1597 matches!(tokens[0], TokenKind::Error(ref e) if e.contains(&expected)),
1598 "expected {input:?} -> {expected:?}, got {:?}",
1599 tokens[0]
1600 );
1601 }
1602 }
1603
1604 #[test]
1605 fn test_lex_bare_param_prefix_is_unrecognized_token() {
1606 for input in [":", "@", "$"] {
1610 let tokens = kinds(input);
1611 let expected = format!("unrecognized token: \"{input}\"");
1612 assert!(
1613 matches!(tokens[0], TokenKind::Error(ref e) if e.contains(&expected)),
1614 "expected bare {input:?} -> {expected:?}, got {:?}",
1615 tokens[0]
1616 );
1617 assert_eq!(tokens[1], TokenKind::Eof);
1618 }
1619 assert_eq!(kinds(":foo")[0], TokenKind::ColonParam("foo".to_owned()));
1621 assert_eq!(kinds("@bar")[0], TokenKind::AtParam("bar".to_owned()));
1622 assert_eq!(kinds("$baz")[0], TokenKind::DollarParam("baz".to_owned()));
1623 }
1624
1625 fn histogram_total(hist: &TokenizeDurationSecondsHistogram) -> u64 {
1626 hist.le_100us + hist.le_250us + hist.le_500us + hist.le_1ms + hist.le_5ms + hist.gt_5ms
1627 }
1628
1629 #[test]
1630 fn test_tokenize_metrics_accumulate_tokens_and_histogram_samples() {
1631 let prev_metrics_enabled = tokenize_metrics_enabled();
1632 reset_tokenize_metrics();
1633 set_tokenize_metrics_enabled(true);
1634
1635 let first = lex("SELECT 1;");
1636 let second = lex("SELECT 2;");
1637
1638 let expected_total_tokens = u64::try_from(first.len() + second.len()).unwrap_or(u64::MAX);
1639 let snap = tokenize_metrics_snapshot();
1640 assert_eq!(snap.fsqlite_tokenize_tokens_total, expected_total_tokens);
1641 assert_eq!(snap.fsqlite_tokenize_duration_seconds_count, 2);
1642 assert_eq!(
1643 histogram_total(&snap.fsqlite_tokenize_duration_seconds),
1644 snap.fsqlite_tokenize_duration_seconds_count
1645 );
1646
1647 set_tokenize_metrics_enabled(prev_metrics_enabled);
1648 reset_tokenize_metrics();
1649 }
1650
1651 #[test]
1652 fn test_tokenize_metrics_reset_clears_all_fields() {
1653 let prev_metrics_enabled = tokenize_metrics_enabled();
1654 reset_tokenize_metrics();
1655 set_tokenize_metrics_enabled(true);
1656 let _ = lex("SELECT 42;");
1657
1658 let before = tokenize_metrics_snapshot();
1659 assert!(before.fsqlite_tokenize_tokens_total > 0);
1660 assert!(before.fsqlite_tokenize_duration_seconds_count > 0);
1661
1662 reset_tokenize_metrics();
1663 let after = tokenize_metrics_snapshot();
1664 assert_eq!(after.fsqlite_tokenize_tokens_total, 0);
1665 assert_eq!(after.fsqlite_tokenize_duration_seconds_count, 0);
1666 assert_eq!(after.fsqlite_tokenize_duration_seconds_sum_micros, 0);
1667 assert_eq!(histogram_total(&after.fsqlite_tokenize_duration_seconds), 0);
1668
1669 set_tokenize_metrics_enabled(prev_metrics_enabled);
1670 }
1671
1672 #[test]
1673 fn test_tokenize_metrics_can_be_disabled_off_hot_path() {
1674 let prev_metrics_enabled = tokenize_metrics_enabled();
1675 reset_tokenize_metrics();
1676 set_tokenize_metrics_enabled(false);
1677
1678 let _ = lex("SELECT 99;");
1679
1680 let snap = tokenize_metrics_snapshot();
1681 assert_eq!(snap.fsqlite_tokenize_tokens_total, 0);
1682 assert_eq!(snap.fsqlite_tokenize_duration_seconds_count, 0);
1683 assert_eq!(snap.fsqlite_tokenize_duration_seconds_sum_micros, 0);
1684 assert_eq!(histogram_total(&snap.fsqlite_tokenize_duration_seconds), 0);
1685
1686 set_tokenize_metrics_enabled(prev_metrics_enabled);
1687 reset_tokenize_metrics();
1688 }
1689}