Skip to content

[SPARK-58814][SQL] Cover CHAR/VARCHAR round-trips and stop ORC truncating scans - #58317

Open
srielau wants to merge 9 commits into
apache:masterfrom
srielau:SPARK-58814-format-roundtrips
Open

[SPARK-58814][SQL] Cover CHAR/VARCHAR round-trips and stop ORC truncating scans#58317
srielau wants to merge 9 commits into
apache:masterfrom
srielau:SPARK-58814-format-roundtrips

Conversation

@srielau

@srielau srielau commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

What changes were proposed in this pull request?

Parent: SPARK-58794 (first-class CHAR/VARCHAR under spark.sql.charVarchar.standardSemantics.enabled).

  • Under standardSemantics, keep Spark responsible for CHAR/VARCHAR assignment and scan checks on ORC by requesting physical ORC STRING. The row decoder accepts all StringType subtypes recursively. Preserve-only mode retains native ORC CHAR/VARCHAR enforcement.
  • Add SPARK-58814 coverage for major formats under standardSemantics:
    • Parquet/ORC nested CHAR/VARCHAR (struct/array/map keys) on file-only inference, V1 and V2
    • Avro nested inference plus explicit-schema pad/overflow (V1 and V2)
    • CSV/JSON user-schema scans, length checks, and catalog INSERT assignment/overflow
    • Cross-flag: standardSemantics=false collapses to STRING; preserveCharVarcharTypeInfo keeps types

Parquet already embeds Spark schema JSON in footer metadata, so nested CHAR/VARCHAR inference needed tests rather than a writer change.

Why are the changes needed?

Without this, Spark-written files and user-specified schemas do not keep first-class CHAR/VARCHAR through round-trips in the remaining format gaps. For ORC specifically, an explicit schema("c CHAR(4)") read sent native ORC char(n) via MAPRED_INPUT_SCHEMA, so ORC truncated "abcdef" to "abcd" before charTypeReadSideCheck and EXCEED_LIMIT_LENGTH never fired.

Does this PR introduce any user-facing change?

Yes, when spark.sql.charVarchar.standardSemantics.enabled is true (unreleased / master):

  • ORC no longer silently truncates oversized CHAR/VARCHAR values on schema-specified reads; Spark rejects them with EXCEED_LIMIT_LENGTH.
  • Parquet/ORC/Avro file-only inference and CSV/JSON user schemas preserve CHAR/VARCHAR types and apply pad/length and INSERT assignment as covered by the new tests.

How was this patch tested?

New tests in BasicCharVarcharTestSuite (SPARK-58814: major formats preserve CHAR/VARCHAR schemas and values) and AvroSuite (V1 and V2). Also ran existing ORC query suites and scalastyle:

build/sbt "sql/testOnly org.apache.spark.sql.BasicCharVarcharTestSuite -- -z SPARK-58814"
build/sbt "avro/testOnly org.apache.spark.sql.avro.AvroV1Suite -- -z SPARK-58814"
build/sbt "avro/testOnly org.apache.spark.sql.avro.AvroV2Suite -- -z SPARK-58814"
build/sbt "sql/testOnly org.apache.spark.sql.execution.datasources.orc.OrcV1QuerySuite"
build/sbt "sql/testOnly org.apache.spark.sql.execution.datasources.orc.OrcV2QuerySuite"
build/sbt "sql/scalastyle" "sql/Test/scalastyle" "avro/scalastyle" "avro/Test/scalastyle"

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

Generated-by: Cursor Grok 4.6

@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.

Please address the non-vectorized ORC decoder incompatibility and preserve-only VARCHAR enforcement, then make the nested ORC regression test materialize the collection fields it introduces.

This mapping is unconditional, so it also runs when preserveCharVarcharTypeInfo=true is the only reason the constrained type is retained and standard semantics is disabled. That branch of addPaddingForScan installs no VARCHAR read-side check. Requesting ORC string therefore removes native varchar(n) enforcement and can expose abcdef under a VARCHAR(4) schema. Please preserve the native constrained schema in preserve-only mode or add equivalent validation, with a value-level regression test for this flag combination.

