Skip to content

[GLUTEN-12807][CORE] Clean up Spark shims APIs after Spark 3.3 deprecation - #12954

Open
LuciferYang wants to merge 6 commits into
apache:mainfrom
LuciferYang:spark33-drop-p2-shims
Open

[GLUTEN-12807][CORE] Clean up Spark shims APIs after Spark 3.3 deprecation#12954
LuciferYang wants to merge 6 commits into
apache:mainfrom
LuciferYang:spark33-drop-p2-shims

Conversation

@LuciferYang

@LuciferYang LuciferYang commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

What changes are proposed in this pull request?

With Spark 3.3 gone, a batch of SparkShims members no longer varies by version. Four changes, none of them meant to alter behaviour on 3.4 through 4.1:

  • Seven methods whose overrides were byte-identical in all four shims move into the trait: generateFileScanRDD, generatePartitionedFile, getBatchScanExecTable, generateMetadataColumns, getKeyGroupedPartitioning, withAnsiEvalMode, withTryEvalMode. generateMetadataColumns used to start by calling super, so its two halves are merged into one body.
  • Two go to their call sites instead, where the indirection cost more than it saved: isFinalAdaptivePlan was a one-line wrapper over AdaptiveSparkPlanExec.isFinalPlan, and extractExpressionTimestampDiffUnit had a single caller.
  • Two of the seven are pure pass-throughs that the same rule would inline: getBatchScanExecTable is batchScan.table, getKeyGroupedPartitioning is batchScan.keyGroupedPartitioning, and both fields are public on 3.4 through 4.1. They stay because their call sites reach into gluten-iceberg and gluten-paimon, which this PR does not otherwise touch. [CORE] Further SparkShims dedup left out of the Spark 3.3 shim cleanup #12953 §2 records them together with generatePartitionedFile, the third of the same shape.
  • getExtendedColumnarPostRules is deleted. It returned List() in every remaining shim, so the register-these-rules blocks in VeloxRuleApi and CHRuleApi and their only callee, GlutenFormatFactory.getExtendedColumnarPostRule, were dead.
  • Twelve trait defaults that all four shims override become abstract, so a new shim has to state an answer rather than inherit a value that only 3.3 needed. This settles both drop Spark 3.3 TODOs in SparkShims.scala.

Each shim loses ten overrides: 37 to 27, 44 to 34, 51 to 41, 53 to 43. After the change no concrete default left in the trait is overridden by all four shims.

createParquetFilters looks liftable and is not: LegacyBehaviorPolicy sits inside SQLConf on 3.4 and at top level from 3.5 on, so no single import in shims/common compiles against all four versions. It becomes liftable once 3.4 is dropped. #12953 tracks the seven abstract methods whose four bodies are also identical, plus two smaller candidates.

shims/common is a published artifact, so this is source and binary incompatible for anything outside the repo: four public members are gone and twelve trait methods no longer have a default. Every in-repo caller and implementor is accounted for.

One thing to read correctly rather than as a regression. postRuleFactory in GlutenFormatWriterInjects is now written and never read, and the ClickHouse registration of NativeWritePostRule has been inert since 3.4 became the floor, because getExtendedColumnarPostRules already returned List() there. Deleting the reader makes that visible instead of causing it. The two registrations belong to the follow-up PR that clears the residual version checks, which already owns the Velox one.

How was this patch tested?

Compile-only. The lifted methods keep their signatures, so callers are unchanged apart from the two inlined ones.

build result
install -Pbackends-velox -Pspark-ut -Pdelta on 3.4, 3.5, 4.0, 4.1 all four exit 0
install -Pbackends-clickhouse -Pspark-3.5 -Pdelta -Piceberg exit 0
spotless:check on the touched modules under all four Spark profiles, plus backends-clickhouse clean
.github/workflows/util/check.sh against main OK for all 11 files

The ClickHouse build is the one that matters for generateMetadataColumns and the CHRuleApi edit. Spark 3.5 was built with -Pscala-2.13 because this machine's 2.12 artifacts for 3.5.5 are unusable; CI covers 3.5 on 2.12.

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

