Skip to content

fix(oracle): Thick mode never loaded in a build, plus the Linux client recipe (#538) - #575

Merged
cevheri merged 4 commits into
mainfrom
fix/oracle-thick-mode-538
Sep 5, 2026
Merged

fix(oracle): Thick mode never loaded in a build, plus the Linux client recipe (#538)#575
cevheri merged 4 commits into
mainfrom
fix/oracle-thick-mode-538

Conversation

@cevheri

@cevheri cevheri commented Sep 5, 2026

Copy link
Copy Markdown
Member

Thick mode has never worked in a build, for four independent reasons, all measured on the published 0.13.4 and 0.13.7 images. Turbopack bundled oracledb into the server chunk and rewrote its __dirname to the literal /ROOT/node_modules/oracledb/lib, so initOracleClient() threw NJS-045 before the Instant Client was consulted and every recipe in the docs was unreachable. Even externalized, Next's file tracing copies the driver's JavaScript but not build/Release/*.node, since the addon is reached through a computed require(path). On Linux initOracleClient({ libDir }) cannot load Instant Client on its own either: libclntsh.so.19.1 has no RUNPATH, so it fails with DPI-1047 unless the directory is also on the system library search path. And on Debian 13 the client asks for libaio.so.1 while the distribution ships only libaio.so.1t64. Separately, mapDatabaseError recognised only NJS-138, so the other Thin-mode refusals a DBA turns into "you must use Thick mode" arrived as a retryable ConnectionError, which is the defect #228 fixed for NJS-138.

Changed: next.config.ts adds oracledb to serverExternalPackages; the Dockerfile runner stage and scripts/build-standalone-payload.sh copy the whole package with a target-addon probe, arch-agnostically, so the ARM64 leg keeps working; src/lib/db/errors.ts gains describeOracleThinModeRefusal (NJS-116, NJS-533, NJS-089 and the generic Thin-mode substring join NJS-138 as non-retryable DatabaseConfigError, checked before the generic password branch because NJS-116's own text contains "password") and describeOracleClientLoadFailure (NJS-045 reads as a packaging defect naming platform and arch, DPI-1047 points at the loader path, libaio.so.1 and section 4.4); src/lib/db/providers/sql/oracle.ts uses it; docs/providers/oracle.md rewrites section 4.4 with a recipe that works, adds the 12.2 case and its driver codes, and extends sections 11 and 14; .env.example and CLAUDE.md are corrected. New guard tests/unit/packaging-oracledb-thick.test.ts, plus tests in tests/unit/db/errors.test.ts and the Thick-mode block of tests/integration/db/oracle-provider.test.ts. The published image stays Thin only, no Instant Client and no libaio.

Verified locally: format, lint, typecheck, knip, chart:check, channels:showcase:check, readme:check, security:check, test:ci (all 34 groups), test:coverage plus coverage:check at 45569/45569 lines (100.00%), build, build:lib and attw all pass. After DOCKER_BUILD=true bun run build, grep -l "/ROOT/node_modules/oracledb" .next/server/chunks/*.js | wc -l is 0 (was 1), and .next/standalone/node_modules/oracledb/package.json exists while build/Release is absent from the traced tree, which is what the explicit COPY is for. The live Thick-mode run against a real Instant Client is not part of this PR.

Fixes #538

…538)

Thick mode has never worked in a real build. Two packaging defects, both
measured on the published 0.13.4 and 0.13.7 images.

Turbopack bundled oracledb into the server chunk and rewrote its __dirname
to the literal string /ROOT/node_modules/oracledb/lib. initOracleClient()
then looked for its native addon under /ROOT and threw NJS-045 before the
Instant Client was ever consulted, so every recipe in the docs was
unreachable. oracledb now joins serverExternalPackages.

Externalizing is not enough on its own. Next's output file tracing copies
index.js, lib/ and package.json but not build/Release/*.node, because the
addon is reached through a computed require(path). The Dockerfile runner
stage and the standalone payload script copy the whole package, the way
better-sqlite3, @libredb/libredb and the @duckdb scope already are. The
whole package because build/ carries one binary per platform and arch, so
naming one would break the ARM64 leg.

The published image stays Thin only. No Instant Client, no libaio.

Adds tests/unit/packaging-oracledb-thick.test.ts, modelled on the DuckDB
guard, and corrects the CLAUDE.md claim that every driver is external in
both build configs. It was not true: mssql and ioredis still are not.
…538)

mapDatabaseError() recognised only NJS-138. The other refusals a DBA turns
into "you must use Thick mode" fell through to the generic retryable
ConnectionError, which is the defect #228 fixed for NJS-138: a permanent
configuration problem reported as something to retry.

NJS-116 (the account has only a 10G password verifier), NJS-533 (the server
requires native network encryption or checksumming), NJS-089 and any
message saying "not supported by node-oracledb in Thin mode" now map to a
non-retryable DatabaseConfigError naming ORACLE_CLIENT_LIB_DIR. NJS-116
also names the fix that needs no Instant Client: a password reset writes a
12C verifier. The check runs before the generic authentication branch on
purpose, since NJS-116's own text contains the word password.

The constructor's Instant Client failure said one thing for every error,
"verify the path", and that advice is wrong for both failures that actually
happen. NJS-045 means this build has no Thick-mode addon, which is a
packaging defect and not a path; DPI-1047 means the libraries could not
load from a directory that is usually correct but not on the loader path.
describeOracleClientLoadFailure() says which one it is.
Section 4.4 told operators to set ORACLE_CLIENT_LIB_DIR and build a derived
image. Neither step was sufficient. On Linux initOracleClient({ libDir })
cannot load Instant Client at all: libclntsh.so.19.1 has no RUNPATH, so its
own siblings are not found and the driver fails with DPI-1047. node-oracledb
documents this and says never to rely on libDir there. The directory has to
be on the system library search path as well, through /etc/ld.so.conf.d plus
ldconfig or through LD_LIBRARY_PATH set before Node starts. Debian 13 adds a
second step: it ships libaio.so.1t64 only, and the client asks for
libaio.so.1.

The derived image recipe now does all of it. The mount alternative says
LD_LIBRARY_PATH has to be set on the container. Both were verified with
Instant Client 19.28 against Oracle Free 23ai: client_driver reports
node-oracledb 6.10.0 thk.

Also adds the 12.2 case the reporter hit. Thin mode covers 12.1 and later as
a version, but a server can still force Thick mode, and the doc now lists
those reasons with their driver codes. Section 11 gains rows for NJS-045,
DPI-1047, NJS-116, NJS-533 and NJS-089, and section 14 states plainly that
the published images could not enter Thick mode at all before this fix.
@codecov

codecov Bot commented Sep 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@cevheri cevheri added bug Something isn't working docker Container base image dependencies labels Sep 5, 2026
Review finding on #575. The "a 12.1+ server can still force Thick mode"
table attributed three cases to NJS-089 that node-oracledb 6.10.0 never
reports that way. Corrected against the driver's own source.

Kerberos, RADIUS and OS authentication have no driver code at all. Thin mode
implements externalAuth for token-based authentication, but nothing for
these, so the server refuses the logon and what arrives is an ORA logon
error. LDAP naming is not a Thin-mode refusal either: there is no LDAP code
under lib/thin, so an ldap:// identifier fails during connect-string
resolution, typically NJS-516. Both are Thick mode only, and neither can be
mapped, because nothing in their text identifies the cause.

An sso-only wallet is NJS-529, not NJS-089. Its text says nothing about Thin
mode, so the generic substring never caught it. It is now mapped by code,
with a message naming both ways out: convert the wallet to ewallet.pem,
which avoids Thick mode entirely, or use Thick mode and keep the sso wallet.

NJS-089 stays mapped but is described honestly. In Thin mode it covers
client-side features the driver has not implemented, heterogeneous pooling,
some database object types and Advanced Queuing among them, none of which a
server can demand at connect time.

Second finding, same section: the derived-image recipe did not build. The
base image has no ca-certificates and --no-install-recommends does not pull
it in with curl, so the Instant Client download died with curl (77). Added
to the apt-get line and pinned by the packaging guard.
@sonarqubecloud

sonarqubecloud Bot commented Sep 5, 2026

Copy link
Copy Markdown

@cevheri
cevheri merged commit a59fb8b into main Sep 5, 2026
31 checks passed
@cevheri
cevheri deleted the fix/oracle-thick-mode-538 branch September 5, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working docker Container base image dependencies

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEATURE] Oracle Thick Mode

1 participant