Skip to content

feat(db): add IBM Db2 LUW provider (#786) - #787

Open
nycjay wants to merge 2 commits into
libredb:mainfrom
nycjay:feat/786-db2-luw-provider
Open

feat(db): add IBM Db2 LUW provider (#786)#787
nycjay wants to merge 2 commits into
libredb:mainfrom
nycjay:feat/786-db2-luw-provider

Conversation

@nycjay

@nycjay nycjay commented Sep 11, 2026

Copy link
Copy Markdown

Description

Adds IBM Db2 LUW as a database provider (type-id db2), over the DRDA protocol with the native ibm_db driver. It extends SQLBaseProvider and overrides only prepareQuery(), because Db2's SQL is Oracle-shaped (double-quoted identifiers, FETCH FIRST / OFFSET ... FETCH NEXT paging).

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Code refactoring
  • Performance improvement
  • Test addition or update

Related Issue

Closes #786

Changes Made

  • Provider (src/lib/db/providers/sql/db2.ts). Identifier escaping is inherited from SQLBaseProvider unchanged; prepareQuery() is the only dialect override. Schema reads the SYSCAT.* catalog views scoped to CURRENT SCHEMA.
  • Native driver, not REST. ibm_db (pinned 4.0.1, in trustedDependencies), added to serverExternalPackages in next.config.ts so Turbopack doesn't bundle the addon. Chosen over the Db2 REST /execsql path because REST is deployed separately from the engine and is commonly disabled, while ibm_db connects straight to the engine and works air-gapped. The tradeoff is a native addon with an install-time driver download.
  • Per-table maintenance. analyze runs RUNSTATS, optimize runs REORG, both through SYSPROC.ADMIN_CMD. Db2 has no whole-database form, so there is no global maintenance card.
  • Bound parameters. query(sql, params) forwards the values array to the driver so inline row edit (UPDATE ... WHERE pk) works. A no-params call uses the two-arg driver form, because ibm_db reads a function in the params slot as the callback and an empty array against a marker-less statement raises CLI0100E.
  • Migration generator. A Db2 branch emits SET DATA TYPE, plus a commented REORG advisory for the alters Db2 documents as leaving a table REORG-pending (drop column, retype, nullability change). It emits guidance rather than running a REORG, which can be slow and lock-heavy on a large table.
  • Registration surfaces. types, factory, ui-config, icon, connection-string parser (db2://), positional placeholder (?), export, showcase, query generators.
  • Docs, tests, fixtures. docs/providers/db2.md, tests/integration/db/db2-provider.test.ts (100% line coverage of db2.ts), a db2 service in database-compose.yml, and the external-engine count copy across READMEs, docs, deploy listings, charts and operator.

Deliberate follow-ups, not gaps: supportsExplain: false (Db2 EXPLAIN populates explain tables rather than returning a single-statement plan, same as MSSQL #126), supportsTransactions: false (no held-session transaction wired, so the toolbar and SANDBOX stay hidden), monitoring panels return neutral empties instead of fabricated zeros, and columnTypes is omitted because the high-level query() exposes no declared type.

Testing

  • I have tested this locally
  • I have added/updated tests
  • All existing tests pass

Verified end-to-end against a live Db2 v11.5.9.0 server, driving the app with Playwright and the API: connect and schema tree (columns, PK/nullable, FKs, indexes, untrimmed names); paging (FETCH FIRST, then OFFSET ... FETCH NEXT, an already-bounded query left alone); CRUD with read-your-writes; value fidelity (BIGINT → lossless string, BLOB → Buffer, CLOB/DECFLOAT/XML, CHAR(n) space-padded as expected); error paths (SQL0204N, SQL0104N throw rather than returning zero rows); per-table RUNSTATS/REORG plus rejection of a global or unsupported op; capability-gated UI (no Explain button or tab, no transaction toolbar/SANDBOX, Create Table present, inline-edit present); and an inline row edit persisting through UPDATE ... WHERE pk. ibm_db loads under both Bun and Node.

Two commands I could not run locally (per the "CI is the merge gate" note in CONTRIBUTING):

  • bun run test:coverage / bun run coverage:check — the test:coverage:core step runs bash tests/run-core.sh, whose line 41 uses mapfile (a bash 4+ builtin), and this machine has only macOS /bin/bash 3.2. The Db2 suites pass under bun run test and db2.ts is at 100% line coverage from the integration test, but CI needs to confirm the gate.
  • bun run chart:check — no Helm locally. The chart and operator count copy was edited by hand to match README.md.

Test Environment

  • LibreDB Studio Version: main
  • Browser: Chromium (Playwright)
  • OS: macOS
  • Node.js/Bun Version: Bun 1.4.0 / Node v26.8.2
  • Database Type: IBM Db2 LUW 11.5.9.0

Screenshots (if applicable)

Checklist

  • My code follows the project's code style guidelines
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have updated the documentation accordingly
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • The required CI test job passes the 100% line-coverage gate (bun run test:coverage and bun run coverage:check)
  • If I changed src/lib/db/providers/, I updated the matching docs/providers/ documentation and tests/integration/db/ tests in the same PR (provider triad)
  • Any dependent changes have been merged and published

Additional Notes

This PR adds a runtime dependency (ibm_db), unlike the recent HTTP-only providers. Rationale is under Changes Made.

@socket-security

socket-security Bot commented Sep 11, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​ibm_db@​4.0.18910010088100

View full report

@cevheri cevheri added the loop:needs-moderator-action Flagged by the maintainer loop: suspicious content or a decision only a human can make label Sep 11, 2026
Jason Knaster added 2 commits September 11, 2026 11:01
Connect to Db2 LUW over DRDA with the native ibm_db driver. The provider
extends SQLBaseProvider and overrides only prepareQuery(): Db2's SQL is
Oracle-shaped (double-quoted identifiers, FETCH FIRST / OFFSET ... FETCH
NEXT paging), so identifier escaping is inherited unchanged. Schema reads
the SYSCAT.* catalog views scoped to CURRENT SCHEMA. Per-table maintenance
maps analyze to RUNSTATS and optimize to REORG through SYSPROC.ADMIN_CMD;
Db2 has no whole-database form, so there is no global maintenance card.

Bound ? parameters are forwarded to the driver so inline row edit works
(UPDATE ... WHERE pk); verified end-to-end against Db2 v11.5.9.0.

Capabilities are conservative and honest: supportsExplain false (Db2
EXPLAIN populates explain tables rather than returning a single-statement
plan, same as MSSQL libredb#126), supportsTransactions false (no held-session
transaction wired, so the toolbar and SANDBOX stay hidden), and monitoring
panels return neutral empties instead of fabricated zeros. All documented
as intentional follow-ups.

Includes the provider triad (db2.ts, docs/providers/db2.md, and the
integration test at 100% line coverage), the registration surfaces, a db2
service in database-compose.yml, and the external-engine count copy.

Closes libredb#786
…#786)

Answers the review ask to check the admin/monitoring capabilities. Every
MON_GET_* / SYSIBMADM.* read is permission-gated and wrapped so a restricted
account degrades to an empty panel instead of an error; a monitoring-authorized
account (verified on Db2 v11.5.9.0) gets real figures.

- getTableStats: real per-table rows from SYSCAT.TABLES — row count (CARD) and
  the RUNSTATS timestamp (STATS_TIME -> lastAnalyze), so the age of the count is
  visible. CARD = -1 / STATS_TIME NULL maps to "no stats" (0 rows, no
  lastAnalyze), never a literal -1. Size deferred (per-object only).
- getActiveSessions: live connections from MON_GET_CONNECTION.
- getSlowQueries: costliest cached statements from MON_GET_PKG_CACHE_STMT,
  filtered to rows that actually carry timings (NUM_EXEC_WITH_METRICS > 0). When
  the database's mon_req_metrics is off there are none, so the panel shows a
  Db2-specific empty state naming the exact enablement command rather than the
  Postgres pg_stat_statements wording (slowQueriesEmptyState label).
- getStorageStats: per-tablespace sizing from MON_GET_TABLESPACE.
- getIndexStats: per-index rows from SYSCAT.INDEXES/INDEXCOLUSE with scan counts
  LEFT JOINed (RTRIM) from MON_GET_INDEX.
- getPerformanceMetrics/getHealth: cache hit ratio from SYSIBMADM.BP_HITRATIO
  (works regardless of mon_obj_metrics, unlike MON_GET_BUFFERPOOL) and deadlocks
  from MON_GET_DATABASE.
- getOverview: uptime (computed IN the database to avoid a timezone-driven
  negative), database size (+ databaseSizeBytes so fleet-health totals it),
  table/index counts, active connections, and maxConnections (maxappls).

Hardening:
- validate() rejects ';' in host/database/user/password: the DRDA attribute
  list has no escaping for its delimiter, so a value with ';' misparses (a
  password fails auth) or injects an attribute (PWD=x;SECURITY=NONE connected in
  testing). Pasted connection strings remain the user's own responsibility.
- prepareQuery: pin the trailing-semicolon and trailing-line-comment cases (the
  clause splices before the trivia, never inside a comment).

Tests updated to 100% line coverage of db2.ts; docs/providers/db2.md rewritten
to describe each surface, its source, and its honest limits (per-object size,
slow-query timings needing mon_req_metrics, the data/index Storage Breakdown
that ADMIN_GET_TAB_INFO makes too slow to fill).
@nycjay
nycjay force-pushed the feat/786-db2-luw-provider branch from af19bea to 173c834 Compare September 11, 2026 15:16
@cevheri cevheri added security Supply-chain, auth, or hardening work core-capabilities database-provider labels Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core-capabilities database-provider loop:needs-moderator-action Flagged by the maintainer loop: suspicious content or a decision only a human can make security Supply-chain, auth, or hardening work

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Add IBM Db2 LUW provider

2 participants