LCOV - code coverage report
Current view: top level - dashql-core/src/utils - mod.rs Coverage Total Hit
Test: DashQL d805a4b Lines: 98.3 % 59 58
Test Date: 2026-11-09 20:22:13

            Line data    Source code
       1              : //! Utilities shared by the modules of the core.
       2              : 
       3              : pub mod rope;
       4              : pub mod topk;
       5              : 
       6              : /// ASCII lower-casing
       7              : #[inline]
       8      1109690 : pub fn tolower_fuzzy(c: u8) -> u8 {
       9      1109690 :     c.to_ascii_lowercase()
      10      1109690 : }
      11              : 
      12              : /// Compare two byte strings case-insensitively
      13       143521 : pub fn ci_cmp(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
      14       218762 :     for (x, y) in a.iter().zip(b) {
      15       218762 :         match tolower_fuzzy(*x).cmp(&tolower_fuzzy(*y)) {
      16        84152 :             std::cmp::Ordering::Equal => continue,
      17       134610 :             other => return other,
      18              :         }
      19              :     }
      20         8911 :     a.len().cmp(&b.len())
      21       143521 : }
      22              : 
      23              : /// Does `text` start with `prefix`, ignoring ASCII case?
      24       319525 : pub fn ci_starts_with(text: &[u8], prefix: &[u8]) -> bool {
      25       336083 :     text.len() >= prefix.len() && text.iter().zip(prefix).all(|(a, b)| tolower_fuzzy(*a) == tolower_fuzzy(*b))
      26       319525 : }
      27              : 
      28              : /// Find `needle` in `haystack`, ignoring ASCII case
      29        72420 : pub fn ci_find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
      30        72420 :     if needle.is_empty() {
      31            0 :         return Some(0);
      32        72420 :     }
      33        72420 :     if needle.len() > haystack.len() {
      34         8405 :         return None;
      35        64015 :     }
      36       318411 :     (0..=haystack.len() - needle.len()).find(|&i| ci_starts_with(&haystack[i..], needle))
      37        72420 : }
      38              : 
      39              : /// A string literal of a text, quotes are doubled: `it's` is `'it''s'`
      40           15 : pub fn quote_string_literal(text: &str) -> String {
      41           15 :     format!("'{}'", text.replace('\'', "''"))
      42           15 : }
      43              : 
      44              : /// The text of a string literal as the scanner reads it, `'it''s'` is `it's`. None for other literals, e.g. the
      45              : /// escape strings `E'...'`.
      46          256 : pub fn unquote_string_literal(literal: &str) -> Option<String> {
      47          256 :     let inner = literal.strip_prefix('\'')?.strip_suffix('\'')?;
      48          256 :     Some(inner.replace("''", "'"))
      49          256 : }
      50              : 
      51              : /// Quote an identifier if it would not round-trip unquoted
      52        10623 : pub fn quote_identifier(text: &str) -> String {
      53        72975 :     let is_start = |c: u8| c.is_ascii_alphabetic() || c == b'_' || c >= 0x80;
      54        62407 :     let is_cont = |c: u8| is_start(c) || c.is_ascii_digit() || c == b'$';
      55        10623 :     let bytes = text.as_bytes();
      56        10623 :     let requires_quotes = bytes.is_empty()
      57        62531 :         || bytes.iter().any(|c| c.is_ascii_uppercase())
      58        10568 :         || !is_start(bytes[0])
      59        62407 :         || !bytes.iter().all(|&c| is_cont(c));
      60        10623 :     if !requires_quotes {
      61        10544 :         return text.to_string();
      62           79 :     }
      63           79 :     let mut out = String::with_capacity(text.len() + 2);
      64           79 :     out.push('"');
      65          689 :     for c in text.chars() {
      66          689 :         if c == '"' {
      67            3 :             out.push('"');
      68          686 :         }
      69          689 :         out.push(c);
      70              :     }
      71           79 :     out.push('"');
      72           79 :     out
      73        10623 : }
      74              : 
      75              : /// Quote a column name for a column reference in SQL text, e.g. the projection of a query result. Quotes like
      76              : /// `quote_identifier` and also quotes the keywords that are not unreserved: `select user` reads the current user and
      77              : /// `select order` does not parse, the columns are `"user"` and `"order"`.
      78           10 : pub fn quote_column_name(text: &str) -> String {
      79              :     use dashql_parser::keywords::{Keyword, KeywordCategory};
      80           10 :     let quoted = quote_identifier(text);
      81           10 :     if quoted != text {
      82            2 :         return quoted;
      83            8 :     }
      84            8 :     match Keyword::find(text.as_bytes()) {
      85            7 :         Some(keyword) if keyword.category != KeywordCategory::SqlUnreserved => format!("\"{text}\""),
      86            3 :         _ => quoted,
      87              :     }
      88           10 : }
        

Generated by: LCOV version 2.0-1