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

yiguolei pushed a commit to branch branch-4.2
in repository https://gitbox.apache.org/repos/asf/doris.git


The following commit(s) were added to refs/heads/branch-4.2 by this push:
     new 3eebe290108 [fix](iceberg) Support GZIP for iceberg/hive Parquet 
writers. (#68854) (#69338)
3eebe290108 is described below

commit 3eebe2901081b139ad6d87d24d884aa93a6b99eb
Author: daidai <[email protected]>
AuthorDate: Thu Oct 22 09:58:37 2026 +0800

    [fix](iceberg) Support GZIP for iceberg/hive Parquet writers. (#68854) 
(#69338)
    
    pick #68854
---
 .../writer/iceberg/viceberg_delete_file_writer.cpp | 28 +++------
 .../writer/iceberg/viceberg_partition_writer.cpp   | 46 ++++++---------
 be/src/exec/sink/writer/vhive_partition_writer.cpp | 34 +++--------
 be/src/format/transformer/vparquet_writer.cpp      | 19 ++++++
 be/src/format/transformer/vparquet_writer.h        | 11 ++++
 .../iceberg/iceberg_partition_writer_test.cpp      | 28 +++++++++
 .../org/apache/doris/datasource/hive/HiveUtil.java |  2 +-
 .../apache/doris/datasource/hive/HiveUtilTest.java | 47 +++++++++++++++
 .../hive/ddl/test_hive_ddl.groovy                  | 27 ++++++++-
 .../test_iceberg_write_parquet_compression.groovy  | 69 +++++++++++++++++++---
 10 files changed, 228 insertions(+), 83 deletions(-)

diff --git a/be/src/exec/sink/writer/iceberg/viceberg_delete_file_writer.cpp 
b/be/src/exec/sink/writer/iceberg/viceberg_delete_file_writer.cpp
index 8ad7751e1ed..5fb4fabb1ce 100644
--- a/be/src/exec/sink/writer/iceberg/viceberg_delete_file_writer.cpp
+++ b/be/src/exec/sink/writer/iceberg/viceberg_delete_file_writer.cpp
@@ -23,6 +23,7 @@
 #include "format/table/iceberg/types.h"
 #include "format/transformer/viceberg_parquet_writer.h"
 #include "format/transformer/vorc_transformer.h"
+#include "format/transformer/vparquet_writer.h"
 #include "io/file_factory.h"
 #include "runtime/runtime_state.h"
 
@@ -77,30 +78,16 @@ Status VIcebergDeleteFileWriter::open(RuntimeState* state, 
RuntimeProfile* profi
     _fs = DORIS_TRY(FileFactory::create_fs(fs_properties, file_description));
 
     io::FileWriterOptions file_writer_options = {.used_by_s3_committer = 
false};
-    RETURN_IF_ERROR(_fs->create_file(file_description.path, &_file_writer, 
&file_writer_options));
+    auto create_file = [&]() {
+        return _fs->create_file(file_description.path, &_file_writer, 
&file_writer_options);
+    };
 
     // Create file format transformer based on format type
     switch (_file_format) {
     case TFileFormatType::FORMAT_PARQUET: {
-        TParquetCompressionType::type parquet_compression_type;
-        switch (_compress_type) {
-        case TFileCompressType::PLAIN:
-            parquet_compression_type = TParquetCompressionType::UNCOMPRESSED;
-            break;
-        case TFileCompressType::SNAPPYBLOCK:
-            parquet_compression_type = TParquetCompressionType::SNAPPY;
-            break;
-        case TFileCompressType::ZSTD:
-            parquet_compression_type = TParquetCompressionType::ZSTD;
-            break;
-        case TFileCompressType::LZ4BLOCK:
-            // Hadoop-framed Parquet LZ4 (not LZ4_RAW) for cross-engine 
compatibility.
-            parquet_compression_type = TParquetCompressionType::LZ4_HADOOP;
-            break;
-        default:
-            return Status::InternalError("Unsupported compress type {} with 
parquet",
-                                         to_string(_compress_type));
-        }
+        TParquetCompressionType::type parquet_compression_type =
+                
DORIS_TRY(ParquetBuildHelper::to_parquet_compression_type(_compress_type));
+        RETURN_IF_ERROR(create_file());
 
         ParquetFileOptions parquet_options = {parquet_compression_type,
                                               TParquetVersion::PARQUET_1_0, 
false, false};
@@ -110,6 +97,7 @@ Status VIcebergDeleteFileWriter::open(RuntimeState* state, 
RuntimeProfile* profi
         return _file_format_transformer->open();
     }
     case TFileFormatType::FORMAT_ORC: {
+        RETURN_IF_ERROR(create_file());
         _file_format_transformer.reset(new VOrcTransformer(state, 
_file_writer.get(), output_exprs,
                                                            "", column_names, 
false, _compress_type,
                                                            
_position_delete_schema.get()));
diff --git a/be/src/exec/sink/writer/iceberg/viceberg_partition_writer.cpp 
b/be/src/exec/sink/writer/iceberg/viceberg_partition_writer.cpp
index 565e47b4686..b84efb0bb06 100644
--- a/be/src/exec/sink/writer/iceberg/viceberg_partition_writer.cpp
+++ b/be/src/exec/sink/writer/iceberg/viceberg_partition_writer.cpp
@@ -25,6 +25,7 @@
 #include "format/table/iceberg/schema.h"
 #include "format/transformer/viceberg_parquet_writer.h"
 #include "format/transformer/vorc_transformer.h"
+#include "format/transformer/vparquet_writer.h"
 #include "io/file_factory.h"
 #include "runtime/runtime_state.h"
 
@@ -93,35 +94,16 @@ Status VIcebergPartitionWriter::open(RuntimeState* state, 
RuntimeProfile* profil
     io::FileDescription file_description = {.path = _path, .fs_name {}};
     _fs = DORIS_TRY(FileFactory::create_fs(fs_properties, file_description));
     io::FileWriterOptions file_writer_options = {.used_by_s3_committer = 
false};
-    RETURN_IF_ERROR(_fs->create_file(file_description.path, &_file_writer, 
&file_writer_options));
+    auto create_file = [&]() {
+        return _fs->create_file(file_description.path, &_file_writer, 
&file_writer_options);
+    };
 
+    Status open_status;
     switch (_file_format_type) {
     case TFileFormatType::FORMAT_PARQUET: {
-        TParquetCompressionType::type parquet_compression_type;
-        switch (_compress_type) {
-        case TFileCompressType::PLAIN: {
-            parquet_compression_type = TParquetCompressionType::UNCOMPRESSED;
-            break;
-        }
-        case TFileCompressType::SNAPPYBLOCK: {
-            parquet_compression_type = TParquetCompressionType::SNAPPY;
-            break;
-        }
-        case TFileCompressType::ZSTD: {
-            parquet_compression_type = TParquetCompressionType::ZSTD;
-            break;
-        }
-        case TFileCompressType::LZ4BLOCK: {
-            // Map Doris LZ4 to the Hadoop-framed Parquet LZ4 codec (not 
LZ4_RAW) so the file
-            // stays readable across Spark/Iceberg/Trino. See 
ParquetBuildHelper.
-            parquet_compression_type = TParquetCompressionType::LZ4_HADOOP;
-            break;
-        }
-        default: {
-            return Status::InternalError("Unsupported compress type {} with 
parquet",
-                                         to_string(_compress_type));
-        }
-        }
+        TParquetCompressionType::type parquet_compression_type =
+                
DORIS_TRY(ParquetBuildHelper::to_parquet_compression_type(_compress_type));
+        RETURN_IF_ERROR(create_file());
         // Parquet 1.0 coerces Arrow nanoseconds to microseconds, including 
nested fields.
         ParquetFileOptions parquet_options = {
                 .compression_type = parquet_compression_type,
@@ -133,19 +115,27 @@ Status VIcebergPartitionWriter::open(RuntimeState* state, 
RuntimeProfile* profil
         _file_format_transformer = std::make_unique<VIcebergParquetWriter>(
                 state, _file_writer.get(), _write_output_expr_ctxs, 
_write_column_names, false,
                 parquet_options, _iceberg_schema_json, _schema);
-        return _file_format_transformer->open();
+        open_status = _file_format_transformer->open();
+        break;
     }
     case TFileFormatType::FORMAT_ORC: {
+        RETURN_IF_ERROR(create_file());
         _file_format_transformer = std::make_unique<VOrcTransformer>(
                 state, _file_writer.get(), _write_output_expr_ctxs, "", 
_write_column_names, false,
                 _compress_type, &_schema, _fs);
-        return _file_format_transformer->open();
+        open_status = _file_format_transformer->open();
+        break;
     }
     default: {
         return Status::InternalError("Unsupported file format type {}",
                                      to_string(_file_format_type));
     }
     }
+    if (!open_status.ok()) {
+        // A transformer failure happens after object creation, so remove any 
published file.
+        WARN_IF_ERROR(_fs->delete_file(_path), "failed to delete Iceberg file 
after open error");
+    }
+    return open_status;
 }
 
 Status VIcebergPartitionWriter::close(const Status& status) {
diff --git a/be/src/exec/sink/writer/vhive_partition_writer.cpp 
b/be/src/exec/sink/writer/vhive_partition_writer.cpp
index 6a116aa9254..a480853bd3f 100644
--- a/be/src/exec/sink/writer/vhive_partition_writer.cpp
+++ b/be/src/exec/sink/writer/vhive_partition_writer.cpp
@@ -24,6 +24,7 @@
 #include "format/transformer/vcsv_transformer.h"
 #include "format/transformer/vhive_parquet_writer.h"
 #include "format/transformer/vorc_transformer.h"
+#include "format/transformer/vparquet_writer.h"
 #include "io/file_factory.h"
 #include "io/fs/s3_file_writer.h"
 #include "runtime/runtime_state.h"
@@ -69,34 +70,15 @@ Status VHivePartitionWriter::open(RuntimeState* state, 
RuntimeProfile* operator_
             .fs_name {}};
     _fs = DORIS_TRY(FileFactory::create_fs(fs_properties, file_description));
     io::FileWriterOptions file_writer_options = {.used_by_s3_committer = true};
-    RETURN_IF_ERROR(_fs->create_file(file_description.path, &_file_writer, 
&file_writer_options));
+    auto create_file = [&]() {
+        return _fs->create_file(file_description.path, &_file_writer, 
&file_writer_options);
+    };
 
     switch (_file_format_type) {
     case TFileFormatType::FORMAT_PARQUET: {
-        TParquetCompressionType::type parquet_compression_type;
-        switch (_hive_compress_type) {
-        case TFileCompressType::PLAIN: {
-            parquet_compression_type = TParquetCompressionType::UNCOMPRESSED;
-            break;
-        }
-        case TFileCompressType::SNAPPYBLOCK: {
-            parquet_compression_type = TParquetCompressionType::SNAPPY;
-            break;
-        }
-        case TFileCompressType::ZSTD: {
-            parquet_compression_type = TParquetCompressionType::ZSTD;
-            break;
-        }
-        case TFileCompressType::LZ4BLOCK: {
-            // Hadoop-framed Parquet LZ4 (not LZ4_RAW) for cross-engine 
compatibility.
-            parquet_compression_type = TParquetCompressionType::LZ4_HADOOP;
-            break;
-        }
-        default: {
-            return Status::InternalError("Unsupported hive compress type {} 
with parquet",
-                                         to_string(_hive_compress_type));
-        }
-        }
+        TParquetCompressionType::type parquet_compression_type =
+                
DORIS_TRY(ParquetBuildHelper::to_parquet_compression_type(_hive_compress_type));
+        RETURN_IF_ERROR(create_file());
         // TODO: INT96 is kept for Hive 2/3 compatibility. Add an explicit 
option before
         // changing the default Hive parquet timestamp encoding to standard 
logical types.
         ParquetFileOptions parquet_options = {parquet_compression_type,
@@ -113,12 +95,14 @@ Status VHivePartitionWriter::open(RuntimeState* state, 
RuntimeProfile* operator_
         return _file_format_transformer->open();
     }
     case TFileFormatType::FORMAT_ORC: {
+        RETURN_IF_ERROR(create_file());
         _file_format_transformer = std::make_unique<VOrcTransformer>(
                 state, _file_writer.get(), _write_output_expr_ctxs, "", 
_write_column_names, false,
                 _hive_compress_type);
         return _file_format_transformer->open();
     }
     case TFileFormatType::FORMAT_CSV_PLAIN: {
+        RETURN_IF_ERROR(create_file());
         // TODO(syt): support hive csv table, only hive text file is 
supportted now
         _file_format_transformer = std::make_unique<VCSVTransformer>(
                 state, _file_writer.get(), _write_output_expr_ctxs, false, 
"csv", "",
diff --git a/be/src/format/transformer/vparquet_writer.cpp 
b/be/src/format/transformer/vparquet_writer.cpp
index d019e43a8de..7ec66e56b28 100644
--- a/be/src/format/transformer/vparquet_writer.cpp
+++ b/be/src/format/transformer/vparquet_writer.cpp
@@ -108,6 +108,25 @@ void ParquetOutputStream::set_written_len(int64_t 
written_len) {
     _written_len = written_len;
 }
 
+Result<TParquetCompressionType::type> 
ParquetBuildHelper::to_parquet_compression_type(
+        TFileCompressType::type compression_type) {
+    switch (compression_type) {
+    case TFileCompressType::PLAIN:
+        return TParquetCompressionType::UNCOMPRESSED;
+    case TFileCompressType::SNAPPYBLOCK:
+        return TParquetCompressionType::SNAPPY;
+    case TFileCompressType::ZSTD:
+        return TParquetCompressionType::ZSTD;
+    case TFileCompressType::GZ:
+        return TParquetCompressionType::GZIP;
+    case TFileCompressType::LZ4BLOCK:
+        return TParquetCompressionType::LZ4_HADOOP;
+    default:
+        return ResultError(Status::InternalError("Unsupported compress type {} 
with parquet",
+                                                 to_string(compression_type)));
+    }
+}
+
 void ParquetBuildHelper::build_compression_type(
         ::parquet::WriterProperties::Builder& builder,
         const TParquetCompressionType::type& compression_type) {
diff --git a/be/src/format/transformer/vparquet_writer.h 
b/be/src/format/transformer/vparquet_writer.h
index cbe0bf9d5f5..faf7c4aa22c 100644
--- a/be/src/format/transformer/vparquet_writer.h
+++ b/be/src/format/transformer/vparquet_writer.h
@@ -73,6 +73,17 @@ private:
 
 class ParquetBuildHelper {
 public:
+    // Maps the Doris file-level compression codec onto the Parquet codec to 
write with, so the
+    // Iceberg/Hive writers share one definition instead of repeating the 
switch:
+    //   PLAIN -> UNCOMPRESSED, SNAPPYBLOCK -> SNAPPY, ZSTD -> ZSTD,
+    //   GZ -> GZIP, LZ4BLOCK -> LZ4_HADOOP.
+    // GZ is what Iceberg's `write.parquet.compression-codec=gzip` (the table 
default before
+    // Iceberg 1.4) and Hive's `parquet.compression=GZIP` resolve to, and GZIP 
is the codec
+    // Spark/Trino write for those properties. LZ4_HADOOP is the Hadoop-framed 
LZ4 codec, which
+    // keeps the file readable by engines that do not support LZ4_RAW.
+    static Result<TParquetCompressionType::type> to_parquet_compression_type(
+            TFileCompressType::type compression_type);
+
     static void build_compression_type(::parquet::WriterProperties::Builder& 
builder,
                                        const TParquetCompressionType::type& 
compression_type);
 
diff --git a/be/test/exec/sink/writer/iceberg/iceberg_partition_writer_test.cpp 
b/be/test/exec/sink/writer/iceberg/iceberg_partition_writer_test.cpp
index d453177cf25..424d94bab29 100644
--- a/be/test/exec/sink/writer/iceberg/iceberg_partition_writer_test.cpp
+++ b/be/test/exec/sink/writer/iceberg/iceberg_partition_writer_test.cpp
@@ -20,6 +20,9 @@
 #include <optional>
 
 #include "exec/sink/writer/iceberg/viceberg_partition_writer.h"
+#include "io/fs/file_system.h"
+#include "runtime/runtime_state.h"
+#include "util/uid_util.h"
 
 namespace doris {
 
@@ -104,4 +107,29 @@ TEST_F(VIcebergPartitionWriterTest, 
MissingPolicyKeepsCollectionEnabledForRollin
     EXPECT_TRUE(collect_column_stats(*writer));
 }
 
+TEST_F(VIcebergPartitionWriterTest, UnsupportedParquetCodecDoesNotCreateFile) {
+    VExprContextSPtrs output_exprs;
+    iceberg::Schema schema(std::vector<iceberg::NestedField> {});
+    std::string schema_json;
+    std::map<std::string, std::string> hadoop_conf;
+    IPartitionWriterBase::WriteInfo write_info;
+    write_info.file_type = TFileType::FILE_LOCAL;
+    write_info.write_path = ".";
+    auto writer = std::make_unique<VIcebergPartitionWriter>(
+            make_table_sink(std::nullopt), std::vector<std::string> {}, 
output_exprs, schema,
+            &schema_json, std::vector<std::string> {}, std::move(write_info),
+            "unsupported_parquet_codec_" + UniqueId::gen_uid().to_string(), 0,
+            TFileFormatType::FORMAT_PARQUET, TFileCompressType::ZLIB, 
hadoop_conf);
+
+    RuntimeState state;
+    Status status = writer->open(&state, nullptr, nullptr);
+
+    EXPECT_FALSE(status.ok());
+    EXPECT_NE(std::string::npos, status.to_string().find("Unsupported compress 
type"));
+    ASSERT_NE(nullptr, writer->_fs);
+    bool exists = true;
+    ASSERT_TRUE(writer->_fs->exists(writer->_path, &exists).ok());
+    EXPECT_FALSE(exists);
+}
+
 } // namespace doris
diff --git 
a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveUtil.java 
b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveUtil.java
index f87f7a02c6c..01b90574456 100644
--- a/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveUtil.java
+++ b/fe/fe-core/src/main/java/org/apache/doris/datasource/hive/HiveUtil.java
@@ -68,7 +68,7 @@ public final class HiveUtil {
     public static final Set<String> SUPPORTED_ORC_COMPRESSIONS =
             ImmutableSet.of("plain", "zlib", "snappy", "zstd", "lz4");
     public static final Set<String> SUPPORTED_PARQUET_COMPRESSIONS =
-            ImmutableSet.of("plain", "snappy", "zstd", "lz4");
+            ImmutableSet.of("plain", "snappy", "gzip", "zstd", "lz4");
     public static final Set<String> SUPPORTED_TEXT_COMPRESSIONS =
             ImmutableSet.of("plain", "gzip", "zstd", "bzip2", "lz4", "snappy");
 
diff --git 
a/fe/fe-core/src/test/java/org/apache/doris/datasource/hive/HiveUtilTest.java 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/hive/HiveUtilTest.java
new file mode 100644
index 00000000000..45ff5f79c6d
--- /dev/null
+++ 
b/fe/fe-core/src/test/java/org/apache/doris/datasource/hive/HiveUtilTest.java
@@ -0,0 +1,47 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.datasource.hive;
+
+import org.apache.doris.catalog.Column;
+import org.apache.doris.catalog.PrimitiveType;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Maps;
+import org.apache.hadoop.hive.metastore.api.Table;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Map;
+import java.util.Optional;
+
+public class HiveUtilTest {
+    @Test
+    public void testExplicitParquetGzipCompression() {
+        Map<String, String> properties = Maps.newHashMap();
+        properties.put(HiveUtil.COMPRESSION_KEY, "gzip");
+        HiveTableMetadata metadata = new HiveTableMetadata(
+                "db", "table", Optional.empty(),
+                ImmutableList.of(new Column("id", PrimitiveType.INT)),
+                ImmutableList.of(), properties, "parquet", "");
+
+        Table table = HiveUtil.toHiveTable(metadata);
+
+        Assertions.assertEquals("gzip", 
table.getParameters().get("parquet.compression"));
+        
Assertions.assertFalse(table.getParameters().containsKey(HiveUtil.COMPRESSION_KEY));
+    }
+}
diff --git 
a/regression-test/suites/external_table_p0/hive/ddl/test_hive_ddl.groovy 
b/regression-test/suites/external_table_p0/hive/ddl/test_hive_ddl.groovy
index 34efc5a86fe..5cf69868e1d 100644
--- a/regression-test/suites/external_table_p0/hive/ddl/test_hive_ddl.groovy
+++ b/regression-test/suites/external_table_p0/hive/ddl/test_hive_ddl.groovy
@@ -419,6 +419,27 @@ suite("test_hive_ddl", 
"p0,external,hive,external_docker,external_docker_hive")
                 }
             }
 
+            // GZIP used to be rejected by the Doris Parquet writer with
+            // "Unsupported compress type GZ with parquet"; the write must 
succeed and land on the
+            // GZIP Parquet codec. Asserted instead of `qt_` because the 
expectation is one codec name.
+            if (file_format.equals("parquet") && compression.equals("gzip")) {
+                sql """ INSERT INTO tbl_${file_format}_${compression} VALUES 
('doris_gzip') """
+                def gzipRows = sql """ SELECT * FROM 
tbl_${file_format}_${compression} ORDER BY col """
+                assertEquals(1, gzipRows.size())
+                assertEquals("doris_gzip", gzipRows[0][0].toString())
+                String hdfsPort = 
context.config.otherConfigs.get("hive2HdfsPort")
+                String externalEnvIp = 
context.config.otherConfigs.get("externalEnvIp")
+                def gzipCodec = sql """
+                    SELECT DISTINCT compression
+                    FROM parquet_meta(
+                        "uri" = 
"hdfs://${externalEnvIp}:${hdfsPort}/user/hive/warehouse/test_hive_compress.db/tbl_parquet_gzip/*",
+                        "hadoop.username" = "doris",
+                        "mode" = "parquet_metadata"
+                    )
+                """
+                assertEquals("GZIP", gzipCodec[0][0].toString().toUpperCase())
+            }
+
             sql """DROP TABLE `tbl_${file_format}_${compression}`"""
             sql """ drop database if exists `test_hive_compress` """;
         }
@@ -774,7 +795,7 @@ suite("test_hive_ddl", 
"p0,external,hive,external_docker,external_docker_hive")
             sql """set enable_fallback_to_original_planner=false;"""
             test_db(catalog_name)
             test_loc_db(externalEnvIp, hdfs_port, catalog_name)
-            def compressions = ["snappy", "zlib", "zstd", "lz4"]
+            def compressions = ["snappy", "zlib", "zstd", "lz4", "gzip"]
             for (String file_format in file_formats) {
                 logger.info("Process file format " + file_format)
                 test_loc_tbl(file_format, externalEnvIp, hdfs_port, 
catalog_name)
@@ -785,6 +806,10 @@ suite("test_hive_ddl", 
"p0,external,hive,external_docker,external_docker_hive")
                     if (file_format.equals("parquet") && 
compression.equals("zlib")) {
                         continue
                     }
+                    // ORC has no GZIP codec (it spells the same codec 
`zlib`), so only test it for Parquet.
+                    if (file_format.equals("orc") && 
compression.equals("gzip")) {
+                        continue
+                    }
                     logger.info("Process file compression " + compression)
                     test_tbl_compress(compression, file_format, catalog_name)
                 }
diff --git 
a/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_parquet_compression.groovy
 
b/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_parquet_compression.groovy
index a0bcdafbfd7..e0aea0458ae 100644
--- 
a/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_parquet_compression.groovy
+++ 
b/regression-test/suites/external_table_p0/iceberg/write/test_iceberg_write_parquet_compression.groovy
@@ -16,7 +16,8 @@
 // under the License.
 
 // Verifies that Doris writes Iceberg Parquet LZ4 as Hadoop-framed Parquet 
"LZ4"
-// (not LZ4_RAW) and that Iceberg ORC/delete-file LZ4 paths are reachable.
+// (not LZ4_RAW), that Iceberg ORC/delete-file LZ4 paths are reachable, and 
that
+// `write.parquet.compression-codec=gzip` is honoured by the Parquet 
data/delete writers.
 suite("test_iceberg_write_parquet_compression", "p0,external") {
     String enabled = context.config.otherConfigs.get("enableIcebergTest")
     if (enabled == null || !enabled.equalsIgnoreCase("true")) {
@@ -137,9 +138,10 @@ suite("test_iceberg_write_parquet_compression", 
"p0,external") {
         ORDER BY compression
     """
 
-    // GZIP is intentionally not yet supported by the Doris Parquet writer; 
the INSERT must
-    // fail explicitly rather than silently fall back. Remove this block when 
GZIP support is
-    // added (and update the cross-engine product test expectation in tandem).
+    // GZIP is a standard Iceberg Parquet codec (the table default before 
Iceberg 1.4) and used to
+    // be rejected by the Doris Parquet writer with "Unsupported compress type 
GZ with parquet".
+    // The assertions below are intentionally not `qt_` cases: the expected 
result is a single
+    // codec name, so an explicit check keeps the intent obvious.
     sql """ drop table if exists ${db}.tbl_gzip """
     sql """
     CREATE TABLE ${db}.tbl_gzip (a STRING, b BIGINT) PROPERTIES (
@@ -148,8 +150,59 @@ suite("test_iceberg_write_parquet_compression", 
"p0,external") {
         'format-version' = '2',
         'write.parquet.compression-codec' = 'gzip'
     )"""
-    test {
-        sql """ INSERT INTO ${db}.tbl_gzip VALUES ('doris0', 0) """
-        exception "Unsupported compress type GZ with parquet"
-    }
+    sql """ INSERT INTO ${db}.tbl_gzip VALUES ('doris0', 0), ('doris1', 1) """
+    def gzipRows = sql """ SELECT a, b FROM ${db}.tbl_gzip ORDER BY b """
+    assertEquals(2, gzipRows.size())
+    assertEquals("doris1", gzipRows[1][0].toString())
+    def gzipDataFiles = sql """ SELECT file_path FROM ${db}.tbl_gzip\$files 
ORDER BY file_path """
+    String gzipDataFile = gzipDataFiles[0][0].toString()
+    def gzipDataCodec = sql """
+        SELECT DISTINCT compression
+        FROM parquet_meta(
+            "uri" = "${gzipDataFile}",
+            "s3.access_key" = "admin",
+            "s3.secret_key" = "password",
+            "s3.endpoint" = "http://${externalEnvIp}:${minio_port}";,
+            "s3.region" = "us-east-1",
+            "mode" = "parquet_metadata"
+        )
+    """
+    assertEquals("GZIP", gzipDataCodec[0][0].toString().toUpperCase())
+
+    // The delete-file writer must honour the same table property, otherwise 
DELETE/merge-on-read
+    // on a gzip table fails where INSERT succeeds.
+    sql """ drop table if exists ${db}.tbl_gzip_delete """
+    sql """
+    CREATE TABLE ${db}.tbl_gzip_delete (id INT, name STRING) PROPERTIES (
+        'write-format' = 'parquet',
+        'write.format.default' = 'parquet',
+        'format-version' = '2',
+        'write.parquet.compression-codec' = 'gzip',
+        'write.delete.mode' = 'merge-on-read',
+        'write.update.mode' = 'merge-on-read',
+        'write.merge.mode' = 'merge-on-read'
+    )"""
+    sql """ INSERT INTO ${db}.tbl_gzip_delete VALUES (1, 'a'), (2, 'b'), (3, 
'c') """
+    sql """ DELETE FROM ${db}.tbl_gzip_delete WHERE id = 2 """
+    def gzipDeleteRows = sql """ SELECT id, name FROM ${db}.tbl_gzip_delete 
ORDER BY id """
+    assertEquals(2, gzipDeleteRows.size())
+    assertEquals("3", gzipDeleteRows[1][0].toString())
+    def gzipDeleteFiles = sql """
+        SELECT file_path
+        FROM ${db}.tbl_gzip_delete\$delete_files
+        ORDER BY file_path
+    """
+    String gzipDeleteFile = gzipDeleteFiles[0][0].toString()
+    def gzipDeleteCodec = sql """
+        SELECT DISTINCT compression
+        FROM parquet_meta(
+            "uri" = "${gzipDeleteFile}",
+            "s3.access_key" = "admin",
+            "s3.secret_key" = "password",
+            "s3.endpoint" = "http://${externalEnvIp}:${minio_port}";,
+            "s3.region" = "us-east-1",
+            "mode" = "parquet_metadata"
+        )
+    """
+    assertEquals("GZIP", gzipDeleteCodec[0][0].toString().toUpperCase())
 }


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

Reply via email to