This is an automated email from the ASF dual-hosted git repository.

jackylee-ch pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gluten.git


The following commit(s) were added to refs/heads/main by this push:
     new 79f0040e9f [VL] Support storeDecimalAsInteger for parquet native write 
compatible with writeLegacyFormat (#12213)
79f0040e9f is described below

commit 79f0040e9fd6fad7e4f3b3d28132fc498df03d5e
Author: lifulong <[email protected]>
AuthorDate: Tue Jun 23 18:31:40 2026 +0800

    [VL] Support storeDecimalAsInteger for parquet native write compatible with 
writeLegacyFormat (#12213)
---
 .../velox/VeloxParquetWriterInjects.scala          |  3 ++
 .../sql/execution/VeloxParquetWriteSuite.scala     | 36 ++++++++++++++++++++++
 cpp/core/config/GlutenConfig.h                     |  8 +++++
 cpp/velox/utils/VeloxWriterUtils.cc                | 14 +++++++++
 .../org/apache/gluten/config/GlutenConfig.scala    |  1 +
 5 files changed, 62 insertions(+)

diff --git 
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/datasources/velox/VeloxParquetWriterInjects.scala
 
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/datasources/velox/VeloxParquetWriterInjects.scala
index 14f1c6d63b..1244aab279 100644
--- 
a/backends-velox/src/main/scala/org/apache/spark/sql/execution/datasources/velox/VeloxParquetWriterInjects.scala
+++ 
b/backends-velox/src/main/scala/org/apache/spark/sql/execution/datasources/velox/VeloxParquetWriterInjects.scala
@@ -31,6 +31,9 @@ class VeloxParquetWriterInjects extends 
VeloxFormatWriterInjects {
     // i.e., compression, block size, block rows.
     val sparkOptions = new mutable.HashMap[String, String]()
     sparkOptions.put(SQLConf.PARQUET_COMPRESSION.key, compressionCodec)
+    sparkOptions.put(
+      SQLConf.PARQUET_WRITE_LEGACY_FORMAT.key,
+      SQLConf.get.writeLegacyParquetFormat.toString)
     val blockSize = options.getOrElse(
       GlutenConfig.PARQUET_BLOCK_SIZE,
       GlutenConfig.get.columnarParquetWriteBlockSize.toString)
diff --git 
a/backends-velox/src/test/scala/org/apache/spark/sql/execution/VeloxParquetWriteSuite.scala
 
b/backends-velox/src/test/scala/org/apache/spark/sql/execution/VeloxParquetWriteSuite.scala
index 9de24103b0..fcd5d45e6c 100644
--- 
a/backends-velox/src/test/scala/org/apache/spark/sql/execution/VeloxParquetWriteSuite.scala
+++ 
b/backends-velox/src/test/scala/org/apache/spark/sql/execution/VeloxParquetWriteSuite.scala
@@ -21,11 +21,13 @@ import 
org.apache.gluten.execution.VeloxWholeStageTransformerSuite
 
 import org.apache.spark.SparkConf
 import org.apache.spark.sql.Row
+import org.apache.spark.sql.internal.SQLConf
 import org.apache.spark.util.Utils
 
 import org.apache.hadoop.fs.Path
 import org.apache.parquet.hadoop.ParquetFileReader
 import org.apache.parquet.hadoop.util.HadoopInputFile
+import org.apache.parquet.schema.PrimitiveType
 
 import java.io.File
 
@@ -74,6 +76,40 @@ class VeloxParquetWriteSuite extends 
VeloxWholeStageTransformerSuite with WriteU
     }
   }
 
+  test("test write parquet decimal with writeLegacyFormat") {
+    // writeLegacyFormat details see `VeloxWriterUtils.cc`
+    Seq(
+      false -> PrimitiveType.PrimitiveTypeName.INT32,
+      true -> PrimitiveType.PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY)
+      .foreach {
+        case (legacyFormat, expectedType) =>
+          withSQLConf(SQLConf.PARQUET_WRITE_LEGACY_FORMAT.key -> 
legacyFormat.toString) {
+            withTempPath {
+              f =>
+                val path = f.getCanonicalPath
+                val expected = spark.sql("SELECT CAST(123.456 AS DECIMAL(8, 
3)) AS value")
+                checkNativeWrite(
+                  s"INSERT OVERWRITE DIRECTORY '$path' USING PARQUET " +
+                    "SELECT CAST(123.456 AS DECIMAL(8, 3)) AS value")
+                val parquetFiles = f.list((_, name) => 
name.contains("parquet"))
+                assert(parquetFiles.nonEmpty)
+                parquetFiles.foreach {
+                  file =>
+                    val filePath = new Path(path, file)
+                    val in = HadoopInputFile.fromPath(filePath, 
spark.sessionState.newHadoopConf())
+                    Utils.tryWithResource(ParquetFileReader.open(in)) {
+                      reader =>
+                        val physicalType = 
reader.getFooter.getFileMetaData.getSchema
+                          
.getFields.get(0).asPrimitiveType.getPrimitiveTypeName
+                        assert(physicalType == expectedType)
+                    }
+                }
+                checkAnswer(spark.read.parquet(path), expected)
+            }
+          }
+      }
+  }
+
   test("test write parquet with compression codec") {
     // compression codec details see `VeloxParquetDatasource.cc`
     Seq("snappy", "gzip", "zstd", "lz4", "none", "uncompressed")
diff --git a/cpp/core/config/GlutenConfig.h b/cpp/core/config/GlutenConfig.h
index 16140a3ac3..3dd99e4cf3 100644
--- a/cpp/core/config/GlutenConfig.h
+++ b/cpp/core/config/GlutenConfig.h
@@ -74,6 +74,14 @@ const std::string kParquetWriterVersion = 
"parquet.writer.version";
 
 const std::string kParquetCompressionCodec = 
"spark.sql.parquet.compression.codec";
 
+/// Spark `spark.sql.parquet.writeLegacyFormat` (passed from the JVM as 
"true"/"false").
+/// Used in VeloxWriterUtils to set 
`WriterOptions::enableStoreDecimalAsInteger` (inverted).
+/// Velox decimal storage when enableStoreDecimalAsInteger is:
+/// - true (Spark legacy off / unset): INT32/INT64 for short DECIMAL 
precisions; higher precisions
+///   use FIXED_LEN_BYTE_ARRAY.
+/// - false (Spark legacy on): FIXED_LEN_BYTE_ARRAY for all DECIMAL precisions.
+const std::string kParquetStoreDecimalAsInteger = 
"spark.sql.parquet.writeLegacyFormat";
+
 const std::string kColumnarToRowMemoryThreshold = 
