Skip to main content

ormdantic_dialects/
lib.rs

1//! SQL dialect support for Ormdantic query compilation.
2//!
3//! ```
4//! use ormdantic_dialects::{AnyDialect, Dialect, DialectKind};
5//!
6//! assert_eq!(
7//!     DialectKind::parse("postgresql+asyncpg://user:pass@localhost/db")?,
8//!     DialectKind::Postgres
9//! );
10//!
11//! let dialect = AnyDialect::parse("postgresql")?;
12//! assert_eq!(dialect.name(), "postgresql");
13//! assert_eq!(dialect.quote_ident("coffee"), "\"coffee\"");
14//! assert_eq!(dialect.placeholder(2), "$2");
15//!
16//! # Ok::<(), ormdantic_core::OrmdanticError>(())
17//! ```
18
19mod ddl;
20mod identifiers;
21mod kind;
22mod reflection;
23mod transactions;
24
25pub use kind::{normalize_dialect_name, DialectKind};
26pub use reflection::{ReflectionQuery, ReflectionQueryKind, ReflectionScope};
27
28use ddl::{
29    compile_add_column, compile_add_constraint, compile_alter_column, compile_column_comment,
30    compile_create_index, compile_create_table, compile_drop_column, compile_drop_index,
31    compile_table_comment, compile_table_mysql_options, compile_table_postgres_inherits,
32    compile_table_postgres_using, compile_table_postgres_with, compile_table_tablespace,
33    MysqlTableOptionsRef,
34};
35use identifiers::{quote_backtick, quote_double};
36use ormdantic_core::{
37    BackendFeature, DeferrableMode, FeatureSet, IsolationLevel, OrmdanticError, OrmdanticResult,
38    SavepointName, TransactionAccessMode, TransactionOptions,
39};
40use ormdantic_schema::{ColumnDef, FieldKind, NamespaceDef, SchemaOperation};
41use reflection::scope_predicate;
42use transactions::render_isolation_level;
43
44pub trait Dialect {
45    fn kind(&self) -> DialectKind;
46    fn name(&self) -> &'static str;
47    fn quote_ident(&self, ident: &str) -> String;
48    fn placeholder(&self, index: usize) -> String;
49    fn max_bind_parameters(&self) -> Option<usize> {
50        Some(match self.kind() {
51            DialectKind::Sqlite => 32_766,
52            DialectKind::Postgres
53            | DialectKind::MySql
54            | DialectKind::MariaDb
55            | DialectKind::Oracle => 65_535,
56            DialectKind::MsSql => 2_100,
57        })
58    }
59    fn supports_returning(&self) -> bool;
60    fn supports_native_uuid(&self) -> bool;
61    fn supports_json(&self) -> bool;
62
63    fn feature_set(&self) -> FeatureSet {
64        let mut features = FeatureSet::new([
65            BackendFeature::Ctes,
66            BackendFeature::Savepoints,
67            BackendFeature::Windows,
68        ]);
69        if self.supports_returning() {
70            features.insert(BackendFeature::Returning);
71        }
72        if self.supports_json() {
73            features.insert(BackendFeature::NativeJson);
74        }
75        if self.supports_native_uuid() {
76            features.insert(BackendFeature::NativeUuid);
77        }
78        features
79    }
80
81    fn supports_feature(&self, feature: BackendFeature) -> bool {
82        self.feature_set().contains(feature)
83    }
84
85    fn render_column_type(&self, column: &ColumnDef) -> String {
86        match column.kind() {
87            FieldKind::String => {
88                render_string_type(self.kind(), column.max_length(), column.is_primary_key())
89            }
90            FieldKind::Enum { name, schema } => match (self.kind(), name) {
91                (DialectKind::Postgres, Some(name)) => match schema {
92                    Some(schema) => {
93                        format!("{}.{}", self.quote_ident(schema), self.quote_ident(name))
94                    }
95                    None => self.quote_ident(name),
96                },
97                _ => "TEXT".to_string(),
98            },
99            FieldKind::Integer => "INTEGER".to_string(),
100            FieldKind::Float => "REAL".to_string(),
101            FieldKind::Boolean => "BOOLEAN".to_string(),
102            FieldKind::Uuid if self.supports_native_uuid() => "UUID".to_string(),
103            FieldKind::Uuid => render_uuid_type(self.kind(), column.is_primary_key()),
104            FieldKind::Date => "DATE".to_string(),
105            FieldKind::DateTime => "TIMESTAMP".to_string(),
106            FieldKind::Json | FieldKind::ModelJson if self.supports_json() => "JSON".to_string(),
107            FieldKind::Json | FieldKind::ModelJson => "TEXT".to_string(),
108            FieldKind::Decimal if self.kind() == DialectKind::Sqlite => {
109                match (column.precision(), column.scale()) {
110                    (Some(precision), Some(scale)) => {
111                        format!("DECIMAL_TEXT({precision}, {scale})")
112                    }
113                    _ => "DECIMAL_TEXT".to_string(),
114                }
115            }
116            FieldKind::Decimal => match (column.precision(), column.scale()) {
117                (Some(precision), Some(scale)) => format!("NUMERIC({precision}, {scale})"),
118                _ => "NUMERIC".to_string(),
119            },
120            FieldKind::Binary => "BLOB".to_string(),
121            FieldKind::ForeignKey { .. } => {
122                render_string_type(self.kind(), column.max_length(), true)
123            }
124            FieldKind::Unknown => "TEXT".to_string(),
125        }
126    }
127
128    fn compile_schema_operation(
129        &self,
130        operation: &SchemaOperation,
131    ) -> OrmdanticResult<Vec<String>> {
132        Ok(match operation {
133            SchemaOperation::CreateNamespace(namespace) => {
134                compile_create_namespace(self, namespace)?
135            }
136            SchemaOperation::DropNamespace { name } => compile_drop_namespace(self, name)?,
137            SchemaOperation::SetNamespaceComment { name, comment } => {
138                compile_namespace_comment(self, name, comment.as_deref())?
139            }
140            SchemaOperation::CreateTable(table) => compile_create_table(self, table)?,
141            SchemaOperation::DropTable { name } => vec![drop_table_sql(self, name)],
142            SchemaOperation::RecreateTable(table) => {
143                let mut statements =
144                    vec![drop_table_sql(self, &table.qualified_name().to_string())];
145                statements.extend(compile_create_table(self, table)?);
146                statements
147            }
148            SchemaOperation::AddColumn { table, column } => {
149                compile_add_column(self, table, column)?
150            }
151            SchemaOperation::DropColumn { table, column } => {
152                vec![compile_drop_column(self, table, column)]
153            }
154            SchemaOperation::AlterColumn { table, column } => {
155                vec![compile_alter_column(self, table, column)?]
156            }
157            SchemaOperation::SetColumnComment { table, column } => {
158                compile_column_comment(self, table, column)?
159                    .into_iter()
160                    .collect()
161            }
162            SchemaOperation::CreateIndex { table, index } => {
163                vec![compile_create_index(self, table, index)?]
164            }
165            SchemaOperation::DropIndex { table, name } => {
166                vec![compile_drop_index(self, table, name)]
167            }
168            SchemaOperation::AddConstraint { table, constraint } => {
169                vec![compile_add_constraint(self, table, constraint)?]
170            }
171            SchemaOperation::DropConstraint { table, name } => vec![format!(
172                "ALTER TABLE {} DROP CONSTRAINT {}",
173                quote_qualified_name(self, table),
174                self.quote_ident(name)
175            )],
176            SchemaOperation::SetTableComment { table, comment } => {
177                compile_table_comment(self, table, comment.as_deref())?
178                    .into_iter()
179                    .collect()
180            }
181            SchemaOperation::SetTableTablespace { table, tablespace } => {
182                compile_table_tablespace(self, table, tablespace.as_deref())?
183                    .into_iter()
184                    .collect()
185            }
186            SchemaOperation::SetTableMysqlOptions {
187                table,
188                engine,
189                charset,
190                collation,
191                row_format,
192                key_block_size,
193                pack_keys,
194                checksum,
195                delay_key_write,
196                stats_persistent,
197                stats_auto_recalc,
198                stats_sample_pages,
199                avg_row_length,
200                max_rows,
201                min_rows,
202                insert_method,
203                data_directory,
204                index_directory,
205                connection,
206                union,
207                partition_by,
208                partitions,
209                subpartition_by,
210                subpartitions,
211                auto_increment,
212            } => compile_table_mysql_options(
213                self,
214                table,
215                MysqlTableOptionsRef {
216                    engine: engine.as_deref(),
217                    charset: charset.as_deref(),
218                    collation: collation.as_deref(),
219                    row_format: row_format.as_deref(),
220                    key_block_size: *key_block_size,
221                    pack_keys: *pack_keys,
222                    checksum: *checksum,
223                    delay_key_write: *delay_key_write,
224                    stats_persistent: *stats_persistent,
225                    stats_auto_recalc: *stats_auto_recalc,
226                    stats_sample_pages: *stats_sample_pages,
227                    avg_row_length: *avg_row_length,
228                    max_rows: *max_rows,
229                    min_rows: *min_rows,
230                    insert_method: insert_method.as_deref(),
231                    data_directory: data_directory.as_deref(),
232                    index_directory: index_directory.as_deref(),
233                    connection: connection.as_deref(),
234                    union,
235                    partition_by: partition_by.as_deref(),
236                    partitions: *partitions,
237                    subpartition_by: subpartition_by.as_deref(),
238                    subpartitions: *subpartitions,
239                    auto_increment: *auto_increment,
240                },
241            )?
242            .into_iter()
243            .collect(),
244            SchemaOperation::SetTablePostgresInherits { table, add, drop } => {
245                compile_table_postgres_inherits(self, table, add, drop)?
246            }
247            SchemaOperation::SetTablePostgresWith { table, set, reset } => {
248                compile_table_postgres_with(self, table, set, reset)?
249            }
250            SchemaOperation::SetTablePostgresUsing { table, using } => {
251                compile_table_postgres_using(self, table, using.as_deref())?
252                    .into_iter()
253                    .collect()
254            }
255            SchemaOperation::SetTablePostgresUnlogged { table, unlogged } => {
256                if self.kind() != DialectKind::Postgres {
257                    return Err(ormdantic_core::OrmdanticError::UnsupportedFeature {
258                        feature: "PostgreSQL unlogged tables".to_string(),
259                        dialect: self.name().to_string(),
260                    });
261                }
262                vec![format!(
263                    "ALTER TABLE {} SET {}",
264                    quote_qualified_name(self, table),
265                    if *unlogged { "UNLOGGED" } else { "LOGGED" }
266                )]
267            }
268            SchemaOperation::AttachPostgresPartition {
269                table,
270                parent,
271                bound,
272            } => {
273                if self.kind() != DialectKind::Postgres {
274                    return Err(ormdantic_core::OrmdanticError::UnsupportedFeature {
275                        feature: "PostgreSQL table partitions".to_string(),
276                        dialect: self.name().to_string(),
277                    });
278                }
279                vec![format!(
280                    "ALTER TABLE {} ATTACH PARTITION {} {}",
281                    quote_qualified_name(self, parent),
282                    quote_qualified_name(self, table),
283                    bound
284                )]
285            }
286            SchemaOperation::DetachPostgresPartition { table, parent } => {
287                if self.kind() != DialectKind::Postgres {
288                    return Err(ormdantic_core::OrmdanticError::UnsupportedFeature {
289                        feature: "PostgreSQL table partitions".to_string(),
290                        dialect: self.name().to_string(),
291                    });
292                }
293                vec![format!(
294                    "ALTER TABLE {} DETACH PARTITION {}",
295                    quote_qualified_name(self, parent),
296                    quote_qualified_name(self, table)
297                )]
298            }
299        })
300    }
301
302    fn begin_transaction_sql(&self, options: &TransactionOptions) -> Vec<String> {
303        match self.kind() {
304            DialectKind::Postgres => postgres_begin_transaction_sql(self, options),
305            DialectKind::MySql | DialectKind::MariaDb => mysql_begin_transaction_sql(self, options),
306            DialectKind::MsSql => mssql_begin_transaction_sql(self, options),
307            DialectKind::Oracle => oracle_begin_transaction_sql(self, options),
308            DialectKind::Sqlite => vec!["BEGIN".to_string()],
309        }
310    }
311
312    fn set_isolation_sql(&self, isolation_level: IsolationLevel) -> String {
313        format!(
314            "SET TRANSACTION ISOLATION LEVEL {}",
315            render_isolation_level(isolation_level)
316        )
317    }
318
319    fn savepoint_sql(&self, name: &SavepointName) -> String {
320        format!("SAVEPOINT {}", self.quote_ident(name.as_str()))
321    }
322
323    fn rollback_to_savepoint_sql(&self, name: &SavepointName) -> String {
324        format!("ROLLBACK TO SAVEPOINT {}", self.quote_ident(name.as_str()))
325    }
326
327    fn release_savepoint_sql(&self, name: &SavepointName) -> String {
328        format!("RELEASE SAVEPOINT {}", self.quote_ident(name.as_str()))
329    }
330
331    fn reflection_queries(&self, scope: &ReflectionScope) -> Vec<ReflectionQuery> {
332        vec![
333            ReflectionQuery::new(
334                ReflectionQueryKind::Tables,
335                format!("SELECT table_name FROM information_schema.tables{}", scope_predicate(scope)),
336            ),
337            ReflectionQuery::new(
338                ReflectionQueryKind::Columns,
339                format!("SELECT table_name, column_name, data_type, is_nullable FROM information_schema.columns{}", scope_predicate(scope)),
340            ),
341            ReflectionQuery::new(
342                ReflectionQueryKind::Constraints,
343                format!("SELECT table_name, constraint_name, constraint_type FROM information_schema.table_constraints{}", scope_predicate(scope)),
344            ),
345        ]
346    }
347
348    fn upsert_conflict_clause(
349        &self,
350        conflict_column: &str,
351        update_columns: &[String],
352    ) -> OrmdanticResult<String> {
353        let target = self.quote_ident(conflict_column);
354        if update_columns.is_empty() {
355            return Ok(format!("ON CONFLICT ({target}) DO NOTHING"));
356        }
357
358        let assignments = update_columns
359            .iter()
360            .map(|column| {
361                format!(
362                    "{} = excluded.{}",
363                    self.quote_ident(column),
364                    self.quote_ident(column)
365                )
366            })
367            .collect::<Vec<_>>()
368            .join(", ");
369        Ok(format!(
370            "ON CONFLICT ({target}) DO UPDATE SET {assignments}"
371        ))
372    }
373}
374
375const DEFAULT_BOUNDED_STRING_LENGTH: u32 = 255;
376
377fn render_string_type(kind: DialectKind, max_length: Option<u32>, keyable: bool) -> String {
378    let max_length = max_length.filter(|length| *length > 0);
379    match kind {
380        DialectKind::Sqlite => "TEXT".to_string(),
381        DialectKind::Postgres => max_length
382            .map(|length| format!("VARCHAR({length})"))
383            .unwrap_or_else(|| "TEXT".to_string()),
384        DialectKind::MsSql => match max_length {
385            Some(length) => format!("NVARCHAR({length})"),
386            None => format!("NVARCHAR({DEFAULT_BOUNDED_STRING_LENGTH})"),
387        },
388        DialectKind::Oracle => match max_length {
389            Some(length) => format!("VARCHAR2({length})"),
390            None => format!("VARCHAR2({DEFAULT_BOUNDED_STRING_LENGTH})"),
391        },
392        DialectKind::MySql | DialectKind::MariaDb => match max_length {
393            Some(length) => format!("VARCHAR({length})"),
394            None if keyable => format!("VARCHAR({DEFAULT_BOUNDED_STRING_LENGTH})"),
395            None => "TEXT".to_string(),
396        },
397    }
398}
399
400fn render_uuid_type(kind: DialectKind, keyable: bool) -> String {
401    match kind {
402        DialectKind::Oracle => "VARCHAR2(36)".to_string(),
403        DialectKind::MySql | DialectKind::MariaDb if keyable => "VARCHAR(36)".to_string(),
404        _ => "TEXT".to_string(),
405    }
406}
407
408fn postgres_begin_transaction_sql(
409    dialect: &(impl Dialect + ?Sized),
410    options: &TransactionOptions,
411) -> Vec<String> {
412    let mut statements = Vec::new();
413    if let Some(isolation_level) = options.isolation_level() {
414        statements.push(dialect.set_isolation_sql(isolation_level));
415    }
416    let mut begin = "BEGIN".to_string();
417    if options.access_mode() == TransactionAccessMode::ReadOnly {
418        begin.push_str(" READ ONLY");
419    }
420    match options.deferrable_mode() {
421        Some(DeferrableMode::Deferrable) => begin.push_str(" DEFERRABLE"),
422        Some(DeferrableMode::NotDeferrable) => begin.push_str(" NOT DEFERRABLE"),
423        None => {}
424    }
425    statements.push(begin);
426    statements
427}
428
429fn mysql_begin_transaction_sql(
430    dialect: &(impl Dialect + ?Sized),
431    options: &TransactionOptions,
432) -> Vec<String> {
433    let mut statements = Vec::new();
434    if let Some(isolation_level) = options.isolation_level() {
435        statements.push(dialect.set_isolation_sql(isolation_level));
436    }
437    let mut begin = "START TRANSACTION".to_string();
438    if options.access_mode() == TransactionAccessMode::ReadOnly {
439        begin.push_str(" READ ONLY");
440    }
441    statements.push(begin);
442    statements
443}
444
445fn mssql_begin_transaction_sql(
446    dialect: &(impl Dialect + ?Sized),
447    options: &TransactionOptions,
448) -> Vec<String> {
449    let mut statements = Vec::new();
450    if let Some(isolation_level) = options.isolation_level() {
451        statements.push(dialect.set_isolation_sql(isolation_level));
452    }
453    statements.push("BEGIN TRANSACTION".to_string());
454    statements
455}
456
457fn oracle_begin_transaction_sql(
458    dialect: &(impl Dialect + ?Sized),
459    options: &TransactionOptions,
460) -> Vec<String> {
461    if options.access_mode() == TransactionAccessMode::ReadOnly {
462        return vec!["SET TRANSACTION READ ONLY".to_string()];
463    }
464    if let Some(isolation_level) = options.isolation_level() {
465        return vec![dialect.set_isolation_sql(isolation_level)];
466    }
467    Vec::new()
468}
469
470fn quote_qualified_name(dialect: &(impl Dialect + ?Sized), name: &str) -> String {
471    name.split('.')
472        .map(|part| dialect.quote_ident(part))
473        .collect::<Vec<_>>()
474        .join(".")
475}
476
477fn drop_table_sql(dialect: &(impl Dialect + ?Sized), name: &str) -> String {
478    let qualified = quote_qualified_name(dialect, name);
479    if dialect.kind() == DialectKind::Oracle {
480        return format!("DROP TABLE {qualified}");
481    }
482    format!("DROP TABLE IF EXISTS {qualified}")
483}
484
485fn reflection_where(
486    scope: &ReflectionScope,
487    schema_column: Option<&str>,
488    table_column: &str,
489    default_schema_expr: Option<&str>,
490    mut predicates: Vec<String>,
491) -> String {
492    if let Some(schema_column) = schema_column {
493        match scope.schema_name() {
494            Some(schema) => {
495                predicates.push(format!("{schema_column} = {}", sql_string_literal(schema)));
496            }
497            None => {
498                if let Some(default_schema_expr) = default_schema_expr {
499                    predicates.push(format!("{schema_column} = {default_schema_expr}"));
500                }
501            }
502        }
503    }
504    if !scope.table_names().is_empty() {
505        predicates.push(format!(
506            "{table_column} IN ({})",
507            scope
508                .table_names()
509                .iter()
510                .map(|table| sql_string_literal(table))
511                .collect::<Vec<_>>()
512                .join(", ")
513        ));
514    }
515    if predicates.is_empty() {
516        String::new()
517    } else {
518        format!(" WHERE {}", predicates.join(" AND "))
519    }
520}
521
522fn oracle_reflection_where(
523    scope: &ReflectionScope,
524    schema_column: Option<&str>,
525    table_column: &str,
526    mut predicates: Vec<String>,
527) -> String {
528    if let (Some(schema_column), Some(schema)) = (schema_column, scope.schema_name()) {
529        predicates.push(format!(
530            "{schema_column} = {}",
531            sql_string_literal(&schema.to_uppercase())
532        ));
533    }
534    if !scope.table_names().is_empty() {
535        predicates.push(format!(
536            "{table_column} IN ({})",
537            scope
538                .table_names()
539                .iter()
540                .map(|table| sql_string_literal(&table.to_uppercase()))
541                .collect::<Vec<_>>()
542                .join(", ")
543        ));
544    }
545    if predicates.is_empty() {
546        String::new()
547    } else {
548        format!(" WHERE {}", predicates.join(" AND "))
549    }
550}
551
552fn compile_create_namespace(
553    dialect: &(impl Dialect + ?Sized),
554    namespace: &NamespaceDef,
555) -> OrmdanticResult<Vec<String>> {
556    let name = namespace.name();
557    let mut statements = match dialect.kind() {
558        DialectKind::Postgres | DialectKind::MySql | DialectKind::MariaDb => vec![format!(
559            "CREATE SCHEMA IF NOT EXISTS {}",
560            dialect.quote_ident(name)
561        )],
562        DialectKind::MsSql => {
563            let create_sql = format!("CREATE SCHEMA {}", dialect.quote_ident(name));
564            vec![format!(
565                "IF SCHEMA_ID({}) IS NULL EXEC({})",
566                mssql_unicode_literal(name),
567                mssql_unicode_literal(&create_sql)
568            )]
569        }
570        DialectKind::Sqlite | DialectKind::Oracle => {
571            return Err(OrmdanticError::UnsupportedFeature {
572                feature: "namespaces/schemas".to_string(),
573                dialect: dialect.name().to_string(),
574            });
575        }
576    };
577    if let Some(comment) = namespace.comment() {
578        statements.extend(compile_namespace_comment(dialect, name, Some(comment))?);
579    }
580    Ok(statements)
581}
582
583fn compile_drop_namespace(
584    dialect: &(impl Dialect + ?Sized),
585    name: &str,
586) -> OrmdanticResult<Vec<String>> {
587    Ok(match dialect.kind() {
588        DialectKind::Postgres | DialectKind::MySql | DialectKind::MariaDb | DialectKind::MsSql => {
589            vec![format!(
590                "DROP SCHEMA IF EXISTS {}",
591                dialect.quote_ident(name)
592            )]
593        }
594        DialectKind::Sqlite | DialectKind::Oracle => {
595            return Err(OrmdanticError::UnsupportedFeature {
596                feature: "namespaces/schemas".to_string(),
597                dialect: dialect.name().to_string(),
598            });
599        }
600    })
601}
602
603fn compile_namespace_comment(
604    dialect: &(impl Dialect + ?Sized),
605    name: &str,
606    comment: Option<&str>,
607) -> OrmdanticResult<Vec<String>> {
608    Ok(match dialect.kind() {
609        DialectKind::Postgres => vec![format!(
610            "COMMENT ON SCHEMA {} IS {}",
611            dialect.quote_ident(name),
612            comment
613                .map(sql_string_literal)
614                .unwrap_or_else(|| "NULL".to_string())
615        )],
616        DialectKind::MsSql => vec![compile_mssql_namespace_comment(name, comment)],
617        _ => {
618            return Err(OrmdanticError::UnsupportedFeature {
619                feature: "namespace comments".to_string(),
620                dialect: dialect.name().to_string(),
621            });
622        }
623    })
624}
625
626fn compile_mssql_namespace_comment(name: &str, comment: Option<&str>) -> String {
627    let schema_literal = mssql_unicode_literal(name);
628    let exists_predicate = format!(
629        "EXISTS (SELECT 1 FROM sys.extended_properties ep \
630         JOIN sys.schemas s ON ep.major_id = s.schema_id \
631         WHERE ep.class = 3 AND ep.minor_id = 0 \
632         AND ep.name = N'MS_Description' \
633         AND s.name = {schema_literal})"
634    );
635    let level_args = format!("@level0type = N'SCHEMA', @level0name = {schema_literal}");
636    match comment {
637        Some(comment) => {
638            let comment_literal = mssql_unicode_literal(comment);
639            format!(
640                "IF {exists_predicate} \
641                 EXEC sys.sp_updateextendedproperty @name = N'MS_Description', \
642                 @value = {comment_literal}, {level_args}; \
643                 ELSE EXEC sys.sp_addextendedproperty @name = N'MS_Description', \
644                 @value = {comment_literal}, {level_args}"
645            )
646        }
647        None => format!(
648            "IF {exists_predicate} \
649             EXEC sys.sp_dropextendedproperty @name = N'MS_Description', {level_args}"
650        ),
651    }
652}
653
654fn sql_string_literal(value: &str) -> String {
655    format!("'{}'", value.replace('\'', "''"))
656}
657
658fn mssql_unicode_literal(value: &str) -> String {
659    format!("N'{}'", value.replace('\'', "''"))
660}
661
662#[derive(Debug, Clone, Copy, PartialEq, Eq)]
663pub struct SqliteDialect;
664
665impl Dialect for SqliteDialect {
666    fn kind(&self) -> DialectKind {
667        DialectKind::Sqlite
668    }
669
670    fn name(&self) -> &'static str {
671        "sqlite"
672    }
673
674    fn quote_ident(&self, ident: &str) -> String {
675        quote_double(ident)
676    }
677
678    fn placeholder(&self, _index: usize) -> String {
679        "?".to_string()
680    }
681
682    fn supports_returning(&self) -> bool {
683        true
684    }
685
686    fn supports_native_uuid(&self) -> bool {
687        false
688    }
689
690    fn supports_json(&self) -> bool {
691        true
692    }
693
694    fn reflection_queries(&self, scope: &ReflectionScope) -> Vec<ReflectionQuery> {
695        let table_where = reflection_where(
696            scope,
697            None,
698            "m.name",
699            None,
700            vec!["m.type = 'table'".to_string()],
701        );
702        vec![
703            ReflectionQuery::new(
704                ReflectionQueryKind::Tables,
705                format!("SELECT m.name AS table_name FROM sqlite_master AS m{table_where} ORDER BY m.name"),
706            ),
707            ReflectionQuery::new(
708                ReflectionQueryKind::Columns,
709                format!(
710                    "SELECT m.name AS table_name, x.name AS column_name, x.type AS data_type, \
711                     NOT x.[notnull] AS is_nullable, x.dflt_value AS column_default, x.pk AS primary_key \
712                     FROM sqlite_master AS m, pragma_table_xinfo(m.name) AS x{table_where} \
713                     AND x.hidden <> 1 ORDER BY m.name, x.cid"
714                ),
715            ),
716            ReflectionQuery::new(
717                ReflectionQueryKind::Indexes,
718                format!(
719                    "SELECT m.name AS table_name, il.name AS index_name, il.[unique] AS is_unique, il.origin \
720                     FROM sqlite_master AS m, pragma_index_list(m.name) AS il{table_where} \
721                     ORDER BY m.name, il.seq"
722                ),
723            ),
724            ReflectionQuery::new(
725                ReflectionQueryKind::ForeignKeys,
726                format!(
727                    "SELECT m.name AS table_name, fk.[table] AS foreign_table, fk.[from] AS column_name, \
728                     fk.[to] AS foreign_column, fk.on_update, fk.on_delete, fk.id AS constraint_id \
729                     FROM sqlite_master AS m, pragma_foreign_key_list(m.name) AS fk{table_where} \
730                     ORDER BY m.name, fk.id, fk.seq"
731                ),
732            ),
733            ReflectionQuery::new(
734                ReflectionQueryKind::Constraints,
735                format!(
736                    "SELECT m.name AS table_name, m.sql AS table_sql FROM sqlite_master AS m{table_where} \
737                     ORDER BY m.name"
738                ),
739            ),
740        ]
741    }
742}
743
744#[derive(Debug, Clone, Copy, PartialEq, Eq)]
745pub struct PostgresDialect;
746
747impl Dialect for PostgresDialect {
748    fn kind(&self) -> DialectKind {
749        DialectKind::Postgres
750    }
751
752    fn name(&self) -> &'static str {
753        "postgresql"
754    }
755
756    fn quote_ident(&self, ident: &str) -> String {
757        quote_double(ident)
758    }
759
760    fn placeholder(&self, index: usize) -> String {
761        format!("${index}")
762    }
763
764    fn supports_returning(&self) -> bool {
765        true
766    }
767
768    fn supports_native_uuid(&self) -> bool {
769        true
770    }
771
772    fn supports_json(&self) -> bool {
773        true
774    }
775
776    fn reflection_queries(&self, scope: &ReflectionScope) -> Vec<ReflectionQuery> {
777        let table_where = reflection_where(
778            scope,
779            Some("table_schema"),
780            "table_name",
781            None,
782            vec!["table_type = 'BASE TABLE'".to_string()],
783        );
784        let column_where =
785            reflection_where(scope, Some("table_schema"), "table_name", None, vec![]);
786        let constraint_where =
787            reflection_where(scope, Some("table_schema"), "table_name", None, vec![]);
788        let index_where = reflection_where(scope, Some("schemaname"), "tablename", None, vec![]);
789        let foreign_key_where = reflection_where(
790            scope,
791            Some("tc.table_schema"),
792            "tc.table_name",
793            None,
794            vec!["tc.constraint_type = 'FOREIGN KEY'".to_string()],
795        );
796        vec![
797            ReflectionQuery::new(
798                ReflectionQueryKind::Tables,
799                format!("SELECT table_schema, table_name FROM information_schema.tables{table_where} ORDER BY table_schema, table_name"),
800            ),
801            ReflectionQuery::new(
802                ReflectionQueryKind::Columns,
803                format!(
804                    "SELECT table_schema, table_name, column_name, data_type, is_nullable, column_default, ordinal_position \
805                     FROM information_schema.columns{column_where} ORDER BY table_schema, table_name, ordinal_position"
806                ),
807            ),
808            ReflectionQuery::new(
809                ReflectionQueryKind::Indexes,
810                format!(
811                    "SELECT schemaname AS table_schema, tablename AS table_name, indexname AS index_name, indexdef \
812                     FROM pg_indexes{index_where} ORDER BY schemaname, tablename, indexname"
813                ),
814            ),
815            ReflectionQuery::new(
816                ReflectionQueryKind::ForeignKeys,
817                format!(
818                    "SELECT tc.table_schema, tc.table_name, tc.constraint_name, kcu.column_name, \
819                     ccu.table_name AS foreign_table, ccu.column_name AS foreign_column \
820                     FROM information_schema.table_constraints AS tc \
821                     JOIN information_schema.key_column_usage AS kcu \
822                       ON tc.constraint_name = kcu.constraint_name AND tc.table_schema = kcu.table_schema \
823                     JOIN information_schema.constraint_column_usage AS ccu \
824                       ON ccu.constraint_name = tc.constraint_name AND ccu.table_schema = tc.table_schema{foreign_key_where} \
825                     ORDER BY tc.table_schema, tc.table_name, tc.constraint_name, kcu.ordinal_position"
826                ),
827            ),
828            ReflectionQuery::new(
829                ReflectionQueryKind::Constraints,
830                format!(
831                    "SELECT table_schema, table_name, constraint_name, constraint_type \
832                     FROM information_schema.table_constraints{constraint_where} \
833                     ORDER BY table_schema, table_name, constraint_name"
834                ),
835            ),
836        ]
837    }
838}
839
840#[derive(Debug, Clone, Copy, PartialEq, Eq)]
841pub struct MySqlDialect;
842
843impl Dialect for MySqlDialect {
844    fn kind(&self) -> DialectKind {
845        DialectKind::MySql
846    }
847
848    fn name(&self) -> &'static str {
849        "mysql"
850    }
851
852    fn quote_ident(&self, ident: &str) -> String {
853        quote_backtick(ident)
854    }
855
856    fn placeholder(&self, _index: usize) -> String {
857        "?".to_string()
858    }
859
860    fn supports_returning(&self) -> bool {
861        false
862    }
863
864    fn supports_native_uuid(&self) -> bool {
865        false
866    }
867
868    fn supports_json(&self) -> bool {
869        true
870    }
871
872    fn release_savepoint_sql(&self, name: &SavepointName) -> String {
873        format!("RELEASE SAVEPOINT {}", self.quote_ident(name.as_str()))
874    }
875
876    fn reflection_queries(&self, scope: &ReflectionScope) -> Vec<ReflectionQuery> {
877        let table_where = reflection_where(
878            scope,
879            Some("table_schema"),
880            "table_name",
881            Some("DATABASE()"),
882            vec!["table_type = 'BASE TABLE'".to_string()],
883        );
884        let column_where = reflection_where(
885            scope,
886            Some("table_schema"),
887            "table_name",
888            Some("DATABASE()"),
889            vec![],
890        );
891        let index_where = reflection_where(
892            scope,
893            Some("table_schema"),
894            "table_name",
895            Some("DATABASE()"),
896            vec!["index_name <> 'PRIMARY'".to_string()],
897        );
898        let foreign_key_where = reflection_where(
899            scope,
900            Some("kcu.table_schema"),
901            "kcu.table_name",
902            Some("DATABASE()"),
903            vec!["kcu.referenced_table_name IS NOT NULL".to_string()],
904        );
905        let constraint_where = reflection_where(
906            scope,
907            Some("table_schema"),
908            "table_name",
909            Some("DATABASE()"),
910            vec![],
911        );
912        vec![
913            ReflectionQuery::new(
914                ReflectionQueryKind::Tables,
915                format!("SELECT table_schema, table_name FROM information_schema.tables{table_where} ORDER BY table_schema, table_name"),
916            ),
917            ReflectionQuery::new(
918                ReflectionQueryKind::Columns,
919                format!(
920                    "SELECT table_schema, table_name, column_name, data_type, is_nullable, column_default, ordinal_position \
921                     FROM information_schema.columns{column_where} ORDER BY table_schema, table_name, ordinal_position"
922                ),
923            ),
924            ReflectionQuery::new(
925                ReflectionQueryKind::Indexes,
926                format!(
927                    "SELECT table_schema, table_name, index_name, non_unique, seq_in_index, column_name \
928                     FROM information_schema.statistics{index_where} ORDER BY table_schema, table_name, index_name, seq_in_index"
929                ),
930            ),
931            ReflectionQuery::new(
932                ReflectionQueryKind::ForeignKeys,
933                format!(
934                    "SELECT kcu.table_schema, kcu.table_name, kcu.constraint_name, kcu.column_name, \
935                     kcu.referenced_table_name AS foreign_table, kcu.referenced_column_name AS foreign_column, \
936                     rc.update_rule, rc.delete_rule \
937                     FROM information_schema.key_column_usage AS kcu \
938                     LEFT JOIN information_schema.referential_constraints AS rc \
939                       ON rc.constraint_schema = kcu.constraint_schema \
940                      AND rc.constraint_name = kcu.constraint_name \
941                      AND rc.table_name = kcu.table_name{foreign_key_where} \
942                     ORDER BY kcu.table_schema, kcu.table_name, kcu.constraint_name, kcu.ordinal_position"
943                ),
944            ),
945            ReflectionQuery::new(
946                ReflectionQueryKind::Constraints,
947                format!(
948                    "SELECT table_schema, table_name, constraint_name, constraint_type \
949                     FROM information_schema.table_constraints{constraint_where} \
950                     ORDER BY table_schema, table_name, constraint_name"
951                ),
952            ),
953        ]
954    }
955
956    fn upsert_conflict_clause(
957        &self,
958        _conflict_column: &str,
959        update_columns: &[String],
960    ) -> OrmdanticResult<String> {
961        if update_columns.is_empty() {
962            return Ok("ON DUPLICATE KEY UPDATE 1 = 1".to_string());
963        }
964        let assignments = update_columns
965            .iter()
966            .map(|column| {
967                format!(
968                    "{} = VALUES({})",
969                    self.quote_ident(column),
970                    self.quote_ident(column)
971                )
972            })
973            .collect::<Vec<_>>()
974            .join(", ");
975        Ok(format!("ON DUPLICATE KEY UPDATE {assignments}"))
976    }
977}
978
979#[derive(Debug, Clone, Copy, PartialEq, Eq)]
980pub struct MariaDbDialect;
981
982impl Dialect for MariaDbDialect {
983    fn kind(&self) -> DialectKind {
984        DialectKind::MariaDb
985    }
986
987    fn name(&self) -> &'static str {
988        "mariadb"
989    }
990
991    fn quote_ident(&self, ident: &str) -> String {
992        quote_backtick(ident)
993    }
994
995    fn placeholder(&self, _index: usize) -> String {
996        "?".to_string()
997    }
998
999    fn supports_returning(&self) -> bool {
1000        true
1001    }
1002
1003    fn supports_native_uuid(&self) -> bool {
1004        false
1005    }
1006
1007    fn supports_json(&self) -> bool {
1008        true
1009    }
1010
1011    fn reflection_queries(&self, scope: &ReflectionScope) -> Vec<ReflectionQuery> {
1012        MySqlDialect.reflection_queries(scope)
1013    }
1014
1015    fn upsert_conflict_clause(
1016        &self,
1017        conflict_column: &str,
1018        update_columns: &[String],
1019    ) -> OrmdanticResult<String> {
1020        MySqlDialect.upsert_conflict_clause(conflict_column, update_columns)
1021    }
1022}
1023
1024#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1025pub struct MsSqlDialect;
1026
1027impl Dialect for MsSqlDialect {
1028    fn kind(&self) -> DialectKind {
1029        DialectKind::MsSql
1030    }
1031
1032    fn name(&self) -> &'static str {
1033        "mssql"
1034    }
1035
1036    fn quote_ident(&self, ident: &str) -> String {
1037        format!("[{}]", ident.replace(']', "]]"))
1038    }
1039
1040    fn placeholder(&self, index: usize) -> String {
1041        format!("@P{index}")
1042    }
1043
1044    fn supports_returning(&self) -> bool {
1045        false
1046    }
1047
1048    fn supports_native_uuid(&self) -> bool {
1049        true
1050    }
1051
1052    fn supports_json(&self) -> bool {
1053        false
1054    }
1055
1056    fn savepoint_sql(&self, name: &SavepointName) -> String {
1057        format!("SAVE TRANSACTION {}", self.quote_ident(name.as_str()))
1058    }
1059
1060    fn rollback_to_savepoint_sql(&self, name: &SavepointName) -> String {
1061        format!("ROLLBACK TRANSACTION {}", self.quote_ident(name.as_str()))
1062    }
1063
1064    fn release_savepoint_sql(&self, _name: &SavepointName) -> String {
1065        String::new()
1066    }
1067
1068    fn reflection_queries(&self, scope: &ReflectionScope) -> Vec<ReflectionQuery> {
1069        let table_where = reflection_where(
1070            scope,
1071            Some("TABLE_SCHEMA"),
1072            "TABLE_NAME",
1073            None,
1074            vec!["TABLE_TYPE = 'BASE TABLE'".to_string()],
1075        );
1076        let column_where =
1077            reflection_where(scope, Some("TABLE_SCHEMA"), "TABLE_NAME", None, vec![]);
1078        let index_where = reflection_where(
1079            scope,
1080            Some("s.name"),
1081            "t.name",
1082            None,
1083            vec![
1084                "i.is_primary_key = 0".to_string(),
1085                "i.name IS NOT NULL".to_string(),
1086            ],
1087        );
1088        let foreign_key_where = reflection_where(
1089            scope,
1090            Some("kcu.TABLE_SCHEMA"),
1091            "kcu.TABLE_NAME",
1092            None,
1093            vec![],
1094        );
1095        let constraint_where =
1096            reflection_where(scope, Some("TABLE_SCHEMA"), "TABLE_NAME", None, vec![]);
1097        vec![
1098            ReflectionQuery::new(
1099                ReflectionQueryKind::Tables,
1100                format!("SELECT TABLE_SCHEMA AS table_schema, TABLE_NAME AS table_name FROM INFORMATION_SCHEMA.TABLES{table_where} ORDER BY TABLE_SCHEMA, TABLE_NAME"),
1101            ),
1102            ReflectionQuery::new(
1103                ReflectionQueryKind::Columns,
1104                format!(
1105                    "SELECT TABLE_SCHEMA AS table_schema, TABLE_NAME AS table_name, COLUMN_NAME AS column_name, \
1106                     DATA_TYPE AS data_type, IS_NULLABLE AS is_nullable, COLUMN_DEFAULT AS column_default, ORDINAL_POSITION AS ordinal_position \
1107                     FROM INFORMATION_SCHEMA.COLUMNS{column_where} ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION"
1108                ),
1109            ),
1110            ReflectionQuery::new(
1111                ReflectionQueryKind::Indexes,
1112                format!(
1113                    "SELECT s.name AS table_schema, t.name AS table_name, i.name AS index_name, i.is_unique, ic.key_ordinal, c.name AS column_name \
1114                     FROM sys.indexes AS i \
1115                     JOIN sys.tables AS t ON t.object_id = i.object_id \
1116                     JOIN sys.schemas AS s ON s.schema_id = t.schema_id \
1117                     LEFT JOIN sys.index_columns AS ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id \
1118                     LEFT JOIN sys.columns AS c ON c.object_id = t.object_id AND c.column_id = ic.column_id{index_where} \
1119                     ORDER BY s.name, t.name, i.name, ic.key_ordinal"
1120                ),
1121            ),
1122            ReflectionQuery::new(
1123                ReflectionQueryKind::ForeignKeys,
1124                format!(
1125                    "SELECT kcu.TABLE_SCHEMA AS table_schema, kcu.TABLE_NAME AS table_name, kcu.CONSTRAINT_NAME AS constraint_name, \
1126                     kcu.COLUMN_NAME AS column_name, ccu.TABLE_NAME AS foreign_table, ccu.COLUMN_NAME AS foreign_column \
1127                     FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE AS kcu \
1128                     JOIN INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS AS rc \
1129                       ON rc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA AND rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME \
1130                     JOIN INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE AS ccu \
1131                       ON ccu.CONSTRAINT_SCHEMA = rc.UNIQUE_CONSTRAINT_SCHEMA AND ccu.CONSTRAINT_NAME = rc.UNIQUE_CONSTRAINT_NAME{foreign_key_where} \
1132                     ORDER BY kcu.TABLE_SCHEMA, kcu.TABLE_NAME, kcu.CONSTRAINT_NAME, kcu.ORDINAL_POSITION"
1133                ),
1134            ),
1135            ReflectionQuery::new(
1136                ReflectionQueryKind::Constraints,
1137                format!(
1138                    "SELECT TABLE_SCHEMA AS table_schema, TABLE_NAME AS table_name, CONSTRAINT_NAME AS constraint_name, CONSTRAINT_TYPE AS constraint_type \
1139                     FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS{constraint_where} ORDER BY TABLE_SCHEMA, TABLE_NAME, CONSTRAINT_NAME"
1140                ),
1141            ),
1142        ]
1143    }
1144
1145    fn upsert_conflict_clause(
1146        &self,
1147        _conflict_column: &str,
1148        _update_columns: &[String],
1149    ) -> OrmdanticResult<String> {
1150        Err(OrmdanticError::UnsupportedFeature {
1151            feature: "INSERT conflict-clause upsert".to_string(),
1152            dialect: self.name().to_string(),
1153        })
1154    }
1155}
1156
1157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1158pub struct OracleDialect;
1159
1160impl Dialect for OracleDialect {
1161    fn kind(&self) -> DialectKind {
1162        DialectKind::Oracle
1163    }
1164
1165    fn name(&self) -> &'static str {
1166        "oracle"
1167    }
1168
1169    fn quote_ident(&self, ident: &str) -> String {
1170        quote_double(ident)
1171    }
1172
1173    fn placeholder(&self, index: usize) -> String {
1174        format!(":{index}")
1175    }
1176
1177    fn supports_returning(&self) -> bool {
1178        false
1179    }
1180
1181    fn supports_native_uuid(&self) -> bool {
1182        false
1183    }
1184
1185    fn supports_json(&self) -> bool {
1186        true
1187    }
1188
1189    fn rollback_to_savepoint_sql(&self, name: &SavepointName) -> String {
1190        format!("ROLLBACK TO SAVEPOINT {}", self.quote_ident(name.as_str()))
1191    }
1192
1193    fn release_savepoint_sql(&self, _name: &SavepointName) -> String {
1194        String::new()
1195    }
1196
1197    fn reflection_queries(&self, scope: &ReflectionScope) -> Vec<ReflectionQuery> {
1198        let scoped = scope.schema_name().is_some();
1199        let table_view = if scoped { "all_tables" } else { "user_tables" };
1200        let columns_view = if scoped {
1201            "all_tab_columns"
1202        } else {
1203            "user_tab_columns"
1204        };
1205        let indexes_view = if scoped {
1206            "all_indexes"
1207        } else {
1208            "user_indexes"
1209        };
1210        let constraints_view = if scoped {
1211            "all_constraints"
1212        } else {
1213            "user_constraints"
1214        };
1215        let cons_columns_view = if scoped {
1216            "all_cons_columns"
1217        } else {
1218            "user_cons_columns"
1219        };
1220        let owner_select = if scoped {
1221            "owner"
1222        } else {
1223            "CAST(NULL AS VARCHAR2(1))"
1224        };
1225        let constraint_owner_select = if scoped {
1226            "c.owner"
1227        } else {
1228            "CAST(NULL AS VARCHAR2(1))"
1229        };
1230        let local_owner_join = if scoped {
1231            "AND c.owner = cc.owner "
1232        } else {
1233            ""
1234        };
1235        let referenced_owner_join = if scoped {
1236            "AND r.owner = rcc.owner "
1237        } else {
1238            ""
1239        };
1240        let referenced_constraint_join = if scoped {
1241            "c.r_constraint_name = r.constraint_name AND c.r_owner = r.owner"
1242        } else {
1243            "c.r_constraint_name = r.constraint_name"
1244        };
1245        let owner_column = scoped.then_some("owner");
1246        let table_where = oracle_reflection_where(scope, owner_column, "table_name", vec![]);
1247        let column_where = oracle_reflection_where(scope, owner_column, "table_name", vec![]);
1248        let index_where = oracle_reflection_where(scope, owner_column, "table_name", vec![]);
1249        let constraint_where = oracle_reflection_where(scope, owner_column, "table_name", vec![]);
1250        let foreign_key_where = oracle_reflection_where(
1251            scope,
1252            scoped.then_some("c.owner"),
1253            "c.table_name",
1254            vec!["c.constraint_type = 'R'".to_string()],
1255        );
1256        vec![
1257            ReflectionQuery::new(
1258                ReflectionQueryKind::Tables,
1259                format!(
1260                    "SELECT {owner_select} AS table_schema, table_name FROM {table_view}{table_where} \
1261                     ORDER BY table_name"
1262                ),
1263            ),
1264            ReflectionQuery::new(
1265                ReflectionQueryKind::Columns,
1266                format!(
1267                    "SELECT {owner_select} AS table_schema, table_name, column_name, data_type, nullable AS is_nullable, data_default AS column_default, column_id AS ordinal_position \
1268                     FROM {columns_view}{column_where} ORDER BY table_name, column_id"
1269                ),
1270            ),
1271            ReflectionQuery::new(
1272                ReflectionQueryKind::Indexes,
1273                format!(
1274                    "SELECT {owner_select} AS table_schema, table_name, index_name, uniqueness \
1275                     FROM {indexes_view}{index_where} ORDER BY table_name, index_name"
1276                ),
1277            ),
1278            ReflectionQuery::new(
1279                ReflectionQueryKind::ForeignKeys,
1280                format!(
1281                    "SELECT {constraint_owner_select} AS table_schema, c.table_name, c.constraint_name, cc.column_name, \
1282                     r.table_name AS foreign_table, rcc.column_name AS foreign_column, c.delete_rule \
1283                     FROM {constraints_view} c \
1284                     JOIN {cons_columns_view} cc ON c.constraint_name = cc.constraint_name {local_owner_join}AND c.table_name = cc.table_name \
1285                     JOIN {constraints_view} r ON {referenced_constraint_join} \
1286                     JOIN {cons_columns_view} rcc ON r.constraint_name = rcc.constraint_name {referenced_owner_join}AND r.table_name = rcc.table_name AND cc.position = rcc.position{foreign_key_where} \
1287                     ORDER BY c.table_name, c.constraint_name, cc.position"
1288                ),
1289            ),
1290            ReflectionQuery::new(
1291                ReflectionQueryKind::Constraints,
1292                format!(
1293                    "SELECT {owner_select} AS table_schema, table_name, constraint_name, constraint_type \
1294                     FROM {constraints_view}{constraint_where} ORDER BY table_name, constraint_name"
1295                ),
1296            ),
1297        ]
1298    }
1299
1300    fn upsert_conflict_clause(
1301        &self,
1302        _conflict_column: &str,
1303        _update_columns: &[String],
1304    ) -> OrmdanticResult<String> {
1305        Err(OrmdanticError::UnsupportedFeature {
1306            feature: "INSERT conflict-clause upsert".to_string(),
1307            dialect: self.name().to_string(),
1308        })
1309    }
1310}
1311
1312#[derive(Debug, Clone, Copy)]
1313pub enum AnyDialect {
1314    Sqlite(SqliteDialect),
1315    Postgres(PostgresDialect),
1316    MySql(MySqlDialect),
1317    MariaDb(MariaDbDialect),
1318    MsSql(MsSqlDialect),
1319    Oracle(OracleDialect),
1320}
1321
1322impl AnyDialect {
1323    pub fn parse(name: &str) -> OrmdanticResult<Self> {
1324        Ok(match DialectKind::parse(name)? {
1325            DialectKind::Sqlite => Self::Sqlite(SqliteDialect),
1326            DialectKind::Postgres => Self::Postgres(PostgresDialect),
1327            DialectKind::MySql => Self::MySql(MySqlDialect),
1328            DialectKind::MariaDb => Self::MariaDb(MariaDbDialect),
1329            DialectKind::MsSql => Self::MsSql(MsSqlDialect),
1330            DialectKind::Oracle => Self::Oracle(OracleDialect),
1331        })
1332    }
1333}
1334
1335impl Dialect for AnyDialect {
1336    fn kind(&self) -> DialectKind {
1337        match self {
1338            Self::Sqlite(dialect) => dialect.kind(),
1339            Self::Postgres(dialect) => dialect.kind(),
1340            Self::MySql(dialect) => dialect.kind(),
1341            Self::MariaDb(dialect) => dialect.kind(),
1342            Self::MsSql(dialect) => dialect.kind(),
1343            Self::Oracle(dialect) => dialect.kind(),
1344        }
1345    }
1346
1347    fn name(&self) -> &'static str {
1348        match self {
1349            Self::Sqlite(dialect) => dialect.name(),
1350            Self::Postgres(dialect) => dialect.name(),
1351            Self::MySql(dialect) => dialect.name(),
1352            Self::MariaDb(dialect) => dialect.name(),
1353            Self::MsSql(dialect) => dialect.name(),
1354            Self::Oracle(dialect) => dialect.name(),
1355        }
1356    }
1357
1358    fn quote_ident(&self, ident: &str) -> String {
1359        match self {
1360            Self::Sqlite(dialect) => dialect.quote_ident(ident),
1361            Self::Postgres(dialect) => dialect.quote_ident(ident),
1362            Self::MySql(dialect) => dialect.quote_ident(ident),
1363            Self::MariaDb(dialect) => dialect.quote_ident(ident),
1364            Self::MsSql(dialect) => dialect.quote_ident(ident),
1365            Self::Oracle(dialect) => dialect.quote_ident(ident),
1366        }
1367    }
1368
1369    fn placeholder(&self, index: usize) -> String {
1370        match self {
1371            Self::Sqlite(dialect) => dialect.placeholder(index),
1372            Self::Postgres(dialect) => dialect.placeholder(index),
1373            Self::MySql(dialect) => dialect.placeholder(index),
1374            Self::MariaDb(dialect) => dialect.placeholder(index),
1375            Self::MsSql(dialect) => dialect.placeholder(index),
1376            Self::Oracle(dialect) => dialect.placeholder(index),
1377        }
1378    }
1379
1380    fn supports_returning(&self) -> bool {
1381        match self {
1382            Self::Sqlite(dialect) => dialect.supports_returning(),
1383            Self::Postgres(dialect) => dialect.supports_returning(),
1384            Self::MySql(dialect) => dialect.supports_returning(),
1385            Self::MariaDb(dialect) => dialect.supports_returning(),
1386            Self::MsSql(dialect) => dialect.supports_returning(),
1387            Self::Oracle(dialect) => dialect.supports_returning(),
1388        }
1389    }
1390
1391    fn supports_native_uuid(&self) -> bool {
1392        match self {
1393            Self::Sqlite(dialect) => dialect.supports_native_uuid(),
1394            Self::Postgres(dialect) => dialect.supports_native_uuid(),
1395            Self::MySql(dialect) => dialect.supports_native_uuid(),
1396            Self::MariaDb(dialect) => dialect.supports_native_uuid(),
1397            Self::MsSql(dialect) => dialect.supports_native_uuid(),
1398            Self::Oracle(dialect) => dialect.supports_native_uuid(),
1399        }
1400    }
1401
1402    fn supports_json(&self) -> bool {
1403        match self {
1404            Self::Sqlite(dialect) => dialect.supports_json(),
1405            Self::Postgres(dialect) => dialect.supports_json(),
1406            Self::MySql(dialect) => dialect.supports_json(),
1407            Self::MariaDb(dialect) => dialect.supports_json(),
1408            Self::MsSql(dialect) => dialect.supports_json(),
1409            Self::Oracle(dialect) => dialect.supports_json(),
1410        }
1411    }
1412
1413    fn feature_set(&self) -> FeatureSet {
1414        match self {
1415            Self::Sqlite(dialect) => dialect.feature_set(),
1416            Self::Postgres(dialect) => dialect.feature_set(),
1417            Self::MySql(dialect) => dialect.feature_set(),
1418            Self::MariaDb(dialect) => dialect.feature_set(),
1419            Self::MsSql(dialect) => dialect.feature_set(),
1420            Self::Oracle(dialect) => dialect.feature_set(),
1421        }
1422    }
1423
1424    fn render_column_type(&self, column: &ColumnDef) -> String {
1425        match self {
1426            Self::Sqlite(dialect) => dialect.render_column_type(column),
1427            Self::Postgres(dialect) => dialect.render_column_type(column),
1428            Self::MySql(dialect) => dialect.render_column_type(column),
1429            Self::MariaDb(dialect) => dialect.render_column_type(column),
1430            Self::MsSql(dialect) => dialect.render_column_type(column),
1431            Self::Oracle(dialect) => dialect.render_column_type(column),
1432        }
1433    }
1434
1435    fn compile_schema_operation(
1436        &self,
1437        operation: &SchemaOperation,
1438    ) -> OrmdanticResult<Vec<String>> {
1439        match self {
1440            Self::Sqlite(dialect) => dialect.compile_schema_operation(operation),
1441            Self::Postgres(dialect) => dialect.compile_schema_operation(operation),
1442            Self::MySql(dialect) => dialect.compile_schema_operation(operation),
1443            Self::MariaDb(dialect) => dialect.compile_schema_operation(operation),
1444            Self::MsSql(dialect) => dialect.compile_schema_operation(operation),
1445            Self::Oracle(dialect) => dialect.compile_schema_operation(operation),
1446        }
1447    }
1448
1449    fn begin_transaction_sql(&self, options: &TransactionOptions) -> Vec<String> {
1450        match self {
1451            Self::Sqlite(dialect) => dialect.begin_transaction_sql(options),
1452            Self::Postgres(dialect) => dialect.begin_transaction_sql(options),
1453            Self::MySql(dialect) => dialect.begin_transaction_sql(options),
1454            Self::MariaDb(dialect) => dialect.begin_transaction_sql(options),
1455            Self::MsSql(dialect) => dialect.begin_transaction_sql(options),
1456            Self::Oracle(dialect) => dialect.begin_transaction_sql(options),
1457        }
1458    }
1459
1460    fn set_isolation_sql(&self, isolation_level: IsolationLevel) -> String {
1461        match self {
1462            Self::Sqlite(dialect) => dialect.set_isolation_sql(isolation_level),
1463            Self::Postgres(dialect) => dialect.set_isolation_sql(isolation_level),
1464            Self::MySql(dialect) => dialect.set_isolation_sql(isolation_level),
1465            Self::MariaDb(dialect) => dialect.set_isolation_sql(isolation_level),
1466            Self::MsSql(dialect) => dialect.set_isolation_sql(isolation_level),
1467            Self::Oracle(dialect) => dialect.set_isolation_sql(isolation_level),
1468        }
1469    }
1470
1471    fn savepoint_sql(&self, name: &SavepointName) -> String {
1472        match self {
1473            Self::Sqlite(dialect) => dialect.savepoint_sql(name),
1474            Self::Postgres(dialect) => dialect.savepoint_sql(name),
1475            Self::MySql(dialect) => dialect.savepoint_sql(name),
1476            Self::MariaDb(dialect) => dialect.savepoint_sql(name),
1477            Self::MsSql(dialect) => dialect.savepoint_sql(name),
1478            Self::Oracle(dialect) => dialect.savepoint_sql(name),
1479        }
1480    }
1481
1482    fn rollback_to_savepoint_sql(&self, name: &SavepointName) -> String {
1483        match self {
1484            Self::Sqlite(dialect) => dialect.rollback_to_savepoint_sql(name),
1485            Self::Postgres(dialect) => dialect.rollback_to_savepoint_sql(name),
1486            Self::MySql(dialect) => dialect.rollback_to_savepoint_sql(name),
1487            Self::MariaDb(dialect) => dialect.rollback_to_savepoint_sql(name),
1488            Self::MsSql(dialect) => dialect.rollback_to_savepoint_sql(name),
1489            Self::Oracle(dialect) => dialect.rollback_to_savepoint_sql(name),
1490        }
1491    }
1492
1493    fn release_savepoint_sql(&self, name: &SavepointName) -> String {
1494        match self {
1495            Self::Sqlite(dialect) => dialect.release_savepoint_sql(name),
1496            Self::Postgres(dialect) => dialect.release_savepoint_sql(name),
1497            Self::MySql(dialect) => dialect.release_savepoint_sql(name),
1498            Self::MariaDb(dialect) => dialect.release_savepoint_sql(name),
1499            Self::MsSql(dialect) => dialect.release_savepoint_sql(name),
1500            Self::Oracle(dialect) => dialect.release_savepoint_sql(name),
1501        }
1502    }
1503
1504    fn reflection_queries(&self, scope: &ReflectionScope) -> Vec<ReflectionQuery> {
1505        match self {
1506            Self::Sqlite(dialect) => dialect.reflection_queries(scope),
1507            Self::Postgres(dialect) => dialect.reflection_queries(scope),
1508            Self::MySql(dialect) => dialect.reflection_queries(scope),
1509            Self::MariaDb(dialect) => dialect.reflection_queries(scope),
1510            Self::MsSql(dialect) => dialect.reflection_queries(scope),
1511            Self::Oracle(dialect) => dialect.reflection_queries(scope),
1512        }
1513    }
1514
1515    fn upsert_conflict_clause(
1516        &self,
1517        conflict_column: &str,
1518        update_columns: &[String],
1519    ) -> OrmdanticResult<String> {
1520        match self {
1521            Self::Sqlite(dialect) => {
1522                dialect.upsert_conflict_clause(conflict_column, update_columns)
1523            }
1524            Self::Postgres(dialect) => {
1525                dialect.upsert_conflict_clause(conflict_column, update_columns)
1526            }
1527            Self::MySql(dialect) => dialect.upsert_conflict_clause(conflict_column, update_columns),
1528            Self::MariaDb(dialect) => {
1529                dialect.upsert_conflict_clause(conflict_column, update_columns)
1530            }
1531            Self::MsSql(dialect) => dialect.upsert_conflict_clause(conflict_column, update_columns),
1532            Self::Oracle(dialect) => {
1533                dialect.upsert_conflict_clause(conflict_column, update_columns)
1534            }
1535        }
1536    }
1537}