LCOV - code coverage report
Current view: top level - dashql-core/src/utils - mod.rs Coverage Total Hit
Test: DashQL 6e74948 Lines: 98.3 % 59 58
Test Date: 2026-11-06 17:46:50

            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      1044228 : pub fn tolower_fuzzy(c: u8) -> u8 {
       9      1044228 :     c.to_ascii_lowercase()
      10      1044228 : }
      11              : 
      12              : /// Compare two byte strings case-insensitively
      13       133667 : pub fn ci_cmp(a: &[u8], b: &[u8]) -> std::cmp::Ordering {
      14       203899 :     for (x, y) in a.iter().zip(b) {
      15       203899 :         match tolower_fuzzy(*x).cmp(&tolower_fuzzy(*y)) {
      16        77198 :             std::cmp::Ordering::Equal => continue,
      17       126701 :             other => return other,
      18              :         }
      19              :     }
      20         6966 :     a.len().cmp(&b.len())
      21       133667 : }
      22              : 
      23              : /// Does `text` start with `prefix`, ignoring ASCII case?
      24       302672 : pub fn ci_starts_with(text: &[u8], prefix: &[u8]) -> bool {
      25       318215 :     text.len() >= prefix.len() && text.iter().zip(prefix).all(|(a, b)| tolower_fuzzy(*a) == tolower_fuzzy(*b))
      26       302672 : }
      27              : 
      28              : /// Find `needle` in `haystack`, ignoring ASCII case
      29        69234 : pub fn ci_find(haystack: &[u8], needle: &[u8]) -> Option<usize> {
      30        69234 :     if needle.is_empty() {
      31            0 :         return Some(0);
      32        69234 :     }
      33        69234 :     if needle.len() > haystack.len() {
      34         8303 :         return None;
      35        60931 :     }
      36       301634 :     (0..=haystack.len() - needle.len()).find(|&i| ci_starts_with(&haystack[i..], needle))
      37        69234 : }
      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          106 : pub fn unquote_string_literal(literal: &str) -> Option<String> {
      47          106 :     let inner = literal.strip_prefix('\'')?.strip_suffix('\'')?;
      48          106 :     Some(inner.replace("''", "'"))
      49          106 : }
      50              : 
      51              : /// Quote an identifier if it would not round-trip unquoted
      52         5893 : pub fn quote_identifier(text: &str) -> String {
      53        38810 :     let is_start = |c: u8| c.is_ascii_alphabetic() || c == b'_' || c >= 0x80;
      54        32968 :     let is_cont = |c: u8| is_start(c) || c.is_ascii_digit() || c == b'$';
      55         5893 :     let bytes = text.as_bytes();
      56         5893 :     let requires_quotes = bytes.is_empty()
      57        33074 :         || bytes.iter().any(|c| c.is_ascii_uppercase())
      58         5843 :         || !is_start(bytes[0])
      59        32967 :         || !bytes.iter().all(|&c| is_cont(c));
      60         5893 :     if !requires_quotes {
      61         5823 :         return text.to_string();
      62           70 :     }
      63           70 :     let mut out = String::with_capacity(text.len() + 2);
      64           70 :     out.push('"');
      65          615 :     for c in text.chars() {
      66          615 :         if c == '"' {
      67            3 :             out.push('"');
      68          612 :         }
      69          615 :         out.push(c);
      70              :     }
      71           70 :     out.push('"');
      72           70 :     out
      73         5893 : }
      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