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 67978a6b92 [format] Validate format table options are not empty at 
table creation time (#8328)
67978a6b92 is described below

commit 67978a6b92298cb946c6411be609e2e73367aadc
Author: Zouxxyy <[email protected]>
AuthorDate: Tue Jun 23 19:25:40 2026 +0800

    [format] Validate format table options are not empty at table creation time 
(#8328)
    
    Validate that CSV/JSON/Text format table options (such as
    `csv.quote-character`, `csv.field-delimiter`, etc.) are not empty at
    table creation time.
    
    Previously, setting `csv.quote-character` to an empty string would
    succeed at table creation but fail with
    `StringIndexOutOfBoundsException` during INSERT or SELECT. Now the error
    is caught early with a clear message.
---
 .../org/apache/paimon/catalog/CatalogUtils.java    | 61 +++++++++++++++++-----
 .../paimon/spark/sql/FormatTableTestBase.scala     | 11 ++++
 2 files changed, 60 insertions(+), 12 deletions(-)

diff --git 
a/paimon-core/src/main/java/org/apache/paimon/catalog/CatalogUtils.java 
b/paimon-core/src/main/java/org/apache/paimon/catalog/CatalogUtils.java
index a33dc2c0c1..9f287a07d2 100644
--- a/paimon-core/src/main/java/org/apache/paimon/catalog/CatalogUtils.java
+++ b/paimon-core/src/main/java/org/apache/paimon/catalog/CatalogUtils.java
@@ -20,6 +20,9 @@ package org.apache.paimon.catalog;
 
 import org.apache.paimon.CoreOptions;
 import org.apache.paimon.TableType;
+import org.apache.paimon.format.csv.CsvOptions;
+import org.apache.paimon.format.json.JsonOptions;
+import org.apache.paimon.format.text.TextOptions;
 import org.apache.paimon.fs.FileIO;
 import org.apache.paimon.fs.Path;
 import org.apache.paimon.manifest.PartitionEntry;
@@ -157,24 +160,58 @@ public class CatalogUtils {
 
         TableType tableType = options.get(CoreOptions.TYPE);
         if (tableType.equals(TableType.FORMAT_TABLE)) {
-            checkArgument(
-                    options.get(PRIMARY_KEY) == null,
-                    "Cannot define %s for format table.",
-                    PRIMARY_KEY.key());
-            if (dataTokenEnabled && options.get(PATH) == null) {
-                checkArgument(
-                        options.get(FORMAT_TABLE_IMPLEMENTATION)
-                                != 
CoreOptions.FormatTableImplementation.ENGINE,
-                        "Cannot define %s is engine for format table when data 
token is enabled and not define %s.",
-                        FORMAT_TABLE_IMPLEMENTATION.key(),
-                        PATH.key());
-            }
+            validateFormatTableOptions(options, dataTokenEnabled);
         }
         for (DataField field : schema.fields()) {
             validateDefaultValue(field.type(), field.defaultValue());
         }
     }
 
+    private static void validateFormatTableOptions(Options options, boolean 
dataTokenEnabled) {
+        checkArgument(
+                options.get(PRIMARY_KEY) == null,
+                "Cannot define %s for format table.",
+                PRIMARY_KEY.key());
+        if (dataTokenEnabled && options.get(PATH) == null) {
+            checkArgument(
+                    options.get(FORMAT_TABLE_IMPLEMENTATION)
+                            != CoreOptions.FormatTableImplementation.ENGINE,
+                    "Cannot define %s is engine for format table when data 
token is enabled and not define %s.",
+                    FORMAT_TABLE_IMPLEMENTATION.key(),
+                    PATH.key());
+        }
+
+        String format = options.get(CoreOptions.FILE_FORMAT);
+        if ("csv".equalsIgnoreCase(format)) {
+            checkArgument(
+                    !options.get(CsvOptions.FIELD_DELIMITER).isEmpty(),
+                    "%s must not be empty.",
+                    CsvOptions.FIELD_DELIMITER.key());
+            checkArgument(
+                    !options.get(CsvOptions.LINE_DELIMITER).isEmpty(),
+                    "%s must not be empty.",
+                    CsvOptions.LINE_DELIMITER.key());
+            checkArgument(
+                    !options.get(CsvOptions.QUOTE_CHARACTER).isEmpty(),
+                    "%s must not be empty.",
+                    CsvOptions.QUOTE_CHARACTER.key());
+            checkArgument(
+                    !options.get(CsvOptions.ESCAPE_CHARACTER).isEmpty(),
+                    "%s must not be empty.",
+                    CsvOptions.ESCAPE_CHARACTER.key());
+        } else if ("json".equalsIgnoreCase(format)) {
+            checkArgument(
+                    !options.get(JsonOptions.LINE_DELIMITER).isEmpty(),
+                    "%s must not be empty.",
+                    JsonOptions.LINE_DELIMITER.key());
+        } else if ("text".equalsIgnoreCase(format)) {
+            checkArgument(
+                    !options.get(TextOptions.LINE_DELIMITER).isEmpty(),
+                    "%s must not be empty.",
+                    TextOptions.LINE_DELIMITER.key());
+        }
+    }
+
     public static void validateNamePattern(Catalog catalog, String 
namePattern) {
         if (Objects.nonNull(namePattern) && !catalog.supportsListByPattern()) {
             throw new UnsupportedOperationException(
diff --git 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/FormatTableTestBase.scala
 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/FormatTableTestBase.scala
index 503005f247..f32447cf43 100644
--- 
a/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/FormatTableTestBase.scala
+++ 
b/paimon-spark/paimon-spark-ut/src/test/scala/org/apache/paimon/spark/sql/FormatTableTestBase.scala
@@ -245,6 +245,17 @@ abstract class FormatTableTestBase extends 
PaimonHiveTestBase with AdaptiveSpark
     }
   }
 
+  test("Format table: csv with empty quote-character should fail") {
+    withTable("t") {
+      withSparkSQLConf("spark.paimon.format-table.implementation" -> "paimon") 
{
+        val error = intercept[IllegalArgumentException] {
+          sql("CREATE TABLE t (f0 INT, f1 STRING) USING CSV OPTIONS 
('csv.quote-character' '')")
+        }
+        assert(error.getMessage.contains("csv.quote-character must not be 
empty"))
+      }
+    }
+  }
+
   test("Format table: format table and spark table props recognize") {
     val paimonFormatTblProps =
       """

Reply via email to