Generated-by: Claude claude-opus-5

Related issue: #12807

Six SparkShims methods have byte-identical overrides in all four remaining
shims. Moved the implementation into the trait and dropped the overrides.

createParquetFilters stays in the shims: LegacyBehaviorPolicy sits inside
SQLConf on 3.4 and at top level from 3.5 on, so no single import in
shims/common satisfies all four versions.
isFinalAdaptivePlan was a one-line wrapper over AdaptiveSparkPlanExec.isFinalPlan;
extractExpressionTimestampDiffUnit had a single caller. Both go straight to the
call sites rather than into the trait.
…ults abstract

getExtendedColumnarPostRules returned List() in every remaining shim, so the
method and the register-these-rules blocks in VeloxRuleApi and CHRuleApi were
dead. GlutenFormatFactory.getExtendedColumnarPostRule went with them, being
their only callee.

Ten trait methods carried a default that all four shims override, so the default
was only ever reached on 3.3. Making them abstract means the compiler requires an
implementation when a new shim lands. This settles both remaining
"drop Spark 3.3" TODOs in SparkShims.scala.
…alMode

Three trait defaults met the same rule as the ten already abstracted but were
missed: getCommonPartitionValues, orderPartitions and
extractExpressionTimestampAddUnit. The last one is the twin of
extractExpressionTimestampDiffUnit, which this branch inlines.

withTryEvalMode moves into the trait instead of becoming abstract: its twin
withAnsiEvalMode is lifted on the adjacent line with the same shape, and the
imports it needs are already there.
Copilot AI lite review requested due to automatic review settings September 2, 2026 06:54
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

Copilot AI 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.

🟡 Changes recommended

It leaves a now-effectless post-rule injection hook (injectPostRuleFactory / postRuleFactory) and includes an avoidable, unclear error message in updated code that should be cleaned up before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Cleans up the SparkShims API surface after dropping Spark 3.3, consolidating identical shim implementations into the shared trait and removing now-dead shim indirections and rule-registration hooks, without intended behavior changes for Spark 3.4–4.1.

Changes:

  • Lifted several byte-identical shim methods into shims/common and removed redundant per-version overrides across Spark 3.4/3.5/4.0/4.1.
  • Inlined a couple of one-callsite / one-line shim wrappers at call sites (isFinalAdaptivePlan, extractExpressionTimestampDiffUnit).
  • Removed getExtendedColumnarPostRules plumbing and the associated factory reader that had become dead code.
File summaries
File Description
shims/spark34/src/main/scala/org/apache/gluten/sql/shims/spark34/Spark34Shims.scala Drops overrides now provided by the shared SparkShims trait (Spark 3.4 shim simplification).
shims/spark35/src/main/scala/org/apache/gluten/sql/shims/spark35/Spark35Shims.scala Drops overrides now provided by the shared SparkShims trait (Spark 3.5 shim simplification).
shims/spark40/src/main/scala/org/apache/gluten/sql/shims/spark40/Spark40Shims.scala Drops overrides now provided by the shared SparkShims trait (Spark 4.0 shim simplification).
shims/spark41/src/main/scala/org/apache/gluten/sql/shims/spark41/Spark41Shims.scala Drops overrides now provided by the shared SparkShims trait (Spark 4.1 shim simplification).
shims/common/src/main/scala/org/apache/gluten/sql/shims/SparkShims.scala Centralizes previously duplicated shim implementations; makes several previously-defaulted methods abstract to force explicit answers in future shims.
shims/common/src/main/scala/org/apache/gluten/execution/datasource/GlutenFormatWriterInjects.scala Removes the (now-dead) extended post-rule reader from GlutenFormatFactory.
gluten-substrait/src/main/scala/org/apache/spark/sql/execution/GlutenImplicits.scala Inlines final-AQE detection to AdaptiveSparkPlanExec.isFinalPlan.
gluten-substrait/src/main/scala/org/apache/spark/sql/execution/GenerateTransformStageId.scala Inlines final-AQE detection and removes shim-loader indirection.
backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxSparkPlanExecApi.scala Inlines TimestampDiff unit extraction at the only call site.
backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxRuleApi.scala Removes injection of extended columnar post rules (previously always empty for supported Spark versions).
backends-clickhouse/src/main/scala/org/apache/gluten/backendsapi/clickhouse/CHRuleApi.scala Removes injection of extended columnar post rules (previously always empty for supported Spark versions).
Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 75 to 77
def injectPostRuleFactory(factory: SparkSession => Rule[SparkPlan]): Unit = {
postRuleFactory = factory
}
Copilot AI review requested due to automatic review settings September 2, 2026 13:43
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

