Skip to main content

sz_orm_core/
dialect.rs

1//! 不同数据库的方言抽象
2//!
3//! 为数据库特定的 SQL 语法提供统一接口
4
5pub use crate::db_type::DbType;
6use crate::error::DbError;
7use std::fmt;
8
9/// L-4 修复:SQL 标识符最大长度(取所有主流数据库最严格值)
10///
11/// - PostgreSQL: 63 chars (NAMEDATALEN default 64, minus 1)
12/// - MySQL: 64 chars
13/// - Oracle: 30 chars (12.2R2 之前) / 128 chars (12.2R2+)
14/// - SQL Server: 128 chars
15/// - SQLite: 实际无限制(但建议遵守 63)
16///
17/// 取 63 作为最严格值,覆盖所有主流数据库。
18pub const MAX_IDENTIFIER_LEN: usize = 63;
19
20/// 数据库方言 trait
21///
22/// 实现者负责处理各数据库特有的 SQL 语法差异
23pub trait Dialect: Send + Sync {
24    /// 克隆为 trait object(用于 `QueryBuilder::clone_for_count`)。
25    fn clone_box(&self) -> Box<dyn Dialect>;
26
27    /// 返回该方言对应的数据库类型
28    fn db_type(&self) -> DbType;
29
30    /// 引用标识符(表名、列名等)
31    fn quote(&self, identifier: &str) -> String;
32
33    /// L-4 修复:带校验的引用标识符
34    ///
35    /// 与 `quote()` 不同,此方法会先校验标识符:
36    /// - 非空
37    /// - 长度 ≤ `MAX_IDENTIFIER_LEN` (63 chars)
38    /// - 不含 SQL 元字符(引号、分号、空格、注释等)
39    ///
40    /// 校验失败时返回 `DbError::InvalidInput`。
41    ///
42    /// 建议在调用方不可信的场景(如用户输入的表名/列名)使用此方法替代 `quote()`。
43    fn quote_checked(&self, identifier: &str) -> Result<String, DbError> {
44        crate::sql_safety::validate_identifier(identifier, "identifier")?;
45        Ok(self.quote(identifier))
46    }
47
48    /// 转义字符串字面量,确保可安全嵌入 SQL
49    fn escape_string(&self, s: &str) -> String;
50
51    /// 该方言是否支持 RETURNING 子句
52    fn supports_returning(&self) -> bool;
53
54    /// 生成分页 SQL
55    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String;
56
57    /// 获取 JSON 类型的 SQL 类型名
58    fn json_type(&self) -> &'static str;
59
60    /// 生成 JSON_EXTRACT 函数调用
61    fn json_extract(&self, column: &str, path: &str) -> String;
62
63    /// 生成全文检索 SQL
64    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String;
65
66    /// 将布尔表达式转换为整型存储
67    fn bool_to_int(&self, expr: &str) -> String;
68
69    /// 生成 CONCAT 函数调用
70    fn concat(&self, parts: &[&str]) -> String;
71
72    /// 该方言是否支持 IF EXISTS
73    fn supports_if_exists(&self) -> bool;
74
75    /// 该方言是否支持 IF NOT EXISTS
76    fn supports_if_not_exists(&self) -> bool;
77
78    /// 获取自增列关键字
79    fn auto_increment_keyword(&self) -> &'static str;
80
81    /// 获取最近插入 ID 的 SQL(独立可执行语句)
82    ///
83    /// 返回 `None` 表示该方言不支持以独立 SQL 获取最后插入 ID(如 Oracle 只能通过
84    /// 在 INSERT 语句末尾附加 `RETURNING ... INTO :bind` 子句的方式获取,无法独立执行)。
85    /// 调用方在拿到 `None` 时必须改用 `supports_returning()` + 在 INSERT 后追加 RETURNING。
86    fn last_insert_id_sql(&self) -> Option<&'static str>;
87
88    /// 生成 CREATE TABLE 语句
89    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String;
90
91    /// 生成 ALTER TABLE 语句
92    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String;
93
94    /// 生成 DROP TABLE 语句
95    ///
96    /// 默认实现生成标准 `DROP TABLE [IF EXISTS] <table>` 语法。
97    /// 不支持 `IF EXISTS` 的方言(如 DB2)应覆盖此方法。
98    fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
99        if if_exists && self.supports_if_exists() {
100            format!("DROP TABLE IF EXISTS {}", self.quote(table))
101        } else {
102            format!("DROP TABLE {}", self.quote(table))
103        }
104    }
105
106    /// P2-6:生成批量 upsert 的冲突处理子句(ON CONFLICT / ON DUPLICATE KEY UPDATE)
107    ///
108    /// 参数(均为原始列名,由各方言自行 quote):
109    /// - `conflict_columns`: 冲突检测列(唯一键/主键),用于 `ON CONFLICT (cols)` 或 `ON DUPLICATE KEY`
110    /// - `update_columns`: 冲突时需要更新的列;空切片表示更新所有 `all_columns` 中非冲突列
111    /// - `all_columns`: 本次 INSERT 的全部列名(原始未 quote),用于确定"更新所有非冲突列"的范围
112    ///
113    /// 返回 `None` 表示该方言不支持 upsert(如 ClickHouse、Db2)。
114    /// 返回 `Some(clause)` 表示完整的冲突处理子句(不含前导空格),如:
115    /// - MySQL: `ON DUPLICATE KEY UPDATE \`c1\`=VALUES(\`c1\`), \`c2\`=VALUES(\`c2\`)`
116    /// - PG/SQLite: `ON CONFLICT ("c1") DO UPDATE SET "c2"=EXCLUDED."c2"`
117    ///
118    /// **L3 实现深度**:返回的子句使用参数化占位符(`VALUES(col)` 或 `EXCLUDED.col`),
119    /// 不拼接用户值,杜绝 SQL 注入。
120    fn build_upsert_on_conflict(
121        &self,
122        conflict_columns: &[&str],
123        update_columns: &[&str],
124        all_columns: &[String],
125    ) -> Option<String> {
126        let _ = (conflict_columns, update_columns, all_columns);
127        None
128    }
129}
130
131/// 建表时的列定义
132#[derive(Debug, Clone)]
133pub struct ColumnDef {
134    pub name: String,
135    pub sql_type: String,
136    pub nullable: bool,
137    pub default: Option<String>,
138    pub auto_increment: bool,
139    pub primary_key: bool,
140}
141
142/// ALTER TABLE 的变更操作
143#[derive(Debug, Clone)]
144pub enum TableChange {
145    AddColumn(ColumnDef),
146    DropColumn(String),
147    ModifyColumn(ColumnDef),
148    AddIndex(String, Vec<String>),
149    DropIndex(String),
150    AddForeignKey {
151        columns: Vec<String>,
152        reference_table: String,
153        reference_columns: Vec<String>,
154    },
155}
156
157/// MySQL 方言实现
158#[derive(Debug, Clone)]
159pub struct MySqlDialect;
160
161impl Dialect for MySqlDialect {
162    fn clone_box(&self) -> Box<dyn Dialect> {
163        Box::new(MySqlDialect)
164    }
165
166    fn db_type(&self) -> DbType {
167        DbType::MySQL
168    }
169
170    fn quote(&self, identifier: &str) -> String {
171        format!("`{}`", identifier.replace('`', "``"))
172    }
173
174    fn escape_string(&self, s: &str) -> String {
175        let mut escaped = String::with_capacity(s.len() * 2);
176        for c in s.chars() {
177            match c {
178                '\\' => escaped.push_str("\\\\"),
179                '\'' => escaped.push_str("\\'"),
180                '\0' => escaped.push_str("\\0"),
181                '\n' => escaped.push_str("\\n"),
182                '\r' => escaped.push_str("\\r"),
183                '\t' => escaped.push_str("\\t"),
184                '\x1a' => escaped.push_str("\\Z"),
185                _ => escaped.push(c),
186            }
187        }
188        escaped
189    }
190
191    fn supports_returning(&self) -> bool {
192        false
193    }
194
195    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
196        // M-2 修复说明:
197        //
198        // 历史上 MySQL 支持 `SQL_CALC_FOUND_ROWS` 提示配合 `FOUND_ROWS()` 函数
199        // 在不分页情况下获取总行数,但该特性在 MySQL 8.0.17 中被弃用并在后续版本移除。
200        // 官方推荐使用独立的 `COUNT(*)` 查询。
201        //
202        // 因此本实现不使用 `SQL_CALC_FOUND_ROWS`,调用方如需总数应单独执行 COUNT 查询。
203        let offset = page.saturating_sub(1).saturating_mul(limit);
204        format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
205    }
206
207    fn json_type(&self) -> &'static str {
208        "JSON"
209    }
210
211    fn json_extract(&self, column: &str, path: &str) -> String {
212        // 规范化 path:确保以 $. 开头
213        let normalized = if path.starts_with('$') {
214            path.to_string()
215        } else {
216            format!("$.{}", path)
217        };
218        format!(
219            "JSON_EXTRACT({}, '{}')",
220            column,
221            self.escape_string(&normalized)
222        )
223    }
224
225    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
226        let cols = columns.join(", ");
227        let escaped = self.escape_string(keyword);
228        format!(
229            "MATCH({}) AGAINST('{}' IN NATURAL LANGUAGE MODE)",
230            cols, escaped
231        )
232    }
233
234    fn bool_to_int(&self, expr: &str) -> String {
235        // 将布尔表达式转换为整数(0/1)用于存储
236        format!("IF({}, 1, 0)", expr)
237    }
238
239    fn concat(&self, parts: &[&str]) -> String {
240        if parts.is_empty() {
241            return "NULL".to_string();
242        }
243        let concat_parts: Vec<String> = parts
244            .iter()
245            .map(|p| format!("CAST({} AS CHAR)", p))
246            .collect();
247        format!("CONCAT({})", concat_parts.join(", "))
248    }
249
250    fn supports_if_exists(&self) -> bool {
251        true
252    }
253
254    fn supports_if_not_exists(&self) -> bool {
255        true
256    }
257
258    fn auto_increment_keyword(&self) -> &'static str {
259        "AUTO_INCREMENT"
260    }
261
262    fn last_insert_id_sql(&self) -> Option<&'static str> {
263        Some("LAST_INSERT_ID()")
264    }
265
266    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
267        let cols: Vec<String> = columns
268            .iter()
269            .map(|col| {
270                let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
271                if !col.nullable {
272                    sql.push_str(" NOT NULL");
273                }
274                if let Some(default) = &col.default {
275                    sql.push_str(&format!(" DEFAULT {}", default));
276                }
277                if col.auto_increment {
278                    sql.push_str(&format!(" {}", self.auto_increment_keyword()));
279                }
280                if col.primary_key {
281                    sql.push_str(" PRIMARY KEY");
282                }
283                sql
284            })
285            .collect();
286
287        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
288    }
289
290    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
291        let stmts: Vec<String> = changes.iter().map(|change| {
292            match change {
293                TableChange::AddColumn(col) => {
294                    let mut sql = format!("ALTER TABLE {} ADD {}", self.quote(table), self.quote(&col.name));
295                    sql.push_str(&format!(" {}", col.sql_type));
296                    if !col.nullable {
297                        sql.push_str(" NOT NULL");
298                    }
299                    if let Some(default) = &col.default {
300                        sql.push_str(&format!(" DEFAULT {}", default));
301                    }
302                    sql
303                }
304                TableChange::DropColumn(name) => {
305                    format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
306                }
307                TableChange::ModifyColumn(col) => {
308                    // MySQL 使用 MODIFY COLUMN
309                    let mut sql = format!("ALTER TABLE {} MODIFY COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
310                    if !col.nullable {
311                        sql.push_str(" NOT NULL");
312                    }
313                    if let Some(default) = &col.default {
314                        sql.push_str(&format!(" DEFAULT {}", default));
315                    }
316                    sql
317                }
318                TableChange::AddIndex(name, cols) => {
319                    format!("ALTER TABLE {} ADD INDEX {} ({})", self.quote(table), name, cols.join(", "))
320                }
321                TableChange::DropIndex(name) => {
322                    format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
323                }
324                TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
325                    format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
326                        self.quote(table),
327                        table,
328                        columns.join("_"),
329                        columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
330                        self.quote(reference_table),
331                        reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
332                }
333            }
334        }).collect();
335
336        stmts.join("; ")
337    }
338
339    /// P2-6:MySQL 批量 upsert — `ON DUPLICATE KEY UPDATE col=VALUES(col), ...`
340    ///
341    /// MySQL 不需要指定冲突列(自动检测唯一键/主键冲突)。
342    /// `conflict_columns` 参数在 MySQL 方言中被忽略。
343    fn build_upsert_on_conflict(
344        &self,
345        _conflict_columns: &[&str],
346        update_columns: &[&str],
347        all_columns: &[String],
348    ) -> Option<String> {
349        // 确定要更新的列:优先使用 update_columns,为空则使用所有列(均已 quote)
350        let cols_to_update: Vec<String> = if update_columns.is_empty() {
351            all_columns.iter().map(|c| self.quote(c)).collect()
352        } else {
353            update_columns.iter().map(|c| self.quote(c)).collect()
354        };
355        if cols_to_update.is_empty() {
356            return None;
357        }
358        let set_clauses: Vec<String> = cols_to_update
359            .iter()
360            .map(|col| format!("{}=VALUES({})", col, col))
361            .collect();
362        Some(format!(
363            "ON DUPLICATE KEY UPDATE {}",
364            set_clauses.join(", ")
365        ))
366    }
367}
368
369/// PostgreSQL 方言实现
370#[derive(Debug, Clone)]
371pub struct PostgreSqlDialect;
372
373impl Dialect for PostgreSqlDialect {
374    fn clone_box(&self) -> Box<dyn Dialect> {
375        Box::new(PostgreSqlDialect)
376    }
377
378    fn db_type(&self) -> DbType {
379        DbType::PostgreSQL
380    }
381
382    fn quote(&self, identifier: &str) -> String {
383        format!("\"{}\"", identifier.replace('"', "\"\""))
384    }
385
386    fn escape_string(&self, s: &str) -> String {
387        // PostgreSQL 标准:使用双单引号转义单引号(standard_conforming_strings=on 默认)
388        // 反斜杠在 standard_conforming_strings=on 时不是转义字符
389        let mut escaped = String::with_capacity(s.len() * 2);
390        for c in s.chars() {
391            match c {
392                '\'' => escaped.push_str("''"),
393                _ => escaped.push(c),
394            }
395        }
396        escaped
397    }
398
399    fn supports_returning(&self) -> bool {
400        true
401    }
402
403    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
404        let offset = page.saturating_sub(1).saturating_mul(limit);
405        format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
406    }
407
408    fn json_type(&self) -> &'static str {
409        "JSONB"
410    }
411
412    fn json_extract(&self, column: &str, path: &str) -> String {
413        // PostgreSQL 使用 #>> 提取文本,path 以字符串数组形式
414        // 支持 $.a.b 或 a.b 格式
415        // 输出形式:column#>>'{a,b,c}'
416        // 路径组件中的特殊字符(逗号、花括号、双引号、反斜杠)需用双引号包裹并转义
417        let normalized = path.trim_start_matches("$.");
418        let parts: Vec<&str> = normalized.split('.').filter(|s| !s.is_empty()).collect();
419        let path_lit = parts
420            .iter()
421            .map(|p| {
422                // 如果包含特殊字符,用双引号包裹并转义内部双引号和反斜杠
423                let needs_quoting = p.chars().any(|c| matches!(c, ',' | '{' | '}' | '"' | '\\'));
424                if needs_quoting {
425                    let escaped = p.replace('\\', "\\\\").replace('"', "\\\"");
426                    format!("\"{}\"", escaped)
427                } else {
428                    p.to_string()
429                }
430            })
431            .collect::<Vec<_>>()
432            .join(",");
433        // 转义 SQL 字符串字面量中的单引号(PG 使用双单引号转义)
434        let path_lit_escaped = path_lit.replace('\'', "''");
435        format!("{}#>>'{{{}}}'", column, path_lit_escaped)
436    }
437
438    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
439        let cols = columns
440            .iter()
441            .map(|c| format!("{}::text", c))
442            .collect::<Vec<_>>()
443            .join(" || ' ' || ");
444        let escaped = self.escape_string(keyword);
445        format!("to_tsvector({}) @@ to_tsquery('{}')", cols, escaped)
446    }
447
448    fn bool_to_int(&self, expr: &str) -> String {
449        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
450    }
451
452    fn concat(&self, parts: &[&str]) -> String {
453        if parts.is_empty() {
454            return "NULL".to_string();
455        }
456        format!("CONCAT({})", parts.join(", "))
457    }
458
459    fn supports_if_exists(&self) -> bool {
460        true
461    }
462
463    fn supports_if_not_exists(&self) -> bool {
464        true
465    }
466
467    fn auto_increment_keyword(&self) -> &'static str {
468        "GENERATED BY DEFAULT AS IDENTITY"
469    }
470
471    fn last_insert_id_sql(&self) -> Option<&'static str> {
472        Some("lastval()")
473    }
474
475    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
476        let cols: Vec<String> = columns
477            .iter()
478            .map(|col| {
479                let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
480                if !col.nullable {
481                    sql.push_str(" NOT NULL");
482                }
483                if let Some(default) = &col.default {
484                    sql.push_str(&format!(" DEFAULT {}", default));
485                }
486                if col.primary_key {
487                    sql.push_str(" PRIMARY KEY");
488                }
489                sql
490            })
491            .collect();
492
493        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
494    }
495
496    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
497        let stmts: Vec<String> = changes.iter().map(|change| {
498            match change {
499                TableChange::AddColumn(col) => {
500                    let mut sql = format!("ALTER TABLE {} ADD COLUMN {} {}", self.quote(table), self.quote(&col.name), col.sql_type);
501                    if !col.nullable {
502                        sql.push_str(" NOT NULL");
503                    }
504                    if let Some(default) = &col.default {
505                        sql.push_str(&format!(" DEFAULT {}", default));
506                    }
507                    sql
508                }
509                TableChange::DropColumn(name) => {
510                    format!("ALTER TABLE {} DROP COLUMN {}", self.quote(table), self.quote(name))
511                }
512                TableChange::ModifyColumn(col) => {
513                    // PostgreSQL 使用 ALTER COLUMN TYPE
514                    let mut sql = format!("ALTER TABLE {} ALTER COLUMN {} TYPE {}", self.quote(table), self.quote(&col.name), col.sql_type);
515                    if !col.nullable {
516                        sql.push_str(&format!(", ALTER COLUMN {} SET NOT NULL", self.quote(&col.name)));
517                    }
518                    if let Some(default) = &col.default {
519                        sql.push_str(&format!(", ALTER COLUMN {} SET DEFAULT {}", self.quote(&col.name), default));
520                    }
521                    sql
522                }
523                TableChange::AddIndex(name, cols) => {
524                    format!("CREATE INDEX {} ON {} ({})", name, self.quote(table), cols.join(", "))
525                }
526                TableChange::DropIndex(name) => {
527                    format!("DROP INDEX {}", name)
528                }
529                TableChange::AddForeignKey { columns, reference_table, reference_columns } => {
530                    format!("ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
531                        self.quote(table),
532                        table,
533                        columns.join("_"),
534                        columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
535                        self.quote(reference_table),
536                        reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "))
537                }
538            }
539        }).collect();
540
541        stmts.join("; ")
542    }
543
544    /// P2-6:PostgreSQL 批量 upsert — `ON CONFLICT (cols) DO UPDATE SET col=EXCLUDED.col, ...`
545    ///
546    /// PostgreSQL 要求显式指定冲突列(主键或唯一键)。
547    fn build_upsert_on_conflict(
548        &self,
549        conflict_columns: &[&str],
550        update_columns: &[&str],
551        all_columns: &[String],
552    ) -> Option<String> {
553        if conflict_columns.is_empty() {
554            return None;
555        }
556        let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
557        // 确定要更新的列:优先使用 update_columns,为空则使用所有非冲突列
558        let conflict_set: std::collections::HashSet<&str> =
559            conflict_columns.iter().copied().collect();
560        let cols_to_update: Vec<String> = if update_columns.is_empty() {
561            all_columns
562                .iter()
563                .filter(|c| !conflict_set.contains(c.as_str()))
564                .map(|c| self.quote(c))
565                .collect()
566        } else {
567            update_columns.iter().map(|c| self.quote(c)).collect()
568        };
569        if cols_to_update.is_empty() {
570            // 没有要更新的列,使用 DO NOTHING
571            return Some(format!(
572                "ON CONFLICT ({}) DO NOTHING",
573                conflict_cols.join(", ")
574            ));
575        }
576        let set_clauses: Vec<String> = cols_to_update
577            .iter()
578            .map(|col| format!("{}=EXCLUDED.{}", col, col))
579            .collect();
580        Some(format!(
581            "ON CONFLICT ({}) DO UPDATE SET {}",
582            conflict_cols.join(", "),
583            set_clauses.join(", ")
584        ))
585    }
586}
587
588/// SQLite 方言实现
589#[derive(Debug, Clone)]
590pub struct SqliteDialect;
591
592impl Dialect for SqliteDialect {
593    fn clone_box(&self) -> Box<dyn Dialect> {
594        Box::new(SqliteDialect)
595    }
596
597    fn db_type(&self) -> DbType {
598        DbType::Sqlite
599    }
600
601    fn quote(&self, identifier: &str) -> String {
602        format!("\"{}\"", identifier.replace('"', "\"\""))
603    }
604
605    fn escape_string(&self, s: &str) -> String {
606        let mut escaped = String::with_capacity(s.len() * 2);
607        for c in s.chars() {
608            match c {
609                '\'' => escaped.push_str("''"),
610                _ => escaped.push(c),
611            }
612        }
613        escaped
614    }
615
616    fn supports_returning(&self) -> bool {
617        true
618    }
619
620    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
621        let offset = page.saturating_sub(1).saturating_mul(limit);
622        format!("{} LIMIT {} OFFSET {}", sql, limit, offset)
623    }
624
625    fn json_type(&self) -> &'static str {
626        "TEXT"
627    }
628
629    fn json_extract(&self, column: &str, path: &str) -> String {
630        // SQLite 使用 json_extract(column, '$.path')
631        let normalized = if path.starts_with('$') {
632            path.to_string()
633        } else {
634            format!("$.{}", path)
635        };
636        format!(
637            "json_extract({}, '{}')",
638            column,
639            self.escape_string(&normalized)
640        )
641    }
642
643    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
644        // SQLite FTS 的 MATCH 操作符必须作用于 FTS 虚拟表本身(`tbl MATCH 'query'`),
645        // 不能用于单个列;本接口仅传入列名,没有 FTS 表名,因此降级使用 LIKE。
646        // 这样既能避免生成错误的 MATCH 语法,又能在普通表上工作(不依赖 FTS 索引)。
647        if columns.is_empty() {
648            return "0".to_string();
649        }
650        let escaped = self.escape_string(keyword);
651        columns
652            .iter()
653            .map(|c| format!("{} LIKE '%{}%'", c.trim(), escaped))
654            .collect::<Vec<_>>()
655            .join(" OR ")
656    }
657
658    fn bool_to_int(&self, expr: &str) -> String {
659        expr.to_string()
660    }
661
662    fn concat(&self, parts: &[&str]) -> String {
663        if parts.is_empty() {
664            return "NULL".to_string();
665        }
666        // SQLite 的 || 操作符在任意参数为 NULL 时整体结果为 NULL,
667        // 需要先用 COALESCE 把每个参数替换为空串,才能保证拼接结果非 NULL。
668        let coalesced: Vec<String> = parts
669            .iter()
670            .map(|p| format!("COALESCE({}, '')", p))
671            .collect();
672        coalesced.join(" || ")
673    }
674
675    fn supports_if_exists(&self) -> bool {
676        true
677    }
678
679    fn supports_if_not_exists(&self) -> bool {
680        true
681    }
682
683    fn auto_increment_keyword(&self) -> &'static str {
684        "AUTOINCREMENT"
685    }
686
687    fn last_insert_id_sql(&self) -> Option<&'static str> {
688        Some("last_insert_rowid()")
689    }
690
691    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
692        let cols: Vec<String> = columns
693            .iter()
694            .map(|col| {
695                let mut sql = format!("{} {}", self.quote(&col.name), col.sql_type);
696                if !col.nullable {
697                    sql.push_str(" NOT NULL");
698                }
699                if let Some(default) = &col.default {
700                    sql.push_str(&format!(" DEFAULT {}", default));
701                }
702                if col.auto_increment {
703                    sql.push_str(" PRIMARY KEY AUTOINCREMENT");
704                } else if col.primary_key {
705                    sql.push_str(" PRIMARY KEY");
706                }
707                sql
708            })
709            .collect();
710
711        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
712    }
713
714    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
715        // SQLite 支持的 ALTER 操作:ADD COLUMN, RENAME COLUMN, DROP COLUMN (3.35+), RENAME TABLE
716        // 不支持:MODIFY COLUMN, ADD INDEX (需用 CREATE INDEX), ADD FOREIGN KEY (语法层面不支持)
717        let stmts: Vec<String> = changes
718            .iter()
719            .map(|change| {
720                match change {
721                    TableChange::AddColumn(col) => {
722                        let mut sql = format!(
723                            "ALTER TABLE {} ADD COLUMN {} {}",
724                            self.quote(table),
725                            self.quote(&col.name),
726                            col.sql_type
727                        );
728                        if !col.nullable {
729                            sql.push_str(" NOT NULL");
730                        }
731                        if let Some(default) = &col.default {
732                            sql.push_str(&format!(" DEFAULT {}", default));
733                        }
734                        sql
735                    }
736                    TableChange::DropColumn(name) => {
737                        // SQLite 3.35.0+ 支持 DROP COLUMN
738                        format!(
739                            "ALTER TABLE {} DROP COLUMN {}",
740                            self.quote(table),
741                            self.quote(name)
742                        )
743                    }
744                    TableChange::ModifyColumn(col) => {
745                        // SQLite 不直接支持 MODIFY COLUMN,需要用 12 步流程
746                        // 这里生成注释 SQL,提示用户需手动处理
747                        format!(
748                            "-- SQLite 不支持 MODIFY COLUMN({} {}),需重建表",
749                            col.name, col.sql_type
750                        )
751                    }
752                    TableChange::AddIndex(name, cols) => {
753                        format!(
754                            "CREATE INDEX {} ON {} ({})",
755                            name,
756                            self.quote(table),
757                            cols.join(", ")
758                        )
759                    }
760                    TableChange::DropIndex(name) => {
761                        format!("DROP INDEX {}", name)
762                    }
763                    TableChange::AddForeignKey {
764                        columns,
765                        reference_table,
766                        reference_columns: _,
767                    } => {
768                        // SQLite 不支持 ALTER TABLE ADD FOREIGN KEY,需重建表
769                        format!(
770                            "-- SQLite 不支持 ADD FOREIGN KEY({} -> {}),需重建表",
771                            columns.join(","),
772                            reference_table
773                        )
774                    }
775                }
776            })
777            .collect();
778
779        stmts.join("; ")
780    }
781
782    /// P2-6:SQLite 批量 upsert — `ON CONFLICT (cols) DO UPDATE SET col=EXCLUDED.col, ...`
783    ///
784    /// SQLite 语法与 PostgreSQL 一致(SQLite 3.24.0+ 支持 ON CONFLICT 子句)。
785    fn build_upsert_on_conflict(
786        &self,
787        conflict_columns: &[&str],
788        update_columns: &[&str],
789        all_columns: &[String],
790    ) -> Option<String> {
791        if conflict_columns.is_empty() {
792            return None;
793        }
794        let conflict_cols: Vec<String> = conflict_columns.iter().map(|c| self.quote(c)).collect();
795        let conflict_set: std::collections::HashSet<&str> =
796            conflict_columns.iter().copied().collect();
797        let cols_to_update: Vec<String> = if update_columns.is_empty() {
798            all_columns
799                .iter()
800                .filter(|c| !conflict_set.contains(c.as_str()))
801                .map(|c| self.quote(c))
802                .collect()
803        } else {
804            update_columns.iter().map(|c| self.quote(c)).collect()
805        };
806        if cols_to_update.is_empty() {
807            return Some(format!(
808                "ON CONFLICT ({}) DO NOTHING",
809                conflict_cols.join(", ")
810            ));
811        }
812        let set_clauses: Vec<String> = cols_to_update
813            .iter()
814            .map(|col| format!("{}=EXCLUDED.{}", col, col))
815            .collect();
816        Some(format!(
817            "ON CONFLICT ({}) DO UPDATE SET {}",
818            conflict_cols.join(", "),
819            set_clauses.join(", ")
820        ))
821    }
822}
823
824/// 将通用 SQL 类型映射为 Oracle 23ai 类型
825///
826/// Oracle 类型与常见类型的对应关系:
827/// - BIGINT → NUMBER(19)
828/// - INT/INTEGER → NUMBER(10)
829/// - VARCHAR(n) → VARCHAR2(n)
830/// - TEXT 系列 → CLOB
831/// - BOOLEAN/BOOL → NUMBER(1)
832fn map_to_oracle_type(sql_type: &str) -> String {
833    let upper = sql_type.to_uppercase();
834    let trimmed = upper.trim();
835
836    if trimmed.starts_with("BIGINT") {
837        sql_type.replacen("BIGINT", "NUMBER(19)", 1)
838    } else if trimmed.starts_with("VARCHAR2") {
839        sql_type.to_string()
840    } else if trimmed.starts_with("VARCHAR") {
841        sql_type.replacen("VARCHAR", "VARCHAR2", 1)
842    } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
843        "CLOB".to_string()
844    } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
845        "NUMBER(1)".to_string()
846    } else if trimmed == "INTEGER" {
847        "NUMBER(10)".to_string()
848    } else if trimmed.starts_with("INT") {
849        sql_type.replacen("INT", "NUMBER(10)", 1)
850    } else {
851        sql_type.to_string()
852    }
853}
854
855/// Oracle 方言实现(Oracle 23ai)
856#[derive(Debug, Clone)]
857pub struct OracleDialect;
858
859impl Dialect for OracleDialect {
860    fn clone_box(&self) -> Box<dyn Dialect> {
861        Box::new(OracleDialect)
862    }
863
864    fn db_type(&self) -> DbType {
865        DbType::Oracle
866    }
867
868    fn quote(&self, identifier: &str) -> String {
869        // Oracle 标准使用双引号包裹标识符,内部双引号双写
870        format!("\"{}\"", identifier.replace('"', "\"\""))
871    }
872
873    fn escape_string(&self, s: &str) -> String {
874        // Oracle 标准转义:单引号双写('O''Brien'),反斜杠不转义
875        let mut escaped = String::with_capacity(s.len() * 2);
876        for c in s.chars() {
877            match c {
878                '\'' => escaped.push_str("''"),
879                _ => escaped.push(c),
880            }
881        }
882        escaped
883    }
884
885    fn supports_returning(&self) -> bool {
886        // Oracle 12c+ 支持 RETURNING,23ai 当然支持
887        true
888    }
889
890    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
891        // Oracle 12c+ 使用 OFFSET/FETCH NEXT 语法
892        // 与其他方言保持一致:page=1 为第一页(offset=0)
893        let offset = page.saturating_sub(1).saturating_mul(limit);
894        format!(
895            "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
896            sql, offset, limit
897        )
898    }
899
900    fn json_type(&self) -> &'static str {
901        // Oracle 21+ 原生支持 JSON 类型,23ai 完整支持
902        "JSON"
903    }
904
905    fn json_extract(&self, column: &str, path: &str) -> String {
906        // Oracle 使用 JSON_VALUE 提取标量值,path 需以 $. 开头
907        let normalized = if path.starts_with('$') {
908            path.to_string()
909        } else {
910            format!("$.{}", path)
911        };
912        format!(
913            "JSON_VALUE({}, '{}')",
914            column,
915            self.escape_string(&normalized)
916        )
917    }
918
919    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
920        // Oracle 使用 CONTAINS 函数(需要 CONTEXT 索引)
921        // CONTAINS(column, keyword, 1) > 0
922        if columns.is_empty() {
923            return "0".to_string();
924        }
925        let escaped = self.escape_string(keyword);
926        let parts: Vec<String> = columns
927            .iter()
928            .map(|c| format!("CONTAINS({}, '{}', 1) > 0", c, escaped))
929            .collect();
930        parts.join(" OR ")
931    }
932
933    fn bool_to_int(&self, expr: &str) -> String {
934        // Oracle 没有原生 BOOL 类型,使用 CASE WHEN 转换为 0/1
935        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
936    }
937
938    fn concat(&self, parts: &[&str]) -> String {
939        // Oracle 使用 || 操作符进行字符串拼接
940        if parts.is_empty() {
941            return "NULL".to_string();
942        }
943        parts.join(" || ")
944    }
945
946    fn supports_if_exists(&self) -> bool {
947        // Oracle 23ai 支持 DROP TABLE IF EXISTS
948        true
949    }
950
951    fn supports_if_not_exists(&self) -> bool {
952        // Oracle 23ai 支持 CREATE TABLE IF NOT EXISTS
953        true
954    }
955
956    fn auto_increment_keyword(&self) -> &'static str {
957        // Oracle 12c+ 使用 IDENTITY 列
958        "GENERATED BY DEFAULT AS IDENTITY"
959    }
960
961    fn last_insert_id_sql(&self) -> Option<&'static str> {
962        // Oracle 没有独立可执行的"获取最后插入 ID"语句。
963        // `RETURNING {pk} INTO :bind` 是 PL/SQL 子句,必须附加在 INSERT 之后,
964        // 不能作为独立 SQL 执行。调用方应改用 `supports_returning()` 在 INSERT
965        // 末尾追加 RETURNING 子句获取自增值。
966        None
967    }
968
969    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
970        let cols: Vec<String> = columns
971            .iter()
972            .map(|col| {
973                let oracle_type = map_to_oracle_type(&col.sql_type);
974                let mut sql = format!("{} {}", self.quote(&col.name), oracle_type);
975                // Oracle IDENTITY 列隐式 NOT NULL,不允许显式 NOT NULL(ORA-03076)
976                if !col.nullable && !col.auto_increment {
977                    sql.push_str(" NOT NULL");
978                }
979                if let Some(default) = &col.default {
980                    sql.push_str(&format!(" DEFAULT {}", default));
981                }
982                if col.auto_increment {
983                    sql.push_str(&format!(" {}", self.auto_increment_keyword()));
984                }
985                if col.primary_key {
986                    sql.push_str(" PRIMARY KEY");
987                }
988                sql
989            })
990            .collect();
991
992        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
993    }
994
995    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
996        let stmts: Vec<String> = changes
997            .iter()
998            .map(|change| match change {
999                TableChange::AddColumn(col) => {
1000                    let oracle_type = map_to_oracle_type(&col.sql_type);
1001                    let mut sql = format!(
1002                        "ALTER TABLE {} ADD {} {}",
1003                        self.quote(table),
1004                        self.quote(&col.name),
1005                        oracle_type
1006                    );
1007                    if !col.nullable {
1008                        sql.push_str(" NOT NULL");
1009                    }
1010                    if let Some(default) = &col.default {
1011                        sql.push_str(&format!(" DEFAULT {}", default));
1012                    }
1013                    sql
1014                }
1015                TableChange::DropColumn(name) => {
1016                    format!(
1017                        "ALTER TABLE {} DROP COLUMN {}",
1018                        self.quote(table),
1019                        self.quote(name)
1020                    )
1021                }
1022                TableChange::ModifyColumn(col) => {
1023                    // Oracle 使用 MODIFY 关键字(不需 COLUMN)
1024                    let oracle_type = map_to_oracle_type(&col.sql_type);
1025                    let mut sql = format!(
1026                        "ALTER TABLE {} MODIFY {} {}",
1027                        self.quote(table),
1028                        self.quote(&col.name),
1029                        oracle_type
1030                    );
1031                    if !col.nullable {
1032                        sql.push_str(" NOT NULL");
1033                    }
1034                    if let Some(default) = &col.default {
1035                        sql.push_str(&format!(" DEFAULT {}", default));
1036                    }
1037                    sql
1038                }
1039                TableChange::AddIndex(name, cols) => {
1040                    format!(
1041                        "CREATE INDEX {} ON {} ({})",
1042                        name,
1043                        self.quote(table),
1044                        cols.join(", ")
1045                    )
1046                }
1047                TableChange::DropIndex(name) => {
1048                    format!("DROP INDEX {}", name)
1049                }
1050                TableChange::AddForeignKey {
1051                    columns,
1052                    reference_table,
1053                    reference_columns,
1054                } => {
1055                    format!(
1056                        "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1057                        self.quote(table),
1058                        table,
1059                        columns.join("_"),
1060                        columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1061                        self.quote(reference_table),
1062                        reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1063                    )
1064                }
1065            })
1066            .collect();
1067
1068        stmts.join("; ")
1069    }
1070}
1071
1072/// 将通用 SQL 类型映射为 SQL Server 类型
1073///
1074/// - BIGINT → BIGINT(保持)
1075/// - INT/INTEGER → INT
1076/// - VARCHAR(n) → NVARCHAR(n)(统一使用 Unicode)
1077/// - TEXT 系列 → NVARCHAR(MAX)
1078/// - BOOLEAN/BOOL → BIT
1079/// - 其他类型保持不变
1080fn map_to_sqlserver_type(sql_type: &str) -> String {
1081    let upper = sql_type.to_uppercase();
1082    let trimmed = upper.trim();
1083
1084    if trimmed.starts_with("BIGINT") {
1085        sql_type.to_string()
1086    } else if matches!(trimmed, "INT" | "INTEGER") {
1087        "INT".to_string()
1088    } else if trimmed.starts_with("NVARCHAR") {
1089        sql_type.to_string()
1090    } else if trimmed.starts_with("VARCHAR") {
1091        sql_type.replacen("VARCHAR", "NVARCHAR", 1)
1092    } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
1093        "NVARCHAR(MAX)".to_string()
1094    } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1095        "BIT".to_string()
1096    } else {
1097        sql_type.to_string()
1098    }
1099}
1100
1101/// SQL Server 方言实现(SQL Server 2012+ / T-SQL)
1102#[derive(Debug, Clone)]
1103pub struct SqlServerDialect;
1104
1105impl Dialect for SqlServerDialect {
1106    fn clone_box(&self) -> Box<dyn Dialect> {
1107        Box::new(SqlServerDialect)
1108    }
1109
1110    fn db_type(&self) -> DbType {
1111        DbType::SqlServer
1112    }
1113
1114    fn quote(&self, identifier: &str) -> String {
1115        // SQL Server 使用 [name] 包裹标识符,内部 ] 双写为 ]]
1116        format!("[{}]", identifier.replace(']', "]]"))
1117    }
1118
1119    fn escape_string(&self, s: &str) -> String {
1120        // SQL Server 标准:单引号双写('O''Brien'),反斜杠不转义
1121        let mut escaped = String::with_capacity(s.len() * 2);
1122        for c in s.chars() {
1123            match c {
1124                '\'' => escaped.push_str("''"),
1125                _ => escaped.push(c),
1126            }
1127        }
1128        escaped
1129    }
1130
1131    fn supports_returning(&self) -> bool {
1132        // SQL Server 使用 OUTPUT 子句,语义上等价于 RETURNING
1133        true
1134    }
1135
1136    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1137        // SQL Server 2012+ 使用 OFFSET ... ROWS FETCH NEXT ... ROWS ONLY
1138        let offset = page.saturating_sub(1).saturating_mul(limit);
1139        format!(
1140            "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1141            sql, offset, limit
1142        )
1143    }
1144
1145    fn json_type(&self) -> &'static str {
1146        // SQL Server 2016+ 使用 NVARCHAR(MAX) 存储 JSON
1147        "NVARCHAR(MAX)"
1148    }
1149
1150    fn json_extract(&self, column: &str, path: &str) -> String {
1151        // SQL Server 2016+ 使用 JSON_VALUE 提取标量
1152        let normalized = if path.starts_with('$') {
1153            path.to_string()
1154        } else {
1155            format!("$.{}", path)
1156        };
1157        format!(
1158            "JSON_VALUE({}, '{}')",
1159            column,
1160            self.escape_string(&normalized)
1161        )
1162    }
1163
1164    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1165        // SQL Server 使用 CONTAINS(需要 FULLTEXT 索引)
1166        if columns.is_empty() {
1167            return "0".to_string();
1168        }
1169        let escaped = self.escape_string(keyword);
1170        let cols = columns.join(", ");
1171        format!("CONTAINS({}, '{}')", cols, escaped)
1172    }
1173
1174    fn bool_to_int(&self, expr: &str) -> String {
1175        // SQL Server 使用 BIT 类型,CASE WHEN 转换为 0/1
1176        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1177    }
1178
1179    fn concat(&self, parts: &[&str]) -> String {
1180        if parts.is_empty() {
1181            return "NULL".to_string();
1182        }
1183        format!("CONCAT({})", parts.join(", "))
1184    }
1185
1186    fn supports_if_exists(&self) -> bool {
1187        // SQL Server 2016+ 支持 IF EXISTS
1188        true
1189    }
1190
1191    fn supports_if_not_exists(&self) -> bool {
1192        // SQL Server 2016+ 支持 IF NOT EXISTS
1193        true
1194    }
1195
1196    fn auto_increment_keyword(&self) -> &'static str {
1197        // SQL Server 使用 IDENTITY(1,1) 列属性
1198        "IDENTITY(1,1)"
1199    }
1200
1201    fn last_insert_id_sql(&self) -> Option<&'static str> {
1202        // SCOPE_IDENTITY() 返回当前作用域内最后生成的标识值
1203        Some("SCOPE_IDENTITY()")
1204    }
1205
1206    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1207        let cols: Vec<String> = columns
1208            .iter()
1209            .map(|col| {
1210                let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1211                let mut sql = format!("{} {}", self.quote(&col.name), sqlserver_type);
1212                if !col.nullable {
1213                    sql.push_str(" NOT NULL");
1214                }
1215                if let Some(default) = &col.default {
1216                    sql.push_str(&format!(" DEFAULT {}", default));
1217                }
1218                if col.auto_increment {
1219                    sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1220                }
1221                if col.primary_key {
1222                    sql.push_str(" PRIMARY KEY");
1223                }
1224                sql
1225            })
1226            .collect();
1227
1228        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1229    }
1230
1231    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1232        let stmts: Vec<String> = changes
1233            .iter()
1234            .map(|change| match change {
1235                TableChange::AddColumn(col) => {
1236                    let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1237                    let mut sql = format!(
1238                        "ALTER TABLE {} ADD {} {}",
1239                        self.quote(table),
1240                        self.quote(&col.name),
1241                        sqlserver_type
1242                    );
1243                    if !col.nullable {
1244                        sql.push_str(" NOT NULL");
1245                    }
1246                    if let Some(default) = &col.default {
1247                        sql.push_str(&format!(" DEFAULT {}", default));
1248                    }
1249                    sql
1250                }
1251                TableChange::DropColumn(name) => {
1252                    format!(
1253                        "ALTER TABLE {} DROP COLUMN {}",
1254                        self.quote(table),
1255                        self.quote(name)
1256                    )
1257                }
1258                TableChange::ModifyColumn(col) => {
1259                    // SQL Server 使用 ALTER COLUMN(不是 MODIFY)
1260                    let sqlserver_type = map_to_sqlserver_type(&col.sql_type);
1261                    let mut sql = format!(
1262                        "ALTER TABLE {} ALTER COLUMN {} {}",
1263                        self.quote(table),
1264                        self.quote(&col.name),
1265                        sqlserver_type
1266                    );
1267                    if !col.nullable {
1268                        sql.push_str(" NOT NULL");
1269                    }
1270                    if let Some(default) = &col.default {
1271                        sql.push_str(&format!(" DEFAULT {}", default));
1272                    }
1273                    sql
1274                }
1275                TableChange::AddIndex(name, cols) => {
1276                    format!(
1277                        "CREATE INDEX {} ON {} ({})",
1278                        name,
1279                        self.quote(table),
1280                        cols.join(", ")
1281                    )
1282                }
1283                TableChange::DropIndex(name) => {
1284                    // SQL Server 的 DROP INDEX 必须指定表名
1285                    format!("DROP INDEX {} ON {}", name, self.quote(table))
1286                }
1287                TableChange::AddForeignKey {
1288                    columns,
1289                    reference_table,
1290                    reference_columns,
1291                } => {
1292                    format!(
1293                        "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1294                        self.quote(table),
1295                        table,
1296                        columns.join("_"),
1297                        columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1298                        self.quote(reference_table),
1299                        reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1300                    )
1301                }
1302            })
1303            .collect();
1304
1305        stmts.join("; ")
1306    }
1307}
1308
1309// 架构说明:build_create_table / build_alter_table / build_drop_table 在四个方言中
1310// 存在部分重复代码。当前选择保持各方言独立实现以确保 SQL 方言差异的显式性,
1311// 避免过度抽象导致维护复杂度上升。如需重构,可抽出公共构建器(参见 dialect 重构 RFC)。
1312
1313// ============================================================================
1314// 兼容方言(基于现有方言委派实现)
1315//
1316// 以下方言在 SQL 语法上与某个基础方言完全兼容,仅在 db_type() 上有区别。
1317// 使用宏减少重复代码,保持维护性。
1318// ============================================================================
1319
1320/// 将方言实现委派给基础方言的宏
1321///
1322/// `$wrapper`:新方言结构体名
1323/// `$base`:基础方言结构体名(如 MySqlDialect)
1324/// `$db_type`:返回的 DbType 变体
1325macro_rules! delegate_dialect_to {
1326    ($wrapper:ident, $base:ident, $db_type:expr) => {
1327        /// 兼容方言(委派给基础方言实现)
1328        #[derive(Debug, Clone)]
1329        pub struct $wrapper;
1330
1331        impl Dialect for $wrapper {
1332            fn clone_box(&self) -> Box<dyn Dialect> {
1333                Box::new($wrapper)
1334            }
1335
1336            fn db_type(&self) -> DbType {
1337                $db_type
1338            }
1339            fn quote(&self, identifier: &str) -> String {
1340                $base.quote(identifier)
1341            }
1342            fn escape_string(&self, s: &str) -> String {
1343                $base.escape_string(s)
1344            }
1345            fn supports_returning(&self) -> bool {
1346                $base.supports_returning()
1347            }
1348            fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1349                $base.build_pagination(sql, page, limit)
1350            }
1351            fn json_type(&self) -> &'static str {
1352                $base.json_type()
1353            }
1354            fn json_extract(&self, column: &str, path: &str) -> String {
1355                $base.json_extract(column, path)
1356            }
1357            fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1358                $base.full_text_search(columns, keyword)
1359            }
1360            fn bool_to_int(&self, expr: &str) -> String {
1361                $base.bool_to_int(expr)
1362            }
1363            fn concat(&self, parts: &[&str]) -> String {
1364                $base.concat(parts)
1365            }
1366            fn supports_if_exists(&self) -> bool {
1367                $base.supports_if_exists()
1368            }
1369            fn supports_if_not_exists(&self) -> bool {
1370                $base.supports_if_not_exists()
1371            }
1372            fn auto_increment_keyword(&self) -> &'static str {
1373                $base.auto_increment_keyword()
1374            }
1375            fn last_insert_id_sql(&self) -> Option<&'static str> {
1376                $base.last_insert_id_sql()
1377            }
1378            fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1379                $base.build_create_table(table, columns)
1380            }
1381            fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1382                $base.build_alter_table(table, changes)
1383            }
1384            fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
1385                $base.build_drop_table(table, if_exists)
1386            }
1387        }
1388    };
1389}
1390
1391// MariaDB:MySQL 兼容方言
1392delegate_dialect_to!(MariaDbDialect, MySqlDialect, DbType::MariaDB);
1393
1394// TiDB:MySQL 兼容分布式数据库
1395delegate_dialect_to!(TiDbDialect, MySqlDialect, DbType::TiDB);
1396
1397// KingbaseES:人大金仓,PostgreSQL 兼容方言
1398delegate_dialect_to!(KingbaseDialect, PostgreSqlDialect, DbType::Kingbase);
1399
1400// PolarDB:阿里云,PostgreSQL 兼容(PG 版本)
1401delegate_dialect_to!(PolarDbDialect, PostgreSqlDialect, DbType::PolarDB);
1402
1403// GaussDB:华为云,PostgreSQL 兼容分布式数据库
1404delegate_dialect_to!(GaussDbDialect, PostgreSqlDialect, DbType::GaussDB);
1405
1406// Dameng:达梦 DM8,Oracle 兼容方言
1407delegate_dialect_to!(DamengDialect, OracleDialect, DbType::Dameng);
1408
1409// Sybase ASE:与 SQL Server T-SQL 高度兼容
1410delegate_dialect_to!(SybaseDialect, SqlServerDialect, DbType::Sybase);
1411
1412// GBase 8s:南大通用,Informix 兼容方言,SQL 语法接近 T-SQL
1413delegate_dialect_to!(GBaseDialect, SqlServerDialect, DbType::GBase);
1414
1415// ============================================================================
1416// ClickHouse 方言(独立实现)
1417//
1418// ClickHouse 是列式 OLAP 数据库,有独特的类型系统和函数:
1419// - 使用 backtick 标识符(与 MySQL 一致)
1420// - 字符串字面量单引号转义为 \'(与 MySQL 一致)
1421// - 不支持事务、不支持 RETURNING
1422// - 分页使用 LIMIT offset, limit 语法(与 MySQL 一致)
1423// - 自增列不支持(使用 UUID 或物化列)
1424// - 类型系统:String/UInt64/Int64/Float64/DateTime 等
1425// ============================================================================
1426
1427/// ClickHouse 方言实现(列式 OLAP 数据库)
1428#[derive(Debug, Clone)]
1429pub struct ClickHouseDialect;
1430
1431impl Dialect for ClickHouseDialect {
1432    fn clone_box(&self) -> Box<dyn Dialect> {
1433        Box::new(ClickHouseDialect)
1434    }
1435
1436    fn db_type(&self) -> DbType {
1437        DbType::ClickHouse
1438    }
1439
1440    fn quote(&self, identifier: &str) -> String {
1441        // ClickHouse 使用 backquote 包裹标识符(与 MySQL 一致)
1442        format!("`{}`", identifier.replace('`', "``"))
1443    }
1444
1445    fn escape_string(&self, s: &str) -> String {
1446        // ClickHouse 使用反斜杠转义(与 MySQL 一致)
1447        let mut escaped = String::with_capacity(s.len() * 2);
1448        for c in s.chars() {
1449            match c {
1450                '\'' => escaped.push_str("\\'"),
1451                '\\' => escaped.push_str("\\\\"),
1452                '\n' => escaped.push_str("\\n"),
1453                '\r' => escaped.push_str("\\r"),
1454                '\t' => escaped.push_str("\\t"),
1455                _ => escaped.push(c),
1456            }
1457        }
1458        escaped
1459    }
1460
1461    fn supports_returning(&self) -> bool {
1462        // ClickHouse 不支持 RETURNING 子句
1463        false
1464    }
1465
1466    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1467        // ClickHouse 使用 LIMIT offset, limit 语法
1468        let offset = page.saturating_sub(1).saturating_mul(limit);
1469        format!("{} LIMIT {}, {}", sql, offset, limit)
1470    }
1471
1472    fn json_type(&self) -> &'static str {
1473        // ClickHouse 使用 String 存储 JSON,或使用 JSON 类型(实验性)
1474        "String"
1475    }
1476
1477    fn json_extract(&self, column: &str, path: &str) -> String {
1478        // ClickHouse 使用 JSONExtractString 函数
1479        let normalized = if path.starts_with('$') {
1480            path.to_string()
1481        } else {
1482            format!("$.{}", path)
1483        };
1484        format!(
1485            "JSONExtractString({}, '{}')",
1486            column,
1487            self.escape_string(&normalized)
1488        )
1489    }
1490
1491    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1492        // ClickHouse 使用 position() + like 进行全文检索(无原生 FTS)
1493        if columns.is_empty() {
1494            return "0".to_string();
1495        }
1496        let escaped = self.escape_string(keyword);
1497        let parts: Vec<String> = columns
1498            .iter()
1499            .map(|c| format!("position({}, '{}') > 0", c, escaped))
1500            .collect();
1501        parts.join(" OR ")
1502    }
1503
1504    fn bool_to_int(&self, expr: &str) -> String {
1505        // ClickHouse 支持 toUInt8 转换
1506        format!("toUInt8({})", expr)
1507    }
1508
1509    fn concat(&self, parts: &[&str]) -> String {
1510        // ClickHouse 使用 concat() 函数
1511        if parts.is_empty() {
1512            return "''".to_string();
1513        }
1514        format!("concat({})", parts.join(", "))
1515    }
1516
1517    fn supports_if_exists(&self) -> bool {
1518        true
1519    }
1520
1521    fn supports_if_not_exists(&self) -> bool {
1522        true
1523    }
1524
1525    fn auto_increment_keyword(&self) -> &'static str {
1526        // ClickHouse 不支持自增列,使用 UUID 默认值
1527        ""
1528    }
1529
1530    fn last_insert_id_sql(&self) -> Option<&'static str> {
1531        // ClickHouse 不支持 last_insert_id
1532        None
1533    }
1534
1535    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1536        let cols: Vec<String> = columns
1537            .iter()
1538            .map(|col| {
1539                let ch_type = map_to_clickhouse_type(&col.sql_type);
1540                let mut sql = format!("{} {}", self.quote(&col.name), ch_type);
1541                if let Some(default) = &col.default {
1542                    sql.push_str(&format!(" DEFAULT {}", default));
1543                }
1544                if col.primary_key {
1545                    sql.push_str(" PRIMARY KEY");
1546                }
1547                sql
1548            })
1549            .collect();
1550
1551        // ClickHouse 必须指定 Engine,默认使用 MergeTree
1552        format!(
1553            "CREATE TABLE {} ({}) ENGINE = MergeTree()",
1554            self.quote(table),
1555            cols.join(", ")
1556        )
1557    }
1558
1559    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1560        let stmts: Vec<String> = changes
1561            .iter()
1562            .map(|change| match change {
1563                TableChange::AddColumn(col) => {
1564                    let ch_type = map_to_clickhouse_type(&col.sql_type);
1565                    format!(
1566                        "ALTER TABLE {} ADD COLUMN {} {}",
1567                        self.quote(table),
1568                        self.quote(&col.name),
1569                        ch_type
1570                    )
1571                }
1572                TableChange::DropColumn(name) => {
1573                    format!(
1574                        "ALTER TABLE {} DROP COLUMN {}",
1575                        self.quote(table),
1576                        self.quote(name)
1577                    )
1578                }
1579                TableChange::ModifyColumn(col) => {
1580                    let ch_type = map_to_clickhouse_type(&col.sql_type);
1581                    format!(
1582                        "ALTER TABLE {} MODIFY COLUMN {} {}",
1583                        self.quote(table),
1584                        self.quote(&col.name),
1585                        ch_type
1586                    )
1587                }
1588                TableChange::AddIndex(name, cols) => {
1589                    format!(
1590                        "ALTER TABLE {} ADD INDEX {} ({})",
1591                        self.quote(table),
1592                        name,
1593                        cols.join(", ")
1594                    )
1595                }
1596                TableChange::DropIndex(name) => {
1597                    format!("ALTER TABLE {} DROP INDEX {}", self.quote(table), name)
1598                }
1599                TableChange::AddForeignKey { .. } => {
1600                    // ClickHouse 不支持外键,跳过
1601                    String::new()
1602                }
1603            })
1604            .filter(|s| !s.is_empty())
1605            .collect();
1606
1607        stmts.join("; ")
1608    }
1609}
1610
1611/// 将通用 SQL 类型映射为 ClickHouse 类型
1612///
1613/// - BIGINT → Int64
1614/// - INT/INTEGER → Int32
1615/// - VARCHAR(n)/TEXT → String
1616/// - BOOLEAN/BOOL → UInt8
1617/// - FLOAT → Float32
1618/// - DOUBLE → Float64
1619/// - DATETIME/TIMESTAMP → DateTime
1620fn map_to_clickhouse_type(sql_type: &str) -> String {
1621    let upper = sql_type.to_uppercase();
1622    let trimmed = upper.trim();
1623
1624    if trimmed.starts_with("BIGINT") {
1625        "Int64".to_string()
1626    } else if matches!(trimmed, "INT" | "INTEGER") {
1627        "Int32".to_string()
1628    } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
1629        "Int16".to_string()
1630    } else if trimmed.starts_with("VARCHAR")
1631        || trimmed.starts_with("CHAR")
1632        || matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT")
1633    {
1634        "String".to_string()
1635    } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1636        "UInt8".to_string()
1637    } else if matches!(trimmed, "FLOAT" | "REAL") {
1638        "Float32".to_string()
1639    } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
1640        "Float64".to_string()
1641    } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
1642        "DateTime".to_string()
1643    } else if matches!(trimmed, "DATE") {
1644        "Date".to_string()
1645    } else if trimmed.starts_with("DECIMAL") || trimmed.starts_with("NUMERIC") {
1646        "Decimal(38, 4)".to_string()
1647    } else {
1648        sql_type.to_string()
1649    }
1650}
1651
1652// ============================================================================
1653// IBM DB2 方言(独立实现)
1654//
1655// DB2 LUW 语法特性:
1656// - 双引号标识符(标准 SQL 风格)
1657// - 字符串字面量单引号转义为 ''(标准 SQL 风格)
1658// - 支持 RETURNING(DB2 11.5+)
1659// - 分页使用 OFFSET x ROWS FETCH NEXT y ROWS ONLY(标准 SQL:2008)
1660// - 自增列使用 GENERATED ALWAYS AS IDENTITY
1661// - 类型系统:VARCHAR/INTEGER/BIGINT/TIMESTAMP/DECFLOAT 等
1662// ============================================================================
1663
1664/// IBM DB2 LUW 方言实现
1665#[derive(Debug, Clone)]
1666pub struct Db2Dialect;
1667
1668impl Dialect for Db2Dialect {
1669    fn clone_box(&self) -> Box<dyn Dialect> {
1670        Box::new(Db2Dialect)
1671    }
1672
1673    fn db_type(&self) -> DbType {
1674        DbType::Db2
1675    }
1676
1677    fn quote(&self, identifier: &str) -> String {
1678        // DB2 使用双引号包裹标识符(标准 SQL 风格)
1679        format!("\"{}\"", identifier.replace('"', "\"\""))
1680    }
1681
1682    fn escape_string(&self, s: &str) -> String {
1683        // DB2 标准转义:单引号双写('O''Brien')
1684        let mut escaped = String::with_capacity(s.len() * 2);
1685        for c in s.chars() {
1686            match c {
1687                '\'' => escaped.push_str("''"),
1688                _ => escaped.push(c),
1689            }
1690        }
1691        escaped
1692    }
1693
1694    fn supports_returning(&self) -> bool {
1695        // DB2 11.5+ 支持 RETURNING(实际使用 SELECT FROM FINAL TABLE 代替)
1696        false
1697    }
1698
1699    fn build_pagination(&self, sql: &str, page: u64, limit: u64) -> String {
1700        // DB2 使用 OFFSET ... ROWS FETCH NEXT ... ROWS ONLY(SQL:2008 标准)
1701        let offset = page.saturating_sub(1).saturating_mul(limit);
1702        format!(
1703            "{} OFFSET {} ROWS FETCH NEXT {} ROWS ONLY",
1704            sql, offset, limit
1705        )
1706    }
1707
1708    fn json_type(&self) -> &'static str {
1709        // DB2 11.5+ 原生支持 JSON 类型
1710        "JSON"
1711    }
1712
1713    fn json_extract(&self, column: &str, path: &str) -> String {
1714        // DB2 使用 JSON_VALUE 函数
1715        let normalized = if path.starts_with('$') {
1716            path.to_string()
1717        } else {
1718            format!("$.{}", path)
1719        };
1720        format!(
1721            "JSON_VALUE({}, '{}')",
1722            column,
1723            self.escape_string(&normalized)
1724        )
1725    }
1726
1727    fn full_text_search(&self, columns: &[&str], keyword: &str) -> String {
1728        // DB2 使用 CONTAINS 函数(需 DB2TEXT 索引)
1729        if columns.is_empty() {
1730            return "0".to_string();
1731        }
1732        let escaped = self.escape_string(keyword);
1733        let parts: Vec<String> = columns
1734            .iter()
1735            .map(|c| format!("CONTAINS({}, '{}') > 0", c, escaped))
1736            .collect();
1737        parts.join(" OR ")
1738    }
1739
1740    fn bool_to_int(&self, expr: &str) -> String {
1741        // DB2 没有原生 BOOL(11.5+ 有 BOOLEAN),使用 CASE WHEN 转换
1742        format!("(CASE WHEN {} THEN 1 ELSE 0 END)", expr)
1743    }
1744
1745    fn concat(&self, parts: &[&str]) -> String {
1746        // DB2 使用 || 操作符进行字符串拼接
1747        if parts.is_empty() {
1748            return "''".to_string();
1749        }
1750        parts.join(" || ")
1751    }
1752
1753    fn supports_if_exists(&self) -> bool {
1754        // DB2 不支持 DROP TABLE IF EXISTS(直到 11.5)
1755        false
1756    }
1757
1758    fn supports_if_not_exists(&self) -> bool {
1759        // DB2 不支持 CREATE TABLE IF NOT EXISTS
1760        false
1761    }
1762
1763    fn auto_increment_keyword(&self) -> &'static str {
1764        // DB2 使用 GENERATED ALWAYS AS IDENTITY
1765        "GENERATED ALWAYS AS IDENTITY"
1766    }
1767
1768    fn last_insert_id_sql(&self) -> Option<&'static str> {
1769        // DB2 使用 IDENTITY_VAL_LOCAL() 函数获取最后插入的 IDENTITY 值
1770        Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
1771    }
1772
1773    fn build_create_table(&self, table: &str, columns: &[ColumnDef]) -> String {
1774        let cols: Vec<String> = columns
1775            .iter()
1776            .map(|col| {
1777                let db2_type = map_to_db2_type(&col.sql_type);
1778                let mut sql = format!("{} {}", self.quote(&col.name), db2_type);
1779                if !col.nullable && !col.auto_increment {
1780                    sql.push_str(" NOT NULL");
1781                }
1782                if let Some(default) = &col.default {
1783                    sql.push_str(&format!(" DEFAULT {}", default));
1784                }
1785                if col.auto_increment {
1786                    sql.push_str(&format!(" {}", self.auto_increment_keyword()));
1787                }
1788                if col.primary_key {
1789                    sql.push_str(" PRIMARY KEY");
1790                }
1791                sql
1792            })
1793            .collect();
1794
1795        format!("CREATE TABLE {} ({})", self.quote(table), cols.join(", "))
1796    }
1797
1798    fn build_alter_table(&self, table: &str, changes: &[TableChange]) -> String {
1799        let stmts: Vec<String> = changes
1800            .iter()
1801            .map(|change| match change {
1802                TableChange::AddColumn(col) => {
1803                    let db2_type = map_to_db2_type(&col.sql_type);
1804                    let mut sql = format!(
1805                        "ALTER TABLE {} ADD COLUMN {} {}",
1806                        self.quote(table),
1807                        self.quote(&col.name),
1808                        db2_type
1809                    );
1810                    if !col.nullable {
1811                        sql.push_str(" NOT NULL");
1812                    }
1813                    if let Some(default) = &col.default {
1814                        sql.push_str(&format!(" DEFAULT {}", default));
1815                    }
1816                    sql
1817                }
1818                TableChange::DropColumn(name) => {
1819                    format!(
1820                        "ALTER TABLE {} DROP COLUMN {}",
1821                        self.quote(table),
1822                        self.quote(name)
1823                    )
1824                }
1825                TableChange::ModifyColumn(col) => {
1826                    let db2_type = map_to_db2_type(&col.sql_type);
1827                    format!(
1828                        "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {}",
1829                        self.quote(table),
1830                        self.quote(&col.name),
1831                        db2_type
1832                    )
1833                }
1834                TableChange::AddIndex(name, cols) => {
1835                    format!(
1836                        "CREATE INDEX {} ON {} ({})",
1837                        name,
1838                        self.quote(table),
1839                        cols.join(", ")
1840                    )
1841                }
1842                TableChange::DropIndex(name) => {
1843                    format!("DROP INDEX {}", name)
1844                }
1845                TableChange::AddForeignKey {
1846                    columns,
1847                    reference_table,
1848                    reference_columns,
1849                } => {
1850                    format!(
1851                        "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} ({})",
1852                        self.quote(table),
1853                        table,
1854                        columns.join("_"),
1855                        columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", "),
1856                        self.quote(reference_table),
1857                        reference_columns.iter().map(|c| self.quote(c)).collect::<Vec<_>>().join(", ")
1858                    )
1859                }
1860            })
1861            .collect();
1862
1863        stmts.join("; ")
1864    }
1865
1866    fn build_drop_table(&self, table: &str, if_exists: bool) -> String {
1867        // DB2 不支持 IF EXISTS,但为兼容性保留参数
1868        let _ = if_exists;
1869        format!("DROP TABLE {}", self.quote(table))
1870    }
1871}
1872
1873/// 将通用 SQL 类型映射为 IBM DB2 类型
1874///
1875/// - BIGINT → BIGINT
1876/// - INT/INTEGER → INTEGER
1877/// - VARCHAR(n) → VARCHAR(n)
1878/// - TEXT 系列 → CLOB(2G)
1879/// - BOOLEAN/BOOL → SMALLINT(DB2 11.5+ 才有 BOOLEAN)
1880/// - DATETIME/TIMESTAMP → TIMESTAMP
1881fn map_to_db2_type(sql_type: &str) -> String {
1882    let upper = sql_type.to_uppercase();
1883    let trimmed = upper.trim();
1884
1885    if trimmed.starts_with("BIGINT") {
1886        "BIGINT".to_string()
1887    } else if matches!(trimmed, "INT" | "INTEGER") {
1888        "INTEGER".to_string()
1889    } else if matches!(trimmed, "TINYINT" | "SMALLINT") {
1890        "SMALLINT".to_string()
1891    } else if trimmed.starts_with("VARCHAR") || trimmed.starts_with("CHAR") {
1892        sql_type.to_string()
1893    } else if matches!(trimmed, "TEXT" | "MEDIUMTEXT" | "LONGTEXT" | "TINYTEXT") {
1894        "CLOB(2G)".to_string()
1895    } else if matches!(trimmed, "BOOLEAN" | "BOOL") {
1896        "SMALLINT".to_string()
1897    } else if matches!(trimmed, "FLOAT" | "REAL") {
1898        "REAL".to_string()
1899    } else if matches!(trimmed, "DOUBLE" | "DOUBLE PRECISION") {
1900        "DOUBLE".to_string()
1901    } else if matches!(trimmed, "DATETIME" | "TIMESTAMP") {
1902        "TIMESTAMP".to_string()
1903    } else if matches!(trimmed, "DATE") {
1904        "DATE".to_string()
1905    } else {
1906        // DECIMAL/NUMERIC 和其他未匹配类型保持原样
1907        sql_type.to_string()
1908    }
1909}
1910
1911/// 根据数据库类型获取对应的方言实例
1912///
1913/// L-5 修复:补充示例文档
1914///
1915/// 返回 `Box<dyn Dialect>`,可用于与 `QueryBuilder`、`Schema` 等组件配合。
1916/// 对于不支持 SQL 方言的数据库类型(如 Redis、MongoDB、向量库),返回
1917/// `DbError::Unsupported`。
1918///
1919/// # 示例
1920///
1921/// ```ignore
1922/// use sz_orm_core::db_type::DbType;
1923/// use sz_orm_core::dialect::{get_dialect, Dialect};
1924///
1925/// let dialect = get_dialect(DbType::MySQL).unwrap();
1926/// assert_eq!(dialect.quote("user"), "`user`");
1927/// assert_eq!(dialect.db_type(), DbType::MySQL);
1928///
1929/// // 不支持的类型返回错误
1930/// let err = get_dialect(DbType::Redis).unwrap_err();
1931/// assert!(matches!(err, sz_orm_core::DbError::Unsupported(_)));
1932/// ```
1933pub fn get_dialect(db_type: DbType) -> Result<Box<dyn Dialect>, DbError> {
1934    match db_type {
1935        DbType::MySQL => Ok(Box::new(MySqlDialect)),
1936        DbType::PostgreSQL => Ok(Box::new(PostgreSqlDialect)),
1937        DbType::Sqlite => Ok(Box::new(SqliteDialect)),
1938        DbType::Redis => Err(DbError::Unsupported(
1939            "Redis does not support standard SQL dialect".to_string(),
1940        )),
1941        DbType::MongoDB => Err(DbError::Unsupported(
1942            "MongoDB uses different query syntax".to_string(),
1943        )),
1944        DbType::ClickHouse => Ok(Box::new(ClickHouseDialect)),
1945        DbType::Oracle => Ok(Box::new(OracleDialect)),
1946        DbType::OceanBase => Ok(Box::new(MySqlDialect)),
1947        DbType::SqlServer => Ok(Box::new(SqlServerDialect)),
1948        DbType::VectorDb => Err(DbError::Unsupported(
1949            "Vector databases have specific APIs".to_string(),
1950        )),
1951        DbType::PureJsDb => Err(DbError::Unsupported(
1952            "PureJS database uses JavaScript".to_string(),
1953        )),
1954        // 国产数据库 & 兼容方言
1955        DbType::Dameng => Ok(Box::new(DamengDialect)),
1956        DbType::Kingbase => Ok(Box::new(KingbaseDialect)),
1957        DbType::Db2 => Ok(Box::new(Db2Dialect)),
1958        DbType::MariaDB => Ok(Box::new(MariaDbDialect)),
1959        DbType::TiDB => Ok(Box::new(TiDbDialect)),
1960        DbType::PolarDB => Ok(Box::new(PolarDbDialect)),
1961        DbType::GaussDB => Ok(Box::new(GaussDbDialect)),
1962        DbType::GBase => Ok(Box::new(GBaseDialect)),
1963        DbType::Sybase => Ok(Box::new(SybaseDialect)),
1964    }
1965}
1966
1967impl fmt::Display for dyn Dialect {
1968    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1969        write!(f, "Dialect({})", self.db_type())
1970    }
1971}
1972
1973#[cfg(test)]
1974mod tests {
1975    use super::*;
1976
1977    #[test]
1978    fn test_mysql_quote() {
1979        let dialect = MySqlDialect;
1980        assert_eq!(dialect.quote("users"), "`users`");
1981        assert_eq!(dialect.quote("user`id"), "`user``id`");
1982    }
1983
1984    #[test]
1985    fn test_mysql_escape() {
1986        let dialect = MySqlDialect;
1987        assert_eq!(dialect.escape_string("hello"), "hello");
1988        assert_eq!(dialect.escape_string("it's"), "it\\'s");
1989        assert_eq!(dialect.escape_string("line\nbreak"), "line\\nbreak");
1990    }
1991
1992    #[test]
1993    fn test_mysql_pagination() {
1994        let dialect = MySqlDialect;
1995        let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
1996        assert_eq!(sql, "SELECT * FROM users LIMIT 10 OFFSET 10");
1997    }
1998
1999    #[test]
2000    fn test_postgres_quote() {
2001        let dialect = PostgreSqlDialect;
2002        assert_eq!(dialect.quote("users"), "\"users\"");
2003        assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2004    }
2005
2006    #[test]
2007    fn test_postgres_pagination() {
2008        let dialect = PostgreSqlDialect;
2009        let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2010        assert_eq!(sql, "SELECT * FROM users LIMIT 20 OFFSET 40");
2011    }
2012
2013    #[test]
2014    fn test_postgres_returning() {
2015        let dialect = PostgreSqlDialect;
2016        assert!(dialect.supports_returning());
2017    }
2018
2019    #[test]
2020    fn test_sqlite_quote() {
2021        let dialect = SqliteDialect;
2022        assert_eq!(dialect.quote("users"), "\"users\"");
2023        assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2024    }
2025
2026    #[test]
2027    fn test_sqlite_escape() {
2028        let dialect = SqliteDialect;
2029        assert_eq!(dialect.escape_string("hello"), "hello");
2030        assert_eq!(dialect.escape_string("it's"), "it''s");
2031    }
2032
2033    #[test]
2034    fn test_get_dialect() {
2035        let dialect = get_dialect(DbType::MySQL);
2036        assert!(dialect.is_ok());
2037
2038        let dialect = get_dialect(DbType::Redis);
2039        assert!(dialect.is_err());
2040    }
2041
2042    #[test]
2043    fn test_bool_to_int() {
2044        let mysql = MySqlDialect;
2045        assert_eq!(mysql.bool_to_int("active"), "IF(active, 1, 0)");
2046
2047        let pg = PostgreSqlDialect;
2048        assert_eq!(
2049            pg.bool_to_int("active"),
2050            "(CASE WHEN active THEN 1 ELSE 0 END)"
2051        );
2052    }
2053
2054    #[test]
2055    fn test_json_extract_with_path() {
2056        let mysql = MySqlDialect;
2057        let sql = mysql.json_extract("data", "$.user.name");
2058        assert!(sql.contains("$.user.name"));
2059        assert!(sql.contains("JSON_EXTRACT"));
2060
2061        let pg = PostgreSqlDialect;
2062        let sql = pg.json_extract("data", "user.name");
2063        assert!(sql.contains("#>>"));
2064
2065        let sqlite = SqliteDialect;
2066        let sql = sqlite.json_extract("data", "$.user.name");
2067        assert!(sql.contains("$.user.name"));
2068        assert!(sql.contains("json_extract"));
2069    }
2070
2071    #[test]
2072    fn test_sqlite_full_text_search() {
2073        let sqlite = SqliteDialect;
2074        let sql = sqlite.full_text_search(&["title", "content"], "hello");
2075        // SQLite 接口只传入列名,无 FTS 表名,因此降级使用 LIKE
2076        assert!(sql.contains("LIKE"));
2077        assert!(sql.contains("title LIKE '%hello%'"));
2078        assert!(sql.contains("content LIKE '%hello%'"));
2079        assert!(sql.contains(" OR "));
2080
2081        // 空列列表返回 "0"(短路避免空 IN/OR)
2082        assert_eq!(sqlite.full_text_search(&[], "hello"), "0");
2083
2084        // 含单引号的关键字需正确转义(SQLite 双单引号)
2085        let sql = sqlite.full_text_search(&["title"], "it's");
2086        assert!(sql.contains("title LIKE '%it''s%'"));
2087    }
2088
2089    #[test]
2090    fn test_alter_table_modify_column() {
2091        let mysql = MySqlDialect;
2092        let col = ColumnDef {
2093            name: "name".to_string(),
2094            sql_type: "VARCHAR(255)".to_string(),
2095            nullable: false,
2096            default: None,
2097            auto_increment: false,
2098            primary_key: false,
2099        };
2100        let sql = mysql.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2101        assert!(sql.contains("MODIFY COLUMN"));
2102
2103        let pg = PostgreSqlDialect;
2104        let col = ColumnDef {
2105            name: "name".to_string(),
2106            sql_type: "VARCHAR(255)".to_string(),
2107            nullable: false,
2108            default: None,
2109            auto_increment: false,
2110            primary_key: false,
2111        };
2112        let sql = pg.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2113        assert!(sql.contains("ALTER COLUMN"));
2114        assert!(sql.contains("TYPE"));
2115    }
2116
2117    #[test]
2118    fn test_alter_table_add_foreign_key() {
2119        let mysql = MySqlDialect;
2120        let sql = mysql.build_alter_table(
2121            "orders",
2122            &[TableChange::AddForeignKey {
2123                columns: vec!["user_id".to_string()],
2124                reference_table: "users".to_string(),
2125                reference_columns: vec!["id".to_string()],
2126            }],
2127        );
2128        assert!(sql.contains("FOREIGN KEY"));
2129        assert!(sql.contains("REFERENCES"));
2130
2131        let sqlite = SqliteDialect;
2132        let sql = sqlite.build_alter_table(
2133            "orders",
2134            &[TableChange::AddForeignKey {
2135                columns: vec!["user_id".to_string()],
2136                reference_table: "users".to_string(),
2137                reference_columns: vec!["id".to_string()],
2138            }],
2139        );
2140        // SQLite 不支持,应返回注释
2141        assert!(sql.starts_with("--"));
2142    }
2143
2144    #[test]
2145    fn test_sqlite_alter_table_add_column() {
2146        let sqlite = SqliteDialect;
2147        let col = ColumnDef {
2148            name: "email".to_string(),
2149            sql_type: "TEXT".to_string(),
2150            nullable: true,
2151            default: None,
2152            auto_increment: false,
2153            primary_key: false,
2154        };
2155        let sql = sqlite.build_alter_table("users", &[TableChange::AddColumn(col)]);
2156        assert!(sql.contains("ADD COLUMN"));
2157        assert!(sql.contains("email"));
2158    }
2159
2160    // ===================== Oracle 方言测试 =====================
2161
2162    #[test]
2163    fn test_oracle_quote_and_escape() {
2164        let dialect = OracleDialect;
2165        // 标识符使用双引号包裹,内部双引号双写
2166        assert_eq!(dialect.quote("users"), "\"users\"");
2167        assert_eq!(dialect.quote("user\"id"), "\"user\"\"id\"");
2168        assert_eq!(dialect.quote("column_name"), "\"column_name\"");
2169
2170        // 字符串字面量转义:单引号双写,反斜杠不转义
2171        assert_eq!(dialect.escape_string("hello"), "hello");
2172        assert_eq!(dialect.escape_string("it's"), "it''s");
2173        assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
2174        assert_eq!(dialect.escape_string("a'b'c"), "a''b''c");
2175        // 反斜杠按原样保留(Oracle 标准行为)
2176        assert_eq!(dialect.escape_string("path\\to"), "path\\to");
2177    }
2178
2179    #[test]
2180    fn test_oracle_pagination() {
2181        let dialect = OracleDialect;
2182        // page=1 为第一页(offset=0),与其他方言保持一致
2183        let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
2184        assert_eq!(
2185            sql,
2186            "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2187        );
2188        // page=3, limit=20 → offset=40
2189        let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2190        assert_eq!(
2191            sql,
2192            "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
2193        );
2194        // page=0(边界)→ offset=0
2195        let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
2196        assert_eq!(
2197            sql,
2198            "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2199        );
2200    }
2201
2202    #[test]
2203    fn test_oracle_json_extract() {
2204        let dialect = OracleDialect;
2205        // 标准 $.path 格式
2206        let sql = dialect.json_extract("data", "$.user.name");
2207        assert!(sql.contains("JSON_VALUE"));
2208        assert!(sql.contains("$.user.name"));
2209        assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
2210
2211        // 自动补全 $. 前缀
2212        let sql = dialect.json_extract("data", "user.name");
2213        assert!(sql.contains("$.user.name"));
2214        assert!(sql.contains("JSON_VALUE"));
2215
2216        // 单引号转义
2217        let sql = dialect.json_extract("data", "$.key's");
2218        assert!(sql.contains("$.key''s"));
2219    }
2220
2221    #[test]
2222    fn test_oracle_create_table() {
2223        let dialect = OracleDialect;
2224        let columns = vec![
2225            ColumnDef {
2226                name: "id".to_string(),
2227                sql_type: "BIGINT".to_string(),
2228                nullable: false,
2229                default: None,
2230                auto_increment: true,
2231                primary_key: true,
2232            },
2233            ColumnDef {
2234                name: "name".to_string(),
2235                sql_type: "VARCHAR(255)".to_string(),
2236                nullable: false,
2237                default: None,
2238                auto_increment: false,
2239                primary_key: false,
2240            },
2241            ColumnDef {
2242                name: "bio".to_string(),
2243                sql_type: "TEXT".to_string(),
2244                nullable: true,
2245                default: None,
2246                auto_increment: false,
2247                primary_key: false,
2248            },
2249            ColumnDef {
2250                name: "is_active".to_string(),
2251                sql_type: "BOOLEAN".to_string(),
2252                nullable: false,
2253                default: Some("1".to_string()),
2254                auto_increment: false,
2255                primary_key: false,
2256            },
2257        ];
2258        let sql = dialect.build_create_table("users", &columns);
2259        // Oracle 类型映射
2260        assert!(
2261            sql.contains("NUMBER(19)"),
2262            "BIGINT should map to NUMBER(19): {}",
2263            sql
2264        );
2265        assert!(
2266            sql.contains("VARCHAR2(255)"),
2267            "VARCHAR should map to VARCHAR2: {}",
2268            sql
2269        );
2270        assert!(sql.contains("CLOB"), "TEXT should map to CLOB: {}", sql);
2271        assert!(
2272            sql.contains("NUMBER(1)"),
2273            "BOOLEAN should map to NUMBER(1): {}",
2274            sql
2275        );
2276        // 自增与主键
2277        assert!(sql.contains("GENERATED BY DEFAULT AS IDENTITY"));
2278        assert!(sql.contains("PRIMARY KEY"));
2279        assert!(sql.contains("NOT NULL"));
2280        assert!(sql.contains("DEFAULT 1"));
2281        // 标识符使用双引号
2282        assert!(sql.contains("\"users\""));
2283        assert!(sql.contains("\"id\""));
2284    }
2285
2286    #[test]
2287    fn test_oracle_bool_to_int_and_concat() {
2288        let dialect = OracleDialect;
2289        // bool_to_int 使用 CASE WHEN
2290        assert_eq!(
2291            dialect.bool_to_int("active"),
2292            "(CASE WHEN active THEN 1 ELSE 0 END)"
2293        );
2294        assert_eq!(
2295            dialect.bool_to_int("x > 0"),
2296            "(CASE WHEN x > 0 THEN 1 ELSE 0 END)"
2297        );
2298        // concat 使用 || 操作符
2299        assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
2300        assert_eq!(
2301            dialect.concat(&["first_name", "last_name"]),
2302            "first_name || last_name"
2303        );
2304        // 空列表返回 NULL
2305        assert_eq!(dialect.concat(&[]), "NULL");
2306    }
2307
2308    #[test]
2309    fn test_oracle_misc_dialect_methods() {
2310        let dialect = OracleDialect;
2311        // db_type
2312        assert_eq!(dialect.db_type(), DbType::Oracle);
2313        // supports_returning: Oracle 12c+ 支持
2314        assert!(dialect.supports_returning());
2315        // supports_if_exists / supports_if_not_exists: Oracle 23ai 支持
2316        assert!(dialect.supports_if_exists());
2317        assert!(dialect.supports_if_not_exists());
2318        // auto_increment_keyword
2319        assert_eq!(
2320            dialect.auto_increment_keyword(),
2321            "GENERATED BY DEFAULT AS IDENTITY"
2322        );
2323        // last_insert_id_sql: Oracle 不支持独立可执行的获取最后插入 ID 语句
2324        // RETURNING ... INTO 是 PL/SQL 子句,必须附加在 INSERT 之后
2325        assert_eq!(dialect.last_insert_id_sql(), None);
2326        // json_type
2327        assert_eq!(dialect.json_type(), "JSON");
2328    }
2329
2330    #[test]
2331    fn test_oracle_get_dialect() {
2332        // get_dialect 应返回 OracleDialect 而非 MySqlDialect
2333        let dialect = get_dialect(DbType::Oracle);
2334        assert!(dialect.is_ok(), "Oracle dialect should be available");
2335        let dialect = dialect.unwrap();
2336        assert_eq!(dialect.db_type(), DbType::Oracle);
2337        // 验证不是 MySqlDialect 的回退:Oracle 使用双引号,MySQL 使用反引号
2338        assert_eq!(dialect.quote("users"), "\"users\"");
2339        // 验证 Oracle 特有功能
2340        assert!(dialect.supports_returning());
2341        // Oracle 没有 standalone 的 last_insert_id SQL
2342        assert_eq!(dialect.last_insert_id_sql(), None);
2343    }
2344
2345    #[test]
2346    fn test_oracle_drop_table() {
2347        let dialect = OracleDialect;
2348        // IF EXISTS
2349        let sql = dialect.build_drop_table("users", true);
2350        assert_eq!(sql, "DROP TABLE IF EXISTS \"users\"");
2351        // 不带 IF EXISTS
2352        let sql = dialect.build_drop_table("users", false);
2353        assert_eq!(sql, "DROP TABLE \"users\"");
2354    }
2355
2356    #[test]
2357    fn test_oracle_alter_table() {
2358        let dialect = OracleDialect;
2359        // MODIFY COLUMN: Oracle 使用 MODIFY(不带 COLUMN 关键字)
2360        let col = ColumnDef {
2361            name: "name".to_string(),
2362            sql_type: "VARCHAR(255)".to_string(),
2363            nullable: false,
2364            default: None,
2365            auto_increment: false,
2366            primary_key: false,
2367        };
2368        let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2369        assert!(sql.contains("MODIFY"));
2370        assert!(sql.contains("VARCHAR2(255)"));
2371        assert!(!sql.contains("MODIFY COLUMN")); // Oracle 不使用 COLUMN 关键字
2372
2373        // ADD COLUMN
2374        let col = ColumnDef {
2375            name: "email".to_string(),
2376            sql_type: "VARCHAR(255)".to_string(),
2377            nullable: true,
2378            default: None,
2379            auto_increment: false,
2380            primary_key: false,
2381        };
2382        let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
2383        assert!(sql.contains("ADD \"email\""));
2384        assert!(sql.contains("VARCHAR2(255)"));
2385
2386        // DROP COLUMN
2387        let sql =
2388            dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
2389        assert!(sql.contains("DROP COLUMN"));
2390        assert!(sql.contains("\"email\""));
2391    }
2392
2393    // ===================== SQLite concat 修复测试 =====================
2394
2395    #[test]
2396    fn test_sqlite_concat_handles_null() {
2397        let sqlite = SqliteDialect;
2398        // 修复前:COALESCE(a || b) 在 a 为 NULL 时整体已 NULL,COALESCE 失效
2399        // 修复后:每个参数 COALESCE 为空串,保证拼接结果非 NULL
2400        let sql = sqlite.concat(&["a", "b"]);
2401        assert_eq!(sql, "COALESCE(a, '') || COALESCE(b, '')");
2402        // 单参数
2403        let sql = sqlite.concat(&["a"]);
2404        assert_eq!(sql, "COALESCE(a, '')");
2405        // 空列表
2406        assert_eq!(sqlite.concat(&[]), "NULL");
2407    }
2408
2409    // ===================== SQL Server 方言测试 =====================
2410
2411    #[test]
2412    fn test_sqlserver_quote_and_escape() {
2413        let dialect = SqlServerDialect;
2414        // 标识符使用 [name] 包裹,内部 ] 双写
2415        assert_eq!(dialect.quote("users"), "[users]");
2416        assert_eq!(dialect.quote("col]name"), "[col]]name]");
2417        // 字符串字面量:单引号双写,反斜杠不转义
2418        assert_eq!(dialect.escape_string("hello"), "hello");
2419        assert_eq!(dialect.escape_string("it's"), "it''s");
2420        assert_eq!(dialect.escape_string("O'Brien"), "O''Brien");
2421        assert_eq!(dialect.escape_string("path\\to"), "path\\to");
2422    }
2423
2424    #[test]
2425    fn test_sqlserver_pagination() {
2426        let dialect = SqlServerDialect;
2427        // SQL Server 2012+ 使用 OFFSET ... ROWS FETCH NEXT ... ROWS ONLY
2428        let sql = dialect.build_pagination("SELECT * FROM users", 1, 10);
2429        assert_eq!(
2430            sql,
2431            "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2432        );
2433        let sql = dialect.build_pagination("SELECT * FROM users", 3, 20);
2434        assert_eq!(
2435            sql,
2436            "SELECT * FROM users OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY"
2437        );
2438        // page=0 边界
2439        let sql = dialect.build_pagination("SELECT * FROM users", 0, 10);
2440        assert_eq!(
2441            sql,
2442            "SELECT * FROM users OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY"
2443        );
2444    }
2445
2446    #[test]
2447    fn test_sqlserver_misc_dialect_methods() {
2448        let dialect = SqlServerDialect;
2449        assert_eq!(dialect.db_type(), DbType::SqlServer);
2450        // supports_returning: SQL Server 使用 OUTPUT,语义等价
2451        assert!(dialect.supports_returning());
2452        // supports_if_exists / supports_if_not_exists: SQL Server 2016+
2453        assert!(dialect.supports_if_exists());
2454        assert!(dialect.supports_if_not_exists());
2455        // auto_increment_keyword
2456        assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
2457        // last_insert_id_sql
2458        assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
2459        // json_type
2460        assert_eq!(dialect.json_type(), "NVARCHAR(MAX)");
2461    }
2462
2463    #[test]
2464    fn test_sqlserver_json_extract() {
2465        let dialect = SqlServerDialect;
2466        let sql = dialect.json_extract("data", "$.user.name");
2467        assert!(sql.starts_with("JSON_VALUE(data, '$.user.name')"));
2468        // 自动补 $. 前缀
2469        let sql = dialect.json_extract("data", "user.name");
2470        assert!(sql.contains("$.user.name"));
2471        assert!(sql.contains("JSON_VALUE"));
2472        // 单引号转义
2473        let sql = dialect.json_extract("data", "$.key's");
2474        assert!(sql.contains("$.key''s"));
2475    }
2476
2477    #[test]
2478    fn test_sqlserver_full_text_search() {
2479        let dialect = SqlServerDialect;
2480        let sql = dialect.full_text_search(&["title", "content"], "hello");
2481        assert!(sql.starts_with("CONTAINS(title, content, 'hello')"));
2482        // 空列列表
2483        assert_eq!(dialect.full_text_search(&[], "hello"), "0");
2484        // 单引号转义
2485        let sql = dialect.full_text_search(&["title"], "it's");
2486        assert!(sql.contains("it''s"));
2487    }
2488
2489    #[test]
2490    fn test_sqlserver_bool_to_int_and_concat() {
2491        let dialect = SqlServerDialect;
2492        assert_eq!(
2493            dialect.bool_to_int("active"),
2494            "(CASE WHEN active THEN 1 ELSE 0 END)"
2495        );
2496        assert_eq!(dialect.concat(&["a", "b", "c"]), "CONCAT(a, b, c)");
2497        assert_eq!(dialect.concat(&[]), "NULL");
2498    }
2499
2500    #[test]
2501    fn test_sqlserver_create_table() {
2502        let dialect = SqlServerDialect;
2503        let columns = vec![
2504            ColumnDef {
2505                name: "id".to_string(),
2506                sql_type: "BIGINT".to_string(),
2507                nullable: false,
2508                default: None,
2509                auto_increment: true,
2510                primary_key: true,
2511            },
2512            ColumnDef {
2513                name: "name".to_string(),
2514                sql_type: "VARCHAR(255)".to_string(),
2515                nullable: false,
2516                default: None,
2517                auto_increment: false,
2518                primary_key: false,
2519            },
2520            ColumnDef {
2521                name: "bio".to_string(),
2522                sql_type: "TEXT".to_string(),
2523                nullable: true,
2524                default: None,
2525                auto_increment: false,
2526                primary_key: false,
2527            },
2528            ColumnDef {
2529                name: "is_active".to_string(),
2530                sql_type: "BOOLEAN".to_string(),
2531                nullable: false,
2532                default: Some("1".to_string()),
2533                auto_increment: false,
2534                primary_key: false,
2535            },
2536        ];
2537        let sql = dialect.build_create_table("users", &columns);
2538        // 标识符使用方括号
2539        assert!(sql.contains("[users]"));
2540        assert!(sql.contains("[id]"));
2541        // 类型映射
2542        assert!(sql.contains("IDENTITY(1,1)"));
2543        assert!(
2544            sql.contains("NVARCHAR(255)"),
2545            "VARCHAR should map to NVARCHAR: {}",
2546            sql
2547        );
2548        assert!(
2549            sql.contains("NVARCHAR(MAX)"),
2550            "TEXT should map to NVARCHAR(MAX): {}",
2551            sql
2552        );
2553        assert!(sql.contains("BIT"), "BOOLEAN should map to BIT: {}", sql);
2554        assert!(sql.contains("PRIMARY KEY"));
2555        assert!(sql.contains("NOT NULL"));
2556        assert!(sql.contains("DEFAULT 1"));
2557    }
2558
2559    #[test]
2560    fn test_sqlserver_drop_table() {
2561        let dialect = SqlServerDialect;
2562        assert_eq!(
2563            dialect.build_drop_table("users", true),
2564            "DROP TABLE IF EXISTS [users]"
2565        );
2566        assert_eq!(
2567            dialect.build_drop_table("users", false),
2568            "DROP TABLE [users]"
2569        );
2570    }
2571
2572    #[test]
2573    fn test_sqlserver_alter_table() {
2574        let dialect = SqlServerDialect;
2575        // MODIFY COLUMN: SQL Server 使用 ALTER COLUMN(不是 MODIFY)
2576        let col = ColumnDef {
2577            name: "name".to_string(),
2578            sql_type: "VARCHAR(255)".to_string(),
2579            nullable: false,
2580            default: None,
2581            auto_increment: false,
2582            primary_key: false,
2583        };
2584        let sql = dialect.build_alter_table("users", &[TableChange::ModifyColumn(col)]);
2585        assert!(sql.contains("ALTER COLUMN"));
2586        assert!(sql.contains("NVARCHAR(255)"));
2587        assert!(!sql.contains("MODIFY"));
2588
2589        // ADD COLUMN
2590        let col = ColumnDef {
2591            name: "email".to_string(),
2592            sql_type: "VARCHAR(255)".to_string(),
2593            nullable: true,
2594            default: None,
2595            auto_increment: false,
2596            primary_key: false,
2597        };
2598        let sql = dialect.build_alter_table("users", &[TableChange::AddColumn(col)]);
2599        assert!(sql.contains("ADD [email]"));
2600        assert!(sql.contains("NVARCHAR(255)"));
2601
2602        // DROP COLUMN
2603        let sql =
2604            dialect.build_alter_table("users", &[TableChange::DropColumn("email".to_string())]);
2605        assert!(sql.contains("DROP COLUMN"));
2606        assert!(sql.contains("[email]"));
2607
2608        // DROP INDEX 必须指定表名
2609        let sql =
2610            dialect.build_alter_table("users", &[TableChange::DropIndex("idx_name".to_string())]);
2611        assert!(sql.contains("DROP INDEX idx_name ON [users]"));
2612    }
2613
2614    #[test]
2615    fn test_sqlserver_get_dialect() {
2616        // get_dialect 应返回 SqlServerDialect,而不是 MySqlDialect 回退
2617        let dialect = get_dialect(DbType::SqlServer);
2618        assert!(dialect.is_ok(), "SqlServer dialect should be available");
2619        let dialect = dialect.unwrap();
2620        assert_eq!(dialect.db_type(), DbType::SqlServer);
2621        // 验证不是 MySqlDialect 的回退:SQL Server 使用方括号,MySQL 使用反引号
2622        assert_eq!(dialect.quote("users"), "[users]");
2623        // SQL Server 特有功能
2624        assert_eq!(dialect.last_insert_id_sql(), Some("SCOPE_IDENTITY()"));
2625        assert_eq!(dialect.auto_increment_keyword(), "IDENTITY(1,1)");
2626    }
2627
2628    #[test]
2629    fn test_clickhouse_get_dialect_unsupported() {
2630        // ClickHouse 现已支持独立方言(不再回退到 MySqlDialect)
2631        let dialect = get_dialect(DbType::ClickHouse);
2632        assert!(dialect.is_ok(), "ClickHouse should be supported");
2633        let dialect = dialect.unwrap();
2634        assert_eq!(dialect.db_type(), DbType::ClickHouse);
2635        // ClickHouse 使用 backquote 标识符(与 MySQL 一致)
2636        assert_eq!(dialect.quote("users"), "`users`");
2637        // ClickHouse 不支持 RETURNING
2638        assert!(!dialect.supports_returning());
2639        // ClickHouse 使用 LIMIT offset, limit 分页
2640        let sql = dialect.build_pagination("SELECT * FROM t", 2, 10);
2641        assert_eq!(sql, "SELECT * FROM t LIMIT 10, 10");
2642        // ClickHouse 自增列为空字符串
2643        assert_eq!(dialect.auto_increment_keyword(), "");
2644    }
2645
2646    #[test]
2647    fn test_get_dialect_all_supported_types() {
2648        // 所有声称为支持的方言应正确返回,不支持的应返回 Err
2649        assert!(get_dialect(DbType::MySQL).is_ok());
2650        assert!(get_dialect(DbType::PostgreSQL).is_ok());
2651        assert!(get_dialect(DbType::Sqlite).is_ok());
2652        assert!(get_dialect(DbType::Oracle).is_ok());
2653        assert!(get_dialect(DbType::SqlServer).is_ok());
2654        assert!(get_dialect(DbType::OceanBase).is_ok());
2655        assert!(get_dialect(DbType::ClickHouse).is_ok());
2656        // 国产数据库 & 兼容方言
2657        assert!(get_dialect(DbType::Dameng).is_ok());
2658        assert!(get_dialect(DbType::Kingbase).is_ok());
2659        assert!(get_dialect(DbType::Db2).is_ok());
2660        assert!(get_dialect(DbType::MariaDB).is_ok());
2661        assert!(get_dialect(DbType::TiDB).is_ok());
2662        assert!(get_dialect(DbType::PolarDB).is_ok());
2663        assert!(get_dialect(DbType::GaussDB).is_ok());
2664        assert!(get_dialect(DbType::GBase).is_ok());
2665        assert!(get_dialect(DbType::Sybase).is_ok());
2666        // 不支持标准 SQL 的
2667        assert!(get_dialect(DbType::Redis).is_err());
2668        assert!(get_dialect(DbType::MongoDB).is_err());
2669        assert!(get_dialect(DbType::VectorDb).is_err());
2670        assert!(get_dialect(DbType::PureJsDb).is_err());
2671    }
2672
2673    // ===== 国产数据库兼容方言测试 =====
2674
2675    #[test]
2676    fn test_mariadb_dialect() {
2677        let dialect = get_dialect(DbType::MariaDB).unwrap();
2678        assert_eq!(dialect.db_type(), DbType::MariaDB);
2679        // MariaDB 兼容 MySQL 语法:backquote 标识符 + 反斜杠转义
2680        assert_eq!(dialect.quote("users"), "`users`");
2681        assert_eq!(dialect.escape_string("it's"), "it\\'s");
2682        assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
2683        // MySQL 家族不支持 RETURNING(MySQL 协议层限制)
2684        assert!(!dialect.supports_returning());
2685    }
2686
2687    #[test]
2688    fn test_tidb_dialect() {
2689        let dialect = get_dialect(DbType::TiDB).unwrap();
2690        assert_eq!(dialect.db_type(), DbType::TiDB);
2691        // TiDB 兼容 MySQL 语法
2692        assert_eq!(dialect.quote("users"), "`users`");
2693        assert_eq!(dialect.escape_string("it's"), "it\\'s");
2694        assert_eq!(dialect.auto_increment_keyword(), "AUTO_INCREMENT");
2695    }
2696
2697    #[test]
2698    fn test_dameng_dialect() {
2699        let dialect = get_dialect(DbType::Dameng).unwrap();
2700        assert_eq!(dialect.db_type(), DbType::Dameng);
2701        // 达梦兼容 Oracle 语法:双引号标识符 + 单引号双写
2702        assert_eq!(dialect.quote("users"), "\"users\"");
2703        assert_eq!(dialect.escape_string("it's"), "it''s");
2704        // Oracle 兼容:使用 IDENTITY 列
2705        assert_eq!(
2706            dialect.auto_increment_keyword(),
2707            "GENERATED BY DEFAULT AS IDENTITY"
2708        );
2709        // Oracle 兼容:支持 RETURNING
2710        assert!(dialect.supports_returning());
2711    }
2712
2713    #[test]
2714    fn test_kingbase_dialect() {
2715        let dialect = get_dialect(DbType::Kingbase).unwrap();
2716        assert_eq!(dialect.db_type(), DbType::Kingbase);
2717        // 人大金仓兼容 PostgreSQL 语法:双引号标识符 + 单引号双写
2718        assert_eq!(dialect.quote("users"), "\"users\"");
2719        assert_eq!(dialect.escape_string("it's"), "it''s");
2720        // PG 兼容:支持 RETURNING
2721        assert!(dialect.supports_returning());
2722        // PG 兼容:使用 GENERATED BY DEFAULT AS IDENTITY(PG 10+ 标准)
2723        assert_eq!(
2724            dialect.auto_increment_keyword(),
2725            "GENERATED BY DEFAULT AS IDENTITY"
2726        );
2727    }
2728
2729    #[test]
2730    fn test_polardb_dialect() {
2731        let dialect = get_dialect(DbType::PolarDB).unwrap();
2732        assert_eq!(dialect.db_type(), DbType::PolarDB);
2733        // PolarDB-PG 兼容 PostgreSQL
2734        assert_eq!(dialect.quote("users"), "\"users\"");
2735        assert!(dialect.supports_returning());
2736    }
2737
2738    #[test]
2739    fn test_gaussdb_dialect() {
2740        let dialect = get_dialect(DbType::GaussDB).unwrap();
2741        assert_eq!(dialect.db_type(), DbType::GaussDB);
2742        // GaussDB 兼容 PostgreSQL
2743        assert_eq!(dialect.quote("users"), "\"users\"");
2744        assert!(dialect.supports_returning());
2745    }
2746
2747    #[test]
2748    fn test_gbase_dialect() {
2749        let dialect = get_dialect(DbType::GBase).unwrap();
2750        assert_eq!(dialect.db_type(), DbType::GBase);
2751        // GBase 8s 兼容 T-SQL(使用方括号)
2752        assert_eq!(dialect.quote("users"), "[users]");
2753    }
2754
2755    #[test]
2756    fn test_sybase_dialect() {
2757        let dialect = get_dialect(DbType::Sybase).unwrap();
2758        assert_eq!(dialect.db_type(), DbType::Sybase);
2759        // Sybase ASE 兼容 T-SQL
2760        assert_eq!(dialect.quote("users"), "[users]");
2761    }
2762
2763    // ===== DB2 独立方言测试 =====
2764
2765    #[test]
2766    fn test_db2_dialect_basic() {
2767        let dialect = get_dialect(DbType::Db2).unwrap();
2768        assert_eq!(dialect.db_type(), DbType::Db2);
2769        // DB2 使用双引号标识符
2770        assert_eq!(dialect.quote("users"), "\"users\"");
2771        // DB2 单引号双写
2772        assert_eq!(dialect.escape_string("it's"), "it''s");
2773        // DB2 使用 IDENTITY
2774        assert_eq!(
2775            dialect.auto_increment_keyword(),
2776            "GENERATED ALWAYS AS IDENTITY"
2777        );
2778        // DB2 不支持 IF EXISTS / IF NOT EXISTS(11.5 之前)
2779        assert!(!dialect.supports_if_exists());
2780        assert!(!dialect.supports_if_not_exists());
2781        // DB2 不支持 RETURNING(使用 SELECT FROM FINAL TABLE 代替)
2782        assert!(!dialect.supports_returning());
2783    }
2784
2785    #[test]
2786    fn test_db2_pagination() {
2787        let dialect = Db2Dialect;
2788        // DB2 使用 OFFSET x ROWS FETCH NEXT y ROWS ONLY
2789        let sql = dialect.build_pagination("SELECT * FROM users", 2, 10);
2790        assert_eq!(
2791            sql,
2792            "SELECT * FROM users OFFSET 10 ROWS FETCH NEXT 10 ROWS ONLY"
2793        );
2794    }
2795
2796    #[test]
2797    fn test_db2_last_insert_id() {
2798        let dialect = Db2Dialect;
2799        // DB2 使用 IDENTITY_VAL_LOCAL() 获取最后插入的 IDENTITY
2800        assert_eq!(
2801            dialect.last_insert_id_sql(),
2802            Some("SELECT IDENTITY_VAL_LOCAL() FROM SYSIBM.SYSDUMMY1")
2803        );
2804    }
2805
2806    #[test]
2807    fn test_db2_concat() {
2808        let dialect = Db2Dialect;
2809        // DB2 使用 || 拼接
2810        assert_eq!(dialect.concat(&["a", "b", "c"]), "a || b || c");
2811        assert_eq!(dialect.concat(&[]), "''");
2812    }
2813
2814    #[test]
2815    fn test_db2_create_table() {
2816        let dialect = Db2Dialect;
2817        let cols = vec![ColumnDef {
2818            name: "id".to_string(),
2819            sql_type: "BIGINT".to_string(),
2820            nullable: false,
2821            default: None,
2822            auto_increment: true,
2823            primary_key: true,
2824        }];
2825        let sql = dialect.build_create_table("users", &cols);
2826        assert!(sql.contains("\"id\" BIGINT"));
2827        assert!(sql.contains("GENERATED ALWAYS AS IDENTITY"));
2828        assert!(sql.contains("PRIMARY KEY"));
2829    }
2830
2831    #[test]
2832    fn test_db2_type_mapping() {
2833        // 验证通用类型到 DB2 类型的映射
2834        assert_eq!(map_to_db2_type("BIGINT"), "BIGINT");
2835        assert_eq!(map_to_db2_type("INT"), "INTEGER");
2836        assert_eq!(map_to_db2_type("INTEGER"), "INTEGER");
2837        assert_eq!(map_to_db2_type("TINYINT"), "SMALLINT");
2838        assert_eq!(map_to_db2_type("SMALLINT"), "SMALLINT");
2839        assert_eq!(map_to_db2_type("TEXT"), "CLOB(2G)");
2840        assert_eq!(map_to_db2_type("LONGTEXT"), "CLOB(2G)");
2841        assert_eq!(map_to_db2_type("BOOLEAN"), "SMALLINT");
2842        assert_eq!(map_to_db2_type("BOOL"), "SMALLINT");
2843        assert_eq!(map_to_db2_type("DATETIME"), "TIMESTAMP");
2844        assert_eq!(map_to_db2_type("TIMESTAMP"), "TIMESTAMP");
2845        assert_eq!(map_to_db2_type("DATE"), "DATE");
2846        assert_eq!(map_to_db2_type("VARCHAR(255)"), "VARCHAR(255)");
2847    }
2848
2849    // ===== ClickHouse 独立方言测试 =====
2850
2851    #[test]
2852    fn test_clickhouse_dialect_basic() {
2853        let dialect = get_dialect(DbType::ClickHouse).unwrap();
2854        assert_eq!(dialect.db_type(), DbType::ClickHouse);
2855        // ClickHouse 使用 backquote 标识符(与 MySQL 一致)
2856        assert_eq!(dialect.quote("users"), "`users`");
2857        // ClickHouse 反斜杠转义(与 MySQL 一致)
2858        assert_eq!(dialect.escape_string("it's"), "it\\'s");
2859        // ClickHouse 不支持 RETURNING
2860        assert!(!dialect.supports_returning());
2861        // ClickHouse 不支持自增列
2862        assert_eq!(dialect.auto_increment_keyword(), "");
2863        // ClickHouse 支持 IF EXISTS / IF NOT EXISTS
2864        assert!(dialect.supports_if_exists());
2865        assert!(dialect.supports_if_not_exists());
2866    }
2867
2868    #[test]
2869    fn test_clickhouse_type_mapping() {
2870        assert_eq!(map_to_clickhouse_type("BIGINT"), "Int64");
2871        assert_eq!(map_to_clickhouse_type("INT"), "Int32");
2872        assert_eq!(map_to_clickhouse_type("INTEGER"), "Int32");
2873        assert_eq!(map_to_clickhouse_type("TINYINT"), "Int16");
2874        assert_eq!(map_to_clickhouse_type("SMALLINT"), "Int16");
2875        assert_eq!(map_to_clickhouse_type("VARCHAR(255)"), "String");
2876        assert_eq!(map_to_clickhouse_type("TEXT"), "String");
2877        assert_eq!(map_to_clickhouse_type("BOOLEAN"), "UInt8");
2878        assert_eq!(map_to_clickhouse_type("BOOL"), "UInt8");
2879        assert_eq!(map_to_clickhouse_type("FLOAT"), "Float32");
2880        assert_eq!(map_to_clickhouse_type("DOUBLE"), "Float64");
2881        assert_eq!(map_to_clickhouse_type("DATETIME"), "DateTime");
2882        assert_eq!(map_to_clickhouse_type("TIMESTAMP"), "DateTime");
2883        assert_eq!(map_to_clickhouse_type("DATE"), "Date");
2884    }
2885
2886    #[test]
2887    fn test_clickhouse_create_table() {
2888        let dialect = ClickHouseDialect;
2889        let cols = vec![ColumnDef {
2890            name: "id".to_string(),
2891            sql_type: "BIGINT".to_string(),
2892            nullable: false,
2893            default: None,
2894            auto_increment: false, // ClickHouse 不支持自增
2895            primary_key: true,
2896        }];
2897        let sql = dialect.build_create_table("users", &cols);
2898        // 必须包含 ENGINE = MergeTree()
2899        assert!(
2900            sql.contains("ENGINE = MergeTree()"),
2901            "ClickHouse CREATE TABLE 必须指定 ENGINE: {}",
2902            sql
2903        );
2904        assert!(sql.contains("`id` Int64"));
2905        assert!(sql.contains("PRIMARY KEY"));
2906    }
2907
2908    #[test]
2909    fn test_clickhouse_json_extract() {
2910        let dialect = ClickHouseDialect;
2911        let sql = dialect.json_extract("data", "$.name");
2912        assert!(
2913            sql.contains("JSONExtractString"),
2914            "ClickHouse 应使用 JSONExtractString: {}",
2915            sql
2916        );
2917    }
2918
2919    #[test]
2920    fn test_clickhouse_concat() {
2921        let dialect = ClickHouseDialect;
2922        // ClickHouse 使用 concat() 函数
2923        assert_eq!(dialect.concat(&["a", "b", "c"]), "concat(a, b, c)");
2924        assert_eq!(dialect.concat(&[]), "''");
2925    }
2926
2927    // ===== DbType 国产数据库变体测试 =====
2928
2929    #[test]
2930    fn test_db_type_dameng_str() {
2931        assert_eq!(DbType::Dameng.as_str(), "dameng");
2932        assert_eq!(DbType::from_str("dameng"), Some(DbType::Dameng));
2933        assert_eq!(DbType::from_str("DM"), Some(DbType::Dameng));
2934        assert_eq!(DbType::from_str("dm8"), Some(DbType::Dameng));
2935        assert_eq!(DbType::Dameng.default_port(), 5236);
2936    }
2937
2938    #[test]
2939    fn test_db_type_kingbase_str() {
2940        assert_eq!(DbType::Kingbase.as_str(), "kingbase");
2941        assert_eq!(DbType::from_str("kingbase"), Some(DbType::Kingbase));
2942        assert_eq!(DbType::Kingbase.default_port(), 54321);
2943    }
2944
2945    #[test]
2946    fn test_db_type_db2_str() {
2947        assert_eq!(DbType::Db2.as_str(), "db2");
2948        assert_eq!(DbType::from_str("db2"), Some(DbType::Db2));
2949        assert_eq!(DbType::Db2.default_port(), 50000);
2950    }
2951
2952    #[test]
2953    fn test_db_type_mariadb_str() {
2954        assert_eq!(DbType::MariaDB.as_str(), "mariadb");
2955        assert_eq!(DbType::from_str("mariadb"), Some(DbType::MariaDB));
2956        assert_eq!(DbType::MariaDB.default_port(), 3306);
2957    }
2958
2959    #[test]
2960    fn test_db_type_tidb_str() {
2961        assert_eq!(DbType::TiDB.as_str(), "tidb");
2962        assert_eq!(DbType::from_str("tidb"), Some(DbType::TiDB));
2963        assert_eq!(DbType::TiDB.default_port(), 4000);
2964    }
2965
2966    #[test]
2967    fn test_db_type_polardb_str() {
2968        assert_eq!(DbType::PolarDB.as_str(), "polardb");
2969        assert_eq!(DbType::from_str("polardb"), Some(DbType::PolarDB));
2970        assert_eq!(DbType::PolarDB.default_port(), 5432);
2971    }
2972
2973    #[test]
2974    fn test_db_type_gaussdb_str() {
2975        assert_eq!(DbType::GaussDB.as_str(), "gaussdb");
2976        assert_eq!(DbType::from_str("gaussdb"), Some(DbType::GaussDB));
2977        assert_eq!(DbType::GaussDB.default_port(), 25308);
2978    }
2979
2980    #[test]
2981    fn test_db_type_gbase_str() {
2982        assert_eq!(DbType::GBase.as_str(), "gbase");
2983        assert_eq!(DbType::from_str("gbase"), Some(DbType::GBase));
2984        assert_eq!(DbType::GBase.default_port(), 9088);
2985    }
2986
2987    #[test]
2988    fn test_db_type_sybase_str() {
2989        assert_eq!(DbType::Sybase.as_str(), "sybase");
2990        assert_eq!(DbType::from_str("sybase"), Some(DbType::Sybase));
2991        assert_eq!(DbType::Sybase.default_port(), 5000);
2992    }
2993
2994    #[test]
2995    fn test_db_type_family_classification() {
2996        // MySQL 家族
2997        assert!(DbType::MySQL.is_mysql_family());
2998        assert!(DbType::MariaDB.is_mysql_family());
2999        assert!(DbType::TiDB.is_mysql_family());
3000        assert!(DbType::OceanBase.is_mysql_family());
3001        assert!(!DbType::PostgreSQL.is_mysql_family());
3002
3003        // PostgreSQL 家族
3004        assert!(DbType::PostgreSQL.is_postgres_family());
3005        assert!(DbType::Kingbase.is_postgres_family());
3006        assert!(DbType::GaussDB.is_postgres_family());
3007        assert!(!DbType::MySQL.is_postgres_family());
3008
3009        // Oracle 家族
3010        assert!(DbType::Oracle.is_oracle_family());
3011        assert!(DbType::Dameng.is_oracle_family());
3012        assert!(!DbType::MySQL.is_oracle_family());
3013    }
3014
3015    #[test]
3016    fn test_db_type_supports_stored_procedure_extended() {
3017        // 所有 SQL 数据库应支持存储过程
3018        assert!(DbType::Dameng.supports_stored_procedure());
3019        assert!(DbType::Kingbase.supports_stored_procedure());
3020        assert!(DbType::Db2.supports_stored_procedure());
3021        assert!(DbType::MariaDB.supports_stored_procedure());
3022        assert!(DbType::TiDB.supports_stored_procedure());
3023        assert!(DbType::PolarDB.supports_stored_procedure());
3024        assert!(DbType::GaussDB.supports_stored_procedure());
3025        assert!(DbType::GBase.supports_stored_procedure());
3026        assert!(DbType::Sybase.supports_stored_procedure());
3027    }
3028
3029    // ===== L-4 修复:表名/列名长度校验 =====
3030
3031    #[test]
3032    fn test_l4_max_identifier_len_constant() {
3033        // MAX_IDENTIFIER_LEN 应为 63(PostgreSQL 最严格值)
3034        assert_eq!(MAX_IDENTIFIER_LEN, 63);
3035    }
3036
3037    #[test]
3038    fn test_l4_quote_checked_valid_identifier() {
3039        let dialect = MySqlDialect;
3040        assert_eq!(dialect.quote_checked("users").unwrap(), "`users`");
3041        assert_eq!(dialect.quote_checked("user_id").unwrap(), "`user_id`");
3042        // 边界:恰好 63 字符
3043        let name_63 = "a".repeat(63);
3044        assert!(dialect.quote_checked(&name_63).is_ok());
3045    }
3046
3047    #[test]
3048    fn test_l4_quote_checked_rejects_too_long() {
3049        let dialect = MySqlDialect;
3050        let long_name = "a".repeat(64); // 64 > 63
3051        let result = dialect.quote_checked(&long_name);
3052        assert!(result.is_err());
3053        match result {
3054            Err(DbError::InvalidInput(msg)) => {
3055                assert!(
3056                    msg.contains("too long"),
3057                    "expected 'too long' error, got: {}",
3058                    msg
3059                );
3060            }
3061            _ => panic!("Expected DbError::InvalidInput"),
3062        }
3063    }
3064
3065    #[test]
3066    fn test_l4_quote_checked_rejects_empty() {
3067        let dialect = MySqlDialect;
3068        let result = dialect.quote_checked("");
3069        assert!(result.is_err());
3070    }
3071
3072    #[test]
3073    fn test_l4_quote_checked_rejects_sql_injection() {
3074        let dialect = MySqlDialect;
3075        // 含分号
3076        assert!(dialect.quote_checked("users; DROP TABLE users").is_err());
3077        // 含引号
3078        assert!(dialect.quote_checked("user'name").is_err());
3079        // 含空格
3080        assert!(dialect.quote_checked("user name").is_err());
3081        // 数字开头
3082        assert!(dialect.quote_checked("1users").is_err());
3083        // 含点号
3084        assert!(dialect.quote_checked("schema.table").is_err());
3085    }
3086
3087    #[test]
3088    fn test_l4_quote_checked_postgres() {
3089        let dialect = PostgreSqlDialect;
3090        assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3091        assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3092    }
3093
3094    #[test]
3095    fn test_l4_quote_checked_sqlite() {
3096        let dialect = SqliteDialect;
3097        assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3098        assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3099    }
3100
3101    #[test]
3102    fn test_l4_quote_checked_oracle() {
3103        let dialect = OracleDialect;
3104        assert_eq!(dialect.quote_checked("users").unwrap(), "\"users\"");
3105        assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3106    }
3107
3108    #[test]
3109    fn test_l4_quote_checked_sql_server() {
3110        let dialect = SqlServerDialect;
3111        assert_eq!(dialect.quote_checked("users").unwrap(), "[users]");
3112        assert!(dialect.quote_checked(&"a".repeat(64)).is_err());
3113    }
3114}