Skip to content

chore: fallback to Spark if legacy sql configurations are set#4799

Open
comphead wants to merge 23 commits into
apache:mainfrom
comphead:chore
Open

chore: fallback to Spark if legacy sql configurations are set#4799
comphead wants to merge 23 commits into
apache:mainfrom
comphead:chore

Conversation

@comphead

@comphead comphead commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Which issue does this PR close?

Closes #4786.

Summary

Splits Spark's spark.sql.legacy.* handling in Comet into two tiers, and hardens a few
Spark 4 features (VariantType, string collation, lpad/rpad BinaryType) that were
previously slipping past the serdes.

1. Session-wide fallback for legacy configs Comet cannot honor per-expression. Introduces
spark.comet.legacyConfFallback.enabled (default true). When any legacy config in a
curated list (parquet datetime/int96 rebase modes, charVarcharAsString, timeParserPolicy,
disableMapKeyNormalization, viewSchemaCompensation, scalarSubqueryCountBugBehavior,
setopsPrecedence, etc. — see ShimLegacyConfFallback) is set to a non-default value,
isCometLoaded returns false for the session with a WARN naming the offending keys. The
defaults are derived from live SQLConf ConfigEntry references on Spark 4 and hardcoded to
matching values on Spark 3.

2. Per-expression codegen-dispatch fallback for legacy configs bound to a supported
expression.
These no longer disable Comet or fall back the whole operator; they route the
expression through the JVM codegen dispatcher (Spark's own doGenCode inside the Comet
kernel) via the CodegenDispatchFallback mixin:

  • Castspark.sql.legacy.castComplexTypesToString.enabled
  • array_insertspark.sql.legacy.negativeIndexInArrayInsert
  • IN / InSetspark.sql.legacy.nullInEmptyListBehavior
  • str_to_mapspark.sql.legacy.truncateForEmptyRegexSplit
  • to_csvspark.sql.legacy.nullValueWrittenAsQuotedEmptyStringCsv

3. Spark 4 hardening.

  • New isVariantType shim; CometCast rejects any cast whose source or target is
    VariantType (including the folded-literal path), before serialization can fail.
  • EqualTo, EqualNullSafe, GreaterThan(OrEqual), LessThan(OrEqual), In, InSet, and
    Not now consult ComparisonUtils.collationSupportLevel and mark non-UTF8_BINARY collated
    operands Unsupported (native compare is byte-wise; 'a' = 'A' under UNICODE_CI would be
    wrong). hasNonDefaultStringCollation walks nested types so collated strings inside
    array/map/struct operands are caught too.
  • lpad/rpad reject BinaryType str argument (surfaced by
    spark.sql.legacy.lpadRpadAlwaysReturnString=true).

4. Tests & diffs.

  • New array_insert_legacy_dispatch.sql covering the dispatch path (no allowIncompatible).
  • cast_complex_types_to_string*.sql: flipped the expected expect_fallback(...) markers to
    plain query now that these run via the dispatcher.
  • New isCometLoaded test covering both the boolean and enum default cases, the opt-out via
    spark.comet.legacyConfFallback.enabled=false, and confirming per-expression legacy configs
    do NOT trigger the session-wide fallback.
  • dev/diffs/4.0.2.diff and 4.1.2.diff: add IgnoreComet to VariantEndToEndSuite cast
    test.

`spark.sql.legacy.timeParserPolicy`). Comet implements current Spark semantics and does **not**
reproduce these legacy behaviors.

By default, when Comet detects that any `spark.sql.legacy.*` config is set to `true`, it disables

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.

can't we just update the serde for specific expressions to return Unsupported rather than fall back for all queries, even queries that would not be affected by a legacy config?

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.

we should just fall back to the codegen dispatch approach in most cases

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.