"spark.gluten.sql.columnarToRowMemoryThreshold";
 
 const std::string kUGIUserName = "spark.gluten.ugi.username";
diff --git a/cpp/velox/utils/VeloxWriterUtils.cc 
b/cpp/velox/utils/VeloxWriterUtils.cc
index bd3cca6854..5616cd9481 100644
--- a/cpp/velox/utils/VeloxWriterUtils.cc
+++ b/cpp/velox/utils/VeloxWriterUtils.cc
@@ -49,6 +49,20 @@ std::unique_ptr<WriterOptions> makeParquetWriteOption(const 
std::unordered_map<s
   }
   auto writeOption = std::make_unique<WriterOptions>();
   writeOption->parquetWriteTimestampUnit = TimestampPrecision::kMicroseconds 
/*micro*/;
+  bool writeLegacyParquetFormat = false;
+  if (auto it = sparkConfs.find(kParquetStoreDecimalAsInteger); it != 
sparkConfs.end()) {
+    writeLegacyParquetFormat = boost::iequals(it->second, "true");
+  }
+  // Maps spark.sql.parquet.writeLegacyFormat to Velox 
enableStoreDecimalAsInteger (inverted).
+  // Controls how DECIMAL values are stored by the Writer:
+  // - true (default when legacy format is off): store as integer using 
INT32/INT64 for short
+  //   DECIMAL precisions; higher precisions use FIXED_LEN_BYTE_ARRAY.
+  // - false (when spark.sql.parquet.writeLegacyFormat is true): store as 
FIXED_LEN_BYTE_ARRAY
+  //   regardless of precision (Spark legacy Parquet decimal layout).
+  // TODO: Only DECIMAL is handled here. Spark's writeLegacyFormat also 
changes ARRAY (bag/array
+  // vs list/element) and MAP (map vs key_value) physical layouts, which are 
not yet supported
+  // in Gluten native Parquet write.
+  writeOption->enableStoreDecimalAsInteger = !writeLegacyParquetFormat;
   auto compressionCodec = CompressionKind::CompressionKind_SNAPPY;
   if (auto it = sparkConfs.find(kParquetCompressionCodec); it != 
sparkConfs.end()) {
     auto compressionCodecStr = it->second;
diff --git 
a/gluten-substrait/src/main/scala/org/apache/gluten/config/GlutenConfig.scala 
b/gluten-substrait/src/main/scala/org/apache/gluten/config/GlutenConfig.scala
index 14610868eb..053d715125 100644
--- 
a/gluten-substrait/src/main/scala/org/apache/gluten/config/GlutenConfig.scala
+++ 
b/gluten-substrait/src/main/scala/org/apache/gluten/config/GlutenConfig.scala
@@ -652,6 +652,7 @@ object GlutenConfig extends ConfigRegistry {
       DEBUG_ENABLED.key,
       // datasource config
       SPARK_SQL_PARQUET_COMPRESSION_CODEC,
+      SQLConf.PARQUET_WRITE_LEGACY_FORMAT.key,
       // datasource config end
       GlutenCoreConfig.COLUMNAR_OVERHEAD_SIZE_IN_BYTES.key,
       GlutenCoreConfig.COLUMNAR_OFFHEAP_SIZE_IN_BYTES.key,


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to