Skip to main content

sz_orm_core/
dialect.rs

1//! 不同数据库的方言抽象
2//!
3//! 为数据库特定的 SQL 语法提供统一接口
4
5pub use crate::db_type::DbType;
6use crate::error::DbError;
7use std::fmt;
8
9/// L-4 修复:SQL 标识符最大长度(取所有主流数据库最严格值)
10///
11/// - PostgreSQL: 63 chars (NAMEDATALEN default 64, minus 1)
12/// - MySQL: 64 chars
13/// - Oracle: 30 chars (12.2R2 之前) / 128 chars (12.2R2+)
14/// - SQL Server: 128 chars
15/// - SQLite: 实际无限制(但建议遵守 63)
16///
17/// 取 63 作为最严格值,覆盖所有主流数据库。
18pub const MAX_IDENTIFIER_LEN: usize = 63;
19
20/// 数据库方言 trait
21///
22/// 实现者负责处理各数据库特有的 SQL 语法差异
23pub trait Dialect: Send + Sync {
24    /// 克隆为 trait object(用于 `QueryBuilder::clone_for_count`)。
25    fn clone_box(&self) -> Box<dyn Dialect>;
26
27    /// 返回该方言对应的数据库类型
28    fn db_type(&self) -> DbType;
29
30    /// 引用标识符(表名、列名等)
31    fn quote(&self, identifier: &str) -> String;
32
33    /// L-4 修复:带校验的引用标识符
34    ///
35    /// 与 `quote()` 不同,此方法会先校验标识符:
36    /// - 非空
37    /// - 长度 ≤ `MAX_IDENTIFIER_LEN` (63 chars)
38    /// - 不含 SQL 元字符(引号、分号、空格、注释等)
39    ///
40    /// 校验失败时返回 `DbError::InvalidInput`。
41    ///
42    /// 建议在调用方不可信的场景(如用户输入的表名/列名)使用此方法替代 `quote()`。
43    fn quote_checked(&self, identifier: &str) -> Result<String, DbError> {
44        crate::sql_safety::validate_identifier(identifier, "identifier")?;
45        Ok(self.quote(identifier))
46    }
47
48    /// 转义字符串字面量,确保可安全嵌入 SQL
49    fn escape_string(&self, s: &str) -> String;
50
51    /// 该方言是否支持 RETURNING 子句
52    fn supports_returning(&self) -> bool;
53
54    /// 生成分页 SQL
55    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String;
56
57    /// 获取 JSON 类型的 SQL 类型名
58    fn json_type(&self) -> &'static str;
59
60    /// 生成 JSON_EXTRACT 函数调用
61    fn json_extract(&self, column: &str, path: &str) -> String;
62
63    /// 生成全文检索 SQL
64    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String;
65
66    /// 将布尔表达式转换为整型存储
67    fn bool_to_int(&self, expr: &str) -> String;
68
69    /// 生成 CONCAT 函数调用
70    fn concat(&self, parts: &[&str]) -> String;
71
72    /// 该方言是否支持 IF EXISTS
73    fn supports_if_exists(&self) -> bool;
74
75    /// 该方言是否支持 IF NOT EXISTS
76    fn supports_if_not_exists(&self) -> bool;
77
78    /// 获取自增列关键字
79    fn auto_increment_keyword(&self) -> &'static str;
80
81    /// 获取最近插入 ID 的 SQL(独立可执行语句)
82    ///
83    /// 返回 `None` 表示该方言不支持以独立 SQL 获取最后插入 ID(如 Oracle 只能通过
84    /// 在 INSERT 语句末尾附加 `RETURNING ... INTO :bind` 子句的方式获取,无法独立执行)。
85    /// 调用方在拿到 `None` 时必须改用 `supports_returning()` + 在 INSERT 后追加 RETURNING。
86    fn last_insert_id_sql(&self) -> Option<&'static str>;
87
88    /// 生成 CREATE TABLE 语句
89    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String;
90
91    /// 生成 ALTER TABLE 语句
92    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String;
93
94    /// 生成 DROP TABLE 语句
95    ///
96    /// 默认实现生成标准 `DROP TABLE [IF EXISTS] <table>` 语法。
97    /// 不支持 `IF EXISTS` 的方言(如 DB2)应覆盖此方法。
98    fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
99        if if_exists && self.supports_if_exists() {
100            format!("DROP TABLE IF EXISTS {}", self.quote(table))
101        } else {
102            format!("DROP TABLE {}", self.quote(table))
103        }
104    }
105
106    /// P2-6:生成批量 upsert 的冲突处理子句(ON CONFLICT / ON DUPLICATE KEY UPDATE)
107    ///
108    /// 参数(均为原始列名,由各方言自行 quote):
109    /// - `conflict_columns`: 冲突检测列(唯一键/主键),用于 `ON CONFLICT (cols)` 或 `ON DUPLICATE KEY`
110    /// - `update_columns`: 冲突时需要更新的列;空切片表示更新所有 `all_columns` 中非冲突列
111    /// - `all_columns`: 本次 INSERT 的全部列名(原始未 quote),用于确定"更新所有非冲突列"的范围
112    ///
113    /// 返回 `None` 表示该方言不支持 upsert(如 ClickHouse、Db2)。
114    /// 返回 `Some(clause)` 表示完整的冲突处理子句(不含前导空格),如:
115    /// - MySQL: `ON DUPLICATE KEY UPDATE \`c1\`=VALUES(\`c1\`), \`c2\`=VALUES(\`c2\`)`
116    /// - PG/SQLite: `ON CONFLICT ("c1") DO UPDATE SET "c2"=EXCLUDED."c2"`
117    ///
118    /// **L3 实现深度**:返回的子句使用参数化占位符(`VALUES(col)` 或 `EXCLUDED.col`),
119    /// 不拼接用户值,杜绝 SQL 注入。
120    fn build_upsert_on_conflict(
121        &self,
122        conflict_columns: &[&str],
123        update_columns: &[&str],
124        all_columns: &[String],
125    ) -> Option<String> {
126        let _ = (conflict_columns, update_columns, all_columns);
127        None
128    }
129
130    /// P2-3:生成行锁子句(TASK-025/026)
131    ///
132    /// 根据 `lock_type` 生成对应的 SQL 锁子句:
133    /// - `ForUpdate`:MySQL 生成 `FOR UPDATE`,PG 生成 `FOR UPDATE`
134    /// - `Shared`:MySQL 生成 `LOCK IN SHARE MODE`,PG 生成 `FOR SHARE`
135    ///
136    /// 返回 `None` 表示该方言不支持行锁(如 SQLite)。
137    /// 返回 `Some(clause)` 表示完整的锁子句(不含前导空格)。
138    fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
139        let _ = lock_type;
140        None
141    }
142
143    /// P2-3:该方言是否支持 `FOR UPDATE` 行锁(TASK-029)
144    ///
145    /// 默认返回 `true`。不支持行锁的方言(如 SQLite、ClickHouse、DuckDB)
146    /// 应覆盖此方法返回 `false`。
147    fn supports_lock_for_update(&self) -> bool {
148        true
149    }
150
151    /// P2-3:该方言是否支持共享锁(TASK-029)
152    ///
153    /// 默认返回 `true`。不支持共享锁的方言(如 SQLite、ClickHouse、DuckDB)
154    /// 应覆盖此方法返回 `false`。
155    fn supports_lock_shared(&self) -> bool {
156        true
157    }
158
159    /// P2-4:生成 `INSERT OR IGNORE` 前缀(TASK-027)
160    ///
161    /// - MySQL:返回 `INSERT IGNORE INTO {table}`
162    /// - PostgreSQL/SQLite:返回 `INSERT OR IGNORE INTO {table}`
163    ///
164    /// 用于"存在则忽略"的插入场景,避免主键/唯一键冲突时报错。
165    fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
166        format!("INSERT OR IGNORE INTO {}", self.quote(table))
167    }
168}
169
170/// P2-3:行锁类型(TASK-024)
171///
172/// 用于 `QueryBuilder::lock_for_update()` 和 `QueryBuilder::lock_shared()`。
173/// 不同数据库对共享锁的语法不同:
174/// - MySQL:`LOCK IN SHARE MODE`
175/// - PostgreSQL:`FOR SHARE`
176/// - SQLite:不支持行锁
177#[derive(Debug, Clone, Copy, PartialEq, Eq)]
178pub enum LockType {
179    /// 排他锁(`FOR UPDATE`):锁定行,其他事务无法读取或修改
180    ForUpdate,
181    /// 共享锁(`FOR SHARE` / `LOCK IN SHARE MODE`):锁定行,其他事务可读取但无法修改
182    Shared,
183}
184
185/// 建表时的列定义
186#[derive(Debug, Clone)]
187pub struct ColumnDef {
188    /// 列名
189    pub name: String,
190    /// SQL 类型字符串
191    pub sql_type: String,
192    /// 是否允许 NULL
193    pub nullable: bool,
194    /// 默认值
195    pub default: Option<String>,
196    /// 是否自增
197    pub auto_increment: bool,
198    /// 是否主键
199    pub primary_key: bool,
200}
201
202/// ALTER TABLE 的变更操作
203#[derive(Debug, Clone)]
204pub enum TableChange {
205    /// 新增列
206    AddColumn(ColumnDef),
207    /// 删除列
208    DropColumn(String),
209    /// 修改列定义
210    ModifyColumn(ColumnDef),
211    /// 新增索引
212    AddIndex(String, Vec<String>),
213    /// 删除索引
214    DropIndex(String),
215    /// 新增外键
216    AddForeignKey {
217        /// 本表列
218        columns: Vec<String>,
219        /// 引用表名
220        reference_table: String,
221        /// 引用列
222        reference_columns: Vec<String>,
223    },
224}
225
226/// MySQL 方言实现
227#[derive(Debug, Clone)]
228pub struct MySqlDialect;
229
230impl Dialect for MySqlDialect {
231    fn clone_box(&self) -> Box<dyn Dialect> {
232        Box::new(MySqlDialect)
233    }
234
235    fn db_type(&self) -> DbType {
236        DbType::MySQL
237    }
238
239    fn quote(&self, identifier: &str) -> String {
240        format!("`{}`", identifier.replace('`', "``"))
241    }
242
243    fn escape_string(&self, s: &str) -> String {
244        let mut escaped = String::with_capacity(s.len() * 2);
245        for c in s.chars() {
246            match c {
247                '\\' => escaped.push_str("\\\\"),
248                '\'' => escaped.push_str("\\'"),
249                '\0' => escaped.push_str("\\0"),
250                '\n' => escaped.push_str("\\n"),
251                '\r' => escaped.push_str("\\r"),
252                '\t' => escaped.push_str("\\t"),
253                '\x1a' => escaped.push_str("\\Z"),
254                _ => escaped.push(c),
255            }
256        }
257        escaped
258    }
259
260    fn supports_returning(&self) -> bool {
261        false
262    }
263
264    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
265        // M-2 修复说明:
266        //
267        // 历史上 MySQL 支持 `SQL_CALC_FOUND_ROWS` 提示配合 `FOUND_ROWS()` 函数
268        // 在不分页情况下获取总行数,但该特性在 MySQL 8.0.17 中被弃用并在后续版本移除。
269        // 官方推荐使用独立的 `COUNT(*)` 查询。
270        //
271        // 因此本实现不使用 `SQL_CALC_FOUND_ROWS`,调用方如需总数应单独执行 COUNT 查询。
272        let offset = page.saturating_sub(1).saturating_mul(limit);
273        format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
274    }
275
276    fn json_type(&self) -> &'static str {
277        "JSON"
278    }
279
280    fn json_extract(&self, column: &str, path: &str) -> String {
281        // 规范化 path:确保以 $. 开头
282        let normalized = if path.starts_with('$') {
283            path.to_string()
284        } else {
285            format!("$.{}", path)
286        };
287        format!(
288            "JSON_EXTRACT({}, '{}')",
289            column,
290            self.escape_string(&normalized)
291        )
292    }
293
294    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
295        let cols = columns.join(", ");
296        let escaped = self.escape_string(keyword);
297        format!(
298            "MATCH({}) AGAINST('{}' IN NATURAL LANGUAGE MODE)",
299            cols, escaped
300        )
301    }
302
303    fn bool_to_int(&self, expr: &str) -> String {
304        // 将布尔表达式转换为整数(0/1)用于存储
305        format!("IF({}, 1, 0)", expr)
306    }
307
308    fn concat(&self, parts: &[&str]) -> String {
309        if parts.is_empty() {
310            return "NULL".to_string();
311        }
312        let concat_parts: Vec<String> = parts
313            .iter()
314            .map(|p| format!("CAST({} AS CHAR)", p))
315            .collect();
316        format!("CONCAT({})", concat_parts.join(", "))
317    }
318
319    fn supports_if_exists(&self) -> bool {
320        true
321    }
322
323    fn supports_if_not_exists(&self) -> bool {
324        true
325    }
326
327    fn auto_increment_keyword(&self) -> &'static str {
328        "AUTO_INCREMENT"
329    }
330
331    fn last_insert_id_sql(&self) -> Option<&'static str> {
332        Some("LAST_INSERT_ID()")
333    }
334
335    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
336        let cols: Vec<String> = columns
337            .iter()
338            .map(|col| {
339                let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
340                if !col.nullable {
341                    sql.push_str(" NOT NULL");
342                }
343                if let Some(default) = &col.default {
344                    sql.push_str(&format!(" DEFAULT {}", default));
345                }
346                if col.auto_increment {
347                    sql.push_str(&format!(" {}", self.auto_increment_keyword()));
348                }
349                if col.primary_key {
350                    sql.push_str(" PRIMARY KEY");
351                }
352                sql
353            })
354            .collect();
355
356        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
357    }
358
359    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
360        let stmts: Vec<String> = changes.iter().map(|change| {
361            match change {
362                TableChange::AddColumn(col) => {
363                    let mut sql = format!("ALTER TABLE {} ADD {}", self.quote(table), self.quote(&col.name));
364                    sql.push_str(&format!(" {}", col.sql_type));
365                    if !col.nullable {
366                        sql.push_str(" NOT NULL");
367                    }
368                    if let Some(default) = &col.default {
369                        sql.push_str(&format!(" DEFAULT {}", default));
370                    }
371                    sql
372                }
373                TableChange::DropColumn(name) => {
374                    format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
375                }
376                TableChange::ModifyColumn(col) => {
377                    // MySQL 使用 MODIFY COLUMN
378                    let mut sql = format!("ALTER TABLE {} MODIFY COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
379                    if !col.nullable {
380                        sql.push_str(" NOT NULL");
381                    }
382                    if let Some(default) = &col.default {
383                        sql.push_str(&format!(" DEFAULT {}", default));
384                    }
385                    sql
386                }
387                TableChange::AddIndex(name, cols) => {
388                    format!("ALTER TABLE {} ADD INDEX {} ({})", self.quote(table), name, cols.join(", "))
389                }
390                TableChange::DropIndex(name) => {
391                    format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
392                }
393                TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
394                    format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
395                        self.quote(table),
396                        table,
397                        columns.join("_"),
398                        columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
399                        self.quote(reference_table),
400                        reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
401                }
402            }
403        }).collect();
404
405        stmts.join("; ")
406    }
407
408    /// P2-6:MySQL 批量 upsert — `ON DUPLICATE KEY UPDATE col=VALUES(col), ...`
409    ///
410    /// MySQL 不需要指定冲突列(自动检测唯一键/主键冲突)。
411    /// `conflict_columns` 参数在 MySQL 方言中被忽略。
412    fn build_upsert_on_conflict(
413        &self,
414        _conflict_columns: &[&str],
415        update_columns: &[&str],
416        all_columns: &[String],
417    ) -> Option<String> {
418        // 确定要更新的列:优先使用 update_columns,为空则使用所有列(均已 quote)
419        let cols_to_update: Vec<String> = if update_columns.is_empty() {
420            all_columns.iter().map(|c| self.quote(c)).collect()
421        } else {
422            update_columns.iter().map(|c| self.quote(c)).collect()
423        };
424        if cols_to_update.is_empty() {
425            return None;
426        }
427        let set_clauses: Vec<String> = cols_to_update
428            .iter()
429            .map(|col| format!("{}=VALUES({})", col, col))
430            .collect();
431        Some(format!(
432            "ON DUPLICATE KEY UPDATE {}",
433            set_clauses.join(", ")
434        ))
435    }
436
437    fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
438        match lock_type {
439            LockType::ForUpdate => Some("FOR UPDATE".to_string()),
440            LockType::Shared => Some("LOCK IN SHARE MODE".to_string()),
441        }
442    }
443
444    fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
445        format!("INSERT IGNORE INTO {}", self.quote(table))
446    }
447}
448
449/// PostgreSQL 方言实现
450#[derive(Debug, Clone)]
451pub struct PostgreSqlDialect;
452
453impl Dialect for PostgreSqlDialect {
454    fn clone_box(&self) -> Box<dyn Dialect> {
455        Box::new(PostgreSqlDialect)
456    }
457
458    fn db_type(&self) -> DbType {
459        DbType::PostgreSQL
460    }
461
462    fn quote(&self, identifier: &str) -> String {
463        format!("\"{}\"", identifier.replace('"', "\"\""))
464    }
465
466    fn escape_string(&self, s: &str) -> String {
467        // PostgreSQL 标准:使用双单引号转义单引号(standard_conforming_strings=on 默认)
468        // 反斜杠在 standard_conforming_strings=on 时不是转义字符
469        let mut escaped = String::with_capacity(s.len() * 2);
470        for c in s.chars() {
471            match c {
472                '\'' => escaped.push_str("''"),
473                _ => escaped.push(c),
474            }
475        }
476        escaped
477    }
478
479    fn supports_returning(&self) -> bool {
480        true
481    }
482
483    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
484        let offset = page.saturating_sub(1).saturating_mul(limit);
485        format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
486    }
487
488    fn json_type(&self) -> &'static str {
489        "JSONB"
490    }
491
492    fn json_extract(&self, column: &str, path: &str) -> String {
493        // PostgreSQL 使用 #>> 提取文本,path 以字符串数组形式
494        // 支持 $.a.b 或 a.b 格式
495        // 输出形式:column#>>'{a,b,c}'
496        // 路径组件中的特殊字符(逗号、花括号、双引号、反斜杠)需用双引号包裹并转义
497        let normalized = path.trim_start_matches("$.");
498        let parts: Vec<&str> = normalized.split('.').filter(|s| !s.is_empty()).collect();
499        let path_lit = parts
500            .iter()
501            .map(|p| {
502                // 如果包含特殊字符,用双引号包裹并转义内部双引号和反斜杠
503                let needs_quoting = p.chars().any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\'));
504                if needs_quoting {
505                    let escaped = p.replace('\\', "\\\\").replace('"', "\\\"");
506                    format!("\"{}\"", escaped)
507                } else {
508                    p.to_string()
509                }
510            })
511            .collect::<Vec<_>>()
512            .join(",");
513        // 转义 SQL 字符串字面量中的单引号(PG 使用双单引号转义)
514        let path_lit_escaped = path_lit.replace('\'', "''");
515        format!("{}#>>'{{{}}}'", column, path_lit_escaped)
516    }
517
518    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
519        let cols = columns
520            .iter()
521            .map(|c| format!("{}::text", c))
522            .collect::<Vec<_>>()
523            .join(" || ' ' || ");
524        let escaped = self.escape_string(keyword);
525        format!("to_tsvector({}) @@ to_tsquery('{}')", cols, escaped)
526    }
527
528    fn bool_to_int(&self, expr: &str) -> String {
529        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
530    }
531
532    fn concat(&self, parts: &[&str]) -> String {
533        if parts.is_empty() {
534            return "NULL".to_string();
535        }
536        format!("CONCAT({})", parts.join(", "))
537    }
538
539    fn supports_if_exists(&self) -> bool {
540        true
541    }
542
543    fn supports_if_not_exists(&self) -> bool {
544        true
545    }
546
547    fn auto_increment_keyword(&self) -> &'static str {
548        "GENERATED BY DEFAULT AS IDENTITY"
549    }
550
551    fn last_insert_id_sql(&self) -> Option<&'static str> {
552        Some("lastval()")
553    }
554
555    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
556        let cols: Vec<String> = columns
557            .iter()
558            .map(|col| {
559                let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
560                if !col.nullable {
561                    sql.push_str(" NOT NULL");
562                }
563                if let Some(default) = &col.default {
564                    sql.push_str(&format!(" DEFAULT {}", default));
565                }
566                if col.primary_key {
567                    sql.push_str(" PRIMARY KEY");
568                }
569                sql
570            })
571            .collect();
572
573        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
574    }
575
576    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
577        let stmts: Vec<String> = changes.iter().map(|change| {
578            match change {
579                TableChange::AddColumn(col) => {
580                    let mut sql = format!("ALTER TABLE {} ADD COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
581                    if !col.nullable {
582                        sql.push_str(" NOT NULL");
583                    }
584                    if let Some(default) = &col.default {
585                        sql.push_str(&format!(" DEFAULT {}", default));
586                    }
587                    sql
588                }
589                TableChange::DropColumn(name) => {
590                    format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
591                }
592                TableChange::ModifyColumn(col) => {
593                    // PostgreSQL 使用 ALTER COLUMN TYPE
594                    let mut sql = format!("ALTER TABLE {} ALTER COLUMN {} TYPE {}", self.quote(table), self.quote(&col.name), col.sql_type);
595                    if !col.nullable {
596                        sql.push_str(&format!(", ALTER COLUMN {} SET NOT NULL", self.quote(&col.name)));
597                    }
598                    if let Some(default) = &col.default {
599                        sql.push_str(&format!(", ALTER COLUMN {} SET DEFAULT {}", self.quote(&col.name), default));
600                    }
601                    sql
602                }
603                TableChange::AddIndex(name, cols) => {
604                    format!("CREATE INDEX {} ON {} ({})", name, self.quote(table), cols.join(", "))
605                }
606                TableChange::DropIndex(name) => {
607                    format!("DROP INDEX {}", name)
608                }
609                TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
610                    format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
611                        self.quote(table),
612                        table,
613                        columns.join("_"),
614                        columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
615                        self.quote(reference_table),
616                        reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
617                }
618            }
619        }).collect();
620
621        stmts.join("; ")
622    }
623
624    /// P2-6:PostgreSQL 批量 upsert — `ON CONFLICT (cols) DO UPDATE SET col=EXCLUDED.col, ...`
625    ///
626    /// PostgreSQL 要求显式指定冲突列(主键或唯一键)。
627    fn build_upsert_on_conflict(
628        &self,
629        conflict_columns: &[&str],
630        update_columns: &[&str],
631        all_columns: &[String],
632    ) -> Option<String> {
633        if conflict_columns.is_empty() {
634            return None;
635        }
636        let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
637        // 确定要更新的列:优先使用 update_columns,为空则使用所有非冲突列
638        let conflict_set: std::collections::HashSet<&str> =
639            conflict_columns.iter().copied().collect();
640        let cols_to_update: Vec<String> = if update_columns.is_empty() {
641            all_columns
642                .iter()
643                .filter(|c| !conflict_set.contains(c.as_str()))
644                .map(|c| self.quote(c))
645                .collect()
646        } else {
647            update_columns.iter().map(|c| self.quote(c)).collect()
648        };
649        if cols_to_update.is_empty() {
650            // 没有要更新的列,使用 DO NOTHING
651            return Some(format!(
652                "ON CONFLICT ({}) DO NOTHING",
653                conflict_cols.join(", ")
654            ));
655        }
656        let set_clauses: Vec<String> = cols_to_update
657            .iter()
658            .map(|col| format!("{}=EXCLUDED.{}", col, col))
659            .collect();
660        Some(format!(
661            "ON CONFLICT ({}) DO UPDATE SET {}",
662            conflict_cols.join(", "),
663            set_clauses.join(", ")
664        ))
665    }
666
667    fn build_lock_clause(&self, lock_type: LockType) -> Option<String> {
668        match lock_type {
669            LockType::ForUpdate => Some("FOR UPDATE".to_string()),
670            LockType::Shared => Some("FOR SHARE".to_string()),
671        }
672    }
673}
674
675/// SQLite 方言实现
676#[derive(Debug, Clone)]
677pub struct SqliteDialect;
678
679impl Dialect for SqliteDialect {
680    fn clone_box(&self) -> Box<dyn Dialect> {
681        Box::new(SqliteDialect)
682    }
683
684    fn db_type(&self) -> DbType {
685        DbType::Sqlite
686    }
687
688    fn quote(&self, identifier: &str) -> String {
689        format!("\"{}\"", identifier.replace('"', "\"\""))
690    }
691
692    fn escape_string(&self, s: &str) -> String {
693        let mut escaped = String::with_capacity(s.len() * 2);
694        for c in s.chars() {
695            match c {
696                '\'' => escaped.push_str("''"),
697                _ => escaped.push(c),
698            }
699        }
700        escaped
701    }
702
703    fn supports_returning(&self) -> bool {
704        true
705    }
706
707    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
708        let offset = page.saturating_sub(1).saturating_mul(limit);
709        format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
710    }
711
712    fn json_type(&self) -> &'static str {
713        "TEXT"
714    }
715
716    fn json_extract(&self, column: &str, path: &str) -> String {
717        // SQLite 使用 json_extract(column, '$.path')
718        let normalized = if path.starts_with('$') {
719            path.to_string()
720        } else {
721            format!("$.{}", path)
722        };
723        format!(
724            "json_extract({}, '{}')",
725            column,
726            self.escape_string(&normalized)
727        )
728    }
729
730    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
731        // SQLite FTS 的 MATCH 操作符必须作用于 FTS 虚拟表本身(`tbl MATCH 'query'`),
732        // 不能用于单个列;本接口仅传入列名,没有 FTS 表名,因此降级使用 LIKE。
733        // 这样既能避免生成错误的 MATCH 语法,又能在普通表上工作(不依赖 FTS 索引)。
734        if columns.is_empty() {
735            return "0".to_string();
736        }
737        let escaped = self.escape_string(keyword);
738        columns
739            .iter()
740            .map(|c| format!("{} LIKE '%{}%'", c.trim(), escaped))
741            .collect::<Vec<_>>()
742            .join(" OR ")
743    }
744
745    fn bool_to_int(&self, expr: &str) -> String {
746        expr.to_string()
747    }
748
749    fn concat(&self, parts: &[&str]) -> String {
750        if parts.is_empty() {
751            return "NULL".to_string();
752        }
753        // SQLite 的 || 操作符在任意参数为 NULL 时整体结果为 NULL,
754        // 需要先用 COALESCE 把每个参数替换为空串,才能保证拼接结果非 NULL。
755        let coalesced: Vec<String> = parts
756            .iter()
757            .map(|p| format!("COALESCE({}, '')", p))
758            .collect();
759        coalesced.join(" || ")
760    }
761
762    fn supports_if_exists(&self) -> bool {
763        true
764    }
765
766    fn supports_if_not_exists(&self) -> bool {
767        true
768    }
769
770    fn auto_increment_keyword(&self) -> &'static str {
771        "AUTOINCREMENT"
772    }
773
774    fn last_insert_id_sql(&self) -> Option<&'static str> {
775        Some("last_insert_rowid()")
776    }
777
778    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
779        let cols: Vec<String> = columns
780            .iter()
781            .map(|col| {
782                let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
783                if !col.nullable {
784                    sql.push_str(" NOT NULL");
785                }
786                if let Some(default) = &col.default {
787                    sql.push_str(&format!(" DEFAULT {}", default));
788                }
789                if col.auto_increment {
790                    sql.push_str(" PRIMARY KEY AUTOINCREMENT");
791                } else if col.primary_key {
792                    sql.push_str(" PRIMARY KEY");
793                }
794                sql
795            })
796            .collect();
797
798        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
799    }
800
801    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
802        // SQLite 支持的 ALTER 操作:ADD COLUMN, RENAME COLUMN, DROP COLUMN (3.35+), RENAME TABLE
803        // 不支持:MODIFY COLUMN, ADD INDEX (需用 CREATE INDEX), ADD FOREIGN KEY (语法层面不支持)
804        let stmts: Vec<String> = changes
805            .iter()
806            .map(|change| {
807                match change {
808                    TableChange::AddColumn(col) => {
809                        let mut sql = format!(
810                            "ALTER TABLE {} ADD COLUMN {} {}",
811                            self.quote(table),
812                            self.quote(&col.name),
813                            col.sql_type
814                        );
815                        if !col.nullable {
816                            sql.push_str(" NOT NULL");
817                        }
818                        if let Some(default) = &col.default {
819                            sql.push_str(&format!(" DEFAULT {}", default));
820                        }
821                        sql
822                    }
823                    TableChange::DropColumn(name) => {
824                        // SQLite 3.35.0+ 支持 DROP COLUMN
825                        format!(
826                            "ALTER TABLE {} DROP COLUMN {}",
827                            self.quote(table),
828                            self.quote(name)
829                        )
830                    }
831                    TableChange::ModifyColumn(col) => {
832                        // SQLite 不直接支持 MODIFY COLUMN,需要用 12 步流程
833                        // 这里生成注释 SQL,提示用户需手动处理
834                        format!(
835                            "-- SQLite 不支持 MODIFY COLUMN({} {}),需重建表",
836                            col.name, col.sql_type
837                        )
838                    }
839                    TableChange::AddIndex(name, cols) => {
840                        format!(
841                            "CREATE INDEX {} ON {} ({})",
842                            name,
843                            self.quote(table),
844                            cols.join(", ")
845                        )
846                    }
847                    TableChange::DropIndex(name) => {
848                        format!("DROP INDEX {}", name)
849                    }
850                    TableChange::AddForeignKey {
851                        columns,
852                        reference_table,
853                        reference_columns: _,
854                    } => {
855                        // SQLite 不支持 ALTER TABLE ADD FOREIGN KEY,需重建表
856                        format!(
857                            "-- SQLite 不支持 ADD FOREIGN KEY({} -> {}),需重建表",
858                            columns.join(","),
859                            reference_table
860                        )
861                    }
862                }
863            })
864            .collect();
865
866        stmts.join("; ")
867    }
868
869    /// P2-6:SQLite 批量 upsert — `ON CONFLICT (cols) DO UPDATE SET col=EXCLUDED.col, ...`
870    ///
871    /// SQLite 语法与 PostgreSQL 一致(SQLite 3.24.0+ 支持 ON CONFLICT 子句)。
872    fn build_upsert_on_conflict(
873        &self,
874        conflict_columns: &[&str],
875        update_columns: &[&str],
876        all_columns: &[String],
877    ) -> Option<String> {
878        if conflict_columns.is_empty() {
879            return None;
880        }
881        let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
882        let conflict_set: std::collections::HashSet<&str> =
883            conflict_columns.iter().copied().collect();
884        let cols_to_update: Vec<String> = if update_columns.is_empty() {
885            all_columns
886                .iter()
887                .filter(|c| !conflict_set.contains(c.as_str()))
888                .map(|c| self.quote(c))
889                .collect()
890        } else {
891            update_columns.iter().map(|c| self.quote(c)).collect()
892        };
893        if cols_to_update.is_empty() {
894            return Some(format!(
895                "ON CONFLICT ({}) DO NOTHING",
896                conflict_cols.join(", ")
897            ));
898        }
899        let set_clauses: Vec<String> = cols_to_update
900            .iter()
901            .map(|col| format!("{}=EXCLUDED.{}", col, col))
902            .collect();
903        Some(format!(
904            "ON CONFLICT ({}) DO UPDATE SET {}",
905            conflict_cols.join(", "),
906            set_clauses.join(", ")
907        ))
908    }
909
910    fn supports_lock_for_update(&self) -> bool {
911        false
912    }
913
914    fn supports_lock_shared(&self) -> bool {
915        false
916    }
917}
918
919/// 将通用 SQL 类型映射为 Oracle 23ai 类型
920///
921/// Oracle 类型与常见类型的对应关系:
922/// - BIGINT → NUMBER(19)
923/// - INT/INTEGER → NUMBER(10)
924/// - VARCHAR(n) → VARCHAR2(n)
925/// - TEXT 系列 → CLOB
926/// - BOOLEAN/BOOL → NUMBER(1)
927fn map_to_oracle_type(sql_type: &str) -> String {
928    let upper = sql_type.to_uppercase();
929    let trimmed = upper.trim();
930
931    if trimmed.starts_with("BIGINT") {
932        sql_type.replacen("BIGINT", "NUMBER(19)", 1)
933    } else if trimmed.starts_with("VARCHAR2") {
934        sql_type.to_string()
935    } else if trimmed.starts_with("VARCHAR") {
936        sql_type.replacen("VARCHAR", "VARCHAR2", 1)
937    } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
938        "CLOB".to_string()
939    } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
940        "NUMBER(1)".to_string()
941    } else if trimmed == "INTEGER" {
942        "NUMBER(10)".to_string()
943    } else if trimmed.starts_with("INT") {
944        sql_type.replacen("INT", "NUMBER(10)", 1)
945    } else {
946        sql_type.to_string()
947    }
948}
949
950/// Oracle 方言实现(Oracle 23ai)
951#[derive(Debug, Clone)]
952pub struct OracleDialect;
953
954impl Dialect for OracleDialect {
955    fn clone_box(&self) -> Box<dyn Dialect> {
956        Box::new(OracleDialect)
957    }
958
959    fn db_type(&self) -> DbType {
960        DbType::Oracle
961    }
962
963    fn quote(&self, identifier: &str) -> String {
964        // Oracle 标准使用双引号包裹标识符,内部双引号双写
965        format!("\"{}\"", identifier.replace('"', "\"\""))
966    }
967
968    fn escape_string(&self, s: &str) -> String {
969        // Oracle 标准转义:单引号双写('O''Brien'),反斜杠不转义
970        let mut escaped = String::with_capacity(s.len() * 2);
971        for c in s.chars() {
972            match c {
973                '\'' => escaped.push_str("''"),
974                _ => escaped.push(c),
975            }
976        }
977        escaped
978    }
979
980    fn supports_returning(&self) -> bool {
981        // Oracle 12c+ 支持 RETURNING,23ai 当然支持
982        true
983    }
984
985    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
986        // Oracle 12c+ 使用 OFFSET/FETCH NEXT 语法
987        // 与其他方言保持一致:page=1 为第一页(offset=0)
988        let offset = page.saturating_sub(1).saturating_mul(limit);
989        format!(
990            "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
991            sql, offset, limit
992        )
993    }
994
995    fn json_type(&self) -> &'static str {
996        // Oracle 21+ 原生支持 JSON 类型,23ai 完整支持
997        "JSON"
998    }
999
1000    fn json_extract(&self, column: &str, path: &str) -> String {
1001        // Oracle 使用 JSON_VALUE 提取标量值,path 需以 $. 开头
1002        let normalized = if path.starts_with('$') {
1003            path.to_string()
1004        } else {
1005            format!("$.{}", path)
1006        };
1007        format!(
1008            "JSON_VALUE({}, '{}')",
1009            column,
1010            self.escape_string(&normalized)
1011        )
1012    }
1013
1014    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1015        // Oracle 使用 CONTAINS 函数(需要 CONTEXT 索引)
1016        // CONTAINS(column, keyword, 1) > 0
1017        if columns.is_empty() {
1018            return "0".to_string();
1019        }
1020        let escaped = self.escape_string(keyword);
1021        let parts: Vec<String> = columns
1022            .iter()
1023            .map(|c| format!("CONTAINS({}, '{}', 1) > 0", c, escaped))
1024            .collect();
1025        parts.join(" OR ")
1026    }
1027
1028    fn bool_to_int(&self, expr: &str) -> String {
1029        // Oracle 没有原生 BOOL 类型,使用 CASE WHEN 转换为 0/1
1030        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1031    }
1032
1033    fn concat(&self, parts: &[&str]) -> String {
1034        // Oracle 使用 || 操作符进行字符串拼接
1035        if parts.is_empty() {
1036            return "NULL".to_string();
1037        }
1038        parts.join(" || ")
1039    }
1040
1041    fn supports_if_exists(&self) -> bool {
1042        // Oracle 23ai 支持 DROP TABLE IF EXISTS
1043        true
1044    }
1045
1046    fn supports_if_not_exists(&self) -> bool {
1047        // Oracle 23ai 支持 CREATE TABLE IF NOT EXISTS
1048        true
1049    }
1050
1051    fn auto_increment_keyword(&self) -> &'static str {
1052        // Oracle 12c+ 使用 IDENTITY 列
1053        "GENERATED BY DEFAULT AS IDENTITY"
1054    }
1055
1056    fn last_insert_id_sql(&self) -> Option<&'static str> {
1057        // Oracle 没有独立可执行的"获取最后插入 ID"语句。
1058        // `RETURNING {pk} INTO :bind` 是 PL/SQL 子句,必须附加在 INSERT 之后,
1059        // 不能作为独立 SQL 执行。调用方应改用 `supports_returning()` 在 INSERT
1060        // 末尾追加 RETURNING 子句获取自增值。
1061        None
1062    }
1063
1064    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1065        let cols: Vec<String> = columns
1066            .iter()
1067            .map(|col| {
1068                let oracle_type = map_to_oracle_type(&col.sql_type);
1069                let mut sql = format!("{} {}", self.quote(&col.name), oracle_type);
1070                // Oracle IDENTITY 列隐式 NOT NULL,不允许显式 NOT NULL(ORA-03076)
1071                if !col.nullable && !col.auto_increment {
1072                    sql.push_str(" NOT NULL");
1073                }
1074                if let Some(default) = &col.default {
1075                    sql.push_str(&format!(" DEFAULT {}", default));
1076                }
1077                if col.auto_increment {
1078                    sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1079                }
1080                if col.primary_key {
1081                    sql.push_str(" PRIMARY KEY");
1082                }
1083                sql
1084            })
1085            .collect();
1086
1087        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1088    }
1089
1090    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1091        let stmts: Vec<String> = changes
1092            .iter()
1093            .map(|change| match change {
1094                TableChange::AddColumn(col) => {
1095                    let oracle_type = map_to_oracle_type(&col.sql_type);
1096                    let mut sql = format!(
1097                        "ALTER TABLE {} ADD {} {}",
1098                        self.quote(table),
1099                        self.quote(&col.name),
1100                        oracle_type
1101                    );
1102                    if !col.nullable {
1103                        sql.push_str(" NOT NULL");
1104                    }
1105                    if let Some(default) = &col.default {
1106                        sql.push_str(&format!(" DEFAULT {}", default));
1107                    }
1108                    sql
1109                }
1110                TableChange::DropColumn(name) => {
1111                    format!(
1112                        "ALTER TABLE {} DROP COLUMN {}",
1113                        self.quote(table),
1114                        self.quote(name)
1115                    )
1116                }
1117                TableChange::ModifyColumn(col) => {
1118                    // Oracle 使用 MODIFY 关键字(不需 COLUMN)
1119                    let oracle_type = map_to_oracle_type(&col.sql_type);
1120                    let mut sql = format!(
1121                        "ALTER TABLE {} MODIFY {} {}",
1122                        self.quote(table),
1123                        self.quote(&col.name),
1124                        oracle_type
1125                    );
1126                    if !col.nullable {
1127                        sql.push_str(" NOT NULL");
1128                    }
1129                    if let Some(default) = &col.default {
1130                        sql.push_str(&format!(" DEFAULT {}", default));
1131                    }
1132                    sql
1133                }
1134                TableChange::AddIndex(name, cols) => {
1135                    format!(
1136                        "CREATE INDEX {} ON {} ({})",
1137                        name,
1138                        self.quote(table),
1139                        cols.join(", ")
1140                    )
1141                }
1142                TableChange::DropIndex(name) => {
1143                    format!("DROP INDEX {}", name)
1144                }
1145                TableChange::AddForeignKey {
1146                    columns,
1147                    reference_table,
1148                    reference_columns,
1149                } => {
1150                    format!(
1151                        "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1152                        self.quote(table),
1153                        table,
1154                        columns.join("_"),
1155                        columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1156                        self.quote(reference_table),
1157                        reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1158                    )
1159                }
1160            })
1161            .collect();
1162
1163        stmts.join("; ")
1164    }
1165}
1166
1167/// 将通用 SQL 类型映射为 SQL Server 类型
1168///
1169/// - BIGINT → BIGINT(保持)
1170/// - INT/INTEGER → INT
1171/// - VARCHAR(n) → NVARCHAR(n)(统一使用 Unicode)
1172/// - TEXT 系列 → NVARCHAR(MAX)
1173/// - BOOLEAN/BOOL → BIT
1174/// - 其他类型保持不变
1175fn map_to_sqlserver_type(sql_type: &str) -> String {
1176    let upper = sql_type.to_uppercase();
1177    let trimmed = upper.trim();
1178
1179    if trimmed.starts_with("BIGINT") {
1180        sql_type.to_string()
1181    } else if matches!(trimmed, "INT" | "INTEGER") {
1182        "INT".to_string()
1183    } else if trimmed.starts_with("NVARCHAR") {
1184        sql_type.to_string()
1185    } else if trimmed.starts_with("VARCHAR") {
1186        sql_type.replacen("VARCHAR", "NVARCHAR", 1)
1187    } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
1188        "NVARCHAR(MAX)".to_string()
1189    } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1190        "BIT".to_string()
1191    } else {
1192        sql_type.to_string()
1193    }
1194}
1195
1196/// SQL Server 方言实现(SQL Server 2012+ / T-SQL)
1197#[derive(Debug, Clone)]
1198pub struct SqlServerDialect;
1199
1200impl Dialect for SqlServerDialect {
1201    fn clone_box(&self) -> Box<dyn Dialect> {
1202        Box::new(SqlServerDialect)
1203    }
1204
1205    fn db_type(&self) -> DbType {
1206        DbType::SqlServer
1207    }
1208
1209    fn quote(&self, identifier: &str) -> String {
1210        // SQL Server 使用 [name] 包裹标识符,内部 ] 双写为 ]]
1211        format!("[{}]", identifier.replace(']', "]]"))
1212    }
1213
1214    fn escape_string(&self, s: &str) -> String {
1215        // SQL Server 标准:单引号双写('O''Brien'),反斜杠不转义
1216        let mut escaped = String::with_capacity(s.len() * 2);
1217        for c in s.chars() {
1218            match c {
1219                '\'' => escaped.push_str("''"),
1220                _ => escaped.push(c),
1221            }
1222        }
1223        escaped
1224    }
1225
1226    fn supports_returning(&self) -> bool {
1227        // SQL Server 使用 OUTPUT 子句,语义上等价于 RETURNING
1228        true
1229    }
1230
1231    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1232        // SQL Server 2012+ 使用 OFFSET ... ROWS FETCH NEXT ... ROWS ONLY
1233        let offset = page.saturating_sub(1).saturating_mul(limit);
1234        format!(
1235            "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1236            sql, offset, limit
1237        )
1238    }
1239
1240    fn json_type(&self) -> &'static str {
1241        // SQL Server 2016+ 使用 NVARCHAR(MAX) 存储 JSON
1242        "NVARCHAR(MAX)"
1243    }
1244
1245    fn json_extract(&self, column: &str, path: &str) -> String {
1246        // SQL Server 2016+ 使用 JSON_VALUE 提取标量
1247        let normalized = if path.starts_with('$') {
1248            path.to_string()
1249        } else {
1250            format!("$.{}", path)
1251        };
1252        format!(
1253            "JSON_VALUE({}, '{}')",
1254            column,
1255            self.escape_string(&normalized)
1256        )
1257    }
1258
1259    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1260        // SQL Server 使用 CONTAINS(需要 FULLTEXT 索引)
1261        if columns.is_empty() {
1262            return "0".to_string();
1263        }
1264        let escaped = self.escape_string(keyword);
1265        let cols = columns.join(", ");
1266        format!("CONTAINS({}, '{}')", cols, escaped)
1267    }
1268
1269    fn bool_to_int(&self, expr: &str) -> String {
1270        // SQL Server 使用 BIT 类型,CASE WHEN 转换为 0/1
1271        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1272    }
1273
1274    fn concat(&self, parts: &[&str]) -> String {
1275        if parts.is_empty() {
1276            return "NULL".to_string();
1277        }
1278        format!("CONCAT({})", parts.join(", "))
1279    }
1280
1281    fn supports_if_exists(&self) -> bool {
1282        // SQL Server 2016+ 支持 IF EXISTS
1283        true
1284    }
1285
1286    fn supports_if_not_exists(&self) -> bool {
1287        // SQL Server 2016+ 支持 IF NOT EXISTS
1288        true
1289    }
1290
1291    fn auto_increment_keyword(&self) -> &'static str {
1292        // SQL Server 使用 IDENTITY(1,1) 列属性
1293        "IDENTITY(1,1)"
1294    }
1295
1296    fn last_insert_id_sql(&self) -> Option<&'static str> {
1297        // SCOPE_IDENTITY() 返回当前作用域内最后生成的标识值
1298        Some("SCOPE_IDENTITY()")
1299    }
1300
1301    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1302        let cols: Vec<String> = columns
1303            .iter()
1304            .map(|col| {
1305                let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1306                let mut sql = format!("{} {}", self.quote(&col.name), sqlserver_type);
1307                if !col.nullable {
1308                    sql.push_str(" NOT NULL");
1309                }
1310                if let Some(default) = &col.default {
1311                    sql.push_str(&format!(" DEFAULT {}", default));
1312                }
1313                if col.auto_increment {
1314                    sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1315                }
1316                if col.primary_key {
1317                    sql.push_str(" PRIMARY KEY");
1318                }
1319                sql
1320            })
1321            .collect();
1322
1323        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1324    }
1325
1326    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1327        let stmts: Vec<String> = changes
1328            .iter()
1329            .map(|change| match change {
1330                TableChange::AddColumn(col) => {
1331                    let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1332                    let mut sql = format!(
1333                        "ALTER TABLE {} ADD {} {}",
1334                        self.quote(table),
1335                        self.quote(&col.name),
1336                        sqlserver_type
1337                    );
1338                    if !col.nullable {
1339                        sql.push_str(" NOT NULL");
1340                    }
1341                    if let Some(default) = &col.default {
1342                        sql.push_str(&format!(" DEFAULT {}", default));
1343                    }
1344                    sql
1345                }
1346                TableChange::DropColumn(name) => {
1347                    format!(
1348                        "ALTER TABLE {} DROP COLUMN {}",
1349                        self.quote(table),
1350                        self.quote(name)
1351                    )
1352                }
1353                TableChange::ModifyColumn(col) => {
1354                    // SQL Server 使用 ALTER COLUMN(不是 MODIFY)
1355                    let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1356                    let mut sql = format!(
1357                        "ALTER TABLE {} ALTER COLUMN {} {}",
1358                        self.quote(table),
1359                        self.quote(&col.name),
1360                        sqlserver_type
1361                    );
1362                    if !col.nullable {
1363                        sql.push_str(" NOT NULL");
1364                    }
1365                    if let Some(default) = &col.default {
1366                        sql.push_str(&format!(" DEFAULT {}", default));
1367                    }
1368                    sql
1369                }
1370                TableChange::AddIndex(name, cols) => {
1371                    format!(
1372                        "CREATE INDEX {} ON {} ({})",
1373                        name,
1374                        self.quote(table),
1375                        cols.join(", ")
1376                    )
1377                }
1378                TableChange::DropIndex(name) => {
1379                    // SQL Server 的 DROP INDEX 必须指定表名
1380                    format!("DROP INDEX {} ON {}", name, self.quote(table))
1381                }
1382                TableChange::AddForeignKey {
1383                    columns,
1384                    reference_table,
1385                    reference_columns,
1386                } => {
1387                    format!(
1388                        "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1389                        self.quote(table),
1390                        table,
1391                        columns.join("_"),
1392                        columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1393                        self.quote(reference_table),
1394                        reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1395                    )
1396                }
1397            })
1398            .collect();
1399
1400        stmts.join("; ")
1401    }
1402
1403    fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
1404        // SQL Server 不支持 `INSERT OR IGNORE` 语法,也没有等价的前缀写法:
1405        // - MERGE 需要完整的 USING/WHEN NOT MATCHED 结构,无法以"前缀"形式表达
1406        // - `IF NOT EXISTS (...) INSERT ...` 是语句级控制流,同样无法作为前缀
1407        // 因此回退为普通 INSERT,应用层可通过唯一索引 + 捕获重复键冲突
1408        // (SQLSTATE 2601/2627)或 MERGE 语句实现幂等插入。
1409        format!("INSERT INTO {}", self.quote(table))
1410    }
1411}
1412
1413// 架构说明:build_create_table / build_alter_table / build_drop_table 在四个方言中
1414// 存在部分重复代码。当前选择保持各方言独立实现以确保 SQL 方言差异的显式性,
1415// 避免过度抽象导致维护复杂度上升。如需重构,可抽出公共构建器(参见 dialect 重构 RFC)。
1416
1417// ============================================================================
1418// 兼容方言(基于现有方言委派实现)
1419//
1420// 以下方言在 SQL 语法上与某个基础方言完全兼容,仅在 db_type() 上有区别。
1421// 使用宏减少重复代码,保持维护性。
1422// ============================================================================
1423
1424/// 将方言实现委派给基础方言的宏
1425///
1426/// `$wrapper`:新方言结构体名
1427/// `$base`:基础方言结构体名(如 MySqlDialect)
1428/// `$db_type`:返回的 DbType 变体
1429macro_rules! delegate_dialect_to {
1430    ($wrapper:ident, $base:ident, $db_type:expr) => {
1431        /// 兼容方言(委派给基础方言实现)
1432        #[derive(Debug, Clone)]
1433        pub struct $wrapper;
1434
1435        impl Dialect for $wrapper {
1436            fn clone_box(&self) -> Box<dyn Dialect> {
1437                Box::new($wrapper)
1438            }
1439
1440            fn db_type(&self) -> DbType {
1441                $db_type
1442            }
1443            fn quote(&self, identifier: &str) -> String {
1444                $base.quote(identifier)
1445            }
1446            fn escape_string(&self, s: &str) -> String {
1447                $base.escape_string(s)
1448            }
1449            fn supports_returning(&self) -> bool {
1450                $base.supports_returning()
1451            }
1452            fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1453                $base.build_pagination(sql, page, limit)
1454            }
1455            fn json_type(&self) -> &'static str {
1456                $base.json_type()
1457            }
1458            fn json_extract(&self, column: &str, path: &str) -> String {
1459                $base.json_extract(column, path)
1460            }
1461            fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1462                $base.full_text_search(columns, keyword)
1463            }
1464            fn bool_to_int(&self, expr: &str) -> String {
1465                $base.bool_to_int(expr)
1466            }
1467            fn concat(&self, parts: &[&str]) -> String {
1468                $base.concat(parts)
1469            }
1470            fn supports_if_exists(&self) -> bool {
1471                $base.supports_if_exists()
1472            }
1473            fn supports_if_not_exists(&self) -> bool {
1474                $base.supports_if_not_exists()
1475            }
1476            fn auto_increment_keyword(&self) -> &'static str {
1477                $base.auto_increment_keyword()
1478            }
1479            fn last_insert_id_sql(&self) -> Option<&'static str> {
1480                $base.last_insert_id_sql()
1481            }
1482            fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1483                $base.build_create_table(table, columns)
1484            }
1485            fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1486                $base.build_alter_table(table, changes)
1487            }
1488            fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
1489                $base.build_drop_table(table, if_exists)
1490            }
1491        }
1492    };
1493}
1494
1495// MariaDB:MySQL 兼容方言
1496delegate_dialect_to!(MariaDbDialect, MySqlDialect, DbType::MariaDB);
1497
1498// TiDB:MySQL 兼容分布式数据库
1499delegate_dialect_to!(TiDbDialect, MySqlDialect, DbType::TiDB);
1500
1501// KingbaseES:人大金仓,PostgreSQL 兼容方言
1502delegate_dialect_to!(KingbaseDialect, PostgreSqlDialect, DbType::Kingbase);
1503
1504// PolarDB:阿里云,PostgreSQL 兼容(PG 版本)
1505delegate_dialect_to!(PolarDbDialect, PostgreSqlDialect, DbType::PolarDB);
1506
1507// GaussDB:华为云,PostgreSQL 兼容分布式数据库
1508delegate_dialect_to!(GaussDbDialect, PostgreSqlDialect, DbType::GaussDB);
1509
1510// Dameng:达梦 DM8,Oracle 兼容方言
1511delegate_dialect_to!(DamengDialect, OracleDialect, DbType::Dameng);
1512
1513// Sybase ASE:与 SQL Server T-SQL 高度兼容
1514delegate_dialect_to!(SybaseDialect, SqlServerDialect, DbType::Sybase);
1515
1516// GBase 8s:南大通用,Informix 兼容方言,SQL 语法接近 T-SQL
1517delegate_dialect_to!(GBaseDialect, SqlServerDialect, DbType::GBase);
1518
1519// CockroachDB:PostgreSQL 兼容分布式数据库(v3.5.0 新增)
1520//
1521// CockroachDB 是 Google Spanner 风格的分布式 SQL 数据库,完全兼容 PostgreSQL 协议:
1522// - 使用 PG wire protocol,可直接用 sqlx::Postgres 驱动连接
1523// - 支持 PG 方言的 SQL 语法(SERIAL/RETURNING/LIMIT OFFSET 等)
1524// - 支持分布式事务(SERIALIZABLE 隔离级别)
1525// - 不支持 PG 的某些高级特性(如 LISTEN/NOTIFY、advisory locks)
1526//
1527// 方言实现通过 delegate_dialect_to 宏委派 PostgreSqlDialect,
1528// 复用 PG 的 quote/escape_string/build_pagination 等方法。
1529#[cfg(feature = "dialect-cockroachdb")]
1530delegate_dialect_to!(CockroachDbDialect, PostgreSqlDialect, DbType::CockroachDB);
1531
1532// YugabyteDB:PostgreSQL 兼容分布式数据库(v3.5.0 新增)
1533//
1534// YugabyteDB 是 Yugabyte 开源的分布式 SQL 数据库,兼容 PostgreSQL 协议:
1535// - 使用 PG wire protocol(YSQL API),可直接用 sqlx::Postgres 驱动连接
1536// - 支持 PG 方言的 SQL 语法(SERIAL/RETURNING/LIMIT OFFSET 等)
1537// - 支持分布式事务(ACID + 全局一致性)
1538// - 支持 PG 存储过程(PL/pgSQL)
1539//
1540// 方言实现通过 delegate_dialect_to 宏委派 PostgreSqlDialect,
1541// 复用 PG 的 quote/escape_string/build_pagination 等方法。
1542#[cfg(feature = "dialect-yugabytedb")]
1543delegate_dialect_to!(YugabyteDbDialect, PostgreSqlDialect, DbType::YugabyteDB);
1544
1545// ============================================================================
1546// Snowflake 方言(v3.6.0 新增,独立实现)
1547//
1548// Snowflake 是云原生数据仓库,有独特的特性:
1549// - 使用双引号标识符(与 PostgreSQL 一致)
1550// - 字符串字面量单引号转义为 ''(与 PG 一致)
1551// - 支持 RETURNING 子句
1552// - 分页使用 LIMIT offset, count 语法
1553// - 支持 VARIANT/OBJECT/ARRAY 半结构化类型
1554// - 支持 COPY INTO 数据加载
1555// - 支持 TIME TRAVEL 查询(AT(OBJECT => ...)/BEFORE(...))
1556// - 自增列不支持(使用 IDENTITY 或 SEQUENCE)
1557// ============================================================================
1558
1559/// Snowflake 方言实现(云数仓,独立实现)
1560///
1561/// 支持 Snowflake 特有特性:
1562/// - VARIANT/OBJECT/ARRAY 半结构化类型
1563/// - COPY INTO 数据加载
1564/// - TIME TRAVEL 时间旅行查询
1565#[cfg(feature = "dialect-snowflake")]
1566#[derive(Debug, Clone)]
1567pub struct SnowflakeDialect;
1568
1569#[cfg(feature = "dialect-snowflake")]
1570impl Dialect for SnowflakeDialect {
1571    fn clone_box(&self) -> Box<dyn Dialect> {
1572        Box::new(SnowflakeDialect)
1573    }
1574
1575    fn db_type(&self) -> DbType {
1576        DbType::Snowflake
1577    }
1578
1579    fn quote(&self, identifier: &str) -> String {
1580        // Snowflake 使用双引号包裹标识符(与 PG 一致)
1581        format!("\"{}\"", identifier.replace('"', "\"\""))
1582    }
1583
1584    fn escape_string(&self, s: &str) -> String {
1585        // Snowflake 使用双单引号转义(与 PG 一致)
1586        let mut escaped = String::with_capacity(s.len() * 2);
1587        for c in s.chars() {
1588            match c {
1589                '\'' => escaped.push_str("''"),
1590                '\\' => escaped.push_str("\\\\"),
1591                _ => escaped.push(c),
1592            }
1593        }
1594        escaped
1595    }
1596
1597    fn supports_returning(&self) -> bool {
1598        // Snowflake 支持 RETURNING 子句
1599        true
1600    }
1601
1602    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1603        // Snowflake 使用 LIMIT offset, count 语法
1604        let offset = page.saturating_sub(1).saturating_mul(limit);
1605        format!("{} LIMIT {}, {}", sql, offset, limit)
1606    }
1607
1608    fn json_type(&self) -> &'static str {
1609        // Snowflake 原生支持 VARIANT 类型
1610        "VARIANT"
1611    }
1612
1613    fn json_extract(&self, column: &str, path: &str) -> String {
1614        // Snowflake 使用 : 路径访问或 GET_PATH 函数
1615        let normalized = path.trim_start_matches("$.");
1616        format!("{}:{}", column, normalized)
1617    }
1618
1619    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1620        // Snowflake 使用 ILIKE 进行全文检索
1621        if columns.is_empty() {
1622            return "FALSE".to_string();
1623        }
1624        let escaped = self.escape_string(keyword);
1625        let parts: Vec<String> = columns
1626            .iter()
1627            .map(|c| format!("{} ILIKE '%{}%'", c, escaped))
1628            .collect();
1629        parts.join(" OR ")
1630    }
1631
1632    fn bool_to_int(&self, expr: &str) -> String {
1633        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1634    }
1635
1636    fn concat(&self, parts: &[&str]) -> String {
1637        if parts.is_empty() {
1638            return "NULL".to_string();
1639        }
1640        // Snowflake 使用 || 拼接或 CONCAT 函数
1641        format!("CONCAT({})", parts.join(", "))
1642    }
1643
1644    fn supports_if_exists(&self) -> bool {
1645        true
1646    }
1647
1648    fn supports_if_not_exists(&self) -> bool {
1649        true
1650    }
1651
1652    fn auto_increment_keyword(&self) -> &'static str {
1653        // Snowflake 使用 IDENTITY 或 AUTOINCREMENT
1654        "AUTOINCREMENT"
1655    }
1656
1657    fn last_insert_id_sql(&self) -> Option<&'static str> {
1658        // Snowflake 不支持 lastval(),需用 RETURNING
1659        None
1660    }
1661
1662    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1663        let cols: Vec<String> = columns
1664            .iter()
1665            .map(|col| {
1666                let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
1667                if !col.nullable {
1668                    sql.push_str(" NOT NULL");
1669                }
1670                if let Some(default) = &col.default {
1671                    sql.push_str(&format!(" DEFAULT {}", default));
1672                }
1673                if col.primary_key {
1674                    sql.push_str(" PRIMARY KEY");
1675                }
1676                sql
1677            })
1678            .collect();
1679        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1680    }
1681
1682    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1683        let stmts: Vec<String> = changes
1684            .iter()
1685            .map(|change| match change {
1686                TableChange::AddColumn(col) => {
1687                    let mut sql = format!(
1688                        "ALTER TABLE {} ADD COLUMN {} {}",
1689                        self.quote(table),
1690                        self.quote(&col.name),
1691                        col.sql_type
1692                    );
1693                    if !col.nullable {
1694                        sql.push_str(" NOT NULL");
1695                    }
1696                    if let Some(default) = &col.default {
1697                        sql.push_str(&format!(" DEFAULT {}", default));
1698                    }
1699                    sql
1700                }
1701                TableChange::DropColumn(name) => {
1702                    format!(
1703                        "ALTER TABLE {} DROP COLUMN {}",
1704                        self.quote(table),
1705                        self.quote(name)
1706                    )
1707                }
1708                TableChange::ModifyColumn(col) => {
1709                    format!(
1710                        "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
1711                        self.quote(table),
1712                        self.quote(&col.name),
1713                        col.sql_type
1714                    )
1715                }
1716                TableChange::AddIndex(name, cols) => {
1717                    format!(
1718                        "CREATE INDEX {} ON {} ({})",
1719                        name,
1720                        self.quote(table),
1721                        cols.join(", ")
1722                    )
1723                }
1724                TableChange::DropIndex(name) => {
1725                    format!("DROP INDEX {}", name)
1726                }
1727                TableChange::AddForeignKey {
1728                    columns,
1729                    reference_table,
1730                    reference_columns,
1731                } => {
1732                    format!(
1733                        "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1734                        self.quote(table),
1735                        table,
1736                        columns.join("_"),
1737                        columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1738                        self.quote(reference_table),
1739                        reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1740                    )
1741                }
1742            })
1743            .collect();
1744        stmts.join("; ")
1745    }
1746}
1747
1748/// Snowflake 特有 SQL 构造方法
1749#[cfg(feature = "dialect-snowflake")]
1750impl SnowflakeDialect {
1751    /// 生成 COPY INTO 数据加载语句
1752    ///
1753    /// `COPY INTO target FROM source`
1754    pub fn build_copy_into(&self, target: &str, source: &str) -> String {
1755        format!("COPY INTO {} FROM {}", self.quote(target), source)
1756    }
1757
1758    /// 生成 TIME TRAVEL AT 查询语句
1759    ///
1760    /// `SELECT * FROM table AT(OBJECT => timestamp)`
1761    pub fn build_time_travel_at(&self, table: &str, timestamp: &str) -> String {
1762        format!(
1763            "SELECT * FROM {} AT(OBJECT => '{}')",
1764            self.quote(table),
1765            self.escape_string(timestamp)
1766        )
1767    }
1768
1769    /// 生成 TIME TRAVEL BEFORE 查询语句
1770    ///
1771    /// `SELECT * FROM table BEFORE(timestamp => timestamp)`
1772    pub fn build_time_travel_before(&self, table: &str, timestamp: &str) -> String {
1773        format!(
1774            "SELECT * FROM {} BEFORE(timestamp => '{}')",
1775            self.quote(table),
1776            self.escape_string(timestamp)
1777        )
1778    }
1779}
1780
1781// ============================================================================
1782// Redshift 方言(v3.6.0 新增,委派 PostgreSqlDialect + 特性扩展)
1783//
1784// Redshift 是 AWS 云数据仓库,基于 PostgreSQL 8.0.2 扩展:
1785// - 大部分 SQL 语法与 PG 兼容(quote/escape_string/pagination 等)
1786// - 支持 COPY 数据加载、UNLOAD 数据卸载
1787// - 不支持 PG 的某些特性(如 LISTEN/NOTIFY、advisory locks)
1788// - 使用 delegate_dialect_to 宏委派 PG,再扩展特有方法
1789// ============================================================================
1790
1791/// Redshift 方言实现(AWS 云数仓,委派 PG + COPY/UNLOAD 特性扩展)
1792// Redshift 委派 PostgreSqlDialect,再扩展 COPY/UNLOAD 特有方法
1793#[cfg(feature = "dialect-redshift")]
1794delegate_dialect_to!(RedshiftDialect, PostgreSqlDialect, DbType::Redshift);
1795
1796/// Redshift 特有 SQL 构造方法
1797#[cfg(feature = "dialect-redshift")]
1798impl RedshiftDialect {
1799    /// 生成 COPY 数据加载语句
1800    ///
1801    /// `COPY target FROM source CREDENTIALS ... FORMAT AS CSV`
1802    pub fn build_copy(&self, target: &str, source: &str, credentials: &str) -> String {
1803        format!(
1804            "COPY {} FROM '{}' CREDENTIALS '{}' FORMAT AS CSV",
1805            PostgreSqlDialect.quote(target),
1806            source,
1807            credentials
1808        )
1809    }
1810
1811    /// 生成 UNLOAD 数据卸载语句
1812    ///
1813    /// `UNLOAD ('query') TO 's3://path' CREDENTIALS ...`
1814    pub fn build_unload(&self, query: &str, s3_path: &str, credentials: &str) -> String {
1815        format!(
1816            "UNLOAD ('{}') TO '{}' CREDENTIALS '{}'",
1817            query, s3_path, credentials
1818        )
1819    }
1820}
1821
1822// ============================================================================
1823// ClickHouse 方言(独立实现)
1824//
1825// ClickHouse 是列式 OLAP 数据库,有独特的类型系统和函数:
1826// - 使用 backtick 标识符(与 MySQL 一致)
1827// - 字符串字面量单引号转义为 \'(与 MySQL 一致)
1828// - 不支持事务、不支持 RETURNING
1829// - 分页使用 LIMIT offset, limit 语法(与 MySQL 一致)
1830// - 自增列不支持(使用 UUID 或物化列)
1831// - 类型系统:String/UInt64/Int64/Float64/DateTime 等
1832// ============================================================================
1833
1834/// ClickHouse 方言实现(列式 OLAP 数据库)
1835#[derive(Debug, Clone)]
1836pub struct ClickHouseDialect;
1837
1838impl Dialect for ClickHouseDialect {
1839    fn clone_box(&self) -> Box<dyn Dialect> {
1840        Box::new(ClickHouseDialect)
1841    }
1842
1843    fn db_type(&self) -> DbType {
1844        DbType::ClickHouse
1845    }
1846
1847    fn quote(&self, identifier: &str) -> String {
1848        // ClickHouse 使用 backquote 包裹标识符(与 MySQL 一致)
1849        format!("`{}`", identifier.replace('`', "``"))
1850    }
1851
1852    fn escape_string(&self, s: &str) -> String {
1853        // ClickHouse 使用反斜杠转义(与 MySQL 一致)
1854        let mut escaped = String::with_capacity(s.len() * 2);
1855        for c in s.chars() {
1856            match c {
1857                '\'' => escaped.push_str("\\'"),
1858                '\\' => escaped.push_str("\\\\"),
1859                '\n' => escaped.push_str("\\n"),
1860                '\r' => escaped.push_str("\\r"),
1861                '\t' => escaped.push_str("\\t"),
1862                _ => escaped.push(c),
1863            }
1864        }
1865        escaped
1866    }
1867
1868    fn supports_returning(&self) -> bool {
1869        // ClickHouse 不支持 RETURNING 子句
1870        false
1871    }
1872
1873    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1874        // ClickHouse 使用 LIMIT offset, limit 语法
1875        let offset = page.saturating_sub(1).saturating_mul(limit);
1876        format!("{} LIMIT {}, {}", sql, offset, limit)
1877    }
1878
1879    fn json_type(&self) -> &'static str {
1880        // ClickHouse 使用 String 存储 JSON,或使用 JSON 类型(实验性)
1881        "String"
1882    }
1883
1884    fn json_extract(&self, column: &str, path: &str) -> String {
1885        // ClickHouse 使用 JSONExtractString 函数
1886        let normalized = if path.starts_with('$') {
1887            path.to_string()
1888        } else {
1889            format!("$.{}", path)
1890        };
1891        format!(
1892            "JSONExtractString({}, '{}')",
1893            column,
1894            self.escape_string(&normalized)
1895        )
1896    }
1897
1898    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1899        // ClickHouse 使用 position() + like 进行全文检索(无原生 FTS)
1900        if columns.is_empty() {
1901            return "0".to_string();
1902        }
1903        let escaped = self.escape_string(keyword);
1904        let parts: Vec<String> = columns
1905            .iter()
1906            .map(|c| format!("position({}, '{}') > 0", c, escaped))
1907            .collect();
1908        parts.join(" OR ")
1909    }
1910
1911    fn bool_to_int(&self, expr: &str) -> String {
1912        // ClickHouse 支持 toUInt8 转换
1913        format!("toUInt8({})", expr)
1914    }
1915
1916    fn concat(&self, parts: &[&str]) -> String {
1917        // ClickHouse 使用 concat() 函数
1918        if parts.is_empty() {
1919            return "''".to_string();
1920        }
1921        format!("concat({})", parts.join(", "))
1922    }
1923
1924    fn supports_if_exists(&self) -> bool {
1925        true
1926    }
1927
1928    fn supports_if_not_exists(&self) -> bool {
1929        true
1930    }
1931
1932    fn auto_increment_keyword(&self) -> &'static str {
1933        // ClickHouse 不支持自增列,使用 UUID 默认值
1934        ""
1935    }
1936
1937    fn last_insert_id_sql(&self) -> Option<&'static str> {
1938        // ClickHouse 不支持 last_insert_id
1939        None
1940    }
1941
1942    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1943        let cols: Vec<String> = columns
1944            .iter()
1945            .map(|col| {
1946                let ch_type = map_to_clickhouse_type(&col.sql_type);
1947                let mut sql = format!("{} {}", self.quote(&col.name), ch_type);
1948                if let Some(default) = &col.default {
1949                    sql.push_str(&format!(" DEFAULT {}", default));
1950                }
1951                if col.primary_key {
1952                    sql.push_str(" PRIMARY KEY");
1953                }
1954                sql
1955            })
1956            .collect();
1957
1958        // ClickHouse 必须指定 Engine,默认使用 MergeTree
1959        format!(
1960            "CREATE TABLE {} ({}) ENGINE = MergeTree()",
1961            self.quote(table),
1962            cols.join(", ")
1963        )
1964    }
1965
1966    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1967        let stmts: Vec<String> = changes
1968            .iter()
1969            .map(|change| match change {
1970                TableChange::AddColumn(col) => {
1971                    let ch_type = map_to_clickhouse_type(&col.sql_type);
1972                    format!(
1973                        "ALTER TABLE {} ADD COLUMN {} {}",
1974                        self.quote(table),
1975                        self.quote(&col.name),
1976                        ch_type
1977                    )
1978                }
1979                TableChange::DropColumn(name) => {
1980                    format!(
1981                        "ALTER TABLE {} DROP COLUMN {}",
1982                        self.quote(table),
1983                        self.quote(name)
1984                    )
1985                }
1986                TableChange::ModifyColumn(col) => {
1987                    let ch_type = map_to_clickhouse_type(&col.sql_type);
1988                    format!(
1989                        "ALTER TABLE {} MODIFY COLUMN {} {}",
1990                        self.quote(table),
1991                        self.quote(&col.name),
1992                        ch_type
1993                    )
1994                }
1995                TableChange::AddIndex(name, cols) => {
1996                    format!(
1997                        "ALTER TABLE {} ADD INDEX {} ({})",
1998                        self.quote(table),
1999                        name,
2000                        cols.join(", ")
2001                    )
2002                }
2003                TableChange::DropIndex(name) => {
2004                    format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
2005                }
2006                TableChange::AddForeignKey { .. } => {
2007                    // ClickHouse 不支持外键,跳过
2008                    String::new()
2009                }
2010            })
2011            .filter(|s| !s.is_empty())
2012            .collect();
2013
2014        stmts.join("; ")
2015    }
2016
2017    fn supports_lock_for_update(&self) -> bool {
2018        // ClickHouse 是列式 OLAP 数据库:无事务、无行级锁
2019        false
2020    }
2021
2022    fn supports_lock_shared(&self) -> bool {
2023        // ClickHouse 是列式 OLAP 数据库:无事务、无共享锁
2024        false
2025    }
2026
2027    fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
2028        // ClickHouse 不支持 INSERT OR IGNORE 语法,回退为普通 INSERT
2029        format!("INSERT INTO {}", self.quote(table))
2030    }
2031}
2032
2033/// 将通用 SQL 类型映射为 ClickHouse 类型
2034///
2035/// - BIGINT → Int64
2036/// - INT/INTEGER → Int32
2037/// - VARCHAR(n)/TEXT → String
2038/// - BOOLEAN/BOOL → UInt8
2039/// - FLOAT → Float32
2040/// - DOUBLE → Float64
2041/// - DATETIME/TIMESTAMP → DateTime
2042fn map_to_clickhouse_type(sql_type: &str) -> String {
2043    let upper = sql_type.to_uppercase();
2044    let trimmed = upper.trim();
2045
2046    if trimmed.starts_with("BIGINT") {
2047        "Int64".to_string()
2048    } else if matches!(trimmed, "INT" | "INTEGER") {
2049        "Int32".to_string()
2050    } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
2051        "Int16".to_string()
2052    } else if trimmed.starts_with("VARCHAR")
2053        || trimmed.starts_with("CHAR")
2054        || matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT")
2055    {
2056        "String".to_string()
2057    } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2058        "UInt8".to_string()
2059    } else if matches!(trimmed, "FLOAT" | "REAL") {
2060        "Float32".to_string()
2061    } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2062        "Float64".to_string()
2063    } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2064        "DateTime".to_string()
2065    } else if matches!(trimmed, "DATE") {
2066        "Date".to_string()
2067    } else if trimmed.starts_with("DECIMAL") || trimmed.starts_with("NUMERIC") {
2068        "Decimal(38, 4)".to_string()
2069    } else {
2070        sql_type.to_string()
2071    }
2072}
2073
2074// ============================================================================
2075// DuckDB 方言(嵌入式 OLAP 数据库)
2076//
2077// DuckDB 语法特性:
2078// - 双引号标识符(标准 SQL 风格)
2079// - 字符串字面量单引号转义为 ''(标准 SQL 风格)
2080// - 不支持 RETURNING 子句
2081// - 分页使用 LIMIT x OFFSET y(与 PostgreSQL 一致)
2082// - 自增列使用 SERIAL 或 BIGINT PRIMARY KEY
2083// - 类型系统:BIGINT/INTEGER/VARCHAR/BOOLEAN/DOUBLE/TIMESTAMP/DATE 等
2084// - 支持 INSERT OR IGNORE(与 SQLite 一致)
2085// ============================================================================
2086
2087/// DuckDB 方言实现(嵌入式 OLAP 数据库)
2088#[derive(Debug, Clone)]
2089pub struct DuckDBDialect;
2090
2091impl Dialect for DuckDBDialect {
2092    fn clone_box(&self) -> Box<dyn Dialect> {
2093        Box::new(DuckDBDialect)
2094    }
2095
2096    fn db_type(&self) -> DbType {
2097        DbType::DuckDB
2098    }
2099
2100    fn quote(&self, identifier: &str) -> String {
2101        // DuckDB 使用双引号包裹标识符(标准 SQL 风格)
2102        format!("\"{}\"", identifier.replace('"', "\"\""))
2103    }
2104
2105    fn escape_string(&self, s: &str) -> String {
2106        // DuckDB 标准转义:单引号双写('O''Brien')
2107        let mut escaped = String::with_capacity(s.len() * 2);
2108        for c in s.chars() {
2109            match c {
2110                '\'' => escaped.push_str("''"),
2111                _ => escaped.push(c),
2112            }
2113        }
2114        escaped
2115    }
2116
2117    fn supports_returning(&self) -> bool {
2118        // DuckDB 不支持 RETURNING 子句
2119        false
2120    }
2121
2122    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2123        // DuckDB 使用 LIMIT x OFFSET y(与 PostgreSQL 一致)
2124        let offset = page.saturating_sub(1).saturating_mul(limit);
2125        format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
2126    }
2127
2128    fn json_type(&self) -> &'static str {
2129        // DuckDB 使用 JSON 类型
2130        "JSON"
2131    }
2132
2133    fn json_extract(&self, column: &str, path: &str) -> String {
2134        // DuckDB 使用 -> 或 ->> 操作符(与 PostgreSQL 一致)
2135        let normalized = if path.starts_with('$') {
2136            path[2..].to_string()
2137        } else {
2138            path.to_string()
2139        };
2140        format!("{} -> '{}'", column, normalized)
2141    }
2142
2143    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2144        // DuckDB 无原生 FTS,降级使用 LIKE
2145        if columns.is_empty() {
2146            return "0".to_string();
2147        }
2148        let escaped = self.escape_string(keyword);
2149        let parts: Vec<String> = columns
2150            .iter()
2151            .map(|c| format!("{} LIKE '%{}%'", c, escaped))
2152            .collect();
2153        parts.join(" OR ")
2154    }
2155
2156    fn bool_to_int(&self, expr: &str) -> String {
2157        // DuckDB 支持 BOOLEAN 类型,但可使用 CASE WHEN 转换
2158        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2159    }
2160
2161    fn concat(&self, parts: &[&str]) -> String {
2162        // DuckDB 使用 || 操作符进行字符串拼接
2163        if parts.is_empty() {
2164            return "''".to_string();
2165        }
2166        parts.join(" || ")
2167    }
2168
2169    fn supports_if_exists(&self) -> bool {
2170        true
2171    }
2172
2173    fn supports_if_not_exists(&self) -> bool {
2174        true
2175    }
2176
2177    fn auto_increment_keyword(&self) -> &'static str {
2178        // DuckDB 不支持自增列关键字,使用 SERIAL 或 BIGINT PRIMARY KEY
2179        ""
2180    }
2181
2182    fn last_insert_id_sql(&self) -> Option<&'static str> {
2183        // DuckDB 不支持 last_insert_id
2184        None
2185    }
2186
2187    fn supports_lock_for_update(&self) -> bool {
2188        // DuckDB 不支持行锁(嵌入式数据库)
2189        false
2190    }
2191
2192    fn supports_lock_shared(&self) -> bool {
2193        // DuckDB 不支持共享锁
2194        false
2195    }
2196
2197    fn build_insert_or_ignore_prefix(&self, table: &str) -> String {
2198        // DuckDB 使用 INSERT OR IGNORE INTO(与 SQLite 一致)
2199        format!("INSERT OR IGNORE INTO {}", self.quote(table))
2200    }
2201
2202    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2203        let cols: Vec<String> = columns
2204            .iter()
2205            .map(|col| {
2206                let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
2207                if col.auto_increment {
2208                    // DuckDB 不支持 AUTO_INCREMENT,使用 BIGINT PRIMARY KEY
2209                    sql = format!("{} BIGINT PRIMARY KEY", self.quote(&col.name));
2210                }
2211                if let Some(default) = &col.default {
2212                    sql.push_str(&format!(" DEFAULT {}", default));
2213                }
2214                if col.primary_key && !col.auto_increment {
2215                    sql.push_str(" PRIMARY KEY");
2216                }
2217                sql
2218            })
2219            .collect();
2220
2221        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2222    }
2223
2224    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2225        let stmts: Vec<String> = changes
2226            .iter()
2227            .map(|change| match change {
2228                TableChange::AddColumn(col) => {
2229                    let mut sql = format!(
2230                        "ALTER TABLE {} ADD COLUMN {} {}",
2231                        self.quote(table),
2232                        self.quote(&col.name),
2233                        col.sql_type
2234                    );
2235                    if let Some(default) = &col.default {
2236                        sql.push_str(&format!(" DEFAULT {}", default));
2237                    }
2238                    sql
2239                }
2240                TableChange::DropColumn(name) => {
2241                    format!(
2242                        "ALTER TABLE {} DROP COLUMN {}",
2243                        self.quote(table),
2244                        self.quote(name)
2245                    )
2246                }
2247                TableChange::ModifyColumn(col) => {
2248                    format!(
2249                        "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
2250                        self.quote(table),
2251                        self.quote(&col.name),
2252                        col.sql_type
2253                    )
2254                }
2255                TableChange::AddIndex(name, _cols) => {
2256                    // DuckDB 支持 CREATE INDEX,但不支持 ALTER TABLE ADD INDEX
2257                    format!(
2258                        "CREATE INDEX {} ON {} (id)",
2259                        self.quote(name),
2260                        self.quote(table)
2261                    )
2262                }
2263                TableChange::DropIndex(name) => {
2264                    format!("DROP INDEX {}", self.quote(name))
2265                }
2266                TableChange::AddForeignKey { .. } => {
2267                    // DuckDB 不支持外键约束
2268                    String::new()
2269                }
2270            })
2271            .filter(|s| !s.is_empty())
2272            .collect();
2273
2274        stmts.join("; ")
2275    }
2276}
2277
2278// ============================================================================
2279// IBM DB2 方言(独立实现)
2280//
2281// DB2 LUW 语法特性:
2282// - 双引号标识符(标准 SQL 风格)
2283// - 字符串字面量单引号转义为 ''(标准 SQL 风格)
2284// - 支持 RETURNING(DB2 11.5+)
2285// - 分页使用 OFFSET x ROWS FETCH NEXT y ROWS ONLY(标准 SQL:2008)
2286// - 自增列使用 GENERATED ALWAYS AS IDENTITY
2287// - 类型系统:VARCHAR/INTEGER/BIGINT/TIMESTAMP/DECFLOAT 等
2288// ============================================================================
2289
2290/// IBM DB2 LUW 方言实现
2291#[derive(Debug, Clone)]
2292pub struct Db2Dialect;
2293
2294impl Dialect for Db2Dialect {
2295    fn clone_box(&self) -> Box<dyn Dialect> {
2296        Box::new(Db2Dialect)
2297    }
2298
2299    fn db_type(&self) -> DbType {
2300        DbType::Db2
2301    }
2302
2303    fn quote(&self, identifier: &str) -> String {
2304        // DB2 使用双引号包裹标识符(标准 SQL 风格)
2305        format!("\"{}\"", identifier.replace('"', "\"\""))
2306    }
2307
2308    fn escape_string(&self, s: &str) -> String {
2309        // DB2 标准转义:单引号双写('O''Brien')
2310        let mut escaped = String::with_capacity(s.len() * 2);
2311        for c in s.chars() {
2312            match c {
2313                '\'' => escaped.push_str("''"),
2314                _ => escaped.push(c),
2315            }
2316        }
2317        escaped
2318    }
2319
2320    fn supports_returning(&self) -> bool {
2321        // DB2 11.5+ 支持 RETURNING(实际使用 SELECT FROM FINAL TABLE 代替)
2322        false
2323    }
2324
2325    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2326        // DB2 使用 OFFSET ... ROWS FETCH NEXT ... ROWS ONLY(SQL:2008 标准)
2327        let offset = page.saturating_sub(1).saturating_mul(limit);
2328        format!(
2329            "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
2330            sql, offset, limit
2331        )
2332    }
2333
2334    fn json_type(&self) -> &'static str {
2335        // DB2 11.5+ 原生支持 JSON 类型
2336        "JSON"
2337    }
2338
2339    fn json_extract(&self, column: &str, path: &str) -> String {
2340        // DB2 使用 JSON_VALUE 函数
2341        let normalized = if path.starts_with('$') {
2342            path.to_string()
2343        } else {
2344            format!("$.{}", path)
2345        };
2346        format!(
2347            "JSON_VALUE({}, '{}')",
2348            column,
2349            self.escape_string(&normalized)
2350        )
2351    }
2352
2353    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2354        // DB2 使用 CONTAINS 函数(需 DB2TEXT 索引)
2355        if columns.is_empty() {
2356            return "0".to_string();
2357        }
2358        let escaped = self.escape_string(keyword);
2359        let parts: Vec<String> = columns
2360            .iter()
2361            .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2362            .collect();
2363        parts.join(" OR ")
2364    }
2365
2366    fn bool_to_int(&self, expr: &str) -> String {
2367        // DB2 没有原生 BOOL(11.5+ 有 BOOLEAN),使用 CASE WHEN 转换
2368        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2369    }
2370
2371    fn concat(&self, parts: &[&str]) -> String {
2372        // DB2 使用 || 操作符进行字符串拼接
2373        if parts.is_empty() {
2374            return "''".to_string();
2375        }
2376        parts.join(" || ")
2377    }
2378
2379    fn supports_if_exists(&self) -> bool {
2380        // DB2 不支持 DROP TABLE IF EXISTS(直到 11.5)
2381        false
2382    }
2383
2384    fn supports_if_not_exists(&self) -> bool {
2385        // DB2 不支持 CREATE TABLE IF NOT EXISTS
2386        false
2387    }
2388
2389    fn auto_increment_keyword(&self) -> &'static str {
2390        // DB2 使用 GENERATED ALWAYS AS IDENTITY
2391        "GENERATED ALWAYS AS IDENTITY"
2392    }
2393
2394    fn last_insert_id_sql(&self) -> Option<&'static str> {
2395        // DB2 使用 IDENTITY_VAL_LOCAL() 函数获取最后插入的 IDENTITY 值
2396        Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
2397    }
2398
2399    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2400        let cols: Vec<String> = columns
2401            .iter()
2402            .map(|col| {
2403                let db2_type = map_to_db2_type(&col.sql_type);
2404                let mut sql = format!("{} {}", self.quote(&col.name), db2_type);
2405                if !col.nullable && !col.auto_increment {
2406                    sql.push_str(" NOT NULL");
2407                }
2408                if let Some(default) = &col.default {
2409                    sql.push_str(&format!(" DEFAULT {}", default));
2410                }
2411                if col.auto_increment {
2412                    sql.push_str(&format!(" {}", self.auto_increment_keyword()));
2413                }
2414                if col.primary_key {
2415                    sql.push_str(" PRIMARY KEY");
2416                }
2417                sql
2418            })
2419            .collect();
2420
2421        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2422    }
2423
2424    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2425        let stmts: Vec<String> = changes
2426            .iter()
2427            .map(|change| match change {
2428                TableChange::AddColumn(col) => {
2429                    let db2_type = map_to_db2_type(&col.sql_type);
2430                    let mut sql = format!(
2431                        "ALTER TABLE {} ADD COLUMN {} {}",
2432                        self.quote(table),
2433                        self.quote(&col.name),
2434                        db2_type
2435                    );
2436                    if !col.nullable {
2437                        sql.push_str(" NOT NULL");
2438                    }
2439                    if let Some(default) = &col.default {
2440                        sql.push_str(&format!(" DEFAULT {}", default));
2441                    }
2442                    sql
2443                }
2444                TableChange::DropColumn(name) => {
2445                    format!(
2446                        "ALTER TABLE {} DROP COLUMN {}",
2447                        self.quote(table),
2448                        self.quote(name)
2449                    )
2450                }
2451                TableChange::ModifyColumn(col) => {
2452                    let db2_type = map_to_db2_type(&col.sql_type);
2453                    format!(
2454                        "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
2455                        self.quote(table),
2456                        self.quote(&col.name),
2457                        db2_type
2458                    )
2459                }
2460                TableChange::AddIndex(name, cols) => {
2461                    format!(
2462                        "CREATE INDEX {} ON {} ({})",
2463                        name,
2464                        self.quote(table),
2465                        cols.join(", ")
2466                    )
2467                }
2468                TableChange::DropIndex(name) => {
2469                    format!("DROP INDEX {}", name)
2470                }
2471                TableChange::AddForeignKey {
2472                    columns,
2473                    reference_table,
2474                    reference_columns,
2475                } => {
2476                    format!(
2477                        "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
2478                        self.quote(table),
2479                        table,
2480                        columns.join("_"),
2481                        columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
2482                        self.quote(reference_table),
2483                        reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
2484                    )
2485                }
2486            })
2487            .collect();
2488
2489        stmts.join("; ")
2490    }
2491
2492    fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
2493        // DB2 不支持 IF EXISTS,但为兼容性保留参数
2494        let _ = if_exists;
2495        format!("DROP TABLE {}", self.quote(table))
2496    }
2497}
2498
2499/// 将通用 SQL 类型映射为 IBM DB2 类型
2500///
2501/// - BIGINT → BIGINT
2502/// - INT/INTEGER → INTEGER
2503/// - VARCHAR(n) → VARCHAR(n)
2504/// - TEXT 系列 → CLOB(2G)
2505/// - BOOLEAN/BOOL → SMALLINT(DB2 11.5+ 才有 BOOLEAN)
2506/// - DATETIME/TIMESTAMP → TIMESTAMP
2507fn map_to_db2_type(sql_type: &str) -> String {
2508    let upper = sql_type.to_uppercase();
2509    let trimmed = upper.trim();
2510
2511    if trimmed.starts_with("BIGINT") {
2512        "BIGINT".to_string()
2513    } else if matches!(trimmed, "INT" | "INTEGER") {
2514        "INTEGER".to_string()
2515    } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
2516        "SMALLINT".to_string()
2517    } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
2518        sql_type.to_string()
2519    } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
2520        "CLOB(2G)".to_string()
2521    } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2522        "SMALLINT".to_string()
2523    } else if matches!(trimmed, "FLOAT" | "REAL") {
2524        "REAL".to_string()
2525    } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2526        "DOUBLE".to_string()
2527    } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2528        "TIMESTAMP".to_string()
2529    } else if matches!(trimmed, "DATE") {
2530        "DATE".to_string()
2531    } else {
2532        // DECIMAL/NUMERIC 和其他未匹配类型保持原样
2533        sql_type.to_string()
2534    }
2535}
2536
2537/// 根据数据库类型获取对应的方言实例
2538///
2539/// L-5 修复:补充示例文档
2540///
2541/// 返回 `Box<dyn Dialect>`,可用于与 `QueryBuilder`、`Schema` 等组件配合。
2542/// 对于不支持 SQL 方言的数据库类型(如 Redis、MongoDB、向量库),返回
2543/// `DbError::Unsupported`。
2544///
2545/// # 示例
2546///
2547/// ```ignore
2548/// use sz_orm_core::db_type::DbType;
2549/// use sz_orm_core::dialect::{get_dialect, Dialect};
2550///
2551/// let dialect = get_dialect(DbType::MySQL).unwrap();
2552/// assert_eq!(dialect.quote("user"), "`user`");
2553/// assert_eq!(dialect.db_type(), DbType::MySQL);
2554///
2555/// // 不支持的类型返回错误
2556/// let err = get_dialect(DbType::Redis).unwrap_err();
2557/// assert!(matches!(err, sz_orm_core::DbError::Unsupported(_)));
2558/// ```
2559pub fn get_dialect(db_type: DbType) -> Result<Box<dyn Dialect>, DbError> {
2560    match db_type {
2561        DbType::MySQL => Ok(Box::new(MySqlDialect)),
2562        DbType::PostgreSQL => Ok(Box::new(PostgreSqlDialect)),
2563        DbType::Sqlite => Ok(Box::new(SqliteDialect)),
2564        DbType::Redis => Err(DbError::Unsupported(
2565            "Redis does not support standard SQL dialect".to_string(),
2566        )),
2567        DbType::MongoDB => Err(DbError::Unsupported(
2568            "MongoDB uses different query syntax".to_string(),
2569        )),
2570        DbType::ClickHouse => Ok(Box::new(ClickHouseDialect)),
2571        DbType::Oracle => Ok(Box::new(OracleDialect)),
2572        DbType::OceanBase => Ok(Box::new(MySqlDialect)),
2573        DbType::SqlServer => Ok(Box::new(SqlServerDialect)),
2574        DbType::VectorDb => Err(DbError::Unsupported(
2575            "Vector databases have specific APIs".to_string(),
2576        )),
2577        DbType::PureJsDb => Err(DbError::Unsupported(
2578            "PureJS database uses JavaScript".to_string(),
2579        )),
2580        // 国产数据库 & 兼容方言
2581        DbType::Dameng => Ok(Box::new(DamengDialect)),
2582        DbType::Kingbase => Ok(Box::new(KingbaseDialect)),
2583        DbType::Db2 => Ok(Box::new(Db2Dialect)),
2584        DbType::MariaDB => Ok(Box::new(MariaDbDialect)),
2585        DbType::TiDB => Ok(Box::new(TiDbDialect)),
2586        DbType::PolarDB => Ok(Box::new(PolarDbDialect)),
2587        DbType::GaussDB => Ok(Box::new(GaussDbDialect)),
2588        DbType::GBase => Ok(Box::new(GBaseDialect)),
2589        DbType::Sybase => Ok(Box::new(SybaseDialect)),
2590        DbType::DuckDB => Ok(Box::new(DuckDBDialect)),
2591        #[cfg(feature = "dialect-cockroachdb")]
2592        DbType::CockroachDB => Ok(Box::new(CockroachDbDialect)),
2593        #[cfg(feature = "dialect-yugabytedb")]
2594        DbType::YugabyteDB => Ok(Box::new(YugabyteDbDialect)),
2595        #[cfg(feature = "dialect-snowflake")]
2596        DbType::Snowflake => Ok(Box::new(SnowflakeDialect)),
2597        #[cfg(feature = "dialect-redshift")]
2598        DbType::Redshift => Ok(Box::new(RedshiftDialect)),
2599        #[cfg(feature = "dialect-informix")]
2600        DbType::Informix => Ok(Box::new(InformixDialect)),
2601        #[cfg(feature = "dialect-saphana")]
2602        DbType::SapHana => Ok(Box::new(SapHanaDialect)),
2603        #[cfg(feature = "dialect-firebird")]
2604        DbType::Firebird => Ok(Box::new(FirebirdDialect)),
2605    }
2606}
2607
2608impl fmt::Display for dyn Dialect {
2609    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2610        write!(f, "Dialect({})", self.db_type())
2611    }
2612}
2613
2614// ============================================================================
2615// v3.7.0 M4: Informix / SAP HANA / Firebird 方言实现
2616// ============================================================================
2617// 三种方言均为 "SQL generation only, no real DB driver":
2618// - Informix: informix_rust v0.0.4 不成熟(依赖 C SDK),无用户需求
2619// - SAP HANA: hdbconnect v0.32.0 较成熟,但 sqlx 不支持,无用户需求
2620// - Firebird: rsfbclient v0.27.0 较成熟,但 sqlx 不支持,无用户需求
2621// ============================================================================
2622
2623/// Informix 方言实现(v3.7.0,SQL generation only)
2624///
2625/// IBM Informix 数据库方言,支持 SERIAL/ROW 类型。
2626/// 无真 DB 驱动(informix_rust v0.0.4 不成熟),仅生成 SQL 语法。
2627#[cfg(feature = "dialect-informix")]
2628#[derive(Debug, Clone)]
2629pub struct InformixDialect;
2630
2631#[cfg(feature = "dialect-informix")]
2632impl Dialect for InformixDialect {
2633    fn clone_box(&self) -> Box<dyn Dialect> {
2634        Box::new(InformixDialect)
2635    }
2636
2637    fn db_type(&self) -> DbType {
2638        DbType::Informix
2639    }
2640
2641    fn quote(&self, identifier: &str) -> String {
2642        format!("\"{}\"", identifier.replace('"', "\"\""))
2643    }
2644
2645    fn escape_string(&self, s: &str) -> String {
2646        let mut escaped = String::with_capacity(s.len() * 2);
2647        for c in s.chars() {
2648            match c {
2649                '\'' => escaped.push_str("''"),
2650                _ => escaped.push(c),
2651            }
2652        }
2653        escaped
2654    }
2655
2656    fn supports_returning(&self) -> bool {
2657        false
2658    }
2659
2660    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2661        let offset = page.saturating_sub(1).saturating_mul(limit);
2662        format!("{} SKIP {} FIRST {}", sql, offset, limit)
2663    }
2664
2665    fn json_type(&self) -> &'static str {
2666        "LVARCHAR"
2667    }
2668
2669    fn json_extract(&self, column: &str, path: &str) -> String {
2670        let normalized = if path.starts_with('$') {
2671            path.to_string()
2672        } else {
2673            format!("$.{}", path)
2674        };
2675        format!(
2676            "JSONExtract({}, '{}')",
2677            column,
2678            self.escape_string(&normalized)
2679        )
2680    }
2681
2682    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2683        if columns.is_empty() {
2684            return "0".to_string();
2685        }
2686        let escaped = self.escape_string(keyword);
2687        let parts: Vec<String> = columns
2688            .iter()
2689            .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2690            .collect();
2691        parts.join(" OR ")
2692    }
2693
2694    fn bool_to_int(&self, expr: &str) -> String {
2695        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2696    }
2697
2698    fn concat(&self, parts: &[&str]) -> String {
2699        if parts.is_empty() {
2700            return "''".to_string();
2701        }
2702        parts.join(" || ")
2703    }
2704
2705    fn supports_if_exists(&self) -> bool {
2706        true
2707    }
2708
2709    fn supports_if_not_exists(&self) -> bool {
2710        true
2711    }
2712
2713    fn auto_increment_keyword(&self) -> &'static str {
2714        "SERIAL"
2715    }
2716
2717    fn last_insert_id_sql(&self) -> Option<&'static str> {
2718        Some("SELECT DBINFO('sqlca.sqlerrd1') FROM systables WHERE tabid = 1")
2719    }
2720
2721    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2722        let cols: Vec<String> = columns
2723            .iter()
2724            .map(|col| {
2725                let informix_type = map_to_informix_type(&col.sql_type);
2726                let mut sql = format!("{} {}", self.quote(&col.name), informix_type);
2727                if !col.nullable && !col.auto_increment {
2728                    sql.push_str(" NOT NULL");
2729                }
2730                if let Some(default) = &col.default {
2731                    sql.push_str(&format!(" DEFAULT {}", default));
2732                }
2733                if col.primary_key {
2734                    sql.push_str(" PRIMARY KEY");
2735                }
2736                sql
2737            })
2738            .collect();
2739
2740        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
2741    }
2742
2743    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2744        let stmts: Vec<String> = changes
2745            .iter()
2746            .map(|change| match change {
2747                TableChange::AddColumn(col) => {
2748                    let informix_type = map_to_informix_type(&col.sql_type);
2749                    let mut sql = format!(
2750                        "ALTER TABLE {} ADD COLUMN {} {}",
2751                        self.quote(table),
2752                        self.quote(&col.name),
2753                        informix_type
2754                    );
2755                    if !col.nullable {
2756                        sql.push_str(" NOT NULL");
2757                    }
2758                    if let Some(default) = &col.default {
2759                        sql.push_str(&format!(" DEFAULT {}", default));
2760                    }
2761                    sql
2762                }
2763                TableChange::DropColumn(name) => {
2764                    format!(
2765                        "ALTER TABLE {} DROP COLUMN {}",
2766                        self.quote(table),
2767                        self.quote(name)
2768                    )
2769                }
2770                TableChange::ModifyColumn(col) => {
2771                    let informix_type = map_to_informix_type(&col.sql_type);
2772                    format!(
2773                        "ALTER TABLE {} MODIFY {} {}",
2774                        self.quote(table),
2775                        self.quote(&col.name),
2776                        informix_type
2777                    )
2778                }
2779                TableChange::AddIndex(name, cols) => {
2780                    format!(
2781                        "CREATE INDEX {} ON {} ({})",
2782                        name,
2783                        self.quote(table),
2784                        cols.join(", ")
2785                    )
2786                }
2787                TableChange::DropIndex(name) => {
2788                    format!("DROP INDEX {}", name)
2789                }
2790                TableChange::AddForeignKey {
2791                    columns,
2792                    reference_table,
2793                    reference_columns,
2794                } => {
2795                    format!(
2796                        "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
2797                        self.quote(table),
2798                        columns
2799                            .iter()
2800                            .map(|c| self.quote(c))
2801                            .collect::<Vec<_>>()
2802                            .join(", "),
2803                        self.quote(reference_table),
2804                        reference_columns
2805                            .iter()
2806                            .map(|c| self.quote(c))
2807                            .collect::<Vec<_>>()
2808                            .join(", ")
2809                    )
2810                }
2811            })
2812            .collect();
2813
2814        stmts.join("; ")
2815    }
2816}
2817
2818/// 将通用 SQL 类型映射为 Informix 类型
2819#[cfg(feature = "dialect-informix")]
2820fn map_to_informix_type(sql_type: &str) -> String {
2821    let upper = sql_type.to_uppercase();
2822    let trimmed = upper.trim();
2823
2824    if trimmed.starts_with("BIGINT") {
2825        "BIGINT".to_string()
2826    } else if matches!(trimmed, "INT" | "INTEGER") {
2827        "INTEGER".to_string()
2828    } else if matches!(trimmed, "SMALLINT") {
2829        "SMALLINT".to_string()
2830    } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
2831        sql_type.to_string()
2832    } else if matches!(trimmed, "TEXT" | "LVARCHAR") {
2833        "LVARCHAR".to_string()
2834    } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
2835        "BOOLEAN".to_string()
2836    } else if matches!(trimmed, "FLOAT" | "REAL") {
2837        "SMALLFLOAT".to_string()
2838    } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
2839        "FLOAT".to_string()
2840    } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
2841        "DATETIME YEAR TO SECOND".to_string()
2842    } else if matches!(trimmed, "DATE") {
2843        "DATE".to_string()
2844    } else if trimmed.starts_with("SERIAL") {
2845        sql_type.to_string()
2846    } else {
2847        sql_type.to_string()
2848    }
2849}
2850
2851/// SAP HANA 方言实现(v3.7.0,SQL generation only)
2852///
2853/// SAP HANA 内存数据库方言,支持计算列 + CE 函数。
2854/// 无真 DB 驱动(sz-orm 使用 sqlx,sqlx 不支持 SAP HANA),仅生成 SQL 语法。
2855#[cfg(feature = "dialect-saphana")]
2856#[derive(Debug, Clone)]
2857pub struct SapHanaDialect;
2858
2859#[cfg(feature = "dialect-saphana")]
2860impl Dialect for SapHanaDialect {
2861    fn clone_box(&self) -> Box<dyn Dialect> {
2862        Box::new(SapHanaDialect)
2863    }
2864
2865    fn db_type(&self) -> DbType {
2866        DbType::SapHana
2867    }
2868
2869    fn quote(&self, identifier: &str) -> String {
2870        format!("\"{}\"", identifier.replace('"', "\"\""))
2871    }
2872
2873    fn escape_string(&self, s: &str) -> String {
2874        let mut escaped = String::with_capacity(s.len() * 2);
2875        for c in s.chars() {
2876            match c {
2877                '\'' => escaped.push_str("''"),
2878                _ => escaped.push(c),
2879            }
2880        }
2881        escaped
2882    }
2883
2884    fn supports_returning(&self) -> bool {
2885        false
2886    }
2887
2888    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
2889        let offset = page.saturating_sub(1).saturating_mul(limit);
2890        format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
2891    }
2892
2893    fn json_type(&self) -> &'static str {
2894        "NCLOB"
2895    }
2896
2897    fn json_extract(&self, column: &str, path: &str) -> String {
2898        let normalized = if path.starts_with('$') {
2899            path.to_string()
2900        } else {
2901            format!("$.{}", path)
2902        };
2903        format!(
2904            "JSON_VALUE({}, '{}')",
2905            column,
2906            self.escape_string(&normalized)
2907        )
2908    }
2909
2910    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
2911        if columns.is_empty() {
2912            return "0".to_string();
2913        }
2914        let escaped = self.escape_string(keyword);
2915        let parts: Vec<String> = columns
2916            .iter()
2917            .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
2918            .collect();
2919        parts.join(" OR ")
2920    }
2921
2922    fn bool_to_int(&self, expr: &str) -> String {
2923        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
2924    }
2925
2926    fn concat(&self, parts: &[&str]) -> String {
2927        if parts.is_empty() {
2928            return "''".to_string();
2929        }
2930        parts.join(" || ")
2931    }
2932
2933    fn supports_if_exists(&self) -> bool {
2934        true
2935    }
2936
2937    fn supports_if_not_exists(&self) -> bool {
2938        true
2939    }
2940
2941    fn auto_increment_keyword(&self) -> &'static str {
2942        "GENERATED BY DEFAULT AS IDENTITY"
2943    }
2944
2945    fn last_insert_id_sql(&self) -> Option<&'static str> {
2946        None
2947    }
2948
2949    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
2950        let cols: Vec<String> = columns
2951            .iter()
2952            .map(|col| {
2953                let hana_type = map_to_saphana_type(&col.sql_type);
2954                let mut sql = format!("{} {}", self.quote(&col.name), hana_type);
2955                if !col.nullable && !col.auto_increment {
2956                    sql.push_str(" NOT NULL");
2957                }
2958                if let Some(default) = &col.default {
2959                    sql.push_str(&format!(" DEFAULT {}", default));
2960                }
2961                if col.auto_increment {
2962                    sql.push_str(&format!(" {}", self.auto_increment_keyword()));
2963                }
2964                if col.primary_key {
2965                    sql.push_str(" PRIMARY KEY");
2966                }
2967                sql
2968            })
2969            .collect();
2970
2971        format!(
2972            "CREATE COLUMN TABLE {} ({})",
2973            self.quote(table),
2974            cols.join(", ")
2975        )
2976    }
2977
2978    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
2979        let stmts: Vec<String> = changes
2980            .iter()
2981            .map(|change| match change {
2982                TableChange::AddColumn(col) => {
2983                    let hana_type = map_to_saphana_type(&col.sql_type);
2984                    let mut sql = format!(
2985                        "ALTER TABLE {} ADD ({})",
2986                        self.quote(table),
2987                        format!("{} {}", self.quote(&col.name), hana_type)
2988                    );
2989                    if !col.nullable {
2990                        sql.push_str(" NOT NULL");
2991                    }
2992                    if let Some(default) = &col.default {
2993                        sql.push_str(&format!(" DEFAULT {}", default));
2994                    }
2995                    sql
2996                }
2997                TableChange::DropColumn(name) => {
2998                    format!(
2999                        "ALTER TABLE {} DROP ({})",
3000                        self.quote(table),
3001                        self.quote(name)
3002                    )
3003                }
3004                TableChange::ModifyColumn(col) => {
3005                    let hana_type = map_to_saphana_type(&col.sql_type);
3006                    format!(
3007                        "ALTER TABLE {} ALTER ({})",
3008                        self.quote(table),
3009                        format!("{} {}", self.quote(&col.name), hana_type)
3010                    )
3011                }
3012                TableChange::AddIndex(name, cols) => {
3013                    format!(
3014                        "CREATE INDEX {} ON {} ({})",
3015                        name,
3016                        self.quote(table),
3017                        cols.join(", ")
3018                    )
3019                }
3020                TableChange::DropIndex(name) => {
3021                    format!("DROP INDEX {}", name)
3022                }
3023                TableChange::AddForeignKey {
3024                    columns,
3025                    reference_table,
3026                    reference_columns,
3027                } => {
3028                    format!(
3029                        "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
3030                        self.quote(table),
3031                        columns
3032                            .iter()
3033                            .map(|c| self.quote(c))
3034                            .collect::<Vec<_>>()
3035                            .join(", "),
3036                        self.quote(reference_table),
3037                        reference_columns
3038                            .iter()
3039                            .map(|c| self.quote(c))
3040                            .collect::<Vec<_>>()
3041                            .join(", ")
3042                    )
3043                }
3044            })
3045            .collect();
3046
3047        stmts.join("; ")
3048    }
3049}
3050
3051/// 将通用 SQL 类型映射为 SAP HANA 类型
3052#[cfg(feature = "dialect-saphana")]
3053fn map_to_saphana_type(sql_type: &str) -> String {
3054    let upper = sql_type.to_uppercase();
3055    let trimmed = upper.trim();
3056
3057    if trimmed.starts_with("BIGINT") {
3058        "BIGINT".to_string()
3059    } else if matches!(trimmed, "INT" | "INTEGER") {
3060        "INTEGER".to_string()
3061    } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
3062        "SMALLINT".to_string()
3063    } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("NVARCHAR") {
3064        sql_type.to_string()
3065    } else if trimmed.starts_with("CHAR") || trimmed.starts_with("NCHAR") {
3066        sql_type.to_string()
3067    } else if matches!(trimmed, "TEXT" | "CLOB") {
3068        "NCLOB".to_string()
3069    } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
3070        "BOOLEAN".to_string()
3071    } else if matches!(trimmed, "FLOAT" | "REAL") {
3072        "REAL".to_string()
3073    } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
3074        "DOUBLE".to_string()
3075    } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
3076        "TIMESTAMP".to_string()
3077    } else if matches!(trimmed, "DATE") {
3078        "DATE".to_string()
3079    } else {
3080        sql_type.to_string()
3081    }
3082}
3083
3084/// Firebird 方言实现(v3.7.0,SQL generation only)
3085///
3086/// Firebird 数据库方言,支持 GENERATOR/SEQUENCE + EXECUTE BLOCK。
3087/// 无真 DB 驱动(sz-orm 使用 sqlx,sqlx 不支持 Firebird),仅生成 SQL 语法。
3088#[cfg(feature = "dialect-firebird")]
3089#[derive(Debug, Clone)]
3090pub struct FirebirdDialect;
3091
3092#[cfg(feature = "dialect-firebird")]
3093impl Dialect for FirebirdDialect {
3094    fn clone_box(&self) -> Box<dyn Dialect> {
3095        Box::new(FirebirdDialect)
3096    }
3097
3098    fn db_type(&self) -> DbType {
3099        DbType::Firebird
3100    }
3101
3102    fn quote(&self, identifier: &str) -> String {
3103        format!("\"{}\"", identifier.replace('"', "\"\""))
3104    }
3105
3106    fn escape_string(&self, s: &str) -> String {
3107        let mut escaped = String::with_capacity(s.len() * 2);
3108        for c in s.chars() {
3109            match c {
3110                '\'' => escaped.push_str("''"),
3111                _ => escaped.push(c),
3112            }
3113        }
3114        escaped
3115    }
3116
3117    fn supports_returning(&self) -> bool {
3118        true
3119    }
3120
3121    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
3122        let offset = page.saturating_sub(1).saturating_mul(limit);
3123        let to = offset + limit;
3124        format!("{} ROWS {} TO {}", sql, offset + 1, to)
3125    }
3126
3127    fn json_type(&self) -> &'static str {
3128        "BLOB SUB_TYPE TEXT"
3129    }
3130
3131    fn json_extract(&self, column: &str, path: &str) -> String {
3132        let normalized = if path.starts_with('$') {
3133            path.to_string()
3134        } else {
3135            format!("$.{}", path)
3136        };
3137        format!(
3138            "JSON_GET({}, '{}')",
3139            column,
3140            self.escape_string(&normalized)
3141        )
3142    }
3143
3144    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
3145        if columns.is_empty() {
3146            return "0".to_string();
3147        }
3148        let escaped = self.escape_string(keyword);
3149        let parts: Vec<String> = columns
3150            .iter()
3151            .map(|c| format!("CONTAINING({}, '{}')", c, escaped))
3152            .collect();
3153        parts.join(" OR ")
3154    }
3155
3156    fn bool_to_int(&self, expr: &str) -> String {
3157        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
3158    }
3159
3160    fn concat(&self, parts: &[&str]) -> String {
3161        if parts.is_empty() {
3162            return "''".to_string();
3163        }
3164        parts.join(" || ")
3165    }
3166
3167    fn supports_if_exists(&self) -> bool {
3168        true
3169    }
3170
3171    fn supports_if_not_exists(&self) -> bool {
3172        true
3173    }
3174
3175    fn auto_increment_keyword(&self) -> &'static str {
3176        "GENERATED BY DEFAULT AS IDENTITY"
3177    }
3178
3179    fn last_insert_id_sql(&self) -> Option<&'static str> {
3180        Some("SELECT GEN_ID(SQLITE_SEQUENCE, 0) FROM RDB$DATABASE")
3181    }
3182
3183    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
3184        let cols: Vec<String> = columns
3185            .iter()
3186            .map(|col| {
3187                let fb_type = map_to_firebird_type(&col.sql_type);
3188                let mut sql = format!("{} {}", self.quote(&col.name), fb_type);
3189                if !col.nullable && !col.auto_increment {
3190                    sql.push_str(" NOT NULL");
3191                }
3192                if let Some(default) = &col.default {
3193                    sql.push_str(&format!(" DEFAULT {}", default));
3194                }
3195                if col.auto_increment {
3196                    sql.push_str(&format!(" {}", self.auto_increment_keyword()));
3197                }
3198                if col.primary_key {
3199                    sql.push_str(" PRIMARY KEY");
3200                }
3201                sql
3202            })
3203            .collect();
3204
3205        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
3206    }
3207
3208    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
3209        let stmts: Vec<String> = changes
3210            .iter()
3211            .map(|change| match change {
3212                TableChange::AddColumn(col) => {
3213                    let fb_type = map_to_firebird_type(&col.sql_type);
3214                    let mut sql = format!(
3215                        "ALTER TABLE {} ADD {} {}",
3216                        self.quote(table),
3217                        self.quote(&col.name),
3218                        fb_type
3219                    );
3220                    if !col.nullable {
3221                        sql.push_str(" NOT NULL");
3222                    }
3223                    if let Some(default) = &col.default {
3224                        sql.push_str(&format!(" DEFAULT {}", default));
3225                    }
3226                    sql
3227                }
3228                TableChange::DropColumn(name) => {
3229                    format!(
3230                        "ALTER TABLE {} DROP {}",
3231                        self.quote(table),
3232                        self.quote(name)
3233                    )
3234                }
3235                TableChange::ModifyColumn(col) => {
3236                    let fb_type = map_to_firebird_type(&col.sql_type);
3237                    format!(
3238                        "ALTER TABLE {} ALTER COLUMN {} TYPE {}",
3239                        self.quote(table),
3240                        self.quote(&col.name),
3241                        fb_type
3242                    )
3243                }
3244                TableChange::AddIndex(name, cols) => {
3245                    format!(
3246                        "CREATE INDEX {} ON {} ({})",
3247                        name,
3248                        self.quote(table),
3249                        cols.join(", ")
3250                    )
3251                }
3252                TableChange::DropIndex(name) => {
3253                    format!("DROP INDEX {}", name)
3254                }
3255                TableChange::AddForeignKey {
3256                    columns,
3257                    reference_table,
3258                    reference_columns,
3259                } => {
3260                    format!(
3261                        "ALTER TABLE {} ADD CONSTRAINT FOREIGN KEY ({}) REFERENCES {} ({})",
3262                        self.quote(table),
3263                        columns
3264                            .iter()
3265                            .map(|c| self.quote(c))
3266                            .collect::<Vec<_>>()
3267                            .join(", "),
3268                        self.quote(reference_table),
3269                        reference_columns
3270                            .iter()
3271                            .map(|c| self.quote(c))
3272                            .collect::<Vec<_>>()
3273                            .join(", ")
3274                    )
3275                }
3276            })
3277            .collect();
3278
3279        stmts.join("; ")
3280    }
3281}
3282
3283/// 将通用 SQL 类型映射为 Firebird 类型
3284#[cfg(feature = "dialect-firebird")]
3285fn map_to_firebird_type(sql_type: &str) -> String {
3286    let upper = sql_type.to_uppercase();
3287    let trimmed = upper.trim();
3288
3289    if trimmed.starts_with("BIGINT") {
3290        "BIGINT".to_string()
3291    } else if matches!(trimmed, "INT" | "INTEGER") {
3292        "INTEGER".to_string()
3293    } else if matches!(trimmed, "SMALLINT") {
3294        "SMALLINT".to_string()
3295    } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
3296        sql_type.to_string()
3297    } else if matches!(trimmed, "TEXT" | "BLOB") {
3298        "BLOB SUB_TYPE TEXT".to_string()
3299    } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
3300        "BOOLEAN".to_string()
3301    } else if matches!(trimmed, "FLOAT" | "REAL") {
3302        "FLOAT".to_string()
3303    } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
3304        "DOUBLE PRECISION".to_string()
3305    } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
3306        "TIMESTAMP".to_string()
3307    } else if matches!(trimmed, "DATE") {
3308        "DATE".to_string()
3309    } else {
3310        sql_type.to_string()
3311    }
3312}
3313
3314#[cfg(test)]
3315mod tests {
3316    use super::*;
3317
3318    #[test]
3319    fn test_mysql_quote() {
3320        let dialect = MySqlDialect;
3321        assert_eq!(dialect.quote("users"), "`users`");
3322        assert_eq!(dialect.quote("user`id"), "`user``id`");
3323    }
3324
3325    #[test]
3326    fn test_mysql_escape() {
3327        let dialect = MySqlDialect;
3328        assert_eq!(dialect.escape_string("hello"), "hello");
3329        assert_eq!(dialect.escape_string("it's"), "it\\'s");
3330        assert_eq!(dialect.escape_string("line\nbreak"), "line\\nbreak");
3331    }
3332
3333    #[test]
3334    fn test_mysql_pagination() {
3335        let dialect = MySqlDialect;
3336        let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
3337        assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
3338    }
3339
3340    #[test]
3341    fn test_postgres_quote() {
3342        let dialect = PostgreSqlDialect;
3343        assert_eq!(dialect.quote("users"), "\"users\"");
3344        assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3345    }
3346
3347    #[test]
3348    fn test_postgres_pagination() {
3349        let dialect = PostgreSqlDialect;
3350        let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3351        assert_eq!(sql, "SELECT * FROM users LIMIT 20 OFFSET 40");
3352    }
3353
3354    #[test]
3355    fn test_postgres_returning() {
3356        let dialect = PostgreSqlDialect;
3357        assert!(dialect.supports_returning());
3358    }
3359
3360    #[test]
3361    fn test_sqlite_quote() {
3362        let dialect = SqliteDialect;
3363        assert_eq!(dialect.quote("users"), "\"users\"");
3364        assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3365    }
3366
3367    #[test]
3368    fn test_sqlite_escape() {
3369        let dialect = SqliteDialect;
3370        assert_eq!(dialect.escape_string("hello"), "hello");
3371        assert_eq!(dialect.escape_string("it's"), "it''s");
3372    }
3373
3374    #[test]
3375    fn test_get_dialect() {
3376        let dialect = get_dialect(DbType::MySQL);
3377        assert!(dialect.is_ok());
3378
3379        let dialect = get_dialect(DbType::Redis);
3380        assert!(dialect.is_err());
3381    }
3382
3383    #[test]
3384    fn test_bool_to_int() {
3385        let mysql = MySqlDialect;
3386        assert_eq!(mysql.bool_to_int("active"), "IF(active, 1, 0)");
3387
3388        let pg = PostgreSqlDialect;
3389        assert_eq!(
3390            pg.bool_to_int("active"),
3391            "(CASE WHEN active THEN 1 ELSE 0 END)"
3392        );
3393    }
3394
3395    #[test]
3396    fn test_json_extract_with_path() {
3397        let mysql = MySqlDialect;
3398        let sql = mysql.json_extract("data", "$.user.name");
3399        assert!(sql.contains("$.user.name"));
3400        assert!(sql.contains("JSON_EXTRACT"));
3401
3402        let pg = PostgreSqlDialect;
3403        let sql = pg.json_extract("data", "user.name");
3404        assert!(sql.contains("#>>"));
3405
3406        let sqlite = SqliteDialect;
3407        let sql = sqlite.json_extract("data", "$.user.name");
3408        assert!(sql.contains("$.user.name"));
3409        assert!(sql.contains("json_extract"));
3410    }
3411
3412    #[test]
3413    fn test_sqlite_full_text_search() {
3414        let sqlite = SqliteDialect;
3415        let sql = sqlite.full_text_search(&["title", "content"], "hello");
3416        // SQLite 接口只传入列名,无 FTS 表名,因此降级使用 LIKE
3417        assert!(sql.contains("LIKE"));
3418        assert!(sql.contains("title LIKE '%hello%'"));
3419        assert!(sql.contains("content LIKE '%hello%'"));
3420        assert!(sql.contains(" OR "));
3421
3422        // 空列列表返回 "0"(短路避免空 IN/OR)
3423        assert_eq!(sqlite.full_text_search(&[], "hello"), "0");
3424
3425        // 含单引号的关键字需正确转义(SQLite 双单引号)
3426        let sql = sqlite.full_text_search(&["title"], "it's");
3427        assert!(sql.contains("title LIKE '%it''s%'"));
3428    }
3429
3430    #[test]
3431    fn test_alter_table_modify_column() {
3432        let mysql = MySqlDialect;
3433        let col = ColumnDef {
3434            name: "name".to_string(),
3435            sql_type: "VARCHAR(255)".to_string(),
3436            nullable: false,
3437            default: None,
3438            auto_increment: false,
3439            primary_key: false,
3440        };
3441        let sql = mysql.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3442        assert!(sql.contains("MODIFY COLUMN"));
3443
3444        let pg = PostgreSqlDialect;
3445        let col = ColumnDef {
3446            name: "name".to_string(),
3447            sql_type: "VARCHAR(255)".to_string(),
3448            nullable: false,
3449            default: None,
3450            auto_increment: false,
3451            primary_key: false,
3452        };
3453        let sql = pg.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3454        assert!(sql.contains("ALTER COLUMN"));
3455        assert!(sql.contains("TYPE"));
3456    }
3457
3458    #[test]
3459    fn test_alter_table_add_foreign_key() {
3460        let mysql = MySqlDialect;
3461        let sql = mysql.build_alter_table(
3462            "orders",
3463            &[TableChange::AddForeignKey {
3464                columns: vec!["user_id".to_string()],
3465                reference_table: "users".to_string(),
3466                reference_columns: vec!["id".to_string()],
3467            }],
3468        );
3469        assert!(sql.contains("FOREIGN KEY"));
3470        assert!(sql.contains("REFERENCES"));
3471
3472        let sqlite = SqliteDialect;
3473        let sql = sqlite.build_alter_table(
3474            "orders",
3475            &[TableChange::AddForeignKey {
3476                columns: vec!["user_id".to_string()],
3477                reference_table: "users".to_string(),
3478                reference_columns: vec!["id".to_string()],
3479            }],
3480        );
3481        // SQLite 不支持,应返回注释
3482        assert!(sql.starts_with("--"));
3483    }
3484
3485    #[test]
3486    fn test_sqlite_alter_table_add_column() {
3487        let sqlite = SqliteDialect;
3488        let col = ColumnDef {
3489            name: "email".to_string(),
3490            sql_type: "TEXT".to_string(),
3491            nullable: true,
3492            default: None,
3493            auto_increment: false,
3494            primary_key: false,
3495        };
3496        let sql = sqlite.build_alter_table("users", &[TableChange::AddColumn(col)]);
3497        assert!(sql.contains("ADD COLUMN"));
3498        assert!(sql.contains("email"));
3499    }
3500
3501    // ===================== Oracle 方言测试 =====================
3502
3503    #[test]
3504    fn test_oracle_quote_and_escape() {
3505        let dialect = OracleDialect;
3506        // 标识符使用双引号包裹,内部双引号双写
3507        assert_eq!(dialect.quote("users"), "\"users\"");
3508        assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
3509        assert_eq!(dialect.quote("column_name"), "\"column_name\"");
3510
3511        // 字符串字面量转义:单引号双写,反斜杠不转义
3512        assert_eq!(dialect.escape_string("hello"), "hello");
3513        assert_eq!(dialect.escape_string("it's"), "it''s");
3514        assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
3515        assert_eq!(dialect.escape_string("a'b'c"), "a''b''c");
3516        // 反斜杠按原样保留(Oracle 标准行为)
3517        assert_eq!(dialect.escape_string("path\\to"), "path\\to");
3518    }
3519
3520    #[test]
3521    fn test_oracle_pagination() {
3522        let dialect = OracleDialect;
3523        // page=1 为第一页(offset=0),与其他方言保持一致
3524        let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
3525        assert_eq!(
3526            sql,
3527            "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3528        );
3529        // page=3, limit=20 → offset=40
3530        let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3531        assert_eq!(
3532            sql,
3533            "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
3534        );
3535        // page=0(边界)→ offset=0
3536        let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
3537        assert_eq!(
3538            sql,
3539            "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3540        );
3541    }
3542
3543    #[test]
3544    fn test_oracle_json_extract() {
3545        let dialect = OracleDialect;
3546        // 标准 $.path 格式
3547        let sql = dialect.json_extract("data", "$.user.name");
3548        assert!(sql.contains("JSON_VALUE"));
3549        assert!(sql.contains("$.user.name"));
3550        assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
3551
3552        // 自动补全 $. 前缀
3553        let sql = dialect.json_extract("data", "user.name");
3554        assert!(sql.contains("$.user.name"));
3555        assert!(sql.contains("JSON_VALUE"));
3556
3557        // 单引号转义
3558        let sql = dialect.json_extract("data", "$.key's");
3559        assert!(sql.contains("$.key''s"));
3560    }
3561
3562    #[test]
3563    fn test_oracle_create_table() {
3564        let dialect = OracleDialect;
3565        let columns = vec![
3566            ColumnDef {
3567                name: "id".to_string(),
3568                sql_type: "BIGINT".to_string(),
3569                nullable: false,
3570                default: None,
3571                auto_increment: true,
3572                primary_key: true,
3573            },
3574            ColumnDef {
3575                name: "name".to_string(),
3576                sql_type: "VARCHAR(255)".to_string(),
3577                nullable: false,
3578                default: None,
3579                auto_increment: false,
3580                primary_key: false,
3581            },
3582            ColumnDef {
3583                name: "bio".to_string(),
3584                sql_type: "TEXT".to_string(),
3585                nullable: true,
3586                default: None,
3587                auto_increment: false,
3588                primary_key: false,
3589            },
3590            ColumnDef {
3591                name: "is_active".to_string(),
3592                sql_type: "BOOLEAN".to_string(),
3593                nullable: false,
3594                default: Some("1".to_string()),
3595                auto_increment: false,
3596                primary_key: false,
3597            },
3598        ];
3599        let sql = dialect.build_create_table("users", &columns);
3600        // Oracle 类型映射
3601        assert!(
3602            sql.contains("NUMBER(19)"),
3603            "BIGINT should map to NUMBER(19): {}",
3604            sql
3605        );
3606        assert!(
3607            sql.contains("VARCHAR2(255)"),
3608            "VARCHAR should map to VARCHAR2: {}",
3609            sql
3610        );
3611        assert!(sql.contains("CLOB"), "TEXT should map to CLOB: {}", sql);
3612        assert!(
3613            sql.contains("NUMBER(1)"),
3614            "BOOLEAN should map to NUMBER(1): {}",
3615            sql
3616        );
3617        // 自增与主键
3618        assert!(sql.contains("GENERATED BY DEFAULT AS IDENTITY"));
3619        assert!(sql.contains("PRIMARY KEY"));
3620        assert!(sql.contains("NOT NULL"));
3621        assert!(sql.contains("DEFAULT 1"));
3622        // 标识符使用双引号
3623        assert!(sql.contains("\"users\""));
3624        assert!(sql.contains("\"id\""));
3625    }
3626
3627    #[test]
3628    fn test_oracle_bool_to_int_and_concat() {
3629        let dialect = OracleDialect;
3630        // bool_to_int 使用 CASE WHEN
3631        assert_eq!(
3632            dialect.bool_to_int("active"),
3633            "(CASE WHEN active THEN 1 ELSE 0 END)"
3634        );
3635        assert_eq!(
3636            dialect.bool_to_int("x > 0"),
3637            "(CASE WHEN x > 0 THEN 1 ELSE 0 END)"
3638        );
3639        // concat 使用 || 操作符
3640        assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
3641        assert_eq!(
3642            dialect.concat(&["first_name", "last_name"]),
3643            "first_name || last_name"
3644        );
3645        // 空列表返回 NULL
3646        assert_eq!(dialect.concat(&[]), "NULL");
3647    }
3648
3649    #[test]
3650    fn test_oracle_misc_dialect_methods() {
3651        let dialect = OracleDialect;
3652        // db_type
3653        assert_eq!(dialect.db_type(), DbType::Oracle);
3654        // supports_returning: Oracle 12c+ 支持
3655        assert!(dialect.supports_returning());
3656        // supports_if_exists / supports_if_not_exists: Oracle 23ai 支持
3657        assert!(dialect.supports_if_exists());
3658        assert!(dialect.supports_if_not_exists());
3659        // auto_increment_keyword
3660        assert_eq!(
3661            dialect.auto_increment_keyword(),
3662            "GENERATED BY DEFAULT AS IDENTITY"
3663        );
3664        // last_insert_id_sql: Oracle 不支持独立可执行的获取最后插入 ID 语句
3665        // RETURNING ... INTO 是 PL/SQL 子句,必须附加在 INSERT 之后
3666        assert_eq!(dialect.last_insert_id_sql(), None);
3667        // json_type
3668        assert_eq!(dialect.json_type(), "JSON");
3669    }
3670
3671    #[test]
3672    fn test_oracle_get_dialect() {
3673        // get_dialect 应返回 OracleDialect 而非 MySqlDialect
3674        let dialect = get_dialect(DbType::Oracle);
3675        assert!(dialect.is_ok(), "Oracle dialect should be available");
3676        let dialect = dialect.unwrap();
3677        assert_eq!(dialect.db_type(), DbType::Oracle);
3678        // 验证不是 MySqlDialect 的回退:Oracle 使用双引号,MySQL 使用反引号
3679        assert_eq!(dialect.quote("users"), "\"users\"");
3680        // 验证 Oracle 特有功能
3681        assert!(dialect.supports_returning());
3682        // Oracle 没有 standalone 的 last_insert_id SQL
3683        assert_eq!(dialect.last_insert_id_sql(), None);
3684    }
3685
3686    #[test]
3687    fn test_oracle_drop_table() {
3688        let dialect = OracleDialect;
3689        // IF EXISTS
3690        let sql = dialect.build_drop_table("users", true);
3691        assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
3692        // 不带 IF EXISTS
3693        let sql = dialect.build_drop_table("users", false);
3694        assert_eq!(sql, "DROP TABLE \"users\"");
3695    }
3696
3697    #[test]
3698    fn test_oracle_alter_table() {
3699        let dialect = OracleDialect;
3700        // MODIFY COLUMN: Oracle 使用 MODIFY(不带 COLUMN 关键字)
3701        let col = ColumnDef {
3702            name: "name".to_string(),
3703            sql_type: "VARCHAR(255)".to_string(),
3704            nullable: false,
3705            default: None,
3706            auto_increment: false,
3707            primary_key: false,
3708        };
3709        let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3710        assert!(sql.contains("MODIFY"));
3711        assert!(sql.contains("VARCHAR2(255)"));
3712        assert!(!sql.contains("MODIFY COLUMN")); // Oracle 不使用 COLUMN 关键字
3713
3714        // ADD COLUMN
3715        let col = ColumnDef {
3716            name: "email".to_string(),
3717            sql_type: "VARCHAR(255)".to_string(),
3718            nullable: true,
3719            default: None,
3720            auto_increment: false,
3721            primary_key: false,
3722        };
3723        let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
3724        assert!(sql.contains("ADD \"email\""));
3725        assert!(sql.contains("VARCHAR2(255)"));
3726
3727        // DROP COLUMN
3728        let sql =
3729            dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
3730        assert!(sql.contains("DROP COLUMN"));
3731        assert!(sql.contains("\"email\""));
3732    }
3733
3734    // ===================== SQLite concat 修复测试 =====================
3735
3736    #[test]
3737    fn test_sqlite_concat_handles_null() {
3738        let sqlite = SqliteDialect;
3739        // 修复前:COALESCE(a || b) 在 a 为 NULL 时整体已 NULL,COALESCE 失效
3740        // 修复后:每个参数 COALESCE 为空串,保证拼接结果非 NULL
3741        let sql = sqlite.concat(&["a", "b"]);
3742        assert_eq!(sql, "COALESCE(a, '') || COALESCE(b, '')");
3743        // 单参数
3744        let sql = sqlite.concat(&["a"]);
3745        assert_eq!(sql, "COALESCE(a, '')");
3746        // 空列表
3747        assert_eq!(sqlite.concat(&[]), "NULL");
3748    }
3749
3750    // ===================== SQL Server 方言测试 =====================
3751
3752    #[test]
3753    fn test_sqlserver_quote_and_escape() {
3754        let dialect = SqlServerDialect;
3755        // 标识符使用 [name] 包裹,内部 ] 双写
3756        assert_eq!(dialect.quote("users"), "[users]");
3757        assert_eq!(dialect.quote("col]name"), "[col]]name]");
3758        // 字符串字面量:单引号双写,反斜杠不转义
3759        assert_eq!(dialect.escape_string("hello"), "hello");
3760        assert_eq!(dialect.escape_string("it's"), "it''s");
3761        assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
3762        assert_eq!(dialect.escape_string("path\\to"), "path\\to");
3763    }
3764
3765    #[test]
3766    fn test_sqlserver_pagination() {
3767        let dialect = SqlServerDialect;
3768        // SQL Server 2012+ 使用 OFFSET ... ROWS FETCH NEXT ... ROWS ONLY
3769        let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
3770        assert_eq!(
3771            sql,
3772            "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3773        );
3774        let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
3775        assert_eq!(
3776            sql,
3777            "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
3778        );
3779        // page=0 边界
3780        let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
3781        assert_eq!(
3782            sql,
3783            "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
3784        );
3785    }
3786
3787    #[test]
3788    fn test_sqlserver_misc_dialect_methods() {
3789        let dialect = SqlServerDialect;
3790        assert_eq!(dialect.db_type(), DbType::SqlServer);
3791        // supports_returning: SQL Server 使用 OUTPUT,语义等价
3792        assert!(dialect.supports_returning());
3793        // supports_if_exists / supports_if_not_exists: SQL Server 2016+
3794        assert!(dialect.supports_if_exists());
3795        assert!(dialect.supports_if_not_exists());
3796        // auto_increment_keyword
3797        assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
3798        // last_insert_id_sql
3799        assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
3800        // json_type
3801        assert_eq!(dialect.json_type(), "NVARCHAR(MAX)");
3802    }
3803
3804    #[test]
3805    fn test_sqlserver_insert_or_ignore_fallback_to_plain_insert() {
3806        let dialect = SqlServerDialect;
3807        let sql = dialect.build_insert_or_ignore_prefix("users");
3808        // SQL Server 不支持 INSERT OR IGNORE,回退为普通 INSERT(不生成非法 SQL)
3809        assert_eq!(sql, "INSERT INTO [users]");
3810        assert!(!sql.contains("OR IGNORE"));
3811    }
3812
3813    #[test]
3814    fn test_sqlserver_json_extract() {
3815        let dialect = SqlServerDialect;
3816        let sql = dialect.json_extract("data", "$.user.name");
3817        assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
3818        // 自动补 $. 前缀
3819        let sql = dialect.json_extract("data", "user.name");
3820        assert!(sql.contains("$.user.name"));
3821        assert!(sql.contains("JSON_VALUE"));
3822        // 单引号转义
3823        let sql = dialect.json_extract("data", "$.key's");
3824        assert!(sql.contains("$.key''s"));
3825    }
3826
3827    #[test]
3828    fn test_sqlserver_full_text_search() {
3829        let dialect = SqlServerDialect;
3830        let sql = dialect.full_text_search(&["title", "content"], "hello");
3831        assert!(sql.starts_with("CONTAINS(title, content, 'hello')"));
3832        // 空列列表
3833        assert_eq!(dialect.full_text_search(&[], "hello"), "0");
3834        // 单引号转义
3835        let sql = dialect.full_text_search(&["title"], "it's");
3836        assert!(sql.contains("it''s"));
3837    }
3838
3839    #[test]
3840    fn test_sqlserver_bool_to_int_and_concat() {
3841        let dialect = SqlServerDialect;
3842        assert_eq!(
3843            dialect.bool_to_int("active"),
3844            "(CASE WHEN active THEN 1 ELSE 0 END)"
3845        );
3846        assert_eq!(dialect.concat(&["a", "b", "c"]), "CONCAT(a, b, c)");
3847        assert_eq!(dialect.concat(&[]), "NULL");
3848    }
3849
3850    #[test]
3851    fn test_sqlserver_create_table() {
3852        let dialect = SqlServerDialect;
3853        let columns = vec![
3854            ColumnDef {
3855                name: "id".to_string(),
3856                sql_type: "BIGINT".to_string(),
3857                nullable: false,
3858                default: None,
3859                auto_increment: true,
3860                primary_key: true,
3861            },
3862            ColumnDef {
3863                name: "name".to_string(),
3864                sql_type: "VARCHAR(255)".to_string(),
3865                nullable: false,
3866                default: None,
3867                auto_increment: false,
3868                primary_key: false,
3869            },
3870            ColumnDef {
3871                name: "bio".to_string(),
3872                sql_type: "TEXT".to_string(),
3873                nullable: true,
3874                default: None,
3875                auto_increment: false,
3876                primary_key: false,
3877            },
3878            ColumnDef {
3879                name: "is_active".to_string(),
3880                sql_type: "BOOLEAN".to_string(),
3881                nullable: false,
3882                default: Some("1".to_string()),
3883                auto_increment: false,
3884                primary_key: false,
3885            },
3886        ];
3887        let sql = dialect.build_create_table("users", &columns);
3888        // 标识符使用方括号
3889        assert!(sql.contains("[users]"));
3890        assert!(sql.contains("[id]"));
3891        // 类型映射
3892        assert!(sql.contains("IDENTITY(1,1)"));
3893        assert!(
3894            sql.contains("NVARCHAR(255)"),
3895            "VARCHAR should map to NVARCHAR: {}",
3896            sql
3897        );
3898        assert!(
3899            sql.contains("NVARCHAR(MAX)"),
3900            "TEXT should map to NVARCHAR(MAX): {}",
3901            sql
3902        );
3903        assert!(sql.contains("BIT"), "BOOLEAN should map to BIT: {}", sql);
3904        assert!(sql.contains("PRIMARY KEY"));
3905        assert!(sql.contains("NOT NULL"));
3906        assert!(sql.contains("DEFAULT 1"));
3907    }
3908
3909    #[test]
3910    fn test_sqlserver_drop_table() {
3911        let dialect = SqlServerDialect;
3912        assert_eq!(
3913            dialect.build_drop_table("users", true),
3914            "DROP TABLE IF EXISTS [users]"
3915        );
3916        assert_eq!(
3917            dialect.build_drop_table("users", false),
3918            "DROP TABLE [users]"
3919        );
3920    }
3921
3922    #[test]
3923    fn test_sqlserver_alter_table() {
3924        let dialect = SqlServerDialect;
3925        // MODIFY COLUMN: SQL Server 使用 ALTER COLUMN(不是 MODIFY)
3926        let col = ColumnDef {
3927            name: "name".to_string(),
3928            sql_type: "VARCHAR(255)".to_string(),
3929            nullable: false,
3930            default: None,
3931            auto_increment: false,
3932            primary_key: false,
3933        };
3934        let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
3935        assert!(sql.contains("ALTER COLUMN"));
3936        assert!(sql.contains("NVARCHAR(255)"));
3937        assert!(!sql.contains("MODIFY"));
3938
3939        // ADD COLUMN
3940        let col = ColumnDef {
3941            name: "email".to_string(),
3942            sql_type: "VARCHAR(255)".to_string(),
3943            nullable: true,
3944            default: None,
3945            auto_increment: false,
3946            primary_key: false,
3947        };
3948        let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
3949        assert!(sql.contains("ADD [email]"));
3950        assert!(sql.contains("NVARCHAR(255)"));
3951
3952        // DROP COLUMN
3953        let sql =
3954            dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
3955        assert!(sql.contains("DROP COLUMN"));
3956        assert!(sql.contains("[email]"));
3957
3958        // DROP INDEX 必须指定表名
3959        let sql =
3960            dialect.build_alter_table("users", &[TableChange::DropIndex("idx_name".to_string())]);
3961        assert!(sql.contains("DROP INDEX idx_name ON [users]"));
3962    }
3963
3964    #[test]
3965    fn test_sqlserver_get_dialect() {
3966        // get_dialect 应返回 SqlServerDialect,而不是 MySqlDialect 回退
3967        let dialect = get_dialect(DbType::SqlServer);
3968        assert!(dialect.is_ok(), "SqlServer dialect should be available");
3969        let dialect = dialect.unwrap();
3970        assert_eq!(dialect.db_type(), DbType::SqlServer);
3971        // 验证不是 MySqlDialect 的回退:SQL Server 使用方括号,MySQL 使用反引号
3972        assert_eq!(dialect.quote("users"), "[users]");
3973        // SQL Server 特有功能
3974        assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
3975        assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
3976    }
3977
3978    #[test]
3979    fn test_clickhouse_get_dialect_unsupported() {
3980        // ClickHouse 现已支持独立方言(不再回退到 MySqlDialect)
3981        let dialect = get_dialect(DbType::ClickHouse);
3982        assert!(dialect.is_ok(), "ClickHouse should be supported");
3983        let dialect = dialect.unwrap();
3984        assert_eq!(dialect.db_type(), DbType::ClickHouse);
3985        // ClickHouse 使用 backquote 标识符(与 MySQL 一致)
3986        assert_eq!(dialect.quote("users"), "`users`");
3987        // ClickHouse 不支持 RETURNING
3988        assert!(!dialect.supports_returning());
3989        // ClickHouse 使用 LIMIT offset, limit 分页
3990        let sql = dialect.build_pagination("SELECT * FROM t", 2, 10);
3991        assert_eq!(sql, "SELECT * FROM t LIMIT 10, 10");
3992        // ClickHouse 自增列为空字符串
3993        assert_eq!(dialect.auto_increment_keyword(), "");
3994    }
3995
3996    #[test]
3997    fn test_get_dialect_all_supported_types() {
3998        // 所有声称为支持的方言应正确返回,不支持的应返回 Err
3999        assert!(get_dialect(DbType::MySQL).is_ok());
4000        assert!(get_dialect(DbType::PostgreSQL).is_ok());
4001        assert!(get_dialect(DbType::Sqlite).is_ok());
4002        assert!(get_dialect(DbType::Oracle).is_ok());
4003        assert!(get_dialect(DbType::SqlServer).is_ok());
4004        assert!(get_dialect(DbType::OceanBase).is_ok());
4005        assert!(get_dialect(DbType::ClickHouse).is_ok());
4006        // 国产数据库 & 兼容方言
4007        assert!(get_dialect(DbType::Dameng).is_ok());
4008        assert!(get_dialect(DbType::Kingbase).is_ok());
4009        assert!(get_dialect(DbType::Db2).is_ok());
4010        assert!(get_dialect(DbType::MariaDB).is_ok());
4011        assert!(get_dialect(DbType::TiDB).is_ok());
4012        assert!(get_dialect(DbType::PolarDB).is_ok());
4013        assert!(get_dialect(DbType::GaussDB).is_ok());
4014        assert!(get_dialect(DbType::GBase).is_ok());
4015        assert!(get_dialect(DbType::Sybase).is_ok());
4016        // 不支持标准 SQL 的
4017        assert!(get_dialect(DbType::Redis).is_err());
4018        assert!(get_dialect(DbType::MongoDB).is_err());
4019        assert!(get_dialect(DbType::VectorDb).is_err());
4020        assert!(get_dialect(DbType::PureJsDb).is_err());
4021    }
4022
4023    // ===== 国产数据库兼容方言测试 =====
4024
4025    #[test]
4026    fn test_mariadb_dialect() {
4027        let dialect = get_dialect(DbType::MariaDB).unwrap();
4028        assert_eq!(dialect.db_type(), DbType::MariaDB);
4029        // MariaDB 兼容 MySQL 语法:backquote 标识符 + 反斜杠转义
4030        assert_eq!(dialect.quote("users"), "`users`");
4031        assert_eq!(dialect.escape_string("it's"), "it\\'s");
4032        assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
4033        // MySQL 家族不支持 RETURNING(MySQL 协议层限制)
4034        assert!(!dialect.supports_returning());
4035    }
4036
4037    #[test]
4038    fn test_tidb_dialect() {
4039        let dialect = get_dialect(DbType::TiDB).unwrap();
4040        assert_eq!(dialect.db_type(), DbType::TiDB);
4041        // TiDB 兼容 MySQL 语法
4042        assert_eq!(dialect.quote("users"), "`users`");
4043        assert_eq!(dialect.escape_string("it's"), "it\\'s");
4044        assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
4045    }
4046
4047    #[test]
4048    fn test_dameng_dialect() {
4049        let dialect = get_dialect(DbType::Dameng).unwrap();
4050        assert_eq!(dialect.db_type(), DbType::Dameng);
4051        // 达梦兼容 Oracle 语法:双引号标识符 + 单引号双写
4052        assert_eq!(dialect.quote("users"), "\"users\"");
4053        assert_eq!(dialect.escape_string("it's"), "it''s");
4054        // Oracle 兼容:使用 IDENTITY 列
4055        assert_eq!(
4056            dialect.auto_increment_keyword(),
4057            "GENERATED BY DEFAULT AS IDENTITY"
4058        );
4059        // Oracle 兼容:支持 RETURNING
4060        assert!(dialect.supports_returning());
4061    }
4062
4063    #[test]
4064    fn test_kingbase_dialect() {
4065        let dialect = get_dialect(DbType::Kingbase).unwrap();
4066        assert_eq!(dialect.db_type(), DbType::Kingbase);
4067        // 人大金仓兼容 PostgreSQL 语法:双引号标识符 + 单引号双写
4068        assert_eq!(dialect.quote("users"), "\"users\"");
4069        assert_eq!(dialect.escape_string("it's"), "it''s");
4070        // PG 兼容:支持 RETURNING
4071        assert!(dialect.supports_returning());
4072        // PG 兼容:使用 GENERATED BY DEFAULT AS IDENTITY(PG 10+ 标准)
4073        assert_eq!(
4074            dialect.auto_increment_keyword(),
4075            "GENERATED BY DEFAULT AS IDENTITY"
4076        );
4077    }
4078
4079    #[test]
4080    fn test_polardb_dialect() {
4081        let dialect = get_dialect(DbType::PolarDB).unwrap();
4082        assert_eq!(dialect.db_type(), DbType::PolarDB);
4083        // PolarDB-PG 兼容 PostgreSQL
4084        assert_eq!(dialect.quote("users"), "\"users\"");
4085        assert!(dialect.supports_returning());
4086    }
4087
4088    #[test]
4089    fn test_gaussdb_dialect() {
4090        let dialect = get_dialect(DbType::GaussDB).unwrap();
4091        assert_eq!(dialect.db_type(), DbType::GaussDB);
4092        // GaussDB 兼容 PostgreSQL
4093        assert_eq!(dialect.quote("users"), "\"users\"");
4094        assert!(dialect.supports_returning());
4095    }
4096
4097    #[test]
4098    fn test_gbase_dialect() {
4099        let dialect = get_dialect(DbType::GBase).unwrap();
4100        assert_eq!(dialect.db_type(), DbType::GBase);
4101        // GBase 8s 兼容 T-SQL(使用方括号)
4102        assert_eq!(dialect.quote("users"), "[users]");
4103    }
4104
4105    #[test]
4106    fn test_sybase_dialect() {
4107        let dialect = get_dialect(DbType::Sybase).unwrap();
4108        assert_eq!(dialect.db_type(), DbType::Sybase);
4109        // Sybase ASE 兼容 T-SQL
4110        assert_eq!(dialect.quote("users"), "[users]");
4111    }
4112
4113    // ===== DB2 独立方言测试 =====
4114
4115    #[test]
4116    fn test_db2_dialect_basic() {
4117        let dialect = get_dialect(DbType::Db2).unwrap();
4118        assert_eq!(dialect.db_type(), DbType::Db2);
4119        // DB2 使用双引号标识符
4120        assert_eq!(dialect.quote("users"), "\"users\"");
4121        // DB2 单引号双写
4122        assert_eq!(dialect.escape_string("it's"), "it''s");
4123        // DB2 使用 IDENTITY
4124        assert_eq!(
4125            dialect.auto_increment_keyword(),
4126            "GENERATED ALWAYS AS IDENTITY"
4127        );
4128        // DB2 不支持 IF EXISTS / IF NOT EXISTS(11.5 之前)
4129        assert!(!dialect.supports_if_exists());
4130        assert!(!dialect.supports_if_not_exists());
4131        // DB2 不支持 RETURNING(使用 SELECT FROM FINAL TABLE 代替)
4132        assert!(!dialect.supports_returning());
4133    }
4134
4135    #[test]
4136    fn test_db2_pagination() {
4137        let dialect = Db2Dialect;
4138        // DB2 使用 OFFSET x ROWS FETCH NEXT y ROWS ONLY
4139        let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
4140        assert_eq!(
4141            sql,
4142            "SELECT * FROM users OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY"
4143        );
4144    }
4145
4146    #[test]
4147    fn test_db2_last_insert_id() {
4148        let dialect = Db2Dialect;
4149        // DB2 使用 IDENTITY_VAL_LOCAL() 获取最后插入的 IDENTITY
4150        assert_eq!(
4151            dialect.last_insert_id_sql(),
4152            Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
4153        );
4154    }
4155
4156    #[test]
4157    fn test_db2_concat() {
4158        let dialect = Db2Dialect;
4159        // DB2 使用 || 拼接
4160        assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
4161        assert_eq!(dialect.concat(&[]), "''");
4162    }
4163
4164    #[test]
4165    fn test_db2_create_table() {
4166        let dialect = Db2Dialect;
4167        let cols = vec![ColumnDef {
4168            name: "id".to_string(),
4169            sql_type: "BIGINT".to_string(),
4170            nullable: false,
4171            default: None,
4172            auto_increment: true,
4173            primary_key: true,
4174        }];
4175        let sql = dialect.build_create_table("users", &cols);
4176        assert!(sql.contains("\"id\" BIGINT"));
4177        assert!(sql.contains("GENERATED ALWAYS AS IDENTITY"));
4178        assert!(sql.contains("PRIMARY KEY"));
4179    }
4180
4181    #[test]
4182    fn test_db2_type_mapping() {
4183        // 验证通用类型到 DB2 类型的映射
4184        assert_eq!(map_to_db2_type("BIGINT"), "BIGINT");
4185        assert_eq!(map_to_db2_type("INT"), "INTEGER");
4186        assert_eq!(map_to_db2_type("INTEGER"), "INTEGER");
4187        assert_eq!(map_to_db2_type("TINYINT"), "SMALLINT");
4188        assert_eq!(map_to_db2_type("SMALLINT"), "SMALLINT");
4189        assert_eq!(map_to_db2_type("TEXT"), "CLOB(2G)");
4190        assert_eq!(map_to_db2_type("LONGTEXT"), "CLOB(2G)");
4191        assert_eq!(map_to_db2_type("BOOLEAN"), "SMALLINT");
4192        assert_eq!(map_to_db2_type("BOOL"), "SMALLINT");
4193        assert_eq!(map_to_db2_type("DATETIME"), "TIMESTAMP");
4194        assert_eq!(map_to_db2_type("TIMESTAMP"), "TIMESTAMP");
4195        assert_eq!(map_to_db2_type("DATE"), "DATE");
4196        assert_eq!(map_to_db2_type("VARCHAR(255)"), "VARCHAR(255)");
4197    }
4198
4199    // ===== ClickHouse 独立方言测试 =====
4200
4201    #[test]
4202    fn test_clickhouse_dialect_basic() {
4203        let dialect = get_dialect(DbType::ClickHouse).unwrap();
4204        assert_eq!(dialect.db_type(), DbType::ClickHouse);
4205        // ClickHouse 使用 backquote 标识符(与 MySQL 一致)
4206        assert_eq!(dialect.quote("users"), "`users`");
4207        // ClickHouse 反斜杠转义(与 MySQL 一致)
4208        assert_eq!(dialect.escape_string("it's"), "it\\'s");
4209        // ClickHouse 不支持 RETURNING
4210        assert!(!dialect.supports_returning());
4211        // ClickHouse 不支持自增列
4212        assert_eq!(dialect.auto_increment_keyword(), "");
4213        // ClickHouse 支持 IF EXISTS / IF NOT EXISTS
4214        assert!(dialect.supports_if_exists());
4215        assert!(dialect.supports_if_not_exists());
4216    }
4217
4218    #[test]
4219    fn test_clickhouse_type_mapping() {
4220        assert_eq!(map_to_clickhouse_type("BIGINT"), "Int64");
4221        assert_eq!(map_to_clickhouse_type("INT"), "Int32");
4222        assert_eq!(map_to_clickhouse_type("INTEGER"), "Int32");
4223        assert_eq!(map_to_clickhouse_type("TINYINT"), "Int16");
4224        assert_eq!(map_to_clickhouse_type("SMALLINT"), "Int16");
4225        assert_eq!(map_to_clickhouse_type("VARCHAR(255)"), "String");
4226        assert_eq!(map_to_clickhouse_type("TEXT"), "String");
4227        assert_eq!(map_to_clickhouse_type("BOOLEAN"), "UInt8");
4228        assert_eq!(map_to_clickhouse_type("BOOL"), "UInt8");
4229        assert_eq!(map_to_clickhouse_type("FLOAT"), "Float32");
4230        assert_eq!(map_to_clickhouse_type("DOUBLE"), "Float64");
4231        assert_eq!(map_to_clickhouse_type("DATETIME"), "DateTime");
4232        assert_eq!(map_to_clickhouse_type("TIMESTAMP"), "DateTime");
4233        assert_eq!(map_to_clickhouse_type("DATE"), "Date");
4234    }
4235
4236    #[test]
4237    fn test_clickhouse_create_table() {
4238        let dialect = ClickHouseDialect;
4239        let cols = vec![ColumnDef {
4240            name: "id".to_string(),
4241            sql_type: "BIGINT".to_string(),
4242            nullable: false,
4243            default: None,
4244            auto_increment: false, // ClickHouse 不支持自增
4245            primary_key: true,
4246        }];
4247        let sql = dialect.build_create_table("users", &cols);
4248        // 必须包含 ENGINE = MergeTree()
4249        assert!(
4250            sql.contains("ENGINE = MergeTree()"),
4251            "ClickHouse CREATE TABLE 必须指定 ENGINE: {}",
4252            sql
4253        );
4254        assert!(sql.contains("`id` Int64"));
4255        assert!(sql.contains("PRIMARY KEY"));
4256    }
4257
4258    #[test]
4259    fn test_clickhouse_json_extract() {
4260        let dialect = ClickHouseDialect;
4261        let sql = dialect.json_extract("data", "$.name");
4262        assert!(
4263            sql.contains("JSONExtractString"),
4264            "ClickHouse 应使用 JSONExtractString: {}",
4265            sql
4266        );
4267    }
4268
4269    #[test]
4270    fn test_clickhouse_concat() {
4271        let dialect = ClickHouseDialect;
4272        // ClickHouse 使用 concat() 函数
4273        assert_eq!(dialect.concat(&["a", "b", "c"]), "concat(a, b, c)");
4274        assert_eq!(dialect.concat(&[]), "''");
4275    }
4276
4277    // ===== DbType 国产数据库变体测试 =====
4278
4279    #[test]
4280    fn test_db_type_dameng_str() {
4281        assert_eq!(DbType::Dameng.as_str(), "dameng");
4282        assert_eq!(DbType::from_str("dameng"), Some(DbType::Dameng));
4283        assert_eq!(DbType::from_str("DM"), Some(DbType::Dameng));
4284        assert_eq!(DbType::from_str("dm8"), Some(DbType::Dameng));
4285        assert_eq!(DbType::Dameng.default_port(), 5236);
4286    }
4287
4288    #[test]
4289    fn test_db_type_kingbase_str() {
4290        assert_eq!(DbType::Kingbase.as_str(), "kingbase");
4291        assert_eq!(DbType::from_str("kingbase"), Some(DbType::Kingbase));
4292        assert_eq!(DbType::Kingbase.default_port(), 54321);
4293    }
4294
4295    #[test]
4296    fn test_db_type_db2_str() {
4297        assert_eq!(DbType::Db2.as_str(), "db2");
4298        assert_eq!(DbType::from_str("db2"), Some(DbType::Db2));
4299        assert_eq!(DbType::Db2.default_port(), 50000);
4300    }
4301
4302    #[test]
4303    fn test_db_type_mariadb_str() {
4304        assert_eq!(DbType::MariaDB.as_str(), "mariadb");
4305        assert_eq!(DbType::from_str("mariadb"), Some(DbType::MariaDB));
4306        assert_eq!(DbType::MariaDB.default_port(), 3306);
4307    }
4308
4309    #[test]
4310    fn test_db_type_tidb_str() {
4311        assert_eq!(DbType::TiDB.as_str(), "tidb");
4312        assert_eq!(DbType::from_str("tidb"), Some(DbType::TiDB));
4313        assert_eq!(DbType::TiDB.default_port(), 4000);
4314    }
4315
4316    #[test]
4317    fn test_db_type_polardb_str() {
4318        assert_eq!(DbType::PolarDB.as_str(), "polardb");
4319        assert_eq!(DbType::from_str("polardb"), Some(DbType::PolarDB));
4320        assert_eq!(DbType::PolarDB.default_port(), 5432);
4321    }
4322
4323    #[test]
4324    fn test_db_type_gaussdb_str() {
4325        assert_eq!(DbType::GaussDB.as_str(), "gaussdb");
4326        assert_eq!(DbType::from_str("gaussdb"), Some(DbType::GaussDB));
4327        assert_eq!(DbType::GaussDB.default_port(), 25308);
4328    }
4329
4330    #[test]
4331    fn test_db_type_gbase_str() {
4332        assert_eq!(DbType::GBase.as_str(), "gbase");
4333        assert_eq!(DbType::from_str("gbase"), Some(DbType::GBase));
4334        assert_eq!(DbType::GBase.default_port(), 9088);
4335    }
4336
4337    #[test]
4338    fn test_db_type_sybase_str() {
4339        assert_eq!(DbType::Sybase.as_str(), "sybase");
4340        assert_eq!(DbType::from_str("sybase"), Some(DbType::Sybase));
4341        assert_eq!(DbType::Sybase.default_port(), 5000);
4342    }
4343
4344    #[test]
4345    fn test_db_type_family_classification() {
4346        // MySQL 家族
4347        assert!(DbType::MySQL.is_mysql_family());
4348        assert!(DbType::MariaDB.is_mysql_family());
4349        assert!(DbType::TiDB.is_mysql_family());
4350        assert!(DbType::OceanBase.is_mysql_family());
4351        assert!(!DbType::PostgreSQL.is_mysql_family());
4352
4353        // PostgreSQL 家族
4354        assert!(DbType::PostgreSQL.is_postgres_family());
4355        assert!(DbType::Kingbase.is_postgres_family());
4356        assert!(DbType::GaussDB.is_postgres_family());
4357        assert!(!DbType::MySQL.is_postgres_family());
4358
4359        // Oracle 家族
4360        assert!(DbType::Oracle.is_oracle_family());
4361        assert!(DbType::Dameng.is_oracle_family());
4362        assert!(!DbType::MySQL.is_oracle_family());
4363    }
4364
4365    #[test]
4366    fn test_db_type_supports_stored_procedure_extended() {
4367        // 所有 SQL 数据库应支持存储过程
4368        assert!(DbType::Dameng.supports_stored_procedure());
4369        assert!(DbType::Kingbase.supports_stored_procedure());
4370        assert!(DbType::Db2.supports_stored_procedure());
4371        assert!(DbType::MariaDB.supports_stored_procedure());
4372        assert!(DbType::TiDB.supports_stored_procedure());
4373        assert!(DbType::PolarDB.supports_stored_procedure());
4374        assert!(DbType::GaussDB.supports_stored_procedure());
4375        assert!(DbType::GBase.supports_stored_procedure());
4376        assert!(DbType::Sybase.supports_stored_procedure());
4377    }
4378
4379    // ===== L-4 修复:表名/列名长度校验 =====
4380
4381    #[test]
4382    fn test_l4_max_identifier_len_constant() {
4383        // MAX_IDENTIFIER_LEN 应为 63(PostgreSQL 最严格值)
4384        assert_eq!(MAX_IDENTIFIER_LEN, 63);
4385    }
4386
4387    #[test]
4388    fn test_l4_quote_checked_valid_identifier() {
4389        let dialect = MySqlDialect;
4390        assert_eq!(dialect.quote_checked("users").unwrap(), "`users`");
4391        assert_eq!(dialect.quote_checked("user_id").unwrap(), "`user_id`");
4392        // 边界:恰好 63 字符
4393        let name_63 = "a".repeat(63);
4394        assert!(dialect.quote_checked(&name_63).is_ok());
4395    }
4396
4397    #[test]
4398    fn test_l4_quote_checked_rejects_too_long() {
4399        let dialect = MySqlDialect;
4400        let long_name = "a".repeat(64); // 64 > 63
4401        let result = dialect.quote_checked(&long_name);
4402        assert!(result.is_err());
4403        match result {
4404            Err(DbError::InvalidInput(msg)) => {
4405                assert!(
4406                    msg.contains("too long"),
4407                    "expected 'too long' error, got: {}",
4408                    msg
4409                );
4410            }
4411            _ => panic!("Expected DbError::InvalidInput"),
4412        }
4413    }
4414
4415    #[test]
4416    fn test_l4_quote_checked_rejects_empty() {
4417        let dialect = MySqlDialect;
4418        let result = dialect.quote_checked("");
4419        assert!(result.is_err());
4420    }
4421
4422    #[test]
4423    fn test_l4_quote_checked_rejects_sql_injection() {
4424        let dialect = MySqlDialect;
4425        // 含分号
4426        assert!(dialect.quote_checked("users; DROP TABLE users").is_err());
4427        // 含引号
4428        assert!(dialect.quote_checked("user'name").is_err());
4429        // 含空格
4430        assert!(dialect.quote_checked("user name").is_err());
4431        // 数字开头
4432        assert!(dialect.quote_checked("1users").is_err());
4433        // 含点号
4434        assert!(dialect.quote_checked("schema.table").is_err());
4435    }
4436
4437    #[test]
4438    fn test_l4_quote_checked_postgres() {
4439        let dialect = PostgreSqlDialect;
4440        assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4441        assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4442    }
4443
4444    #[test]
4445    fn test_l4_quote_checked_sqlite() {
4446        let dialect = SqliteDialect;
4447        assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4448        assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4449    }
4450
4451    #[test]
4452    fn test_l4_quote_checked_oracle() {
4453        let dialect = OracleDialect;
4454        assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
4455        assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4456    }
4457
4458    #[test]
4459    fn test_l4_quote_checked_sql_server() {
4460        let dialect = SqlServerDialect;
4461        assert_eq!(dialect.quote_checked("users").unwrap(), "[users]");
4462        assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
4463    }
4464
4465    // ---- DuckDB 方言测试(TASK-033~036) ----
4466
4467    #[test]
4468    fn test_duckdb_quote() {
4469        let dialect = DuckDBDialect;
4470        assert_eq!(dialect.quote("users"), "\"users\"");
4471        assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
4472    }
4473
4474    #[test]
4475    fn test_duckdb_escape() {
4476        let dialect = DuckDBDialect;
4477        assert_eq!(dialect.escape_string("hello"), "hello");
4478        assert_eq!(dialect.escape_string("it's"), "it''s");
4479    }
4480
4481    #[test]
4482    fn test_duckdb_pagination() {
4483        let dialect = DuckDBDialect;
4484        let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
4485        assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
4486    }
4487
4488    #[test]
4489    fn test_duckdb_supports() {
4490        let dialect = DuckDBDialect;
4491        assert!(!dialect.supports_returning());
4492        assert!(!dialect.supports_lock_for_update());
4493        assert!(!dialect.supports_lock_shared());
4494        assert!(dialect.supports_if_exists());
4495        assert!(dialect.supports_if_not_exists());
4496    }
4497
4498    #[test]
4499    fn test_duckdb_insert_or_ignore() {
4500        let dialect = DuckDBDialect;
4501        let sql = dialect.build_insert_or_ignore_prefix("users");
4502        assert_eq!(sql, "INSERT OR IGNORE INTO \"users\"");
4503    }
4504
4505    #[test]
4506    fn test_duckdb_create_table() {
4507        let dialect = DuckDBDialect;
4508        let columns = vec![
4509            ColumnDef {
4510                name: "id".to_string(),
4511                sql_type: "BIGINT".to_string(),
4512                nullable: false,
4513                default: None,
4514                auto_increment: true,
4515                primary_key: true,
4516            },
4517            ColumnDef {
4518                name: "name".to_string(),
4519                sql_type: "VARCHAR(255)".to_string(),
4520                nullable: false,
4521                default: None,
4522                auto_increment: false,
4523                primary_key: false,
4524            },
4525        ];
4526        let sql = dialect.build_create_table("users", &columns);
4527        assert!(sql.contains("CREATE TABLE \"users\""));
4528        assert!(sql.contains("\"id\" BIGINT PRIMARY KEY"));
4529        assert!(sql.contains("\"name\" VARCHAR(255)"));
4530    }
4531
4532    #[test]
4533    fn test_duckdb_alter_table() {
4534        let dialect = DuckDBDialect;
4535        let changes = vec![TableChange::AddColumn(ColumnDef {
4536            name: "age".to_string(),
4537            sql_type: "INTEGER".to_string(),
4538            nullable: true,
4539            default: None,
4540            auto_increment: false,
4541            primary_key: false,
4542        })];
4543        let sql = dialect.build_alter_table("users", &changes);
4544        assert!(sql.contains("ALTER TABLE \"users\" ADD COLUMN \"age\" INTEGER"));
4545    }
4546
4547    #[test]
4548    fn test_duckdb_json_extract() {
4549        let dialect = DuckDBDialect;
4550        let sql = dialect.json_extract("data", "$.user.name");
4551        assert!(sql.contains("->"));
4552        assert!(sql.contains("user.name"));
4553    }
4554
4555    #[test]
4556    fn test_duckdb_concat() {
4557        let dialect = DuckDBDialect;
4558        let sql = dialect.concat(&["a", "b", "c"]);
4559        assert_eq!(sql, "a || b || c");
4560    }
4561
4562    #[test]
4563    fn test_duckdb_bool_to_int() {
4564        let dialect = DuckDBDialect;
4565        let sql = dialect.bool_to_int("active");
4566        assert!(sql.contains("CASE WHEN"));
4567        assert!(sql.contains("THEN 1"));
4568        assert!(sql.contains("ELSE 0"));
4569    }
4570
4571    #[test]
4572    fn test_get_dialect_duckdb() {
4573        let result = get_dialect(DbType::DuckDB);
4574        assert!(result.is_ok());
4575        let dialect = result.unwrap();
4576        assert_eq!(dialect.db_type(), DbType::DuckDB);
4577    }
4578}
4579
4580// ============================================================================
4581// v3.4.0 M3-T3:enum dispatch 优化(perf-enum-dispatch feature)
4582// ============================================================================
4583
4584/// 方言种类枚举(用于 match 分发替代 `Box<dyn Dialect>` vtable 查找)
4585///
4586/// 当 `perf-enum-dispatch` feature 启用时,可通过 `DialectKind` 直接 match 分发
4587/// 到具体方言实现,避免动态分发的 vtable 间接寻址开销。
4588#[cfg(feature = "perf-enum-dispatch")]
4589#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4590pub enum DialectKind {
4591    /// MySQL 方言
4592    MySQL,
4593    /// PostgreSQL 方言
4594    PostgreSQL,
4595    /// SQLite 方言
4596    SQLite,
4597    /// Oracle 方言
4598    Oracle,
4599    /// SQL Server 方言
4600    MSSQL,
4601}
4602
4603#[cfg(feature = "perf-enum-dispatch")]
4604impl DialectKind {
4605    /// 从 `DbType` 创建 `DialectKind`(不支持返回 None)
4606    pub fn from_db_type(db_type: DbType) -> Option<Self> {
4607        match db_type {
4608            DbType::MySQL | DbType::MariaDB | DbType::TiDB | DbType::OceanBase => Some(Self::MySQL),
4609            DbType::PostgreSQL | DbType::Kingbase | DbType::PolarDB | DbType::GaussDB => {
4610                Some(Self::PostgreSQL)
4611            }
4612            DbType::Sqlite => Some(Self::SQLite),
4613            DbType::Oracle | DbType::Dameng => Some(Self::Oracle),
4614            DbType::SqlServer | DbType::Sybase | DbType::GBase => Some(Self::MSSQL),
4615            _ => None,
4616        }
4617    }
4618
4619    /// 引用标识符(match 分发,避免 vtable 查找)
4620    pub fn quote(&self, identifier: &str) -> String {
4621        match self {
4622            Self::MySQL => MySqlDialect.quote(identifier),
4623            Self::PostgreSQL => PostgreSqlDialect.quote(identifier),
4624            Self::SQLite => SqliteDialect.quote(identifier),
4625            Self::Oracle => OracleDialect.quote(identifier),
4626            Self::MSSQL => SqlServerDialect.quote(identifier),
4627        }
4628    }
4629
4630    /// 转义字符串字面量(match 分发)
4631    pub fn escape_string(&self, s: &str) -> String {
4632        match self {
4633            Self::MySQL => MySqlDialect.escape_string(s),
4634            Self::PostgreSQL => PostgreSqlDialect.escape_string(s),
4635            Self::SQLite => SqliteDialect.escape_string(s),
4636            Self::Oracle => OracleDialect.escape_string(s),
4637            Self::MSSQL => SqlServerDialect.escape_string(s),
4638        }
4639    }
4640
4641    /// 返回对应的 `DbType`
4642    pub fn db_type(&self) -> DbType {
4643        match self {
4644            Self::MySQL => DbType::MySQL,
4645            Self::PostgreSQL => DbType::PostgreSQL,
4646            Self::SQLite => DbType::Sqlite,
4647            Self::Oracle => DbType::Oracle,
4648            Self::MSSQL => DbType::SqlServer,
4649        }
4650    }
4651
4652    /// 转为 `Box<dyn Dialect>`
4653    pub fn to_dialect(&self) -> Box<dyn Dialect> {
4654        match self {
4655            Self::MySQL => Box::new(MySqlDialect),
4656            Self::PostgreSQL => Box::new(PostgreSqlDialect),
4657            Self::SQLite => Box::new(SqliteDialect),
4658            Self::Oracle => Box::new(OracleDialect),
4659            Self::MSSQL => Box::new(SqlServerDialect),
4660        }
4661    }
4662}
4663
4664#[cfg(all(test, feature = "perf-enum-dispatch"))]
4665mod enum_dispatch_tests {
4666    use super::*;
4667
4668    #[test]
4669    fn test_dialect_kind_from_db_type() {
4670        assert_eq!(
4671            DialectKind::from_db_type(DbType::MySQL),
4672            Some(DialectKind::MySQL)
4673        );
4674        assert_eq!(
4675            DialectKind::from_db_type(DbType::PostgreSQL),
4676            Some(DialectKind::PostgreSQL)
4677        );
4678        assert_eq!(
4679            DialectKind::from_db_type(DbType::Sqlite),
4680            Some(DialectKind::SQLite)
4681        );
4682        assert_eq!(
4683            DialectKind::from_db_type(DbType::Oracle),
4684            Some(DialectKind::Oracle)
4685        );
4686        assert_eq!(
4687            DialectKind::from_db_type(DbType::SqlServer),
4688            Some(DialectKind::MSSQL)
4689        );
4690        assert_eq!(DialectKind::from_db_type(DbType::Redis), None);
4691    }
4692
4693    #[test]
4694    fn test_dialect_kind_quote_diff() {
4695        let kinds = [
4696            DialectKind::MySQL,
4697            DialectKind::PostgreSQL,
4698            DialectKind::SQLite,
4699            DialectKind::Oracle,
4700            DialectKind::MSSQL,
4701        ];
4702        for kind in &kinds {
4703            let enum_result = kind.quote("users");
4704            let box_result = kind.to_dialect().quote("users");
4705            assert_eq!(enum_result, box_result);
4706        }
4707    }
4708
4709    #[test]
4710    fn test_dialect_kind_escape_diff() {
4711        let kinds = [
4712            DialectKind::MySQL,
4713            DialectKind::PostgreSQL,
4714            DialectKind::SQLite,
4715            DialectKind::Oracle,
4716            DialectKind::MSSQL,
4717        ];
4718        for kind in &kinds {
4719            let enum_result = kind.escape_string("it's");
4720            let box_result = kind.to_dialect().escape_string("it's");
4721            assert_eq!(enum_result, box_result);
4722        }
4723    }
4724}