Copilot AI 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.

🟢 Approval recommended

The refactor cleanly removes dead shim APIs and deduplicates identical implementations, and repository-wide references to deleted members appear fully updated.

Review details
  • Files reviewed: 11/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@LuciferYang

Copy link
Copy Markdown
Contributor Author

@jackylee-ch Could you please re‑trigger the failed tasks? Thanks

@LuciferYang

Copy link
Copy Markdown
Contributor Author

Thanks. Fixed the first one; the second is real but I would rather not do it here, and the reason is a cascade I did not expect.

Error message. Dropped the pointless s prefix. I left the wording alone: the identical string appears three times, at VeloxSparkPlanExecApi.scala:1460 for TimestampAdd thirteen lines above this one and at CHSparkPlanExecApi.scala:1059, so rewording only the copy my diff happens to touch would make the pair inconsistent and rewording all three is unrelated to this PR.

injectPostRuleFactory. You are right that nothing reads postRuleFactory any more, and it is worth being precise about when that started: getExtendedColumnarPostRules already returned List() in every shim from 3.4 on, so GlutenFormatFactory.getExtendedColumnarPostRule has not been called on any supported version since 3.3 left. Deleting the reader here exposes that rather than causing it. The PR description says as much.

I tried removing the setter and both registrations, and the diff does not stop there. NativeWritePostRule is then constructed by nobody, so it goes too; that orphans the private getNativeFormat, which orphans BackendSettingsApi.skipNativeCtas and skipNativeInsertInto and their VeloxBackend overrides. That is the whole pre-planned-write 3.3 path, six files, and it is one coherent removal rather than something to fold into a shim-API change. Removing only the setter is worse than either end state: it leaves NativeWritePostRule as a rule class nobody registers, which reads exactly as misleadingly as what you flagged.

So it stays as is here, and the follow-up PR that clears the residual 3.3 version checks takes the path as a unit. It already owns the if (SparkShimLoader.getSparkVersion.startsWith("3.3")) guard around the Velox registration, which is the other half of the same thing.

@LuciferYang

Copy link
Copy Markdown
Contributor Author


def runtimeReplaceableExpressionMappings: Seq[Sig]

