feat(transfer): add rebuild structure and data mode
This commit is contained in:
1 parent
2b2d1f0d95
commit
538aeed99c
37 files changed
+6720
-404
No files matched your search
@@ -118,6 +118,12 @@ pub struct DdlDialectProfile {
|
||||
pub drop_index_uses_on_table: bool,
|
||||
/// `DROP FOREIGN KEY` (MySQL) vs `DROP CONSTRAINT`.
|
||||
pub drop_fk_as_foreign_key: bool,
|
||||
/// `DROP TABLE t CASCADE` is accepted. Oracle's `CASCADE CONSTRAINTS` is a
|
||||
/// different clause and does not count.
|
||||
pub drop_table_supports_cascade: bool,
|
||||
/// `DROP TABLE IF EXISTS t` is valid grammar. False for Oracle (pre-23c),
|
||||
/// Access, Firebird, Db2, Informix, SAP HANA and Teradata.
|
||||
pub drop_table_supports_if_exists: bool,
|
||||
/// Index method placement.
|
||||
pub index_type_placement: IndexTypePlacement,
|
||||
/// `INCLUDE (cols)` on indexes.
|
||||
@@ -165,6 +171,9 @@ pub struct DdlDialectProfile {
|
||||
pub rule_drop_template: Option<&'static str>,
|
||||
/// `ALTER {object_type} {name} OWNER TO {owner};`
|
||||
pub owner_alter_template: Option<&'static str>,
|
||||
/// `DROP TABLE {table}{cascade};` — the caller quotes `{table}` itself. Engines
|
||||
/// that drop a differently-named object override the whole shape.
|
||||
pub drop_table_template: &'static str,
|
||||
/// Optional session lock-timeout preamble for generated scripts.
|
||||
pub lock_timeout_sql: Option<&'static str>,
|
||||
/// Data-driven base-type rewrites for this target (empty → rely on matrix / normalize only).
|
||||
@@ -222,6 +231,9 @@ const SQLSERVER_SEQ_ALTER: &str =
|
||||
"ALTER SEQUENCE {name} RESTART WITH {start_value} INCREMENT BY {increment} MINVALUE {min_value} MAXVALUE {max_value} {cycle};";
|
||||
const RULE_DROP: &str = "DROP RULE IF EXISTS {rule_name} ON {table_name}{cascade};";
|
||||
const OWNER_ALTER: &str = "ALTER {object_type} {name} OWNER TO {owner};";
|
||||
/// Shared ANSI `DROP TABLE` shape. `{if_exists}` is deliberately absent: whether a
|
||||
/// dialect accepts `IF EXISTS` is a capability flag, not part of the rendered shape.
|
||||
const DROP_TABLE: &str = "DROP TABLE {table}{cascade};";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Type maps (data only — no per-database logic at call sites)
|
||||
@@ -325,6 +337,8 @@ fn mysql_family(db: DatabaseType) -> DdlDialectProfile {
|
||||
foreign_keys_inline_in_create: false,
|
||||
drop_index_uses_on_table: true,
|
||||
drop_fk_as_foreign_key: true,
|
||||
drop_table_supports_cascade: false,
|
||||
drop_table_supports_if_exists: true,
|
||||
index_type_placement: IndexTypePlacement::UsingBeforeOn,
|
||||
index_supports_include: false,
|
||||
index_supports_filter: false,
|
||||
@@ -354,6 +368,7 @@ fn mysql_family(db: DatabaseType) -> DdlDialectProfile {
|
||||
sequence_drop_template: None,
|
||||
rule_drop_template: None,
|
||||
owner_alter_template: None,
|
||||
drop_table_template: DROP_TABLE,
|
||||
lock_timeout_sql: Some("SET SESSION lock_wait_timeout = 3;"),
|
||||
type_map: &[],
|
||||
}
|
||||
@@ -371,6 +386,8 @@ fn postgres_family(db: DatabaseType) -> DdlDialectProfile {
|
||||
foreign_keys_inline_in_create: false,
|
||||
drop_index_uses_on_table: false,
|
||||
drop_fk_as_foreign_key: false,
|
||||
drop_table_supports_cascade: true,
|
||||
drop_table_supports_if_exists: true,
|
||||
index_type_placement: IndexTypePlacement::UsingSuffix,
|
||||
index_supports_include: true,
|
||||
index_supports_filter: true,
|
||||
@@ -400,6 +417,7 @@ fn postgres_family(db: DatabaseType) -> DdlDialectProfile {
|
||||
sequence_drop_template: Some(SEQ_DROP),
|
||||
rule_drop_template: Some(RULE_DROP),
|
||||
owner_alter_template: Some(OWNER_ALTER),
|
||||
drop_table_template: DROP_TABLE,
|
||||
lock_timeout_sql: Some("SET lock_timeout = '3s';"),
|
||||
type_map: &[],
|
||||
}
|
||||
@@ -420,6 +438,9 @@ fn oracle_family(db: DatabaseType) -> DdlDialectProfile {
|
||||
foreign_keys_inline_in_create: false,
|
||||
drop_index_uses_on_table: false,
|
||||
drop_fk_as_foreign_key: false,
|
||||
// Oracle spells it `CASCADE CONSTRAINTS`, and `IF EXISTS` only landed in 23c.
|
||||
drop_table_supports_cascade: false,
|
||||
drop_table_supports_if_exists: false,
|
||||
index_type_placement: IndexTypePlacement::None,
|
||||
index_supports_include: false,
|
||||
index_supports_filter: false,
|
||||
@@ -450,6 +471,7 @@ fn oracle_family(db: DatabaseType) -> DdlDialectProfile {
|
||||
sequence_drop_template: Some(SEQ_DROP),
|
||||
rule_drop_template: None,
|
||||
owner_alter_template: None,
|
||||
drop_table_template: DROP_TABLE,
|
||||
lock_timeout_sql: Some("ALTER SESSION SET DDL_LOCK_TIMEOUT = 3;"),
|
||||
type_map: &[],
|
||||
}
|
||||
@@ -467,6 +489,9 @@ fn sqlserver_family(db: DatabaseType) -> DdlDialectProfile {
|
||||
foreign_keys_inline_in_create: false,
|
||||
drop_index_uses_on_table: false,
|
||||
drop_fk_as_foreign_key: false,
|
||||
drop_table_supports_cascade: false,
|
||||
// `DROP TABLE IF EXISTS` is valid on SQL Server 2016+.
|
||||
drop_table_supports_if_exists: true,
|
||||
index_type_placement: IndexTypePlacement::TypePrefix,
|
||||
index_supports_include: true,
|
||||
index_supports_filter: true,
|
||||
@@ -496,6 +521,7 @@ fn sqlserver_family(db: DatabaseType) -> DdlDialectProfile {
|
||||
sequence_drop_template: Some(SEQ_DROP),
|
||||
rule_drop_template: None,
|
||||
owner_alter_template: None,
|
||||
drop_table_template: DROP_TABLE,
|
||||
lock_timeout_sql: Some("SET LOCK_TIMEOUT 3000;"),
|
||||
type_map: &[],
|
||||
}
|
||||
@@ -513,6 +539,8 @@ fn sqlite_family(db: DatabaseType) -> DdlDialectProfile {
|
||||
foreign_keys_inline_in_create: true,
|
||||
drop_index_uses_on_table: false,
|
||||
drop_fk_as_foreign_key: false,
|
||||
drop_table_supports_cascade: false,
|
||||
drop_table_supports_if_exists: true,
|
||||
index_type_placement: IndexTypePlacement::None,
|
||||
index_supports_include: false,
|
||||
index_supports_filter: true,
|
||||
@@ -542,6 +570,7 @@ fn sqlite_family(db: DatabaseType) -> DdlDialectProfile {
|
||||
sequence_drop_template: None,
|
||||
rule_drop_template: None,
|
||||
owner_alter_template: None,
|
||||
drop_table_template: DROP_TABLE,
|
||||
lock_timeout_sql: None,
|
||||
type_map: SQLITE_TYPE_MAP,
|
||||
}
|
||||
@@ -559,6 +588,9 @@ fn access_profile() -> DdlDialectProfile {
|
||||
foreign_keys_inline_in_create: false,
|
||||
drop_index_uses_on_table: false,
|
||||
drop_fk_as_foreign_key: false,
|
||||
// Jet SQL has neither CASCADE nor IF EXISTS on DROP TABLE.
|
||||
drop_table_supports_cascade: false,
|
||||
drop_table_supports_if_exists: false,
|
||||
index_type_placement: IndexTypePlacement::None,
|
||||
index_supports_include: false,
|
||||
index_supports_filter: false,
|
||||
@@ -588,6 +620,7 @@ fn access_profile() -> DdlDialectProfile {
|
||||
sequence_drop_template: None,
|
||||
rule_drop_template: None,
|
||||
owner_alter_template: None,
|
||||
drop_table_template: DROP_TABLE,
|
||||
lock_timeout_sql: None,
|
||||
type_map: ACCESS_TYPE_MAP,
|
||||
}
|
||||
@@ -605,6 +638,10 @@ fn h2_profile() -> DdlDialectProfile {
|
||||
foreign_keys_inline_in_create: false,
|
||||
drop_index_uses_on_table: false,
|
||||
drop_fk_as_foreign_key: false,
|
||||
// H2 accepts CASCADE, but it stays off to keep the cascade set aligned with
|
||||
// the frontend preview list in `dbAdminSql.ts`.
|
||||
drop_table_supports_cascade: false,
|
||||
drop_table_supports_if_exists: true,
|
||||
index_type_placement: IndexTypePlacement::None,
|
||||
index_supports_include: false,
|
||||
index_supports_filter: false,
|
||||
@@ -634,6 +671,7 @@ fn h2_profile() -> DdlDialectProfile {
|
||||
sequence_drop_template: None,
|
||||
rule_drop_template: None,
|
||||
owner_alter_template: None,
|
||||
drop_table_template: DROP_TABLE,
|
||||
lock_timeout_sql: None,
|
||||
type_map: &[],
|
||||
}
|
||||
@@ -656,6 +694,10 @@ fn conservative_ansi(db: DatabaseType) -> DdlDialectProfile {
|
||||
foreign_keys_inline_in_create: false,
|
||||
drop_index_uses_on_table: false,
|
||||
drop_fk_as_foreign_key: false,
|
||||
drop_table_supports_cascade: false,
|
||||
// Most engines in this bucket accept `IF EXISTS`; `profile_for` opts out the
|
||||
// ones that do not.
|
||||
drop_table_supports_if_exists: true,
|
||||
index_type_placement: IndexTypePlacement::None,
|
||||
index_supports_include: false,
|
||||
index_supports_filter: false,
|
||||
@@ -685,6 +727,7 @@ fn conservative_ansi(db: DatabaseType) -> DdlDialectProfile {
|
||||
sequence_drop_template: None,
|
||||
rule_drop_template: None,
|
||||
owner_alter_template: None,
|
||||
drop_table_template: DROP_TABLE,
|
||||
lock_timeout_sql: None,
|
||||
type_map: &[],
|
||||
}
|
||||
@@ -698,6 +741,14 @@ fn clickhouse_profile() -> DdlDialectProfile {
|
||||
p
|
||||
}
|
||||
|
||||
fn influxdb_profile() -> DdlDialectProfile {
|
||||
let mut p = conservative_ansi(DatabaseType::InfluxDb);
|
||||
// InfluxDB drops a measurement, not a table, and has no IF EXISTS form.
|
||||
p.drop_table_template = "DROP MEASUREMENT {table};";
|
||||
p.drop_table_supports_if_exists = false;
|
||||
p
|
||||
}
|
||||
|
||||
/// Resolve DDL profile for a concrete target database type.
|
||||
///
|
||||
/// This is the **only** place that maps [`DatabaseType`] → profile data.
|
||||
@@ -709,8 +760,19 @@ pub fn profile_for(db_type: DatabaseType) -> DdlDialectProfile {
|
||||
Mysql | Doris | StarRocks | Goldendb | Sundb | Databend | Gbase | ManticoreSearch => mysql_family(db_type),
|
||||
|
||||
// PostgreSQL family
|
||||
Postgres | Redshift | Gaussdb | Kingbase | Highgo | Vastbase | OpenGauss | Kwdb | Firebird | Vertica
|
||||
| Exasol | Uxdb => postgres_family(db_type),
|
||||
Postgres | Redshift | Gaussdb | Kingbase | Highgo | Vastbase | OpenGauss | Kwdb | Uxdb => {
|
||||
postgres_family(db_type)
|
||||
}
|
||||
|
||||
// PostgreSQL-shaped DDL, but not the PostgreSQL `DROP TABLE` grammar:
|
||||
// Firebird has neither CASCADE nor IF EXISTS; Vertica and Exasol are kept off
|
||||
// CASCADE so the set stays aligned with the frontend preview list.
|
||||
Firebird | Vertica | Exasol => {
|
||||
let mut profile = postgres_family(db_type);
|
||||
profile.drop_table_supports_cascade = false;
|
||||
profile.drop_table_supports_if_exists = db_type != Firebird;
|
||||
profile
|
||||
}
|
||||
|
||||
// Oracle family
|
||||
Oracle | Dameng | OceanbaseOracle | Yashandb | Xugu | Iris => oracle_family(db_type),
|
||||
@@ -728,13 +790,23 @@ pub fn profile_for(db_type: DatabaseType) -> DdlDialectProfile {
|
||||
|
||||
ClickHouse => clickhouse_profile(),
|
||||
|
||||
DuckDb | Questdb | SapHana | Teradata | Snowflake | Trino | PrestoSql | Hive | Kyuubi | Impala | Argo
|
||||
| Spark | Db2 | Informix | Bigquery | Spanner | Kylin | Ignite | Ignite3 | Oscar | Tdengine | Iotdb
|
||||
| Databricks | Jdbc => conservative_ansi(db_type),
|
||||
// Engines whose DROP TABLE grammar has no `IF EXISTS`.
|
||||
Db2 | Informix | SapHana | Teradata => {
|
||||
let mut profile = conservative_ansi(db_type);
|
||||
profile.drop_table_supports_if_exists = false;
|
||||
profile
|
||||
}
|
||||
|
||||
DuckDb | Questdb | Snowflake | Trino | PrestoSql | Hive | Kyuubi | Impala | Argo | Spark | Bigquery
|
||||
| Spanner | Kylin | Ignite | Ignite3 | Oscar | Tdengine | Iotdb | Databricks | Jdbc => {
|
||||
conservative_ansi(db_type)
|
||||
}
|
||||
|
||||
InfluxDb => influxdb_profile(),
|
||||
|
||||
// Non-tabular / not applicable for relational CREATE TABLE
|
||||
Redis | MongoDb | DynamoDb | Elasticsearch | Easysearch | Meilisearch | Qdrant | Milvus | Weaviate
|
||||
| ChromaDb | Neo4j | Cassandra | Etcd | ZooKeeper | Nacos | Consul | InfluxDb | InfluxDb3 | VictoriaMetrics
|
||||
| ChromaDb | Neo4j | Cassandra | Etcd | ZooKeeper | Nacos | Consul | InfluxDb3 | VictoriaMetrics
|
||||
| MessageQueue | Mqtt | Hbase => conservative_ansi(db_type),
|
||||
}
|
||||
}
|
||||
@@ -810,4 +882,74 @@ mod tests {
|
||||
);
|
||||
assert_eq!(rendered, "DROP FUNCTION IF EXISTS \"public\".\"f\" CASCADE;");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_table_if_exists_support_matches_dialect_grammar() {
|
||||
for db in [
|
||||
DatabaseType::Mysql,
|
||||
DatabaseType::Postgres,
|
||||
DatabaseType::SqlServer,
|
||||
DatabaseType::Sqlite,
|
||||
DatabaseType::DuckDb,
|
||||
DatabaseType::ClickHouse,
|
||||
DatabaseType::H2,
|
||||
DatabaseType::Snowflake,
|
||||
] {
|
||||
assert!(profile_for(db).drop_table_supports_if_exists, "{db:?} does support DROP TABLE IF EXISTS");
|
||||
}
|
||||
// Oracle only gained `IF EXISTS` in 23c; the rest have no such grammar at all.
|
||||
for db in [
|
||||
DatabaseType::Oracle,
|
||||
DatabaseType::Dameng,
|
||||
DatabaseType::OceanbaseOracle,
|
||||
DatabaseType::Access,
|
||||
DatabaseType::Firebird,
|
||||
DatabaseType::Db2,
|
||||
DatabaseType::Informix,
|
||||
DatabaseType::SapHana,
|
||||
DatabaseType::Teradata,
|
||||
DatabaseType::InfluxDb,
|
||||
] {
|
||||
assert!(!profile_for(db).drop_table_supports_if_exists, "{db:?} does not support DROP TABLE IF EXISTS");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_table_cascade_support_is_limited_to_the_postgres_grammar() {
|
||||
for db in [
|
||||
DatabaseType::Postgres,
|
||||
DatabaseType::Redshift,
|
||||
DatabaseType::Gaussdb,
|
||||
DatabaseType::Kwdb,
|
||||
DatabaseType::Kingbase,
|
||||
DatabaseType::Highgo,
|
||||
DatabaseType::Uxdb,
|
||||
DatabaseType::Vastbase,
|
||||
DatabaseType::OpenGauss,
|
||||
] {
|
||||
assert!(profile_for(db).drop_table_supports_cascade, "{db:?} should keep DROP TABLE … CASCADE");
|
||||
}
|
||||
// Oracle's `CASCADE CONSTRAINTS` is a different clause; Firebird, Vertica and Exasol
|
||||
// share the PostgreSQL profile but stay out of the cascade set so the backend matches
|
||||
// the frontend preview list in `dbAdminSql.ts`.
|
||||
for db in [
|
||||
DatabaseType::Oracle,
|
||||
DatabaseType::Firebird,
|
||||
DatabaseType::Vertica,
|
||||
DatabaseType::Exasol,
|
||||
DatabaseType::Mysql,
|
||||
DatabaseType::Sqlite,
|
||||
DatabaseType::DuckDb,
|
||||
DatabaseType::SqlServer,
|
||||
DatabaseType::H2,
|
||||
] {
|
||||
assert!(!profile_for(db).drop_table_supports_cascade, "{db:?} must not emit DROP TABLE … CASCADE");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn drop_table_template_carries_the_whole_statement_shape() {
|
||||
assert_eq!(profile_for(DatabaseType::Postgres).drop_table_template, "DROP TABLE {table}{cascade};");
|
||||
assert_eq!(profile_for(DatabaseType::InfluxDb).drop_table_template, "DROP MEASUREMENT {table};");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user