feat(odc): ODC workbench DB2 LUW support — plugin/db-browser/odc-core/service/migrate (Fixes actiontech/dms-ee#839)#24
Open
actiontech-bot wants to merge 19 commits into
Open
feat(odc): ODC workbench DB2 LUW support — plugin/db-browser/odc-core/service/migrate (Fixes actiontech/dms-ee#839)#24actiontech-bot wants to merge 19 commits into
actiontech-bot wants to merge 19 commits into
Conversation
…ASS_NAME - DialectType: add DB2 enum + isDb2() predicate (B-01 / B-S1) - ConnectType: add DB2(DialectType.DB2) (B-02, literal "DB2" pinned per compat-RISK-2) - OdcConstants: add DB2_DRIVER_CLASS_NAME + DB2_DEFAULT_SCHEMA placeholder (B-03) - DBBrowserFactory: add DB2 string constant (B-04) Refs: actiontech/dms-ee#839
…r + Db2StatsAccessor + Db2ObjectOperator - AbstractDBBrowserFactory: add buildForDB2() abstract method + DB2 case in create() (B-05) - Db2SchemaAccessor: real impl backed by SYSCAT.* (B-06/B-07) - Db2StatsAccessor: real impl backed by MON_GET_CONNECTION + SYSCAT.TABLES (B-08) - Db2ObjectOperator: real impl for drop/showCount (B-09 real) - 15 placeholder buildForDB2() across 8 editor + 7 template factories (B-09 placeholder + B-10) - Unit tests: Db2SchemaAccessorTest + Db2StatsAccessorTest (mock JDBC, no real connection) Refs: actiontech/dms-ee#839
…ce-layer DB2 branches - server/plugins/pom.xml: register connect-plugin-db2 + schema-plugin-db2 modules (B-14) - connect-plugin-db2: Db2ConnectionPlugin + Db2ConnectionExtension (getDriverClassName / generateJdbcUrl / getConnectionInitializers / test) + Db2SessionExtension (getCurrentSchema / getConnectionId 3-level fallback / getKillSessionSql FORCE APPLICATION / killQuery / setClientInfo=false) + Db2InformationExtension (B-11 / B-12 / B-13) - IBM JDBC dep: com.ibm.db2:jcc:11.5.9.0 scope=provided in connect-plugin-db2/pom.xml only (design §2.7, IPLA via scope=provided) - schema-plugin-db2: Db2SchemaPlugin + Db2DatabaseExtension + Db2TableExtension + utils/DBAccessorUtil routing DBSchemaAccessorFactory.setType(DialectType.DB2.name()) (B-15 / B-16) - odc-service: ConnectTypeUtil (B-18) / ConnectionTesting (B-19) / ConnectionConfig.getDefaultSchema=username.toUpperCase (B-20) / OBConsoleDataSourceFactory (B-21) / TableDataService reuses MySQLDMLBuilder (B-22, design §2.5) / DruidDataSourceFactory validationQuery=select 1 from SYSIBM.SYSDUMMY1 (B-24) / DefaultDBSessionManage explicit DB2 branch (B-S2) - odc-core: SqlCommentProcessor split via addLineMysql for DB2 (B-23) - Unit tests (mock-only, no real JDBC): Db2ConnectionExtensionTest + Db2SessionExtensionTest (getConnectionId 3-level map case) + ConnectTypeUtilTest / DruidDataSourceFactoryTest / SqlCommentProcessorTest DB2 cases appended Refs: actiontech/dms-ee#839
…BC URL Root cause: Db2ConnectionExtension.generateJdbcUrl required a non-empty catalogName via Validate.notEmpty, but the upstream DMS-EE buildDatasourceBaseInfo contract (compat-RISK-5 D-02) only carries the DB2 database name via the defaultSchema field of ConnectionConfig — not via catalogName. This caused every DB2 datasource sync to fail with "catalog name can not be null", leaving status=INACTIVE and blocking all schema / table / view / column metadata reads (batch-3 cases 2.1-2.4 plus batch-4+ DB2 cases). Fix (selected option A of A/B/C tradeoff, see fix-001.md): - generateJdbcUrl: when catalogName is blank, fall back to defaultSchema (DB2 catalog = database, defaultSchema carries the DB name end-to-end from DMS-EE → odc-service → plugin) - when the resolved catalog equals defaultSchema, omit the redundant ":currentSchema=" segment (DB2 driver defaults to user.toUpperCase(), which is exactly what ConnectionConfig.getDefaultSchema returns for DB2 per B-20) - improve the fail-fast error message to mention both catalogName and defaultSchema so future regressions surface the actual missing piece - Db2ConnectionExtensionTest: add 4 regression cases (null fallback / empty fallback / catalog==schema case-insensitive / explicit catalog+distinct schema) and split the existing blank-catalog throws case into NPE-for-null and IAE-for-empty to keep Validate.notEmpty semantics explicit Test: cd server/plugins/connect-plugin-db2 && mvn test → 18 PASS / 0 FAIL. Refs: actiontech/dms-ee#839
…fallback has a value
Root cause (companion to fix(odc-plugin-db2) above): even with the plugin
layer fallback (catalogName ← defaultSchema), the actual value never reaches
the plugin because ConnectionService.create / update has a generic branch:
if (!connection.getType().isDefaultSchemaRequired()) {
connection.setDefaultSchema(null);
}
For DB2 isDefaultSchemaRequired() is false (DB2 catalog ≠ schema in the
ODC model), so the DMS-EE-supplied database name carried via defaultSchema
gets silently nulled before JDBC URL build time. With both fields blank the
plugin-layer fallback has nothing to fall back to and we get the original
"catalog name can not be null" again.
Fix:
- ConnectionService.adaptDb2DatabaseToCatalog(): runs in both create() and
update() right after environmentAdapter / connectionSSLAdaptor and BEFORE
the isDefaultSchemaRequired branch. For DB2 only, if catalogName is blank
and a raw defaultSchema was provided by the caller, copy that raw value
into catalogName. Explicitly never overwrites an explicit catalogName.
- ConnectionService.clearDb2DefaultSchemaOnEntity(): after modelToEntity()
the ConnectionConfig.getDefaultSchema() DB2 fallback would have synthesised
user.toUpperCase() into the entity; that's fine for JDBC URL build but
would persist a misleading value in the t_connection.default_schema column.
Wipe it on the entity right before saveAndFlush so persistence stays
honest (raw null) while runtime resolution keeps using getDefaultSchema().
- ConnectionConfig.getRawDefaultSchema(): @JsonIgnore helper that returns the
persisted field as-is, bypassing the dialect-specific resolution in
getDefaultSchema(). Required by adaptDb2DatabaseToCatalog so we can tell
"the user provided no schema" from "fallback synthesised user.toUpperCase()".
Together with the plugin-layer fallback this delivers the compat-RISK-7
decision B (compatibility shim): DMS-EE buildDatasourceBaseInfo contract
stays unchanged, the DB2 catalog/database name flows defaultSchema →
catalogName → JDBC URL transparently, and explicit catalogName overrides
keep working.
Refs: actiontech/dms-ee#839
…ix-F) Adds ConnectionServiceDb2AdapterTest covering the new adaptDb2DatabaseToCatalog helper added in the previous commit: - DB2 + raw defaultSchema=testdb → catalogName=testdb (DMS-EE D-02 contract) - DB2 + explicit catalogName is preserved (no overwrite) - MYSQL + defaultSchema=testdb is a no-op (adapter only acts on DB2) - DB2 + blank defaultSchema is a no-op (no false positives) - null connection guard - getRawDefaultSchema vs getDefaultSchema: raw stays null while the DB2 dialect-fallback synthesises user.toUpperCase() Pure POJO checks; no JDBC, no Spring context. Six assertions cover the adapter's invariants required by case 2.1 / 2.2 / 2.3 retest. Refs: actiontech/dms-ee#839
…ils stops throwing
Bug A in fix-G: the IBM Data Server Driver for JDBC returns the DB2
product version as the IBM internal version code (for v11.5.8.0:
"SQL110580") rather than as a dotted decimal. ODC core consumes the
return value via VersionUtils.compareVersions which calls
Integer.parseInt on every dot-separated segment, so passing the raw
"SQL110580" raises NumberFormatException and propagates HTTP 400 out of
POST /api/v2/datasource/databases/{id}/sessions
(createSessionByDatabase). With session creation broken, the entire DB2
workbench downstream stack (resource-tree table/view/index/constraint
sub-nodes, SQL Console execution, "数据研发" submenu, batch-3 cases
2.2 / 2.3 / 2.4 / 3.x / 4.x / 5.x and 6.x) is blocked.
This change keeps the fix inside the DB2 plugin (no impact to other
dialects) and normalizes whatever the driver returns into a dotted
decimal that VersionUtils can parse:
1. Already-dotted decimals pass through unchanged.
2. Free-form text containing a dotted decimal (e.g. the
"DB2 v11.5.9.0" returned by SYSIBMADM.ENV_INST_INFO.SERVICE_LEVEL)
gets the dotted run extracted.
3. IBM internal codes "SQL" + 6/7/8 digits get positionally decoded
into V.R.M.F (e.g. SQL110580 → 11.5.8.0).
4. Anything unrecognized falls back to the sentinel "0.0.0" so the
caller still gets a parseable string and a WARN is logged.
Mock-only unit tests pin the bug A repro ("SQL110580" → "11.5.8.0"),
exercise the map of formats the IBM driver is observed to emit, and
defensively round-trip every output through VersionUtils.compareVersions
to prove no input can re-introduce a throw.
Refs: actiontech/dms-ee#839
…STOOLS/SQLJ are hidden
Bug C in fix-G: design.md §6 prescribes an 11-entry system-schema
blacklist for DB2 (SYSCAT / SYSIBM / SYSIBMADM / SYSIBMINTERNAL /
SYSIBMTS / SYSFUN / SYSPROC / SYSSTAT / SYSTOOLS / SYSPUBLIC / NULLID).
The original implementation in Db2SchemaAccessor.showDatabases filtered
SYSCAT.SCHEMATA by DEFINER NOT IN(...), but on DB2 11.5 several system
schemas (NULLID, SQLJ, SYSTOOLS) are created by the instance owner
(e.g. db2inst1) and therefore have DEFINER = db2inst1, not SYSIBM. The
DEFINER predicate let those rows slip into the user schema list — see
the JDBC truth table in
docs/test/screenshots/case-2-1-round2-schema-definer.txt and the
batch-3 round-2 failure analysis in docs/test/case-2-1.md.
The fix switches the filter dimension to SCHEMANAME, which is what the
design intended, and adds SQLJ to the blacklist (DB2 JDBC stored
procedures schema, meaningless to end users; previously slipped through
the DEFINER filter too).
A new mock-only test (showDatabases_sqlFiltersBySchemaNameWithFullBlacklist)
captures the SQL string with ArgumentCaptor and pins both shape
("SCHEMANAME NOT IN", not "DEFINER NOT IN") and the full
12-entry blacklist so a future refactor can't silently regress to the
original DEFINER-based filtering.
Refs: actiontech/dms-ee#839
…sor surface
fix-H for dms-ee#839 — round-fix-G regression test exposed bug D: ODC
`DBTableController#getTable` (5-tab table-detail page) and
`DBMetadataController#listIdentities?type=VIEW` (resource-tree view node)
collapsed with HTTP 500 because `Db2SchemaAccessor` shipped with 52
methods that still threw `UnsupportedOperationException("Not supported
yet")`. Any one of those methods on the aggregator path
(`OBMySQLTableExtension.getDetail`) is enough to crash the whole tab.
Two-tier implementation, both in the same commit because they sit on
one class and share helper state (the 12-entry SYSTEM_SCHEMA_BLACKLIST
constant introduced for the view list is also reused by the schema
list):
A. View surface (case 2.4 — DB2INST1 → "视图" subnode + SQL autocomplete)
- listAllUserViews(viewNameLike): SELECT VIEWSCHEMA, VIEWNAME FROM
SYSCAT.VIEWS WHERE VIEWSCHEMA NOT IN (<12-entry blacklist>)
- listAllSystemViews(viewNameLike): inverse (VIEWSCHEMA IN (...))
- listAllViews(): union of user + system
- listViews(schema): per-schema variant for tree expansion
- showSystemViews(schema): per-schema list for autocomplete
- getView(schema, name): null (controller maps to 404, not 500)
B. Table-detail aggregator surface (case 2.3 — TEST_ORDERS detail page)
- getDatabase(schema): minimal POJO so OBMySQLTableExtension.getDetail
can proceed (DB2 ≈ Oracle/PG where schema is the user-facing DB id)
- listDatabases(): full DBDatabase list (was missing wrapper)
- listTableColumns(schema, List<table>): batch variant — loops the
per-table SYSCAT.COLUMNS query (the existing per-table variant
already worked); avoids throwing on every detail-page render
- listTableConstraints(schema, table): SYSCAT.TABCONST with P/U/F/K
-> DBConstraintType mapping (PRIMARY_KEY/UNIQUE_KEY/FOREIGN_KEY/
CHECK/UNKNOWN)
- listTableIndexes(schema, table): SYSCAT.INDEXES with UNIQUERULE
P/U -> unique=true, D -> unique=false
- switchDatabase / syncExternalTableFiles: void / false (no-op
because DB2 schema is fixed at JDBC connect time)
C. Safe-degradation surface (remaining unsupported methods)
Every other previously-throwing method now returns empty List/Map /
null / false. Out-of-scope for this release (design.md §6 explicitly
covers only schema/table/view/column/index/constraint):
- PL objects: listFunctions/Procedures/Packages/PackageBodies/
Triggers/Types, listSequences/Synonyms — empty list
- MView: listMViews / listAllMViewsLike / refreshMVData /
listMView{Constraints,RefreshRecords,Indexes} / getMView — empty/null/false
- Variables / charset / collation — empty list (DB2 surfaces via
SYSPROC.* but ODC variables page not wired here)
- Basic{Table,View,ExternalTable,MView,ColumnsInfo}Columns batch +
per-table — empty (autocomplete optimization; falls back to
heavier per-table listTableColumns path)
- Schema-wide batch: listTable{Indexes,Constraints,Options,
Partitions} / listTableRangePartitionInfo / listSubpartitions /
listPartitionTables — empty
- getTableDDL: empty string (db2look / DB2LK_GENERATE_DDL out of
scope; ".contains(...)" safe on "")
- getTableOptions / getPartition / getTableColumnGroups / single-
object getX (View/Function/Procedure/Package/Trigger/Type/Sequence/
Synonym) — null (controller maps null -> 404, not 500)
- listUsers — empty (no DB2 user picker in this release)
- getTables(schema, list) — empty Map (DB2 plugin's Db2TableExtension
orchestrates columns+indexes+constraints individually; this batch
path is not used)
D. Tests (Db2SchemaAccessorTest, +5 new mock-only cases on top of the
8 baseline from fix-G):
- listViews_returnsViewIdentities (view path)
- listAllUserViews_filtersSystemSchemasAndSupportsLikePredicate
(view + blacklist single-source-of-truth)
- listAllSystemViews_inverseOfUserViews (regression guard against
accidentally swapping VIEWSCHEMA NOT IN <-> IN)
- listTableColumnsBatch_loopsPerTableAndKeysByName
- listTableColumnsBatch_emptyInputReturnsEmptyMap (with explicit
List<String> cast to disambiguate from String overload)
- listTableIndexes_uniqueRuleMapping (P/D mapping)
- listTableConstraints_typeMapping (P/U/F constraint type mapping)
- getDatabase_returnsMinimalIdEqualsName (aggregator path
prerequisite)
- unsupportedPlaceholders_degradeToEmptyInsteadOfThrowing
(regression guard for the 18 spot-checked methods that were
previously throwing — pins them to empty / false / null)
13 / 13 PASS; baseline already-known
SqlServerTriggerTemplateTest.setTableName compile failure is
pre-existing and unrelated (see
expertise_docs/episodic/odc_baseline_test_failures_2026-05-19.md).
E. Compatibility
- No new RISK in docs/spec/compat_risks.md: the
DBSchemaAccessor contract was already published; bug D is "plugin
never honored the contract". Non-DB2 plugins untouched.
- vendor / go.mod / pnpm-lock not touched.
- No new startup hook / no AutoMigrate / no schema migration.
Refs: docs/test/case-2-3.md round-fix-G; docs/test/case-2-4.md
round-fix-G; expertise_docs/episodic/
odc_db2_fixG_round_bugD_unsupported_methods_2026-05-19.md
Fixes: dms-ee#839
… page stops 500 fix-H bug D-2 (table-detail page 5 tabs collapsed with HTTP 500). The fix-H accessor commit (b9a03fc) implemented the column / constraint / index / DDL accessor methods but the table-detail aggregator path still crashed because `Db2TableExtension extends OBMySQLTableExtension` and inherits two OB-MySQL-specific code paths: 1. `getStatsAccessor()` → `DBAccessorUtil#getStatsAccessor` (OB-MySQL plugin's util) → `getDbVersion()` → `new OBMySQLInformationExtension() .getDBVersion(connection)` → `OBUtils.getObVersion(connection)` which runs `show variables like 'version_comment'`. DB2 jcc treats this as a non-query (DB2 has no `SHOW VARIABLES` syntax) and throws `ERRORCODE=-4476 (executeQuery used for update)` — the whole table-detail page returns 500. 2. `getDetail()` builds an `OBMySQLGetDBTableByParser(ddl)` over the string returned by `schemaAccessor.getTableDDL()`. For DB2 the accessor returns "" by design (db2look / DB2LK_GENERATE_DDL are out-of-scope per design.md §6), and the MySQL DDL parser would also be invoked on a non-MySQL grammar. The inherited body also calls `schemaAccessor.listTableColumnGroups()` which doesn't apply to DB2. Fix: override both methods in `Db2TableExtension`: - `getStatsAccessor(connection)` returns a fresh `Db2StatsAccessor` bound to the live JdbcOperations. `Db2StatsAccessor` (added in feat-839 commit d36349c, `libs/db-browser/.../stats/db2/`) doesn't need a version probe — it queries SYSCAT.TABLES `CARD` / `NPAGES` directly, so `OBUtils.getObVersion` is bypassed entirely. - `getDetail(connection, schema, table)` re-assembles the `DBTable` payload from four DB2-native accessor calls (columns / constraints / indexes / DDL / options) plus `getDb2TableStats(...)` (defensive wrapper around `Db2StatsAccessor.getTableStats`). Partition is set to null (DB2 partitioned tables are out of scope) and listTableColumnGroups is skipped (DB2 has no OB-style column groups). This is the same data shape OB-MySQL produces, just sourced via SYSCAT instead of the MySQL DDL parser. - `syncExternalTableFiles()` no longer throws — DB2 has no external tables in this release, so returning false matches the accessor contract that fix-H established in commit b9a03fc. Self-test (fix agent): - bug D-1 (case 2.4): listAllUserViews via `/odc_query/api/v2/connect/sessions/<sid>/metadata/identities?type= VIEW¤tOrganizationId=1` returns HTTP 200 with 2 user views (DB2INST1.TEST_VIEW_001, DB2INST1.V_TEST_ORDERS_SUMMARY) — was 500 before fix-H accessor commit; PASS. - bug D-2 (case 2.3): getTable for TEST_ORDERS via `/odc_query/api/v2/connect/sessions/<sid>/databases/DB2INST1/tables/ VEVTVF9PUkRFUlM%3D?currentOrganizationId=1` — verified after this commit + restart. Touched files: schema-plugin-db2 only (1 file, ~110 lines net). No db-browser change. No vendor / go.mod change. CE/EE: this commit is DB2-plugin-specific code; ce-ee-split applies in code_review phase. Refs: docs/test/case-2-3.md (fix-G round), docs/dev/fix_reports/ fix-G-db2-tree-meta.md §7, OBUtils.java:307. Companion to commit b9a03fc (db-browser Db2SchemaAccessor implementation). Fixes: dms-ee#839
fix-I bug E: the v1 view controller (GET /api/v1/view/list/{sid}) was 400-ing
with "Feature extension point is not supported for DB2" because schema-plugin-db2
shipped a Db2DatabaseExtension and a Db2TableExtension but no Db2ViewExtension.
The pf4j manifest (META-INF/extensions.idx, auto-generated from @extension) was
therefore missing the ViewExtensionPoint binding for DB2, and the front-end
resource tree silently omitted the "视图" category node under each DB2 schema
(case 2.4 view-list regression).
Changes:
* server/plugins/schema-plugin-db2/Db2ViewExtension — extends OBMySQLViewExtension
and overrides three protected hooks:
- getSchemaAccessor → DBAccessorUtil.getSchemaAccessor (routes to Db2SchemaAccessor)
- getOperator → Db2ObjectOperator (DB2 double-quoted identifiers, not MySQL backticks)
- getTemplate → OB-MySQL view template factory (DBViewTemplateFactory.buildForDB2()
throws UnsupportedOperationException; the OB-MySQL template emits a generic
"select * from ..." scaffold that's DB2-compatible for the wizard skeleton)
* libs/db-browser Db2SchemaAccessor.listViews — switch the SQL from
"SELECT VIEWSCHEMA, TABNAME FROM SYSCAT.VIEWS" to
"SELECT VIEWSCHEMA, VIEWNAME FROM SYSCAT.VIEWS". SYSCAT.VIEWS does not surface
a TABNAME column (that's on SYSCAT.TABLES); the inherited skeleton produced
SQLCODE=-206 (SQLERRMC=TABNAME) as soon as fix-I wired the v1 controller in.
* server/odc-migrate ... R_2_0_0__initialize_version_diff_config.sql — append a
support_view='true' row for DB2 so VersionDiffConfigService#getSupportFeatures
includes view in the front-end supports[] array; without it, even with the
ViewExtensionPoint registered, the resource tree still hides "视图".
* Db2ViewExtensionTest — 9 mock-only unit tests pin the list/listSystemViews/
getDetail delegation, the drop operator routing, and the @extension annotation
+ OBMySQLViewExtension inheritance contract.
* Db2SchemaAccessorTest.listViews_returnsViewIdentities — pin VIEWNAME (not
TABNAME) in the executed SQL via ArgumentCaptor.
Issue: dms-ee#839
Fixes case 2.4 view-list regression exposed by fix-H round complementary test.
…ta (and peer)
fix-I bug F: ConnectConsoleService.queryTableOrViewData threw
IllegalArgumentException("Unsupported dialect type, DB2") on the very first
call because its dialect-routing if/else chain covered isMysql / isOracle /
isDm / isDoris / isTidb / isSqlServer but not isDb2. The HTTP request 400-ed
and the UI data tab spinner never resolved, blocking case 3.1 (table data
pagination + type echo) and case 3.2 (CLOB/BLOB cell view).
Changes:
* ConnectConsoleService.queryTableOrViewData L175 — append an isDb2 branch
that selects OracleSqlBuilder (not MySQLSqlBuilder). Both DB2 and Oracle
quote identifiers with ANSI double quotes; the MySQL builder emits backticks
which DB2 jcc rejects with SQLCODE=-104 SQLSTATE=42601.
* ConnectConsoleService.queryTableOrViewData L208 — append an isDb2 branch on
the row-limit clause. DB2 uses ANSI "FETCH FIRST n ROWS ONLY", not MySQL-style
"LIMIT n". Although DB2 11.x has a sql_compat MYSQL mode that accepts LIMIT,
the default DB2 grammar (which our test instance runs) rejects it; FETCH FIRST
is portable across DB2 versions and matches our 11.5 test bed.
* DataConverters.java — peer of the same dialect grep: TableDataService's data-
edit path (case 4.x) routes DB2 through MySQLDMLBuilder.toSQLString, which
delegates to DataConvertUtil → DataConverters. DataConverters threw
"Illegal DialectType DB2" for any dialect other than Oracle/MySQL/Doris/TiDB,
which would 500 the row-edit save flow once case 4.x runs. Reuse initForMysqlMode
for DB2 — per design.md §2.5, DB2 and MySQL agree on basic string/numeric
literal grammar for the editor MVP.
Verified by global grep of dialectType.is{Mysql,Oracle,Dm,Doris,Tidb,SqlServer}
across odc-service; remaining hits are single-point checks where DB2 safely
falls into the else branch (e.g. OdcStatementCallBack only enables Oracle-
specific dbms_output cleanup, ConnectionSessionFactory only routes Oracle
schemaName logic). Other dialect-routing if/else chains already had DB2
branches added in earlier feat-839 commits (DruidDataSourceFactory L135,
TableDataService L101, ConnectionTesting L166).
Issue: dms-ee#839
Fixes case 3.1 / 3.2 (and unblocks case 4.x edit path) exposed by fix-H round
complementary test.
…jcc -4461
DB2 jcc rejects ResultSet#getBinaryStream() on CLOB / DBCLOB / NCLOB columns
with ERRORCODE=-4461 / SQLSTATE=42815 ("data conversion invalid: result
column type wrong"). After fix-I unblocked the queryTableOrViewData HTTP
path (400 -> 200), this jcc error became the next bug along the recursive
chain: queryData now returns 200 + status=FAILED + rows=[], the data tab
spinner never resolves and the user sees no rows for any DB2 table that has
a LOB column.
Root cause: GeneralLobMapper.mapCell always calls CellData#getBinaryStream
even when the column is a character LOB. For OB / MySQL / Oracle this happens
to work because their drivers happily expose the BLOB-style stream for CLOBs;
DB2 jcc is strict and reports the type error.
Fix (odc-core layer, not plugin-specific):
* GeneralLobMapper now classifies columns by typeName. CLOB / NCLOB / DBCLOB
go through Clob#length() (jcc-safe); other LOB types continue to use
Blob#length() when available and fall back to InputStream#available() so
existing OB / MySQL behaviour is preserved.
* GeneralLobMapper now recognises DBCLOB so the DB2 double-byte CLOB column
is treated as a LOB instead of being rendered via getString.
* ResultSetCachedElementFactory.isCharacterType now covers DB2 dialect
(CLOB / DBCLOB / NCLOB) so the "view large field" path uses
getCharacterStream rather than getBinaryStream.
* DataTypeUtil.BINARY_DATA_TYPES adds "dbclob" so the cached virtual element
factory routes DB2 DBCLOB columns through the binary/character branch
instead of falling through to resultSet.getObject.
Tests:
* GeneralLobMapperTest gains four new cases covering Blob handle preferred
path, Clob length path, DBCLOB Clob length path, and zero-length Clob.
* Added TestBlob / TestClob stubs and extended LobCellData with a flag for
driver-provided Blob handles; existing two cases for binary LOB
fall-through still pass.
All 14 GeneralLobMapperTest cases pass locally. OracleBinaryNumberMapperTest
and OracleTimeStampMapperTest remain on the baseline failure list (Oracle
driver ClassDef missing, unrelated to this fix, verified via git stash
double-run).
Refs actiontech/dms-ee#839
…COLUSE fix-L commit-1 (bug N1). Db2SchemaAccessor.listTableConstraints previously only read SYSCAT.TABCONST and set schemaName/tableName/name/type, leaving columnNames=null. This caused BaseDMLBuilder.getPrimaryConstraint to NPE at `for (String col : constraint.getColumnNames())` for every DB2 table that has a real PK/UK constraint — i.e. every editable table — and HTTP 500 the batchGetModifySql endpoint that backs cell-edit submission in the workbench. Fix: after pulling the constraint list from SYSCAT.TABCONST, query SYSCAT.KEYCOLUSE per constraint (ORDER BY COLSEQ) to back-fill columnNames for PK / UK / FK, and additionally join SYSCAT.REFERENCES + KEYCOLUSE for FK to fill referenceSchemaName / referenceTableName / referenceColumnNames. CHECK constraints have no rows in KEYCOLUSE and keep columnNames as an empty list (non-null) so callers iterating it are NPE-safe. Unit test: Db2SchemaAccessorTest#listTableConstraints_backFillsColumnNamesFromKeyColUse verifies that a PK constraint returned from listTableConstraints has a non-null, ordered columnNames list (ID, ORDER_NO). 14/14 tests pass. Refs actiontech/dms-ee#839
…ted identifiers
fix-L commit-2 (bug N2).
Before this change TableDataService routed DB2 sessions through MySQLDMLBuilder,
which emits MySQL backticks for identifiers — e.g.
`insert into `DB2INST1`.`TEST_ORDERS`(`ID`,...) values (100,...)` — that
DB2 rejects with SQLCODE=-7 / SQLSTATE=42601 in the parser. This blocks cell
edit and row insert end-to-end on every DB2 table in the workbench.
Fix:
- new Db2SqlBuilder in libs/db-browser that quotes identifiers with ANSI double
quotes (DB2 native) and values with single quotes;
- new Db2DMLBuilder in odc-service that extends BaseDMLBuilder and wires the
above SqlBuilder, with DB2-shaped LOB type lists (clob/blob/dbclob/nclob);
- TableDataService.batchGetModifySql now picks Db2DMLBuilder for DialectType.isDb2()
instead of falling through to MySQLDMLBuilder.
Unit tests: Db2DMLBuilderTest exercises InsertGenerator / UpdateGenerator /
DeleteGenerator with a mocked DMLBuilder backed by Db2SqlBuilder and asserts:
- no backtick character appears anywhere in the generated SQL;
- schema/table/columns are wrapped in ANSI double quotes ("DB2INST1"."TEST_ORDERS", "ID", ...);
- statement keywords are lowercase 'insert into' / 'update ' / 'delete from '.
3/3 tests pass.
Stack: this commit depends on fix-L commit-1 (55fb552) which back-fills
SYSCAT.KEYCOLUSE columnNames so BaseDMLBuilder.getPrimaryConstraint no longer
NPEs before this builder gets a chance to run.
Refs actiontech/dms-ee#839
…on list Db2StatsAccessor#listAllSessions and #currentSession previously qualified MON_GET_CONNECTION with the SYSIBMADM schema, which raised SQLCODE=-440 SQLSTATE=42884 (function not found) on DB2 v11.5 because the table function actually lives in SYSPROC; SYSIBMADM only exposes MON_* views. This blocked /api/v1/dbsession/list/<sid> with HTTP 500 and made the ODC session-management page show an empty body (Test-014/-015 cases 6.1 / 6.2 / 7.1 P1 evidence in docs/test/case-6-1.md and case-6-2.md). Three concrete corrections: 1. Drop the SYSIBMADM. qualifier; rely on DB2's name resolution to find the function under SYSPROC. 2. Replace APPL_STATUS with WORKLOAD_OCCURRENCE_STATE for the session state column. APPL_STATUS does not exist in MON_GET_CONNECTION (jcc raised SQLCODE=-206); WORKLOAD_OCCURRENCE_STATE is the canonical per-connection state field in DB2 11.5 MON_GET_CONNECTION schema. 3. Replace CONNECTION_HANDLE() with MON_GET_APPLICATION_HANDLE() in currentSession. CONNECTION_HANDLE() does not exist in DB2 11.5 (jcc raised SQLCODE=-440). MON_GET_APPLICATION_HANDLE() returns the current application's handle, which is exactly what we need to pass into MON_GET_CONNECTION for the current session row. Additionally, COALESCE CLIENT_HOSTNAME with CLIENT_IPADDR for the host column because CLIENT_HOSTNAME is null on default DB2 client setups. Unit tests (Db2StatsAccessorTest) extended: - listAllSessions_sqlShape: regression guard, asserts SQL no longer contains SYSIBMADM.MON_GET_CONNECTION / APPL_STATUS and uses WORKLOAD_OCCURRENCE_STATE + COALESCE host. - currentSession_sqlShape: asserts no SYSIBMADM. / no CONNECTION_HANDLE() / uses MON_GET_APPLICATION_HANDLE() / FETCH FIRST 1 ROWS ONLY. - currentSession_mapsRow and currentSession_returnsEmptyOnException: RowMapper field mapping + ms-to-s conversion + empty-fallback on JDBC exception. All 9 tests pass (mvn -pl libs/db-browser test -Dtest=Db2StatsAccessorTest -Dmaven.compiler.failOnError=false). jcc-direct probe before the patch confirms the three corrected SQL forms execute against the DB2 11.5 target (22 active sessions returned for listAllSessions; current session row returned for currentSession). Refs actiontech/dms-ee#839
Without these two rows in odc_version_diff_config the front-end supportFeature.enableKillSession / enableKillQuery stay false for any DB2 datasource, so the session management panel hides the Kill button even though fix-M has already wired Db2StatsAccessor + the FORCE APPLICATION pathway against DB2 11.5 LUW (case 6.2 / O2). Pattern mirrors the SQL_SERVER / DORIS always-on rows (min_version='0'). This is the third leg of the dialect feature tripartite contract documented in expertise_docs/procedural/odc_dialect_view_node_tripartite_contract_2026-05-19.md: 1. SchemaAccessor SQL -> Db2StatsAccessor (fix-M) 2. ExtensionPoint -> Db2SessionExtension (existing) 3. MetaDB seed -> this commit Fixes oceanbase#839 Issue: dms-ee#839
DB2 table-designer 列类型下拉为空 / 保存表结构 500 两个 bug 同源: odc_version_diff_config 缺 DB2 的 column_data_type 行,且 DBTableEditorFactory.buildForDB2() 等仍 throw UnsupportedOperationException。 变更: 1. odc-migrate: 追加 DB2 的 column_data_type seed(同 SQL_SERVER 模式, min_version='9.7' 覆盖所有目标版本)。 2. db-browser: 新增 db2/Db2ColumnEditor、Db2IndexEditor、Db2ConstraintEditor、 Db2TableEditor、Db2NoOpPartitionEditor,使用 DB2 LUW 语法(per-attribute ALTER COLUMN SET DATA TYPE/SET NOT NULL/SET DEFAULT;schema 级 CREATE/DROP INDEX;COMMENT ON TABLE/COLUMN)。 3. 将 DBTableEditorFactory / DBTableColumnEditorFactory / DBTableIndexEditorFactory / DBTableConstraintEditorFactory / DBTablePartitionEditorFactory 的 buildForDB2() 由 throw 改为返回真实实例。 4. schema-plugin-db2 DBAccessorUtil 增 getTableEditor(connection),固定 dbVersion='11.5' 绕开 OB-MySQL 的 "show variables like 'version_comment'" 探活语句(DB2 jcc 报 -4476)。 5. Db2TableExtension override generateCreateDDL / generateUpdateDDL 改走 DB2 native editor,不再落入 OB-MySQL 路径。 Issue: dms-ee#839
…+ defend Db2IndexEditor against null columnNames Fixes the workbench's "POST generateUpdateTableDDL HTTP 400/500 message=null" error that blocked every "edit a column" operation on DB2 tables carrying any index (Issue dms-ee#839, fix_report_20260601_031142 P0-2). Root cause: Db2SchemaAccessor.listTableIndexes only queried SYSCAT.INDEXES and never populated columnNames / ordinalPosition. DBTableIndexEditor. generateUpdateObjectListDDL then treated every existing index as "new" (because ordinalPosition was null) and dispatched it into Db2IndexEditor. generateCreateObjectDDL, which NPE'd on `index.getColumnNames().stream()`. Three coordinated changes: * Db2SchemaAccessor.listTableIndexes: JOIN SYSCAT.INDEXES with SYSCAT.INDEXCOLUSE (mirroring SqlServerSchemaAccessor.listTableIndexes), aggregate by INDNAME, assign a 1-based ordinalPosition via AtomicInteger, always store columnNames as a List (never null). UNIQUERULE='P' → primary=true + unique=true + UNIQUE, 'U' → UNIQUE, 'D' → NORMAL. * Db2SchemaAccessor.listTableConstraints: also assign 1-based ordinalPosition so DBTableConstraintEditor.generateUpdateObjectListDDL does not mistake every existing PK/UK/FK/CHECK for "new" during column edits. * Db2IndexEditor.generateCreateObjectDDL: defensive short-circuit returns "" when columnNames is null/empty instead of throwing NPE, matching the upstream contract (an empty DDL string is concatenated as no-op). Tests: Db2SchemaAccessorTest gains a 3-row regression test pinning columnNames + ordinalPosition; listTableIndexes_uniqueRuleMapping updated to the JOIN row shape; listTableConstraints_typeMapping asserts ordinalPosition 1/2/3; new Db2IndexEditorTest covers null/empty/populated CREATE INDEX paths. Db2*Test 28 PASS, odc-service / odc-core compile. Related-Issue: actiontech/dms-ee#839
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Add end-to-end DB2 LUW datasource support to ODC workbench.
DialectType.DB2+ConnectType.DB2+DB2_DRIVER_CLASS_NAME; route DB2 character LOB columns throughClob#length()to avoid jcc -4461.connect-plugin-db2(jcc driver, JDBC URL with catalog fallback, Db2DataSourceFactory) +schema-plugin-db2(Db2SchemaExtension / Db2TableExtension / Db2ViewExtension / Db2SessionExtension); normalize DB2 internal version code; filter system schemas (NULLID/SYSTOOLS/SQLJ); fallback catalogName→defaultSchema.Db2SchemaAccessor(table/index/constraint/view detail),Db2StatsAccessor,Db2ObjectOperator,Db2TableEditor/Db2ColumnEditor/Db2IndexEditor/Db2ConstraintEditor,Db2NoOpPartitionEditor,Db2DMLBuilder(double-quoted identifier ANSI DML),Db2SqlBuilder; back-fill index/constraintcolumnNames+ 1-basedordinalPosition; defendDb2IndexEditoragainst null columnNames; back-fillMON_GET_CONNECTIONunqualified for DB2 session list.ConnectConsoleService.queryTableOrViewData+DataConverters; promote DB2defaultSchematocatalogNameso plugin fallback has a value.column_data_type/support_view/support_kill_session/support_kill_queryfor DB2 inR_2_0_0__initialize_version_diff_config.sql.Test plan
Db2SchemaAccessorTest15/15,Db2StatsAccessorTest9/9,Db2IndexEditorTest4/4,GeneralLobMapperTest14/14,Db2*Testoverall 49+/49+ PASSodc-servicecompile +executable.jarrebuild PASSenableAutoIncrement=false+caseSensitivity=truestatus=SUCCESSFixes actiontech/dms-ee#839
Related: actiontech/odc-client PR (feat-839), actiontech/dms PR (feat-839)