fix: read local files without Hadoop in COPY INTO (JDK 23+) — R1FIX.4 / #183 - #188
Merged
Conversation
Every local file read in COPY INTO went through Hadoop, whose UserGroupInformation.getCurrentUser() calls Subject.getSubject. JDK 23 re-specified that method to throw by default, and JEP 486 (JDK 24) made it throw unconditionally with no escape hatch, so COPY INTO from a local file failed on any modern JVM (reported from DBeaver's bundled Temurin 25). Add LocalPath, a pure classifier with zero Hadoop imports, plus an openStream dispatcher and a validateFile split. Local paths (schemeless, or file: with an empty/localhost authority) are now opened through java.nio.file and never touch Hadoop; every other scheme is unchanged. Seven Hadoop entry points were rewired, not the five the issue listed - the two missing ones were FileSource.validateFile (which runs before every read, so a five-site fix would still have thrown) and FileFormatDetector.isDeltaTable. Parquet, Delta Lake and all remote schemes stay Hadoop-bound on purpose and remain unsupported on JDK 23+; that boundary is now documented per source with a three-column JDK matrix rather than the misleading "JDK 24+". Also unifies scheme detection: HadoopConfigurationFactory.forPath now uses LocalPath.scheme instead of its own Try(new URI(path).getScheme), fixing two silent credential losses - S3A://bucket/x (uppercase matched no lowercase literal) and s3a://bucket/my file.jsonl (new URI throws on the space) both fell through to localConf() with no S3 credentials. Regression guards are JDK-independent and armed: a Configuration whose fs.file.impl is unresolvable (with fs.file.impl.disable.cache, without which Hadoop's static cache makes the guard vacuous) proves Hadoop was not consulted, and each suite carries a self-check asserting the poisoning really throws. Verified: 67/67 on zulu-11/17/21; on zulu-25 LocalPathSpec 41/41 and FileSourceSpec 19 pass with only the 7 documented Parquet/Delta tests failing with "getSubject is not supported" - which doubles as an in-suite control. Probe183 on JDK 25 confirms the same JVM reproduces the bug before the fixed path reads the file. ES8 integration 56/56 against live ES 8.18.3. Closed Issue #183
fupelaqu
marked this pull request as ready for review
August 3, 2026 04:59
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.
Story R1FIX.4 (epic
R1FIX, R1 defect closure). Closes #183.Docs mirror PR: SOFTNETWORK-APP/softclient4es-web#30 — must merge together (dual-docs rule).
Problem
Every local file read in
COPY INTOwent through Hadoop.FileSystem.get→UserGroupInformation.getCurrentUser()→Subject.getSubject(AccessControlContext).The break starts at JDK 23, not 24 — the issue title under-states it:
getSubject is supported only if a security manager is allowed(default)-Djava.security.manager=allowgetSubject is not supportedunconditionally (JEP 486)Upgrading Hadoop does not help: 3.4.2 is already the pin and still takes that path.
Fix
New
LocalPath— a pure classifier with zero Hadoop imports, so it is unit-testable on a JVMwhere Hadoop cannot initialise — plus an
openStreamdispatcher and avalidateFilesplit.Local paths (schemeless, or
file:with an empty/localhostauthority) go throughjava.nio.file; every other scheme is byte-for-byte unchanged.🔴 Seven call sites, not the five the issue listed
The two missing ones matter:
FileSource.validateFileruns before every read, so a fix thatpatched only the five listed sites would still have thrown on JDK 23+.
FileSource.validateFileJsonFileSource.fromFile,JsonArrayFileSource.fromFile/fromFileInMemory/getMetadataFileFormatDetector.detectJsonTypeFileFormatDetector.isDeltaTableParquet, Delta Lake and all remote schemes stay Hadoop-bound on purpose and remain unsupported
on JDK 23+ (AC 3).
Also: unified scheme detection (AD-10, lead-approved)
HadoopConfigurationFactory.forPathhad its own classifier(
Try(new URI(path).getScheme)) that disagreed withLocalPath's. Two silent credential losses:S3A://bucket/x.jsonl— uppercase matched no lowercase literal ⇒localConf(), no S3 credentialss3a://bucket/my file.jsonl—new URIthrows on the space ⇒ schemenull⇒ sameBoth now reach
s3aConf().file:, schemeless andC:/data/x.jsonlstill resolve tolocalConf().Evidence
AC 1b — JDK 25 (Zulu 25.0.4, arm64) — with its mandatory CONTROL
The CONTROL fails with
getSubjecton the same JVM where the fixed path reads the file — agreen run on a JVM that never had the bug would not be evidence.
core/testOnly *LocalPathSpecon 25 → 41/41core/testOnly *FileSourceSpecon 25 → 19 pass, 7 fail — the 7 are exactly the Parquet +Delta tests, each
UnsupportedOperationException: getSubject is not supported. That is thedocumented limitation, not a regression, and it makes the run self-controlling.
OQ-4 — Akka 2.6.20 does start on JDK 25
Only
WARNING: sun.misc.Unsafe::objectFieldOffset has been called by akka.util.Unsafe— not afailure. All 16 JSON/detection tests run inside
Source.unfoldResourcein anActorSystemand pass.⇒ the docs ✔ for JDK 24/25 is earned, not deferred, and no product-wide Akka blocker exists.
AC 1a — the regression guard is armed (JDK-independent)
Poisoned
fs.file.impl+fs.file.impl.disable.cache(without the latter Hadoop's staticFileSystemcache — keyed on scheme/authority/ugi, not theConfiguration— makes the guardvacuous). Each suite carries a self-check asserting the poisoning really throws.
Sanity-checked: reverting one call site to
HadoopInputFile…turns the suite RED withClassNotFoundException: Class does.not.Exist not found. A guard that cannot fail is worse than none.AC 2 / AC 3 / integration
+ core/compileand+ core/Test/compile(2.12.20 + 2.13.16) — both greenes8java/testOnly *JavaClient8ReplGateway*→ 56/56 against live ES 8.18.3, including the newfile://+ spaced-path COPY INTO caseheaderCheck,scalafmtCheck,test:scalafmtCheck,scalafmtSbtCheck— all greenAC 4 — fat-jar size: measured, no reduction, and here is why
Both jars assembled at the same
0.20.3-SNAPSHOTcoordinate:softclient4es8-cli-…-assembly.jarThe +7 KB is just the two new
LocalPathclass files. Δ = 0 by construction — no dependency isadded, removed or re-scoped;
hadoop-clientandparquet-avrostay compile deps because Parquet,Delta and remote schemes still need them. Follow-up: #187 (make Hadoop optional for JSON-only
deployments) — that is what would move the ~314 MB driver figure. The JDBC-driver number is
R1FIX.7's, since it needs artefacts only the project lead can release.
Notable deviations from the spec (all verified)
LocalFileSystemrejects a foreign authority incheckPathbefore stat'ing →Wrong FS: …, expected: file:///, notdoes not exist; and on JDK ≥ 23 the same call throwsgetSubject. Since AC 1b forks this suite onto 25, the test now accepts either — both provevalidateHadoopPathran, which is the real invariant.deleteOnExitordering.DeleteOnExitHookdeletes in reverse registration order, so thechild must be registered last. The spec's comment stated it inverted; both snippets leaked a
temp dir every run. Fixed in two tests.
io.file.buffer.size = 0deliberately left unguarded — Hadoop throws identically, so amax(1, …)would create a divergence. Parity beats defensiveness.Not done, deliberately
~stays literal, pinned bya test.
isDeltaTable's remote branch callsFileSystem.get(conf)with no URI, so it resolves the default filesystem, not the one impliedby the path — remote Delta detection has always been wrong. Recorded so it is not lost.
Release protocol
build.sbt:23already read0.20.3-SNAPSHOT(set by R1FIX.3 / #186) — verified and leftuntouched; it is absent from this diff. 🔴 Nothing was published: no
publish, nopublishLocal, norelease.ymldispatch, no tag. The project lead merges and releases.