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

JingsongLi pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/paimon.git


The following commit(s) were added to refs/heads/master by this push:
     new ca24a99454 [format] Resolve the ORC compression kind independent of 
the default locale (#9671)
ca24a99454 is described below

commit ca24a99454c69f2c7e844acad7394a799f31d42a
Author: jackylee <[email protected]>
AuthorDate: Fri Sep 11 14:12:57 2026 +0800

    [format] Resolve the ORC compression kind independent of the default locale 
(#9671)
---
 .../apache/paimon/format/orc/OrcWriterFactory.java | 31 ++++++++-
 .../paimon/format/orc/OrcWriterFactoryTest.java    | 79 ++++++++++++++++++++++
 2 files changed, 107 insertions(+), 3 deletions(-)

diff --git 
a/paimon-format/src/main/java/org/apache/paimon/format/orc/OrcWriterFactory.java
 
b/paimon-format/src/main/java/org/apache/paimon/format/orc/OrcWriterFactory.java
index 5e43c566ca..2aecf9ee88 100644
--- 
a/paimon-format/src/main/java/org/apache/paimon/format/orc/OrcWriterFactory.java
+++ 
b/paimon-format/src/main/java/org/apache/paimon/format/orc/OrcWriterFactory.java
@@ -45,6 +45,7 @@ import org.apache.orc.impl.writer.WriterEncryptionVariant;
 
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
 import java.util.UUID;
@@ -58,6 +59,8 @@ import static 
org.apache.paimon.utils.Preconditions.checkNotNull;
  */
 public class OrcWriterFactory implements FormatWriterFactory, 
SupportsShreddingWritePlan {
 
+    private static final String COMPRESS_ATTRIBUTE = 
OrcConf.COMPRESS.getAttribute();
+
     private final Vectorizer<InternalRow> vectorizer;
     private final Properties writerProperties;
     private final Map<String, String> confMap;
@@ -84,7 +87,7 @@ public class OrcWriterFactory implements FormatWriterFactory, 
SupportsShreddingW
             MemorySize writeBatchMemory,
             boolean legacyTimestampLtzType) {
         this.vectorizer = checkNotNull(vectorizer);
-        this.writerProperties = checkNotNull(writerProperties);
+        this.writerProperties = 
upperCaseCompression(checkNotNull(writerProperties));
         this.confMap = new HashMap<>();
         this.legacyTimestampLtzType = legacyTimestampLtzType;
 
@@ -92,15 +95,37 @@ public class OrcWriterFactory implements 
FormatWriterFactory, SupportsShreddingW
         for (Map.Entry<String, String> entry : configuration) {
             confMap.put(entry.getKey(), entry.getValue());
         }
+        String compress = confMap.get(COMPRESS_ATTRIBUTE);
+        if (compress != null) {
+            confMap.put(COMPRESS_ATTRIBUTE, compress.toUpperCase(Locale.ROOT));
+        }
         this.writeBatchSize = writeBatchSize;
         this.writeBatchMemory = writeBatchMemory;
     }
 
+    /**
+     * ORC resolves the compression kind through a locale sensitive {@code 
toUpperCase}, which turns
+     * the 'i' of zlib into 'İ' under a Turkish or Azeri default locale and 
then matches no {@link
+     * CompressionKind}. Upper case the option once here instead.
+     */
+    private static Properties upperCaseCompression(Properties properties) {
+        String compress = properties.getProperty(COMPRESS_ATTRIBUTE);
+        if (compress == null) {
+            return properties;
+        }
+        Properties normalized = new Properties();
+        for (String name : properties.stringPropertyNames()) {
+            normalized.setProperty(name, properties.getProperty(name));
+        }
+        normalized.setProperty(COMPRESS_ATTRIBUTE, 
compress.toUpperCase(Locale.ROOT));
+        return normalized;
+    }
+
     @Override
     public FormatWriter create(PositionOutputStream out, String compression) 
throws IOException {
         OrcFile.WriterOptions opts = getWriterOptions();
-        if (!writerProperties.containsKey(OrcConf.COMPRESS.getAttribute())) {
-            opts.compress(CompressionKind.valueOf(compression.toUpperCase()));
+        if (!writerProperties.containsKey(COMPRESS_ATTRIBUTE)) {
+            
opts.compress(CompressionKind.valueOf(compression.toUpperCase(Locale.ROOT)));
         }
 
         opts.physicalWriter(
diff --git 
a/paimon-format/src/test/java/org/apache/paimon/format/orc/OrcWriterFactoryTest.java
 
b/paimon-format/src/test/java/org/apache/paimon/format/orc/OrcWriterFactoryTest.java
index 3354275a84..6ba889ebeb 100644
--- 
a/paimon-format/src/test/java/org/apache/paimon/format/orc/OrcWriterFactoryTest.java
+++ 
b/paimon-format/src/test/java/org/apache/paimon/format/orc/OrcWriterFactoryTest.java
@@ -22,10 +22,13 @@ import org.apache.paimon.data.InternalRow;
 import org.apache.paimon.format.orc.writer.RowDataVectorizer;
 import org.apache.paimon.format.orc.writer.Vectorizer;
 import org.apache.paimon.fs.local.LocalFileIO.LocalPositionOutputStream;
+import org.apache.paimon.options.MemorySize;
 import org.apache.paimon.types.DataField;
 import org.apache.paimon.types.DataTypes;
 
+import org.apache.hadoop.conf.Configuration;
 import org.apache.hadoop.fs.Path;
+import org.apache.orc.CompressionKind;
 import org.apache.orc.MemoryManager;
 import org.apache.orc.OrcFile;
 import org.apache.orc.TypeDescription;
@@ -37,6 +40,8 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
+import java.util.Properties;
 
 import static org.apache.paimon.utils.Preconditions.checkNotNull;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -113,4 +118,78 @@ class OrcWriterFactoryTest {
                                 false));
         
assertThat(factory.getWriterOptions()).isNotSameAs(factory.getWriterOptions());
     }
+
+    @Test
+    void testLowerCaseFileCompressionUnderTurkishLocale(@TempDir 
java.nio.file.Path tmpDir)
+            throws IOException {
+        // 'i' uppercases to 'İ' in Turkish, so a locale sensitive conversion 
turns zlib into
+        // ZLİB and CompressionKind.valueOf rejects it
+        CapturingOrcWriterFactory factory = capturingFactory(new Properties());
+        withTurkishLocale(
+                () -> factory.create(outputStream(tmpDir, 
"file-compression.orc"), "zlib").close());
+        
assertThat(factory.captured.getCompress()).isEqualTo(CompressionKind.ZLIB);
+    }
+
+    @Test
+    void testLowerCaseOrcCompressPropertyUnderTurkishLocale(@TempDir 
java.nio.file.Path tmpDir)
+            throws IOException {
+        // the orc.compress table option is resolved by OrcFile.WriterOptions 
instead, one call
+        // earlier than the branch above
+        Properties properties = new Properties();
+        properties.setProperty("orc.compress", "zlib");
+        CapturingOrcWriterFactory factory = capturingFactory(properties);
+        withTurkishLocale(
+                () -> factory.create(outputStream(tmpDir, "orc-compress.orc"), 
"zstd").close());
+        
assertThat(factory.captured.getCompress()).isEqualTo(CompressionKind.ZLIB);
+    }
+
+    private static LocalPositionOutputStream outputStream(java.nio.file.Path 
tmpDir, String name)
+            throws IOException {
+        return new LocalPositionOutputStream(tmpDir.resolve(name).toFile());
+    }
+
+    private static CapturingOrcWriterFactory capturingFactory(Properties 
writerProperties) {
+        return new CapturingOrcWriterFactory(
+                new RowDataVectorizer(
+                        TypeDescription.createString(),
+                        Collections.singletonList(new DataField(0, "f0", 
DataTypes.STRING())),
+                        false),
+                writerProperties);
+    }
+
+    private static void withTurkishLocale(ThrowingRunnable body) throws 
IOException {
+        Locale original = Locale.getDefault();
+        try {
+            Locale.setDefault(new Locale("tr", "TR"));
+            body.run();
+        } finally {
+            Locale.setDefault(original);
+        }
+    }
+
+    private interface ThrowingRunnable {
+        void run() throws IOException;
+    }
+
+    private static class CapturingOrcWriterFactory extends OrcWriterFactory {
+
+        private OrcFile.WriterOptions captured;
+
+        private CapturingOrcWriterFactory(
+                Vectorizer<InternalRow> vectorizer, Properties 
writerProperties) {
+            super(
+                    vectorizer,
+                    writerProperties,
+                    new Configuration(false),
+                    1024,
+                    MemorySize.ZERO,
+                    false);
+        }
+
+        @Override
+        protected OrcFile.WriterOptions getWriterOptions() {
+            captured = super.getWriterOptions();
+            return captured;
+        }
+    }
 }

Reply via email to