ADR-0006: Multi-DB reverse Dialect SPI
- Status: Accepted (2026-08-01)
- Decision makers: Project maintainers
Context
Reverse parsing long piled in DBReverseParseCommand if (dbType) branches; index/FK precision depends on JDBC drivers; multi-DB and import UX (schema pick, on-demand load) hard to extend. After surveying DBeaver / SchemaCrawler / jOOQ-meta / Apache Calcite·MetaModel: industry hot paths use dictionary SQL, JDBC only as Generic fallback; Apache libs unsuitable as ER reverse core.
Decision
- Introduce
com.erdonline.erd.reverse: ReverseDialect SPI + DialectCapability + ReverseDialectRegistry - P0 first-class (full: table/column/PK/index, FK later): MySQL (incl. MariaDB), PostgreSQL, Oracle, SQL Server
- GenericJdbcReverseDialect: any JDBC fallback (table/column/PK; index best-effort, failure non-blocking)
- MySQL indexes via
INFORMATION_SCHEMA.STATISTICS(aligned with DBeaver/jOOQ), notgetIndexInfoprecision; MySQL 8 functional keys readEXPRESSION→indexs[].fields[](column fallback if missing); PostgreSQL expression keyspg_get_indexdef→ same field - Do not introduce SchemaCrawler (EPL) / MetaModel / Calcite as reverse dependencies
- Code style follows Alibaba Java Development Manual (naming, constants, try-with-resources, no raw types, etc.)
Consequences
- Positive: per-DB extension, capability matrix drives frontend, mapping rules unit-testable
- Cost: each DB needs a maintained Dialect; precision not promised outside P0
- Shipped:
POST /connector/dbReverseMeta+ schema selection; JDBCgetImportedKeys→associations - P0 four-DB dictionary FK: MySQL
KEY_COLUMN_USAGE; PGreferential_constraints; SQL Serversys.foreign_keys; OracleALL_CONSTRAINTS(R); JDBC fallback on failure; composite columns split into ordered edges - Comment fidelity:
DialectCapability.supportsComment; MySQL via JDBCREMARKS; PostgreSQL dictionaryobj_description/col_description; SQL Server dictionarysys.extended_properties(MS_Description); Oracle dictionaryALL_TAB_COMMENTS/ALL_COL_COMMENTS→entity.chnname/fields[].chnname(JDBC fallback on failure) - Column defaults: JDBC
COLUMN_DEF→fields[].defaultValue(DefaultValueMapper: quote strings, numbers/expressions as-is; strip PG::type) - Trigger fidelity:
DialectCapability.supportsTrigger; MySQL/MariaDB dictionaryINFORMATION_SCHEMA.TRIGGERS, PostgreSQL dictionaryinformation_schema.triggers, SQL Server dictionarysys.triggers/sys.trigger_events+OBJECT_DEFINITION, Oracle dictionaryALL_TRIGGERS+ALL_SOURCE(multi-event split rows) →entity.triggers[](name/timing/event/statement/ rebuild or rawddl); warn-skip on failure; P0 four-DB closed loop - Expression/functional indexes: PostgreSQL
pg_get_indexdef, MySQL 8STATISTICS.EXPRESSION, OracleALL_IND_EXPRESSIONS, SQL Serversys.computed_columns.definition→indexs[].fields[]as raw strings (same slot as DBML interop); P0 four-DB closed loop; missing keys / unavailable dictionary soft-skip or column-name-only fallback - Partial/filtered index predicates: PostgreSQL
pg_get_expr(indpred), SQL Serversys.indexes.filter_definition→indexs[].filter(not infields[]); no MySQL/Oracle equivalent; export side PG/SQL Server DDLWHERE+ DBMLnote: 'filter: …'(@dbml/corerejects officialwhere:) - Composite FK
fields[]: see ADR-0011 (still deferred; unblocking needs FE multi-field edge protocol). Shipped additive metadata:constraintName/deleteRule/updateRule(same name on split edges, not aggregated); table list on-demand pagination TBD