def generateFileScanRDD(

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.

The lift/inline split reads well for five of the seven, but two look like they fail the rule the PR itself states ("where the indirection cost more than it saved"): getBatchScanExecTable is batchScan.table and getKeyGroupedPartitioning is batchScan.keyGroupedPartitioning — the same one-line-wrapper shape, and roughly the same call-site count (3 each), as the isFinalAdaptivePlan you deleted. Both are public on 3.4/3.5/4.0/4.1 and the trait bodies already compile from a non-Spark package, so ScanTransformerFactory.scala:48-49, IcebergScanTransformer.scala:359-360 and PaimonScanTransformer.scala:224-225 could read them directly. #12953 §2 already records this, so mainly two follow-up questions: is generateFileScanRDD meant to be in that list too? It has zero production callers — the only reference in the tree is backends-clickhouse/src/test/.../CHAggAndShuffleBenchmark.scala:334. And should withTryEvalMode/withAnsiEvalMode land in gluten-substrait/.../expression/ExpressionUtils.scala instead, the way #11687 relocated genDecimalRoundExpressionOutput to SparkPlanExecApi? They are shared logic rather than version logic, and UnaryExpressionTransformer is already in that package.

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.

You are right about the two pass-throughs, and the reason I put in #12953 §2 does not hold: it says the callers sit outside shims/, but isFinalAdaptivePlan's callers were outside too (GenerateTransformStageId, GlutenImplicits) and I inlined it anyway. The real line is which modules. Those two are in gluten-substrait, already in this diff, whereas getBatchScanExecTable / getKeyGroupedPartitioning reach into gluten-iceberg and gluten-paimon, which this PR does not touch and which are profile-gated. I would rather widen into two extra optional modules in the follow-up than here, and I will fix the issue's wording. Your visibility point checks out on 3.4/3.5/4.0/4.1 in both source and bytecode; one note for whoever does it, keyGroupedPartitioning is a constructor val on 3.4 but a def over spjParams on 3.5+, so reads port cleanly and a copy(keyGroupedPartitioning = ...) would not. §2 lists generatePartitionedFile as a third of the same shape.

generateFileScanRDD: the dead-caller half is right, CHAggAndShuffleBenchmark.scala:334 is the only reference in the tree. It is not the same case otherwise, though. It has a real body, and the four shim copies were byte-identical before this PR, so lifting was de-duplication rather than a choice against inlining. It did bridge a difference until recently: 3.2 took the 3-arg constructor, and 3.3 passed metadataColumns where 3.4+ passes fileConstantMetadataColumns. Whether a shim method should survive for one benchmark caller is a fair question, so I will add it to #12953.

ExpressionUtils: the file is there, UnaryExpressionTransformer is in that package, and one of the two callers sits in it, so the move is cheap; all four call sites are in modules this diff already touches. I still think the shim is the right home, and the reason is the history. The 3.3 shim implemented withAnsiEvalMode as case c: Cast => c.ansiEnabled and carried no withTryEvalMode at all, because EvalMode does not exist before 3.4. These two predicates are about a Spark API whose shape has already diverged by version once; being uniform across 3.4 to 4.1 today is not the same as being version-independent, and ExpressionUtils would have to hand them back the next time evalMode moves.

On the precedent itself, #11687 is not quite that shape: it did not move genDecimalRoundExpressionOutput into SparkPlanExecApi, because that default was already there byte-identical. What the commit deleted was the duplicate in shims/common plus a ClickHouse override that only forwarded to it, and its stated reason was that the shim API existed for a 3.2-vs-later difference that no longer does, so the implementation goes back to the caller side. That reasoning does support pulling a method out of the shim once the difference is gone; it just is not an example of an *Utils object as the landing spot, and here the difference is gone only as of 3.3's removal. If you read the tradeoff the other way I will move them, but that is why I left them.

@philo-he philo-he Sep 5, 2026

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.

I posted some comments before being aware of this comment. I would recommend to do the further refactor to remove those shim APIs if their implementations are consistent across the supported Spark versions. We can move them to the caller side or a proper module (if the shim API implementation is a bit complex and they are called from two or more places) for meeting the dependency requirement.

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.

Since the s prefix came off the TimestampDiff copy 11 lines below, the TimestampAdd copy here is now the odd one out — same for CHSparkPlanExecApi.scala:1059. Worth dropping this one at least, it is in a file the PR already touches.

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.

Fair, and it's my own change that created the mismatch. Dropped the s on the TimestampAdd copy at :1460 as well.

Left CHSparkPlanExecApi.scala:1059 as it is, since this PR does not touch that file; the only ClickHouse file in the diff is CHRuleApi.scala.

Copilot AI review requested due to automatic review settings September 4, 2026 05:18
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

Copilot AI 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.

🔵 Needs a closer look

It leaves behind newly dead/inert post-rule injection state (postRuleFactory) and introduces avoidable overhead in a hot-path metadata helper that should be addressed before merging.

Review details

Suppressed comments (4)

Previously missed (2) — in code that hasn't changed since the last review.

backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxSparkPlanExecApi.scala:1460

  • The exception message here is ungrammatical and the nearby comment still references Spark 3.3 even though Spark 3.3 support has been removed in this PR series. Updating both improves debuggability and keeps the comment accurate.
    shims/common/src/main/scala/org/apache/gluten/sql/shims/SparkShims.scala:187
  • generateMetadataColumns does non-trivial work (building a mutable map and new Path(...)) even when metadataColumnNames is empty (the default). Adding a fast-path for the empty case and lazily constructing Path avoids unnecessary per-call overhead.

backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxSparkPlanExecApi.scala:1474

  • The thrown exception message is ungrammatical; consider aligning it with typical Spark/Gluten wording to make logs clearer.
    val unit = original match {
      case timestampDiff: TimestampDiff => timestampDiff.unit
      case _ =>
        throw new UnsupportedOperationException("Not support expression TimestampDiff.")
    }

shims/common/src/main/scala/org/apache/gluten/execution/datasource/GlutenFormatWriterInjects.scala:77

  • After removing getExtendedColumnarPostRule, postRuleFactory is now write-only and injectPostRuleFactory(...) has no effect (no reads remain). This leaves inert initialization code at call sites (e.g., listener APIs) and a misleading API surface; consider removing the field/method and updating the callers in the same PR to avoid dead code.
  def injectPostRuleFactory(factory: SparkSession => Rule[SparkPlan]): Unit = {
    postRuleFactory = factory
  }
  • Files reviewed: 11/11 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@philo-he philo-he left a comment

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.

Thanks for the work. Please check if my comments make sense.


// Spark3.4 new add table parameter in BatchScanExec.
def getBatchScanExecTable(batchScan: BatchScanExec): Table
def getBatchScanExecTable(batchScan: BatchScanExec): Table = batchScan.table

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.

Should this shim API be removed? Then, directly call batchScan.table on the caller side.

}

