Skip to content

[SPARK-58347][SDP] Thread conf.resolver through ColumnSelection.applyToSchema - #57610

Open
anew wants to merge 5 commits into
apache:masterfrom
anew:spark-58347-resolver-columnselection
Open

[SPARK-58347][SDP] Thread conf.resolver through ColumnSelection.applyToSchema#57610
anew wants to merge 5 commits into
apache:masterfrom
anew:spark-58347-resolver-columnselection

Conversation

@anew

@anew anew commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

This is a follow-up refactor for SDP AutoCDC. It reworks ColumnSelection.applyToSchema (and its private helper lookupFieldIndices) to accept a Resolver instead of a caseSensitive: Boolean:

  • ColumnSelection.applyToSchema / lookupFieldIndices now take a Resolver. lookupFieldIndices resolves each requested column via schema.fieldNames.indexWhere(resolver(_, name)) rather than switching between StructType.getFieldIndex / getFieldIndexCaseInsensitive on a boolean.
  • Scd2BatchProcessor.computeTrackedHistoryColumns now takes a Resolver directly instead of deriving caseSensitiveResolution / caseInsensitiveResolution from a boolean.
  • All callers pass spark.sessionState.conf.resolver instead of conf.caseSensitiveAnalysis: Scd1BatchProcessor.projectTargetColumnsOntoMicrobatch (2 sites), Scd2BatchProcessor.projectTargetColumnsOntoMicrobatch (2 sites) and its computeTrackedHistoryColumns instance method, and AutoCdcMergeFlow (the userSelectedSchema projection and the construction-time track-history validation).
  • The caseSensitivity message parameter of AUTOCDC_COLUMNS_NOT_FOUND_IN_SCHEMA is preserved via a new CaseSensitivityLabels.of(resolver) overload that classifies the resolver by probing it (!resolver("a", "A")), so error messages are byte-for-byte unchanged.

Why are the changes needed?

The AutoCDC code already resolves identifiers everywhere else through conf.resolver (the canonical Spark abstraction for case-aware identifier comparison). applyToSchema was the odd one out, threading a raw caseSensitive boolean and re-deriving a resolver at each layer. Passing the Resolver directly makes column matching consistent with the rest of the pipeline, removes the boolean-to-resolver round-trips, and lets computeTrackedHistoryColumns take the resolver it actually needs rather than reconstructing one.

Does this PR introduce any user-facing change?

No. This is a pure refactor with no behavior change; the caseSensitivity label in the AUTOCDC_COLUMNS_NOT_FOUND_IN_SCHEMA error is preserved.

How was this patch tested?

Existing tests, updated to pass resolvers instead of booleans:

  • ChangeArgsSuite and AutoCdcFlowSuite (65 tests) — including the case-sensitive/insensitive selection and missing-column error-message cases.
  • Scd2BatchProcessor* and Scd1BatchProcessorMergeSuite (150 tests) — exercising the microbatch projection and history-tracking paths under both case-sensitivity settings.

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

Generated-by: Claude Code (Opus 4.8)

…ToSchema

Rework `ColumnSelection.applyToSchema` and its private `lookupFieldIndices`
to accept a `Resolver` instead of a `caseSensitive: Boolean`. Callers now pass
`conf.resolver` directly, matching how the rest of the AutoCDC code resolves
identifiers, and `Scd2BatchProcessor.computeTrackedHistoryColumns` takes a
`Resolver` too instead of deriving one from a boolean.

The `caseSensitivity` label in `AUTOCDC_COLUMNS_NOT_FOUND_IN_SCHEMA` is
preserved via a new `CaseSensitivityLabels.of(resolver)` overload that
classifies the resolver by probing it, so error messages are unchanged.

This is a pure refactor with no behavior change.

Co-authored-by: Opus 4.8
* two identifiers differing only in case as equal, so we classify it by probing with such a
* pair rather than by reference identity; the label then stays correct for any resolver.
*/
def of(resolver: Resolver): String = of(caseSensitive = !resolver("a", "A"))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Nit: probing with "a" / "A" feels a bit awkward for what is only an error-message label.

Spark already classifies resolvers by reference identity against the two singletons — see SchemaUtils.isCaseSensitiveAnalysis — and SQLConf.resolver only ever returns those. Something like:

def of(resolver: Resolver): String =
  of(caseSensitive = resolver == caseSensitiveResolution)

(or the same check at the one call site, without a new overload) would match that pattern. The "works for any resolver" rationale seems oversold here: AutoCDC callers pass conf.resolver / the test singletons, and SchemaUtils treats anything else as unreachable. Also Flow.scala still uses of(caseSensitiveAnalysis), so this invents a third classification strategy alongside the boolean overload.

anew added 2 commits July 29, 2026 00:05
…sitivityLabels.of

Address review: classify the resolver for the error-message label by reference
identity against the two session resolver singletons, matching the existing
SchemaUtils.isCaseSensitiveAnalysis pattern, instead of probing with an "a"/"A"
pair. conf.resolver only ever returns one of these two singletons.

Co-authored-by: Opus 4.8
…solver overload

Remove the boolean `of(caseSensitive)` overload and keep only `of(resolver)`,
which classifies by reference identity against the session resolver singletons.
Update the three Flow.scala call sites to pass the local `resolver` they already
hold, instead of re-reading `conf.caseSensitiveAnalysis`. All AutoCDC error
labels now derive from a single resolver value per method.

Co-authored-by: Opus 4.8
if (caseSensitive) CaseSensitive else CaseInsensitive
/**
* Maps a [[Resolver]] to its user-facing label. Classifies by reference identity against the
* two session resolver singletons, matching `SchemaUtils.isCaseSensitiveAnalysis`;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: it doesnt match SchemaUtils.isCaseSensitiveAnalysis which throws for unknown resolvers. should we just remove such comment?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I kept the comment but made it throw as well. I think that is more future-proof just in case there is ever a third resolver in the future.

anew added 2 commits July 29, 2026 04:17
…esolver case

The unreachable branch in CaseSensitivityLabels.of threw java.lang.InternalError,
a fatal JVM Error that bypasses Spark's NonFatal handling and error-class
framework. Switch to SparkException.internalError (INTERNAL_ERROR), consistent
with the existing internalError call in this file and with
SchemaUtils.isCaseSensitiveAnalysis, which this classification mirrors.

Co-authored-by: Opus 4.8
…anch

Relocate the "conf.resolver only returns one of these two" note from the
method scaladoc to the unreachable else branch it explains, where the reader
encounters the internalError throw.

Co-authored-by: Opus 4.8
@uros-b

uros-b commented Jul 29, 2026

Copy link
Copy Markdown
Member

Thank you @anew and @szehon-ho! Let's wait for CI

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.

3 participants