Comment thread sql/core/src/test/scala/org/apache/spark/sql/CharVarcharTestSuite.scala Outdated

@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.

Review summary

Two changes are needed before this is safe to merge: bind ORC reader schema selection to the persisted/analyzed CHAR/VARCHAR semantics so view results cannot silently change with the caller configuration, and make the Avro regression test observe its array and map values.

Findings

2 total: 0 P0, 1 P1, 1 P2, 0 P3.

Blocking (P1)

  • Keep the view-bound CHAR/VARCHAR mode for ORC scanssql/core/src/main/scala/org/apache/spark/sql/execution/datasources/orc/OrcUtils.scala:443 — see inline.

Non-blocking (P2)

  • Assert the Avro array and map values that the test createsconnector/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala:3742 — see inline.

Comment thread connector/avro/src/test/scala/org/apache/spark/sql/avro/AvroSuite.scala Outdated
@srielau
srielau requested a review from cloud-fan August 31, 2026 17:22

@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.

Review summary

The ordinary standard-semantics paths and the previously raised ORC/Avro issues are addressed, but the scan-binding mechanism puts trusted analysis state into caller-controlled schemas and connector options. The blocking request is therefore one owner-level carrier redesign, with option isolation and both configuration transitions serving as its validation, plus one separate preserve-only writer regression test.

Findings

2 total: 0 P0, 1 P1, 1 P2, 0 P3.

Blocking (P1)

  • Carry the analyzed ORC semantics mode in trusted relation statesql/core/src/main/scala/org/apache/spark/sql/execution/datasources/ApplyCharTypePadding.scala:91
    Could we model the CHAR/VARCHAR mode captured during analysis as explicit engine-private, relation-scoped state and pass it directly to ORC schema construction? The current carrier uses caller-owned namespaces: V1 trusts StructField metadata, while V2 stores an ORC-only marker in the public, case-insensitive options map that is forwarded to every SupportsRead connector. This lets a caller forge or clobber the mode, can make Catalyst's length checks disagree with the physical ORC schema, and can break unrelated connectors that reject or collide with the undocumented option. It also represents preserve-only by absence, so false is indistinguishable from unbound or lost state. Please bind both standard and preserve-only modes explicitly through a trusted internal carrier, leave non-ORC r.options unchanged, and consume the state only when deriving the ORC request schema. The regression coverage should include caller-supplied marker attempts, a strict non-ORC connector whose options remain unchanged, and both analysis/execution configuration directions across V1/V2 and row/vector readers.

Non-blocking (P2)

  • Restore preserve-only ORC writer coveragesql/core/src/main/scala/org/apache/spark/sql/execution/datasources/orc/OrcUtils.scala:451 — see inline.

Re-review status

2 addressed, 0 remaining, 2 new to this AI review.

New attribution: 2 newly introduced, 0 late catch, 0 previously raised, 0 unattributed.

Remaining findings

No prior AI findings remain.

Verification

  • The carrier defect spans both V1 and V2: caller schema metadata and read options can select the physical ORC schema independently of the mode that installed Catalyst length checks.
  • A correct owner-level repair must preserve unrelated V2 connector options and bind standard and preserve-only modes across both analysis/execution configuration directions.

@srielau

srielau commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the blocking carrier finding in 5117aaf.

The analyzed mode is now stored as an explicit private TreeNodeTag[Boolean], including false for preserve-only mode. V1 transfers that tag to FileSourceScanExec and passes it directly to the ORC reader. V2 binds it only on an internal interface implemented by OrcScanBuilder; DataSourceV2Relation.options is unchanged for ORC and every other connector. The previous schema-metadata and public-option carriers were removed.

The regression matrix now covers both analysis/execution configuration directions in V1/V2 and row/vector modes, caller attempts to forge the old metadata/option markers, and exact preservation of a non-ORC V2 relation's options.

Verification: the focused SPARK-58814 test and SQL main/test scalastyle checks pass.

@srielau

srielau commented Sep 1, 2026

Copy link
Copy Markdown
Contributor Author

