flowscope_core/generated/
case_sensitivity.rs

1//! Case sensitivity rules per dialect.
2//!
3//! Generated from dialects.json and normalization_overrides.toml
4//!
5//! This module defines how SQL identifiers (table names, column names, etc.)
6//! should be normalized for comparison. Different SQL dialects have different
7//! rules for identifier case sensitivity.
8
9use std::borrow::Cow;
10
11use crate::Dialect;
12
13/// Normalization strategy for identifier handling.
14///
15/// SQL dialects differ in how they handle identifier case. This enum represents
16/// the different strategies used for normalizing identifiers during analysis.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum NormalizationStrategy {
19    /// Fold to lowercase (Postgres, Redshift)
20    Lowercase,
21    /// Fold to uppercase (Snowflake, Oracle)
22    Uppercase,
23    /// Case-insensitive comparison without folding
24    CaseInsensitive,
25    /// Case-sensitive, preserve exactly
26    CaseSensitive,
27}
28
29impl NormalizationStrategy {
30    /// Applies this normalization strategy to a string.
31    ///
32    /// Returns a `Cow<str>` to avoid allocation when no transformation is needed
33    /// (i.e., for `CaseSensitive` strategy or when the string is already in the
34    /// correct case).
35    ///
36    /// For `CaseInsensitive`, lowercase folding is used as the canonical form.
37    ///
38    /// # Example
39    ///
40    /// ```
41    /// use std::borrow::Cow;
42    /// use flowscope_core::generated::NormalizationStrategy;
43    ///
44    /// let strategy = NormalizationStrategy::Lowercase;
45    /// assert_eq!(strategy.apply("MyTable"), "mytable");
46    ///
47    /// // CaseSensitive returns a borrowed reference (no allocation)
48    /// let strategy = NormalizationStrategy::CaseSensitive;
49    /// assert!(matches!(strategy.apply("MyTable"), Cow::Borrowed(_)));
50    /// ```
51    pub fn apply<'a>(&self, s: &'a str) -> Cow<'a, str> {
52        match self {
53            Self::CaseSensitive => Cow::Borrowed(s),
54            Self::Lowercase | Self::CaseInsensitive => {
55                // Optimization: only allocate if the string contains uppercase chars
56                if s.chars().any(|c| c.is_uppercase()) {
57                    Cow::Owned(s.to_lowercase())
58                } else {
59                    Cow::Borrowed(s)
60                }
61            }
62            Self::Uppercase => {
63                // Optimization: only allocate if the string contains lowercase chars
64                if s.chars().any(|c| c.is_lowercase()) {
65                    Cow::Owned(s.to_uppercase())
66                } else {
67                    Cow::Borrowed(s)
68                }
69            }
70        }
71    }
72}
73
74impl Dialect {
75    /// Get the normalization strategy for this dialect.
76    pub const fn normalization_strategy(&self) -> NormalizationStrategy {
77        match self {
78            Dialect::Bigquery => NormalizationStrategy::CaseInsensitive,
79            Dialect::Clickhouse => NormalizationStrategy::CaseSensitive,
80            Dialect::Databricks => NormalizationStrategy::CaseInsensitive,
81            Dialect::Duckdb => NormalizationStrategy::CaseInsensitive,
82            Dialect::Hive => NormalizationStrategy::CaseInsensitive,
83            Dialect::Mssql => NormalizationStrategy::CaseInsensitive,
84            Dialect::Mysql => NormalizationStrategy::CaseSensitive,
85            Dialect::Postgres => NormalizationStrategy::Lowercase,
86            Dialect::Redshift => NormalizationStrategy::CaseInsensitive,
87            Dialect::Snowflake => NormalizationStrategy::Uppercase,
88            Dialect::Sqlite => NormalizationStrategy::CaseInsensitive,
89            Dialect::Generic => NormalizationStrategy::CaseInsensitive,
90            Dialect::Ansi => NormalizationStrategy::Uppercase,
91        }
92    }
93
94    /// Returns true if this dialect has custom normalization logic
95    /// that cannot be captured by a simple strategy.
96    pub const fn has_custom_normalization(&self) -> bool {
97        matches!(self, Dialect::Bigquery)
98    }
99
100    /// Get pseudocolumns for this dialect (implicit columns like _PARTITIONTIME).
101    pub fn pseudocolumns(&self) -> &'static [&'static str] {
102        match self {
103            Dialect::Bigquery => &["_FILE_NAME", "_PARTITIONDATE", "_PARTITIONTIME", "_TABLE_SUFFIX"],
104            Dialect::Snowflake => &["LEVEL"],
105            _ => &[],
106        }
107    }
108
109    /// Get the identifier quote characters for this dialect.
110    /// Note: Some dialects use paired quotes (like SQLite's []) which are represented
111    /// as single characters here - the opening bracket.
112    pub fn identifier_quotes(&self) -> &'static [&'static str] {
113        match self {
114            Dialect::Bigquery => &["`"],
115            Dialect::Clickhouse => &["\"", "`"],
116            Dialect::Databricks => &["`"],
117            Dialect::Duckdb => &["\""],
118            Dialect::Hive => &["`"],
119            Dialect::Mssql => &["[", "\""],
120            Dialect::Mysql => &["`"],
121            Dialect::Postgres => &["\""],
122            Dialect::Redshift => &["\""],
123            Dialect::Snowflake => &["\""],
124            Dialect::Sqlite => &["\"", "[", "`"],
125            _ => &["\""],
126        }
127    }
128}