Skip to content

feat: push down nested struct field projections#706

Open
yyzhao2025 wants to merge 1 commit into
lance-format:mainfrom
yyzhao2025:struct-subfiled-pushdown
Open

feat: push down nested struct field projections#706
yyzhao2025 wants to merge 1 commit into
lance-format:mainfrom
yyzhao2025:struct-subfiled-pushdown

Conversation

@yyzhao2025

Copy link
Copy Markdown

Summary

This PR implements nested struct subfield projection pushdown for Lance Spark.

Spark's nestedSchemaPruning can pass a pruned nested schema to data sources, but Lance Spark previously still projected the top-level struct column into the Lance scanner. While this remained correct through ReadSchemaNestedStructWidening, it meant Lance still had to read the full struct from disk, so the IO benefit of nested schema pruning was lost.

This change bridges Spark's required nested schema to Lance's leaf column projection support, allowing non-nullable nested struct subfields to be read as leaf projections during Lance scans.

Main Changes

  • Add ReadSchemaNestedColumnProjection to derive Lance projection columns from Spark's required read schema.
  • Carry projectedColumns through LanceScan and LanceInputPartition.
  • Include projectedColumns in LanceScan.equals() and hashCode() so Spark ReusedExchange does not incorrectly reuse scans with different nested projections.
  • Pass projected leaf paths to Lance ScanOptions.columns() in the fragment scanner.
  • Add ProjectedStructColumnVector to reconstruct Spark struct vectors from projected child vectors in the columnar read path.
  • Preserve existing top-level projection behavior for nullable structs, arrays, and maps.

Correctness Boundaries

This implementation is intentionally conservative.

Only non-nullable parent structs are expanded into leaf projections. Nullable parent structs continue to use top-level projection so that parent validity bitmap semantics are preserved. Arrays and maps also stop projection recursion for now.

This PR does not change existing _rowaddr, blob, zonemap, or SPJ-related read behavior.

Testing

Ran full module-level validation locally with an arm64 JDK sandbox:

JAVA_HOME=/tmp/lance-spark-jdk17-arm64 ./mvnw test \
  -pl lance-spark-3.5_2.12 \
  -DrecompileMode=all

Result:

Tests run: 1094, Failures: 0, Errors: 0, Skipped: 58
BUILD SUCCESS

Also validated the affected test matrix across Spark/Scala modules:

JAVA_HOME=/tmp/lance-spark-jdk17-arm64 ./mvnw test \
  -pl lance-spark-3.4_2.12,lance-spark-3.4_2.13,lance-spark-3.5_2.12,lance-spark-3.5_2.13,lance-spark-4.0_2.13,lance-spark-4.1_2.13 \
  -DrecompileMode=all \
  -Dtest=ReadSchemaNestedColumnProjectionTest,ProjectedStructColumnVectorTest,LanceFragmentScannerTest,LanceScanTest,LanceColumnarPartitionReaderTest,LanceCountStarPartitionReaderTest,LanceDatasetReadTest,SparkConnectorReadTest

Result:

Tests run: 396, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

Closes #704.

Bridge Spark nested schema pruning to Lance scanner column projection so non-nullable struct subfields can be read as leaf columns instead of forcing Lance to read the full top-level struct.

This adds a nested projection planning layer that derives Lance leaf column paths from Spark's required schema, carries those projected columns through LanceScan and LanceInputPartition, and uses them when constructing ScanOptions on executors.

It also reconstructs projected struct columns for Spark's columnar path by wrapping projected child vectors and filling unprojected siblings with null constant vectors. Nullable parent structs, arrays, and maps remain on the conservative top-level projection path to preserve existing correctness semantics.

Include projected columns in scan equality/hashCode so Spark ReusedExchange does not incorrectly reuse scans with different nested projections.

Closes lance-format#704.
@github-actions github-actions Bot added the enhancement New feature or request label Jul 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Push down nested struct subfield projections to Lance scanner for IO pruning

1 participant