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