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}