halo/
flavor.rs

1//! SQL Flavor(方言):控制占位符、Quote、Interpolate 等行为。
2
3use std::fmt;
4use std::sync::atomic::{AtomicU8, Ordering};
5use std::sync::{Mutex, MutexGuard};
6
7/// 与 go-sqlbuilder `Flavor` 对齐的方言枚举。
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
9pub enum Flavor {
10    #[default]
11    MySQL,
12    PostgreSQL,
13    SQLite,
14    SQLServer,
15    CQL,
16    ClickHouse,
17    Presto,
18    Oracle,
19    Informix,
20    Doris,
21}
22
23static DEFAULT_FLAVOR: AtomicU8 = AtomicU8::new(Flavor::MySQL as u8);
24static DEFAULT_FLAVOR_LOCK: Mutex<()> = Mutex::new(());
25
26impl Flavor {
27    fn from_u8(v: u8) -> Self {
28        match v {
29            0 => Self::MySQL,
30            1 => Self::PostgreSQL,
31            2 => Self::SQLite,
32            3 => Self::SQLServer,
33            4 => Self::CQL,
34            5 => Self::ClickHouse,
35            6 => Self::Presto,
36            7 => Self::Oracle,
37            8 => Self::Informix,
38            9 => Self::Doris,
39            _ => Self::MySQL,
40        }
41    }
42
43    fn to_u8(self) -> u8 {
44        self as u8
45    }
46}
47
48/// 获取当前全局默认 Flavor(对齐 go-sqlbuilder `DefaultFlavor`)。
49pub fn default_flavor() -> Flavor {
50    Flavor::from_u8(DEFAULT_FLAVOR.load(Ordering::Relaxed))
51}
52
53/// 设置全局默认 Flavor,返回旧值(对齐 go-sqlbuilder 的用法习惯)。
54pub fn set_default_flavor(flavor: Flavor) -> Flavor {
55    let old = DEFAULT_FLAVOR.swap(flavor.to_u8(), Ordering::Relaxed);
56    Flavor::from_u8(old)
57}
58
59/// 修改全局默认 Flavor 的 RAII guard(会持有一个全局锁,避免并行测试互相干扰)。
60pub struct DefaultFlavorGuard {
61    _lock: MutexGuard<'static, ()>,
62    old: Flavor,
63}
64
65impl Drop for DefaultFlavorGuard {
66    fn drop(&mut self) {
67        set_default_flavor(self.old);
68    }
69}
70
71/// 在一个作用域内临时设置 DefaultFlavor,并保证退出作用域后自动恢复。
72pub fn set_default_flavor_scoped(flavor: Flavor) -> DefaultFlavorGuard {
73    let lock = DEFAULT_FLAVOR_LOCK
74        .lock()
75        .unwrap_or_else(|e| e.into_inner());
76    let old = set_default_flavor(flavor);
77    DefaultFlavorGuard { _lock: lock, old }
78}
79
80impl fmt::Display for Flavor {
81    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
82        let s = match self {
83            Self::MySQL => "MySQL",
84            Self::PostgreSQL => "PostgreSQL",
85            Self::SQLite => "SQLite",
86            Self::SQLServer => "SQLServer",
87            Self::CQL => "CQL",
88            Self::ClickHouse => "ClickHouse",
89            Self::Presto => "Presto",
90            Self::Oracle => "Oracle",
91            Self::Informix => "Informix",
92            Self::Doris => "Doris",
93        };
94        f.write_str(s)
95    }
96}
97
98#[derive(Debug, thiserror::Error, PartialEq, Eq)]
99pub enum InterpolateError {
100    #[error("builder interpolation for this flavor is not implemented")]
101    NotImplemented,
102    #[error("builder not enough args when interpolating")]
103    MissingArgs,
104    #[error("builder unsupported args when interpolating")]
105    UnsupportedArgs,
106    #[error("{0}")]
107    ValuerError(#[from] crate::valuer::ValuerError),
108}
109
110impl Flavor {
111    /// 对齐 go-sqlbuilder `Flavor#Quote`:为标识符加引号。
112    pub fn quote(self, name: &str) -> String {
113        match self {
114            Self::MySQL | Self::ClickHouse | Self::Doris => format!("`{name}`"),
115            Self::PostgreSQL
116            | Self::SQLServer
117            | Self::SQLite
118            | Self::Presto
119            | Self::Oracle
120            | Self::Informix => {
121                format!("\"{name}\"")
122            }
123            Self::CQL => format!("'{name}'"),
124        }
125    }
126
127    /// 对齐 go-sqlbuilder `Flavor.PrepareInsertIgnore` 的核心逻辑。
128    pub fn prepare_insert_ignore(self) -> &'static str {
129        match self {
130            Flavor::MySQL | Flavor::Oracle => "INSERT IGNORE",
131            Flavor::PostgreSQL => "INSERT",
132            Flavor::SQLite => "INSERT OR IGNORE",
133            _ => "INSERT",
134        }
135    }
136}