Follow-up in 5f0006d removes the remaining unbound fallback: first-class CHAR/VARCHAR relations now bind an explicit Boolean even when read-side padding is disabled, and neither V1 nor V2 ORC readers consult execution-time SQLConf when no binding is present. The preserve-only cross-configuration tests now disable read-side padding to cover this path. The focused SPARK-58814 test and SQL main/test scalastyle checks pass.

@srielau
srielau requested a review from cloud-fan September 1, 2026 16:55

@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.

Review summary

This revision addresses the earlier review feedback: ORC now handles all StringType subtypes in row readers, covers nested collections, preserves analyzed view semantics, avoids caller-controlled metadata/options as the carrier, and explicitly binds preserve-only mode. The expanded V1/V2 and row/vectorized regression coverage is strong.

One blocking correctness issue remains. The bound mode affects ORC results but is not represented in Spark's semantic plan identity, allowing cache or plan reuse across incompatible modes.

Findings

1 total: 0 P0, 1 P1, 0 P2, 0 P3.

Blocking (P1)

  • Make the bound ORC semantics mode part of plan identityApplyCharTypePadding.scala:43

    TreeNodeTag is excluded from canonicalization and sameResult. A preserve-only cached relation that truncated "abcdef" to "abcd" can therefore replace the relation beneath a standard-semantics length check, causing the expected EXCEED_LIMIT_LENGTH error to be missed.

    V2 has the same identity gap because OrcScan.equals does not compare charVarcharStandardSemantics. Please carry the mode in semantically compared relation/scan state and add V1/V2 cache-reuse coverage.

Re-review status

All previously reported issues are addressed. This is one newly identified issue in the replacement carrier design.

Verification

Reviewed head 5f0006d949ed876e788b9fefb6ae5ca21728700e. All current GitHub CI checks pass, including SQL, Avro, extended tests, and linters. No local tests were run.

@srielau

srielau commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

Addressed in 57a9c67.

TreeNodeTag is excluded from canonicalization/sameResult, so a preserve-only cached ORC scan that truncated "abcdef" to "abcd" could replace a standard-semantics scan and skip EXCEED_LIMIT_LENGTH. The bound mode is now case-class state on LogicalRelation, DataSourceV2Relation, HiveTableRelation, and FileSourceScanExec; OrcScan.equals compares it as well. Added V1/V2 coverage that preserve-only vs standard plans are not sameResult and that CACHE TABLE under preserve-only is not reused under standard semantics.

@srielau
srielau requested a review from cloud-fan September 2, 2026 15:36

@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.

Review summary

Two blocking correctness issues remain: V1 writes can miss bound-mode cache entries and leave stale rows, and bound ORC scans can bypass reader overrides on OrcFileFormat subclasses. The new ORC mode parameter also needs its missing Scaladoc entry. Previously reported row-decoder, nested-value, persisted-view, carrier, preserve-only, and cross-mode cache-reuse issues are resolved at the pinned head.

Findings

3 total: 0 P0, 2 P1, 0 P2, 1 P3.

Blocking (P1)

  • Invalidate every bound-mode cache after a V1 savesql/core/src/main/scala/org/apache/spark/sql/execution/datasources/SaveIntoDataSourceCommand.scala:74 — see inline.
  • Preserve OrcFileFormat subclass reader dispatchsql/core/src/main/scala/org/apache/spark/sql/execution/DataSourceScanExec.scala:763 — see inline.

Nit (P3)

  • Document the ORC semantics parametersql/core/src/main/scala/org/apache/spark/sql/execution/datasources/orc/OrcUtils.scala:552 — see inline.

Re-review status

2 addressed, 0 remaining, 3 new to this AI review.

New attribution: 3 newly introduced, 0 late catch, 0 previously raised, 0 unattributed.

Remaining findings

No prior AI findings remain.

Comment thread sql/core/src/main/scala/org/apache/spark/sql/execution/DataSourceScanExec.scala Outdated
@srielau
srielau requested a review from cloud-fan September 3, 2026 18:46
…ting scans

Keep Spark responsible for CHAR/VARCHAR assignment and scan checks: ORC
was mapping an explicit CHAR/VARCHAR read schema to native ORC types,
which truncated oversize values before Spark could reject them. Add
Parquet/ORC/CSV/Avro coverage for nested inference, user schemas, and
INSERT assignment under standardSemantics.
…cement

Decode all string-family types in the row reader, while retaining native ORC
constraints in preserve-only mode. Materialize nested collections and verify
nested overflow checks across V1/V2 and vectorized/row readers.
Carry analyzed semantics through private plan state so callers and unrelated connectors cannot alter the ORC request schema.
Bind preserve-only mode even when read-side padding is disabled and remove execution-session fallbacks from ORC readers.
TreeNodeTags are dropped by canonicalization, so a preserve-only cached
ORC scan could be reused under standard semantics. Carry the bound mode
on relation/scan case-class state that sameResult compares.
…ate ORC reader

Invalidate cached V1 relations by matching the written BaseRelation while
ignoring CHAR/VARCHAR scan-mode identity, so a successful save/insert refreshes
None, Some(false), and Some(true) cache entries without weakening ordinary
cross-mode cache identity. Restrict the mode-aware private ORC reader overload to
the exact built-in OrcFileFormat so subclasses keep public buildReaderWithPartitionValues
dispatch. Document the orcResultSchemaString charVarcharStandardSemantics param.
@srielau
srielau force-pushed the SPARK-58814-format-roundtrips branch from 4b6e0e5 to 2ad887c Compare September 3, 2026 18:55

@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.

Review summary

The earlier row-decoder, nested-value, persisted-view, carrier, plan-identity, cache-invalidation, and subclass-dispatch findings are addressed. One blocking correctness gap remains in the V1 compatibility path: the exact-class guard restores the existing subclass override, but a delegating override loses the analysis-bound standard mode when it calls the public OrcFileFormat super method, which defaults to preserve-native behavior.

Please remove this ORC-specific dispatch split and make the semantic state explicit end to end.

Requested design

Define the mode in Catalyst, where both V1/V2 logical relations can use it:

private[sql] sealed trait CharVarcharScanMode extends Product with Serializable

private[sql] object CharVarcharScanMode {
  case object PreserveNative extends CharVarcharScanMode
  case object SparkStandard extends CharVarcharScanMode
}

Store Option[CharVarcharScanMode] in LogicalRelation, DataSourceV2Relation,
HiveTableRelation, and their physical scans. None means that first-class CHAR/VARCHAR
semantics do not apply; preserve-only and standard modes are both explicit values. Analysis binds
the value once and does not replace a value already captured for a view. Because it is case-class
state, result identity remains mode-sensitive; the existing BaseRelation-specific cache
invalidation remains deliberately mode-insensitive.

Add this overload to FileFormat:

def buildReaderWithPartitionValues(
    sparkSession: SparkSession,
    dataSchema: StructType,
    partitionSchema: StructType,
    requiredSchema: StructType,
    filters: Seq[Filter],
    options: Map[String, String],
    hadoopConf: Configuration,
    charVarcharScanMode: CharVarcharScanMode)
  : PartitionedFile => Iterator[InternalRow]

Its default implementation should clone hadoopConf, overwrite an engine-private mode entry,
and invoke the existing seven-argument method virtually. FileSourceScanExec selects the old or
new overload based only on whether the bound mode is present; it does not inspect the format class.
The existing public ORC method reads the compatibility entry and calls a non-virtual internal
builder. Thus an existing subclass still receives its old override, and super recovers the
bound mode. Direct callers of the old overload retain preserve-native behavior.

Use the same typed mode in the V2 builder, OrcScan, reader factory, and
OrcUtils.orcResultSchemaString. Only ORC maps SparkStandard to physical STRING and
PreserveNative to native constrained types.

Findings

2 total: 0 P0, 1 P1, 0 P2, 1 P3.

Blocking (P1)

  • Define and preserve CHAR/VARCHAR scan mode through the generic FileFormat overload
    sql/core/src/main/scala/org/apache/spark/sql/execution/DataSourceScanExec.scala:762
    see inline.

Nit (P3)

  • Describe TreeNodeTag equality rather than tag removal
    sql/core/src/main/scala/org/apache/spark/sql/execution/datasources/ApplyCharTypePadding.scala:56
    see inline.

Re-review status

The three findings from the prior AI review are addressed. The blocking finding below is introduced
by that dispatch fix; the comment correction was identified during the broader re-review.

Comment thread sql/core/src/main/scala/org/apache/spark/sql/execution/DataSourceScanExec.scala Outdated
…eFormat overload

Introduce a typed `CharVarcharScanMode` (`PreserveNative` / `SparkStandard`) in
Catalyst and carry `Option[CharVarcharScanMode]` on the relation carriers
(`LogicalRelation`, `DataSourceV2Relation`, `HiveTableRelation`) and the V1 scan
node (`FileSourceScanExec`), replacing the previous `Option[Boolean]` field.

Add a mode-aware overload to `FileFormat.buildReaderWithPartitionValues`. Its
default implementation clones the per-call Hadoop configuration, writes an
engine-private entry with the explicit mode, and invokes the existing
seven-argument method virtually. `FileSourceScanExec` calls this overload
whenever the relation has a bound mode, regardless of the concrete file format,
so the ORC-specific format match is removed. `OrcFileFormat`'s public
seven-argument override reads the bridged entry and passes the mode to its
non-virtual reader builder. Existing subclasses therefore keep their override,
and a delegating `super` call retains the analyzed mode instead of defaulting to
preserve-native (which could bypass standard-semantics length checks).

The Hadoop entry is only a bridge across the legacy signature; the authoritative
state remains the typed plan field and overload parameter.

Extend the `OrcFileFormat` subclass regression with an over-length value:
`SparkStandard` must raise `EXCEED_LIMIT_LENGTH`, while `PreserveNative` retains
native ORC truncation, covering both the row and vectorized readers. Also reword
the `ApplyCharTypePadding` comment: a `TreeNodeTag` survives canonicalization
(`makeCopy` calls `copyTagsFrom`) but does not participate in structural plan
equality / `sameResult`, which is why a case-class field is used instead.
@srielau
srielau requested a review from cloud-fan September 4, 2026 00:29

@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.

Review summary

The typed scan-mode redesign resolves the earlier carrier and dispatch problems, but one blocking gap remains: catalog-less V2 writes recache through an unbound relation and can leave mode-bound cached reads stale after a successful append or overwrite. The ORC compatibility key also needs isolation from caller-controlled options. Finally, update the canonicalization rationale and the V1 cache-test comment to match the actual implementation. No local tests were run.

Findings

4 total: 0 P0, 1 P1, 1 P2, 2 P3.

Blocking (P1)

  • Invalidate mode-bound cache entries after catalog-less V2 writessql/catalyst/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Relation.scala:122 — see inline.

Non-blocking (P2)

  • Keep the ORC mode bridge out of caller optionssql/core/src/main/scala/org/apache/spark/sql/execution/datasources/FileFormat.scala:314 — see inline.

Nit (P3)

  • Do not claim all relation tags survive canonicalizationsql/core/src/main/scala/org/apache/spark/sql/execution/datasources/ApplyCharTypePadding.scala:58 — see inline.
  • Name the typed scan modes in the V1 cache testsql/core/src/test/scala/org/apache/spark/sql/execution/datasources/SaveIntoDataSourceCommandSuite.scala:76 — see inline.

Re-review status

Prior AI findings: 3 addressed, 0 still present; additional unresolved findings in this review: 4.

New attribution: 3 newly introduced, 1 late catch, 0 previously raised, 0 unattributed.

Remaining prior AI findings

No prior AI findings remain.

Existing discussions

  • existing discussion — The revised comment correctly identifies structural equality as a problem, but its new blanket canonicalization-survival claim is false for LogicalRelation and HiveTableRelation direct canonicalization copies.

@srielau
srielau requested a review from cloud-fan September 8, 2026 19:07

@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.

Review summary

The remaining blocking issue is at the shared relation-cache boundary: analysis-bound charVarcharScanMode is preserved when a cached V2 table relation is reused by a fresh analysis, so the query can inherit another session's semantics and silently truncate an over-length ORC value instead of raising EXCEED_LIMIT_LENGTH. The normal cache key should remain mode-sensitive, but a SharedState table-relation cache hit must be rebound to the current analysis. Non-blocking follow-ups are to keep the FileFormat bridge internal, preserve V2 cache storage across rename, cover the InsertIntoDataSourceCommand and positional ORC branches under bound modes, correct the documented configuration key, and remove three discarded relation copies.

Findings

7 total: 0 P0, 1 P1, 4 P2, 2 P3.

Blocking (P1)

  • Rebind a shared V2 relation to the current analysis modesql/catalyst/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Relation.scala:122 — see inline.

Non-blocking (P2)

  • Keep the scan-mode bridge internal to Spark SQLsql/core/src/main/scala/org/apache/spark/sql/execution/datasources/FileFormat.scala:182 — see inline.
  • Exercise InsertIntoDataSourceCommand recaching in both bound modessql/core/src/main/scala/org/apache/spark/sql/execution/datasources/InsertIntoDataSourceCommand.scala:48 — see inline.
  • Preserve a V2 table cache across rename in bound modessql/core/src/main/scala/org/apache/spark/sql/execution/datasources/v2/DataSourceV2Strategy.scala:75 — see inline.
  • Cover CHAR/VARCHAR checks through positional ORC evolutionsql/core/src/main/scala/org/apache/spark/sql/execution/datasources/orc/OrcUtils.scala:559 — see inline.

Nit (P3)

  • Name the registered standard-semantics configurationsql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/util/CharVarcharScanMode.scala:50 — see inline.
  • Remove discarded relation copies from the padding rulesql/core/src/main/scala/org/apache/spark/sql/execution/datasources/ApplyCharTypePadding.scala:99 — see inline.

Re-review status

Prior AI findings: 4 addressed, 0 still present; additional unresolved findings in this review: 7.

New attribution: 2 newly introduced, 5 late catch, 0 previously raised, 0 unattributed.

Remaining prior AI findings

No prior AI findings remain.

timeTravelSpec: Option[TimeTravelSpec] = None,
// Bound at analysis so sameResult / cache reuse distinguish preserve-only vs standard
// CHAR/VARCHAR scans. None means the relation was not analyzed under first-class types.
charVarcharScanMode: Option[CharVarcharScanMode] = None)

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.

Blocking (P1): Because this mode is analysis-bound, retaining it on a relation returned by the SharedState relation cache lets a fresh analysis inherit the mode that populated the cache. For example, a relation cached under PreserveNative can remain preserve-bound when a later session analyzes it under SparkStandard, allowing an over-length ORC value to appear truncated rather than raising EXCEED_LIMIT_LENGTH. Please clear the mode specifically when adapting a shared cached table relation so the current analysis rebinds it, while preserving the mode stored in persisted view plans, and add a same-SharedState mode-switch regression.

Recommended change: Rebind relations obtained from the shared table-relation cache to the current analysis mode.

Why this works: Clear charVarcharScanMode only on RelationResolution's adaptation of a SharedState cache hit, before ApplyCharTypePadding binds the current SQLConf mode.

Scope: The shared V2 table-relation cache adaptation and a focused cross-session or cross-configuration cache regression.

Compatibility: Keep ordinary cached-plan equality mode-sensitive and preserve analysis-bound mode state embedded in persisted view plans.

Risks: Clearing the field on persisted view plans would regress their caller-independent semantics. Making normal cache equality mode-insensitive would permit reuse of incompatible cached results.

Constraints: Limit rebinding to relations obtained from the SharedState table-relation cache. Exercise two analyses sharing SharedState with different modes and an over-length ORC value.

Success: A fresh SparkStandard analysis cannot inherit PreserveNative from an earlier shared cache entry and raises EXCEED_LIMIT_LENGTH, while persisted views retain their analyzed mode.

def fromName(name: String): CharVarcharScanMode = name match {
case "PreserveNative" => PreserveNative
case "SparkStandard" => SparkStandard
case other => throw new IllegalArgumentException(s"Unknown CharVarcharScanMode: $other")

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.

Nit (P3): This names a configuration that does not exist. The registered key is spark.sql.charVarchar.standardSemantics.enabled; please use that exact name here.

* and a call to `super` retains the bound mode. The Hadoop entry is only a transport across the
* legacy signature; the authoritative state remains the typed plan field and this parameter.
*/
def buildReaderWithPartitionValues(

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.

Non-blocking (P2): This overload is an internal planner bridge, but leaving it public enlarges the supported custom FileFormat surface and can make an existing untyped eta-expansion of buildReaderWithPartitionValues ambiguous. Please make this overload, CHAR_VARCHAR_SCAN_MODE, and CharVarcharScanMode package-private to Spark SQL. Internal callers retain access, and the default implementation can still dispatch through a third-party subclass's existing seven-argument override.

// Re-cache all cached plans(including this relation itself, if it's cached) that refer to this
// data source relation.
sparkSession.sharedState.cacheManager.recacheByPlan(sparkSession, logicalRelation)
sparkSession.sharedState.cacheManager.recacheByV1Relation(

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.

Non-blocking (P2): Please exercise this changed caller with a materialized cache under both PreserveNative and SparkStandard. InsertSuite currently tests this command only with an unbound relation, while the new two-mode regression covers the sibling SaveIntoDataSourceCommand. The focused test should perform the overwrite, verify that the query remains cached, and verify that it returns only the replacement rows; that will fail if this path regresses to mode-sensitive plan matching.

if (conf.readSideCharPadding || standardSemantics) {
val newPlan = boundPlan.resolveOperatorsUpWithNewOutput {
case r: LogicalRelation =>
bindStandardSemantics(r)

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.

Nit (P3): The result of this pure helper is discarded, so an unbound relation is copied and has its tags copied without affecting the plan. The same applies to the standalone calls in the V2 and Hive cases below. Please remove all three; the initial binding traversal and the cleaned-relation thunks already retain the returned nodes that are needed.

cacheManager.recacheTableOrView(session, nameParts, includeTimeTravel = false)
case _ =>
cacheManager.recacheByPlan(session, r)
cacheManager.recacheByV2Relation(session, r)

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.

Non-blocking (P2): This rename path still discovers the old cache entry with normal sameResult semantics. Under either first-class mode the cached relation is bound to Some(mode), while invalidateTableCache constructs an unbound probe, so the lookup returns no storage level and the renamed table is left uncached. Please use the mutation-specific comparison that ignores only charVarcharScanMode here, retain the existing table/time-travel restrictions, and run the rename-cache regression under both bound modes.

Recommended change: Use mode-insensitive relation matching only for V2 rename cache discovery.

Why this works: Route invalidateTableCache's lookup through a mutation-specific relation comparison that ignores charVarcharScanMode while retaining table identity and time-travel exclusions.

Scope: DataSourceV2Strategy's rename invalidation path and the existing V2 rename-cache regression under both bound modes.

Compatibility: Preserve ordinary mode-sensitive cache equality and preserve the cached table's prior storage level across rename.

Risks: A matcher that ignores more than the scan mode could select an unrelated or time-travel cache entry. Changing normal lookupCachedData semantics would allow incompatible read caches to match.

Constraints: Keep the relaxed comparison local to mutation-specific rename discovery. Retain current table identity and time-travel filtering.

Success: Renaming a cached V2 table under PreserveNative or SparkStandard restores it under the new identifier at the prior storage level.

OrcUtils.getOrcSchemaString(resultSchema, charVarcharStandardSemantics)
} else {
OrcUtils.getOrcSchemaString(StructType(dataSchema.fields ++ partitionSchema.fields))
OrcUtils.getOrcSchemaString(

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.

Non-blocking (P2): The new CHAR/VARCHAR matrix reaches only the named-field (canPruneCols=true) path, so this changed full-schema branch has no standard-semantics regression. Please add an OrcSourceSuite case using forced positional evolution or an all-_col physical schema. It should prove that an in-range CHAR is padded and an over-length positional STRING raises EXCEED_LIMIT_LENGTH rather than being truncated natively by ORC.

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