Let me investigate this, sometimes the conf param is vaguely explained like

  val LEGACY_JAVA_CHARSETS = buildConf("spark.sql.legacy.javaCharsets")
    .internal()
    .doc("When set to true, the functions like `encode()` can use charsets from JDK while " +
      "encoding or decoding string values. If it is false, such functions support only one of " +
      "the charsets: 'US-ASCII', 'ISO-8859-1', 'UTF-8', 'UTF-16BE', 'UTF-16LE', 'UTF-16', " +
      "'UTF-32'.")
    .version("4.0.0")
    .booleanConf
    .createWithDefault(false)

it refers to encode() explicitly but also to something else. Maybe I can fish out exact functions from Spark code depending on legacy params and in this case we can fallback to codegen dispatch

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.

Also, Spark has legacy configs that default to true, so this will make all jobs fall back by default.

  val VIEW_SCHEMA_BINDING_ENABLED = buildConf("spark.sql.legacy.viewSchemaBindingMode")
    .internal()
    .doc("Set to false to disable the WITH SCHEMA clause for view DDL and suppress the line in " +
      "DESCRIBE EXTENDED and SHOW CREATE TABLE.")
    .version("4.0.0")
    .booleanConf
    .createWithDefault(true)

@comphead comphead marked this pull request as draft July 2, 2026 21:28
@comphead comphead force-pushed the chore branch 4 times, most recently from f5d2abc to 7125db0 Compare July 10, 2026 00:27
@comphead comphead marked this pull request as ready for review July 13, 2026 23:21
@comphead comphead requested review from andygrove and mbutrovich July 13, 2026 23:21
@comphead comphead added this to the 1.0.0 milestone Jul 14, 2026
Comment thread dev/diffs/4.0.2.diff Outdated
Comment on lines +1393 to +1395
- test("cast to variant/to_variant_object with scan input") {
+ test("cast to variant/to_variant_object with scan input",
+ IgnoreComet("\"VariantType\" not supported")) {

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.

This seems like a regression. This test was previously passing because we fell back to Spark and now we fail the query?

Comment on lines +450 to +465
// without duplicating the legacy branch natively.
private val legacyNegativeIndexConfig = "spark.sql.legacy.negativeIndexInArrayInsert"

private val legacyNegativeIndexReason =
s"`$legacyNegativeIndexConfig=true` legacy negative-index semantics are not implemented" +
" natively"

override def getIncompatibleReasons(): Seq[String] = Seq(legacyNegativeIndexReason)

override def getSupportLevel(expr: ArrayInsert): SupportLevel = {
if (SQLConf.get.getConfString(legacyNegativeIndexConfig, "false").toBoolean) {
Incompatible(Some(legacyNegativeIndexReason))
} else {
Compatible()
}
}

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.

This change looks good and could really be a standalone PR. This also looks like a bug fix for an issue where we were lacking tests?

}

object CometCount extends CometAggregateExpressionSerde[Count] {

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.

could you revert this whitespace change

Comment on lines 113 to 135
@@ -119,6 +128,8 @@ object CometEqualTo extends CometExpressionSerde[EqualTo] {
}

object CometEqualNullSafe extends CometExpressionSerde[EqualNullSafe] {
override def getSupportLevel(expr: EqualNullSafe): SupportLevel =
ComparisonUtils.collationSupportLevel(expr.left, expr.right)
override def convert(
expr: EqualNullSafe,
inputs: Seq[Attribute],

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.

These changes look good and probably should be separate PR since this is unrelated to legacy configs?

Comment on lines +47 to +51
"spark.sql.legacy.parquet.datetimeRebaseModeInRead" -> "CORRECTED",
"spark.sql.legacy.parquet.datetimeRebaseModeInWrite" -> "CORRECTED",
"spark.sql.legacy.parquet.int96RebaseModeInRead" -> "CORRECTED",
"spark.sql.legacy.parquet.int96RebaseModeInWrite" -> "CORRECTED",
"spark.sql.legacy.parquet.nanosAsLong" -> "false",

@andygrove andygrove Jul 15, 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.

Can we not handle the parquet ones in scan planning?

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.

bug: null IN () returns false instead of null under legacy null-in-empty behavior

2 participants