def withAnsiEvalMode(expr: Expression): Boolean = false
def withAnsiEvalMode(expr: Expression): Boolean = {

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.

It seems both withAnsiEvalMode and withTryEvalMode can be moved from this shim class, since no divergence among the supported Spark versions with Spark 3.3 removed. Perhaps, it would be better to move them to an existing or new util class.


def getKeyGroupedPartitioning(batchScan: BatchScanExec): Option[Seq[Expression]] = Option(Seq())
def getKeyGroupedPartitioning(batchScan: BatchScanExec): Option[Seq[Expression]] = {
batchScan.keyGroupedPartitioning

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.

It looks like this method can be removed, and let batchScan.keyGroupedPartitioning be directly called on the caller side.

case _ =>
}
}
metadataColumn.toMap

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.

Perhaps, it would be better to move this shim API to other existing or new util class.

// TODO, remove this shim once we drop Spark3.3 and previous
sc.broadcast(value)
}
def broadcastInternal[T: ClassTag](sc: SparkContext, value: T): Broadcast[T]

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.

Seems we can remove this shim API also.

def getLimitAndOffsetFromGlobalLimit(plan: GlobalLimitExec): (Int, Int)

def getExtendedColumnarPostRules(): List[SparkSession => Rule[SparkPlan]]
def getLimitAndOffsetFromTopK(plan: TakeOrderedAndProjectExec): (Int, Int)

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.

It seems that the above two shims APIs can be removed now. And the implementation for them are consistent for Spark 3.4 and later versions now. Consider to use their implementations on the caller side.

length: Long,
@transient locations: Array[String] = Array.empty): PartitionedFile
@transient locations: Array[String] = Array.empty): PartitionedFile =
PartitionedFile(partitionValues, SparkPath.fromPathString(filePath), start, length, locations)

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.

Ditto to directly call the implementation on the call side and remove this shim API.

new StructType(
fileSourceScanExec.requiredSchema.fields ++
fileSourceScanExec.relation.partitionSchema.fields),
fileSourceScanExec.fileConstantMetadataColumns

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.

Ditto to directly use the implementation on the caller side or move to a util class or method if it is called from two or more places.


def runtimeReplaceableExpressionMappings: Seq[Sig]

def generateFileScanRDD(

@philo-he philo-he Sep 5, 2026

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.

I posted some comments before being aware of this comment. I would recommend to do the further refactor to remove those shim APIs if their implementations are consistent across the supported Spark versions. We can move them to the caller side or a proper module (if the shim API implementation is a bit complex and they are called from two or more places) for meeting the dependency requirement.

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

Labels

CLICKHOUSE CORE works for Gluten Core VELOX

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants