Skip to content

[GLUTEN-12436][VL] Map ORC files by position per-file to match Spark's OrcUtils, and remove the orcUseColumnNames config - #12453

Merged
rui-mo merged 1 commit into
apache:mainfrom
beliefer:new_12436
Jul 31, 2026
Merged

[GLUTEN-12436][VL] Map ORC files by position per-file to match Spark's OrcUtils, and remove the orcUseColumnNames config#12453
rui-mo merged 1 commit into
apache:mainfrom
beliefer:new_12436

Conversation

@beliefer

@beliefer beliefer commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

What changes are proposed in this pull request?

Fixies #12436. This PR is related to facebookincubator/velox#18027
Gluten currently chooses ORC/DWRF column mapping (by name vs. by position) globally for a whole session, driven by
spark.gluten.sql.columnar.backend.velox.orcUseColumnNames (default true, map by name) combined with an override for orc.force.positional.evolution.

Vanilla Spark does not work that way. OrcUtils.requestedColumnIds decides the mapping mode per file:

if (forcePositionalEvolution || orcFieldNames.forall(_.startsWith("_col"))) {
  // map physical schema to data schema by index (position)
} else {
  // map by name
}

Because Gluten only had a single global switch, it could not match Spark when a single query touches ORC tables that require opposite mapping modes — for example a join between:

  • a table with real physical column names (must be read by name), and
  • a table written by old Hive whose physical schema is all placeholder names _col0, _col1, ... (must be read by position).

With the global switch, one value is always wrong for one of the tables: orcUseColumnNames=true reads the _col* table back as NULL (name lookup fails), while setting orc.force.positional.evolution=true globally forces the real-named table to be read by position and mis-binds its columns.

This PR aligns Gluten with Spark's per-file behavior (backed by a companion Velox change) and, as requested, removes the now-redundant orcUseColumnNames config and its accessor.

How the per-file decision works now

The native (Velox) reader makes the decision per ORC/DWRF file:

  • a file whose physical schema is all _col* placeholder names is mapped by position, regardless of any flag;
  • a file is also mapped by position when orc.force.positional.evolution=true is forwarded to native;
  • otherwise the file is mapped by name (the default).

For this to work, Gluten must always hand the table (data) schema to the native reader for ORC/DWRF so it has a target to remap file columns to.

Notes

This is the Gluten side of a two-part change. The companion Velox change (see: facebookincubator/velox#18027) adds per-file positional mapping in the DWRF reader: it maps a file by position when the physical schema is all _col* placeholder names or when the orc.force-positional-evolution session property is set. This Gluten PR depends on that Velox change being present in the Velox build.

Before / after behavior comparison

The table below compares how an ORC/DWRF file is read before and after this PR. "By name" means table columns are matched to file columns by field name; "by position" means by column index. orcUseColumnNames is the removed Gluten config; orc.force.positional.evolution is the standard Spark/Hive property (unchanged, still honored).

Scenario Before (with orcUseColumnNames) After (this PR) Changed?
File has real physical names, default config (orcUseColumnNames=true) By name By name No
File has all-_col* placeholder names, default config By name → columns read back as NULL/empty (name lookup fails) Detected per file, read by position Yes (bug fixed)
orc.force.positional.evolution=true By position (whole scan) By position (whole scan) No
Mixed scan: one file real-named + one file _col* (e.g. a join), default config Impossible to get both right with a single global switch Each file decided independently (real → by name, _col* → by position) Yes (bug fixed)
File has real physical names, orcUseColumnNames=false By position (a Gluten-only behavior; vanilla Spark never maps real names by position) Config removed → read by name; use orc.force.positional.evolution=true to force position Yes (breaking)

The only breaking row is the last one: reading a file with real physical names positionally by index. That was a Gluten-only extension with no vanilla Spark equivalent. Users who relied on it should set orc.force.positional.evolution=true, which produces the same positional mapping through the Spark-compatible path. All other rows are either unchanged or fix a pre-existing correctness bug.

Why removing the config is safe

The only capability lost by removing orcUseColumnNames=false is "read a file with real physical names positionally by index" — which vanilla Spark does not do. Spark only maps by position when the file is all _col* or
orc.force.positional.evolution=true, both of which are still supported (and now per-file). This removes a Gluten-only divergence and makes ORC reads match Spark.

How was this patch tested?

  • gluten-ut (spark33/34/35/40/41) GlutenHiveSQLQuerySuite — new regression test: two ORC tables over the same _col* files (placeholder names) plus a real-named table, all read in one session without setting the positional flag. Asserts the _col* table reads correctly (positional fallback), the real-named table reads correctly by name (opposite mode), and a join of the two returns a non-empty result (the original failure folded the join to an empty LocalTableScan).
  • VeloxScanSuite — the former "ORC index based schema evolution" test (which relied on orcUseColumnNames=false to read real-named files by index, a non-Spark behavior) is rewritten as "ORC positional schema evolution" using orc.force.positional.evolution=true, which produces the same positional mapping via the Spark-compatible path. Expected results are unchanged.
  • FallbackSuite — the "fallback with index based schema evolution" test drops the orcUseColumnNames dimension (the parquet dimension is retained).

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

co-authored Claude code.

Copilot AI review requested due to automatic review settings July 6, 2026 09:28

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.

Pull request overview

Note

Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.

Aligns Gluten’s ORC/DWRF column mapping behavior with vanilla Spark by enabling per-file mapping decisions (name vs. position) and removing the redundant global orcUseColumnNames flag.

Changes:

  • Added regression tests ensuring _col* ORC files fall back to positional mapping without setting orc.force.positional.evolution, while real-named ORC files in the same session still map by name.
  • Replaced the removed Velox ORC “use column names” session behavior with forwarding Spark’s orc.force.positional.evolution into a new native Velox flag.
  • Updated Velox iterator/schema attachment and cleaned up configs/docs/tests referencing orcUseColumnNames.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
gluten-ut/spark33..41/.../GlutenHiveSQLQuerySuite.scala Adds a regression test for mixed per-file ORC column mapping modes within one session/query.
gluten-substrait/.../GlutenConfig.scala Forwards Spark positional evolution flag to native via a new Velox config key.
cpp/velox/utils/ConfigExtractor.cc Forces ORC name-mapping default and forwards new positional evolution session property to Velox.
cpp/velox/config/VeloxConfig.h Replaces old ORC config constant with the new positional evolution config key.
backends-velox/.../VeloxIteratorApi.scala Always attaches schema for ORC/DWRF to enable per-file mapping in native reader.
backends-velox/.../VeloxConfig.scala Removes orcUseColumnNames config and accessor.
backends-velox/.../VeloxScanSuite.scala Updates ORC evolution test to use Spark’s orc.force.positional.evolution.
backends-velox/.../FallbackSuite.scala Removes ORC config dimension now that orcUseColumnNames is gone.
backends-velox/.../VeloxBackend.scala Adjusts schema validation gating after ORC orcUseColumnNames removal.
docs/velox-configuration.md Removes orcUseColumnNames from documented configuration list.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +167 to +168
val colStarLoc = s"file:///$dir/test_orc_colstar"
val namedLoc = s"file:///$dir/test_orc_named"
Comment on lines +152 to +162
testGluten(
"GLUTEN: Hive ORC files with _col* names read by position without positional flag") {
// Regression for the case where two ORC tables must use OPPOSITE column
// mapping modes in the same query: one with real column names (by name) and
// one written by old Hive with placeholder _col* names (by position). The
// native reader must decide the mode per file (matching vanilla Spark's
// OrcUtils.requestedColumnIds), so a _col* file reads correctly even though
// orc.force.positional.evolution is NOT set (ORC is read by name by
// default). Without the fix the _col* columns would read back as NULL.
val hiveClient: HiveClient =
spark.sharedState.externalCatalog.unwrapped.asInstanceOf[HiveExternalCatalog].client
Comment on lines 57 to 58
| spark.gluten.sql.columnar.backend.velox.orc.scan.enabled | 🔄 Dynamic | true | Enable velox orc scan. If disabled, vanilla spark orc scan will be used. |
| spark.gluten.sql.columnar.backend.velox.orcUseColumnNames | 🔄 Dynamic | true | Maps table field names to file field names using names, not indices for ORC files. |
| spark.gluten.sql.columnar.backend.velox.parquet.dictionaryPageSizeBytes | 🔄 Dynamic | 2MB | The maximum size in bytes for a Parquet dictionary page |
Comment on lines 595 to 602
if (
backendName == "velox" &&
conf.getOrElse(SPARK_ORC_FORCE_POSITIONAL_EVOLUTION, "false").toBoolean
) {
nativeConfMap.put("spark.gluten.sql.columnar.backend.velox.orcUseColumnNames", "false")
nativeConfMap.put(
"spark.gluten.sql.columnar.backend.velox.orcForcePositionalEvolution",
"true")
}
@github-actions

github-actions Bot commented Jul 6, 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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Comment on lines 235 to 238
def validateDataSchema(): Option[String] = {
if (VeloxConfig.get.parquetUseColumnNames && VeloxConfig.get.orcUseColumnNames) {
if (VeloxConfig.get.parquetUseColumnNames) {
return None
}
Copilot AI review requested due to automatic review settings July 8, 2026 09:28
@github-actions

github-actions Bot commented Jul 8, 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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 1 comment.

Comment on lines 235 to 238
def validateDataSchema(): Option[String] = {
if (VeloxConfig.get.parquetUseColumnNames && VeloxConfig.get.orcUseColumnNames) {
if (VeloxConfig.get.parquetUseColumnNames) {
return None
}
Copilot AI review requested due to automatic review settings July 8, 2026 09:40
@github-actions

github-actions Bot commented Jul 8, 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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Comment on lines +236 to 238
if (VeloxConfig.get.parquetUseColumnNames) {
return None
}
Comment on lines 316 to 338
format =>
Seq("true", "false").foreach {
parquetUseColumnNames =>
Seq("true", "false").foreach {
orcUseColumnNames =>
withSQLConf(
VeloxConfig.PARQUET_USE_COLUMN_NAMES.key -> parquetUseColumnNames,
VeloxConfig.ORC_USE_COLUMN_NAMES.key -> orcUseColumnNames
) {
withTable("test") {
spark
.range(100)
.selectExpr("to_timestamp_ntz(from_unixtime(id % 3)) as c1", "id as c2")
.write
.format(format)
.saveAsTable("test")

runQueryAndCompare(query) {
df =>
val plan = df.queryExecution.executedPlan
assert(collect(plan) { case g: GlutenPlan => g }.nonEmpty)
}
}
withSQLConf(
VeloxConfig.PARQUET_USE_COLUMN_NAMES.key -> parquetUseColumnNames
) {
withTable("test") {
spark
.range(100)
.selectExpr("to_timestamp_ntz(from_unixtime(id % 3)) as c1", "id as c2")
.write
.format(format)
.saveAsTable("test")

runQueryAndCompare(query) {
df =>
val plan = df.queryExecution.executedPlan
assert(collect(plan) { case g: GlutenPlan => g }.nonEmpty)
}
}
}
}
}
Copilot AI review requested due to automatic review settings July 9, 2026 09:32
@github-actions

github-actions Bot commented Jul 9, 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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated 2 comments.

Comment on lines 235 to 238
def validateDataSchema(): Option[String] = {
if (VeloxConfig.get.parquetUseColumnNames && VeloxConfig.get.orcUseColumnNames) {
if (VeloxConfig.get.parquetUseColumnNames) {
return None
}
Comment on lines 315 to +321
Seq("parquet", "orc").foreach {
format =>
Seq("true", "false").foreach {
parquetUseColumnNames =>
Seq("true", "false").foreach {
orcUseColumnNames =>
withSQLConf(
VeloxConfig.PARQUET_USE_COLUMN_NAMES.key -> parquetUseColumnNames,
VeloxConfig.ORC_USE_COLUMN_NAMES.key -> orcUseColumnNames
) {
withTable("test") {
spark
.range(100)
.selectExpr("to_timestamp_ntz(from_unixtime(id % 3)) as c1", "id as c2")
.write
.format(format)
.saveAsTable("test")

runQueryAndCompare(query) {
df =>
val plan = df.queryExecution.executedPlan
assert(collect(plan) { case g: GlutenPlan => g }.nonEmpty)
}
}
withSQLConf(
VeloxConfig.PARQUET_USE_COLUMN_NAMES.key -> parquetUseColumnNames
) {
Copilot AI review requested due to automatic review settings July 28, 2026 02:58
@github-actions

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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxBackend.scala:238

  • validateDataSchema() now short-circuits solely on parquetUseColumnNames, but VeloxIteratorApi attaches the table schema for ORC/DWRF unconditionally. That means ORC/DWRF scans may pass unsupported Spark data types down to Velox without this validation (previously guarded by orcUseColumnNames). Consider validating the table schema whenever it will be sent to native: always for ORC/DWRF, and for Parquet only when parquetUseColumnNames=false.
    def validateDataSchema(): Option[String] = {
      if (VeloxConfig.get.parquetUseColumnNames) {
        return None
      }

Copilot AI review requested due to automatic review settings July 28, 2026 06:37
@github-actions

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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxBackend.scala:242

  • validateDataSchema() now only runs when Parquet is using index-based mapping, but VeloxIteratorApi always attaches dataSchema for ORC/DWRF scans as well. That means unsupported Spark data types in an ORC/DWRF table schema could reach native without pre-validation/fallback, potentially causing runtime failures that this validator is meant to avoid. Consider triggering the same schema-type validation whenever the scan format is ORC/DWRF (since schema is always forwarded there) in addition to the existing Parquet index-mapping case.
    def validateDataSchema(): Option[String] = {
      if (VeloxConfig.get.parquetUseColumnNames) {
        return None
      }

      // If we are using column indices for schema evolution, we need to pass the table schema to
      // Velox. We need to ensure all types in the table schema are supported.
      val validationResults =

…OrcUtils, and remove the orcUseColumnNames config
Copilot AI review requested due to automatic review settings July 28, 2026 09:15
@github-actions

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.

Pull request overview

Copilot reviewed 14 out of 14 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

backends-velox/src/main/scala/org/apache/gluten/backendsapi/velox/VeloxBackend.scala:238

  • validateDataSchema() now skips validation whenever parquetUseColumnNames is true, but this validation also becomes relevant for ORC/DWRF after this PR because VeloxIteratorApi always attaches the table schema for ORC/DWRF scans. Skipping validation in mixed-format tables (or pure ORC/DWRF scans) can let unsupported Spark types reach native and fail later at runtime instead of cleanly falling back.
    def validateDataSchema(): Option[String] = {
      if (VeloxConfig.get.parquetUseColumnNames) {
        return None
      }

@beliefer

beliefer commented Jul 29, 2026

Copy link
Copy Markdown
Contributor Author

@beliefer
beliefer requested review from rui-mo, wForget and zml1206 July 29, 2026 03:26

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

Thanks @beliefer. It looks good to me to bring the ORC reader's position/name selection behavior fully in line with Spark. One concern is that this change could be a breaking change for existing users who rely on this option. Could you add a before-and-after behavior comparison to the PR description?

@beliefer

Copy link
Copy Markdown
Contributor Author

Thanks @beliefer. It looks good to me to bring the ORC reader's position/name selection behavior fully in line with Spark. One concern is that this change could be a breaking change for existing users who rely on this option. Could you add a before-and-after behavior comparison to the PR description?

Updated.

@rui-mo
rui-mo merged commit c0637cc into apache:main Jul 31, 2026
133 of 134 checks passed
@beliefer

beliefer commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

@rui-mo Thank you!

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

Labels

CORE works for Gluten Core DOCS VELOX

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants