Skip to content

[SPARK-58609][SQL] Add archivePathFilter option to select inner archive entries - #57808

Open
akshatshenoi-db wants to merge 2 commits into
apache:masterfrom
akshatshenoi-db:archive-pathfilter
Open

[SPARK-58609][SQL] Add archivePathFilter option to select inner archive entries#57808
akshatshenoi-db wants to merge 2 commits into
apache:masterfrom
akshatshenoi-db:archive-pathfilter

Conversation

@akshatshenoi-db

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

This PR adds a new file-source option archivePathFilter: a glob that selects which inner entries of an archive are ingested, matched against each entry's full path within the archive, so patterns like subdir/* and */*.csv work.

It continues the archive-read series (SPARK-57135 / SPARK-57321 CSV, SPARK-57419 JSON, SPARK-57478 text, SPARK-57479 XML, SPARK-57481 Avro, SPARK-57590 Parquet, SPARK-57591 ORC, SPARK-58382 binaryFile, SPARK-58110 trait extraction).

Details:

  • FileSourceOptions gains the archivePathFilter option (the glob string) plus archivePathFilterPattern, a @transient lazy val holding the compiled Hadoop GlobPattern. Keeping the compiled matcher on the options object means the glob is compiled once per executor JVM rather than once per archive; it is transient because GlobPattern is not serializable, so the string travels and executors recompile on first use. The glob string is validated on the driver, so an invalid glob raises a clear IllegalArgumentException naming the option.
  • SupportsArchiveFormat.readArchiveEntries and the random-access readLocalizedEntries / localizeEntries take an optional filter (default None, so existing callers are unaffected), applied in shouldSkipEntry.
  • The option is threaded from the read paths of CSV, JSON, text, XML, Avro, binaryFile, Parquet and ORC.

The filter is applied in addition to ignoredPathSegmentRegex (both must pass), so hidden entries stay hidden even when they match the glob.

Why are the changes needed?

pathGlobFilter cannot express this. It is applied during file listing, so it must already match the archive file itself for that archive to be read at all; it cannot simultaneously select a subset of the entries inside the archive. Without archivePathFilter there is no way to read only part of an archive, so a user wanting a few entries has to read and discard the rest.

Does this PR introduce any user-facing change?

Yes. A new read option archivePathFilter (a glob string, no default) is available to the file sources that support archive reads. When set, only archive entries whose full inner path matches the glob are ingested. Reads that do not set it are unaffected, and the whole archive-read feature remains gated by spark.sql.files.archive.reader.enabled (default false).

How was this patch tested?

New shared cases in ArchiveReadSuiteBase, which run for every (format, container) pair -- CSV, JSON, XML, text, Avro, Parquet, ORC and binaryFile across tar, zip and 7z:

  • selection by inner path (sub/*),
  • an extension glob matching across subdirectories, including an entry that does not match the glob,
  • a glob matching no entry yields zero rows,
  • composition with ignoredPathSegmentRegex (a hidden entry matching the glob is still skipped).

New unit cases in SupportsArchiveFormatSuite cover the entry-skip logic directly (glob matching the full path, * crossing /, no-match, interaction with hidden-entry filtering) and invalid-glob rejection.

Was this patch authored or co-authored using generative AI tooling?

Generated-by: Claude Code

…ve entries

Adds a new file-source option `archivePathFilter`, a glob that selects which
inner entries of an archive are ingested, matched against each entry's full
path within the archive so patterns like `subdir/*` and `*/*.csv` work.

`pathGlobFilter` cannot express this: it must already match the archive file
for the file to be listed, so it cannot simultaneously select a subset of the
entries inside the archive.

The glob is applied in addition to `ignoredPathSegmentRegex` (both must pass),
and is inert when archive reading is disabled.

`FileSourceOptions` carries the glob string plus `archivePathFilterPattern`, a
transient lazy val holding the compiled Hadoop `GlobPattern`, so the glob is
compiled once per executor JVM rather than once per archive. The glob string is
validated on the driver so an invalid glob fails fast.

`SupportsArchiveFormat.readArchiveEntries` and the random-access
`readLocalizedEntries` / `localizeEntries` take an optional filter (default
`None`, so existing callers are unaffected) applied in `shouldSkipEntry`. It is
threaded from the read paths of CSV, JSON, text, XML, Avro, binaryFile, Parquet
and ORC.
…ply it to inference

- An empty `archivePathFilter` was retained and compiled to a `GlobPattern` that
  matches only the empty string, so every archive entry was skipped and the scan
  returned zero rows. Treat an empty value as absent, matching how an empty
  `ignoredPathSegmentRegex` disables that filter.
- Remove the `None` default from the `archivePathFilter` parameter of
  `readArchiveEntries`, `readLocalizedEntries` and `localizeEntries`, so no call
  site can silently skip it.
- Apply the filter to schema inference, which previously read every entry while
  the scan honored the filter. The inferred schema was therefore computed over a
  superset of the scanned entries. Threaded through CSV/JSON/XML (capturing the
  glob string, since the compiled `GlobPattern` is not serializable), Avro, ORC
  and Parquet.
- Add a shared `ArchiveReadSuiteBase` case that puts an extra column in a
  filtered-out entry and asserts the inferred schema excludes it, so it runs for
  every format and container.

@cloud-fan cloud-fan left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 blocking, 1 non-blocking, 1 nit.
The filtering behavior and cross-format propagation are coherent, with two non-blocking cleanup items around matcher cache scope and repeated inference-time compilation.

Nits: 1 minor item (see inline comments).

Suggestions (1)

  • sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/csv/CSVDataSource.scala:185: This compilation runs once per archive in the flatMap callback, so partitions containing multiple archives repeatedly compile the same glob; JSON and XML inference have the same shape. Please compile once in mapPartitions and reuse the matcher across that partition's archive streams. -- see inline

Verification

I traced the option from FileSourceOptions through the shared archive-entry predicate and through scan and schema-inference paths for the supported formats. The filter is applied before parsing and composes with the existing per-segment hidden-path check; Parquet and ORC transport the string through Hadoop configuration for parallel schema work, while CSV, JSON, and XML currently compile it inside each archive callback.

PR metadata suggestions

  • Correct the claim that archivePathFilterPattern is compiled once per executor JVM; it is cached per FileSourceOptions instance, and CSV/JSON/XML inference currently compiles once per archive.

}

/**
* The effective [[archivePathFilter]] compiled once per JVM, so archive reads reuse a single

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lazy val caches per FileSourceOptions instance, not per JVM. Please describe the actual scope; the CSV, JSON, and XML inference paths also construct matchers directly instead of sharing this value.

SupportsArchiveFormat.readArchiveEntries(
path, stream.getConfiguration,
archivePathFilter =
archivePathFilterGlob.map(FileSourceOptions.compileArchivePathFilter)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This compiles the same glob once per archive processed by the partition; JsonDataSource.scala:307 and XmlDataSource.scala:389 do likewise. Please use mapPartitions to compile once and reuse the matcher for every archive stream in that partition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants