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

github-merge-queue[bot] pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 886a3ac6e5 [Fix][Connector-V2][JDBC] Make the SQL structure guards 
locale independent (#12996)
886a3ac6e5 is described below

commit 886a3ac6e59aad5e46755761cf9fb8ac2a6d3df9
Author: Sepuri Sai Krishna <[email protected]>
AuthorDate: Wed Oct 28 17:29:44 2026 +0000

    [Fix][Connector-V2][JDBC] Make the SQL structure guards locale independent 
(#12996)
---
 .../jdbc/source/SqlWhereConditionHelper.java       |  15 +--
 .../jdbc/source/SqlWhereConditionHelperTest.java   | 104 +++++++++++++++++++++
 2 files changed, 112 insertions(+), 7 deletions(-)

diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/SqlWhereConditionHelper.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/SqlWhereConditionHelper.java
index 577bd6b32e..10f7c567e1 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/SqlWhereConditionHelper.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/SqlWhereConditionHelper.java
@@ -27,6 +27,7 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.regex.Matcher;
@@ -171,7 +172,7 @@ public class SqlWhereConditionHelper {
      * top level would make auto-adding columns unsafe.
      */
     public static boolean hasTopLevelSetOperator(String sql) {
-        String upper = sql.toUpperCase();
+        String upper = sql.toUpperCase(Locale.ROOT);
         int length = upper.length();
         int parenCount = 0;
         boolean inString = false;
@@ -237,7 +238,7 @@ public class SqlWhereConditionHelper {
         String cleaned = sql.replaceAll("'[^']*'|\"[^\"]*\"", "");
 
         // Find the column list between SELECT and FROM
-        String upperCleaned = cleaned.toUpperCase();
+        String upperCleaned = cleaned.toUpperCase(Locale.ROOT);
         int selectIdx = -1;
 
         // Find SELECT keyword (not inside parentheses)
@@ -275,9 +276,9 @@ public class SqlWhereConditionHelper {
         String columnList = cleaned.substring(selectEndIdx, fromIdx).trim();
 
         // Skip DISTINCT/ALL keywords
-        if (columnList.toUpperCase().startsWith("DISTINCT ")) {
+        if (columnList.toUpperCase(Locale.ROOT).startsWith("DISTINCT ")) {
             columnList = columnList.substring(9).trim();
-        } else if (columnList.toUpperCase().startsWith("ALL ")) {
+        } else if (columnList.toUpperCase(Locale.ROOT).startsWith("ALL ")) {
             columnList = columnList.substring(4).trim();
         }
 
@@ -335,7 +336,7 @@ public class SqlWhereConditionHelper {
      * and CTE (WITH ... AS).
      */
     public static boolean hasComplexClause(String sql) {
-        String upper = sql.toUpperCase();
+        String upper = sql.toUpperCase(Locale.ROOT);
         int length = upper.length();
         int parenCount = 0;
         boolean inString = false;
@@ -502,7 +503,7 @@ public class SqlWhereConditionHelper {
 
     /** Find the index of the main FROM clause in the SQL query. */
     private static int findMainFromClauseIndex(String sql) {
-        String upperSql = sql.toUpperCase();
+        String upperSql = sql.toUpperCase(Locale.ROOT);
         int length = sql.length();
         int parenthesisCount = 0;
         boolean inQuote = false;
@@ -563,7 +564,7 @@ public class SqlWhereConditionHelper {
 
     /** Check if a string is a SQL keyword that should be ignored. */
     private static boolean isKeyword(String word) {
-        String upper = word.toUpperCase();
+        String upper = word.toUpperCase(Locale.ROOT);
         return upper.equals("AND")
                 || upper.equals("OR")
                 || upper.equals("NOT")
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/SqlWhereConditionHelperTest.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/SqlWhereConditionHelperTest.java
index ebcc02cf4d..9034b998ee 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/SqlWhereConditionHelperTest.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/SqlWhereConditionHelperTest.java
@@ -23,6 +23,7 @@ import org.junit.jupiter.api.Test;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Locale;
 import java.util.Set;
 
 /** Unit tests for SqlWhereConditionHelper */
@@ -1455,4 +1456,107 @@ public class SqlWhereConditionHelperTest {
                 "Outer simple query with GROUP BY only in subquery should add 
fields. Result: "
                         + result);
     }
+
+    /**
+     * Locales to drive the guards under. Turkish and Azerbaijani both 
uppercase "i" to the dotted
+     * capital "\u0130", so every guard keyword containing an "i" stops 
matching once the SQL is
+     * uppercased with the default locale.
+     */
+    private static final List<Locale> DOTTED_I_LOCALES =
+            Arrays.asList(Locale.forLanguageTag("tr-TR"), 
Locale.forLanguageTag("az-AZ"));
+
+    private static Locale[] snapshotDefaultLocales() {
+        return new Locale[] {
+            Locale.getDefault(),
+            Locale.getDefault(Locale.Category.FORMAT),
+            Locale.getDefault(Locale.Category.DISPLAY)
+        };
+    }
+
+    /** Restores the base locale first, because setting it overwrites both 
category defaults. */
+    private static void restoreDefaultLocales(Locale[] saved) {
+        Locale.setDefault(saved[0]);
+        Locale.setDefault(Locale.Category.FORMAT, saved[1]);
+        Locale.setDefault(Locale.Category.DISPLAY, saved[2]);
+    }
+
+    @Test
+    public void testSqlStructureGuardsAreIndependentOfTheDefaultLocale() {
+        Locale[] saved = snapshotDefaultLocales();
+        try {
+            for (Locale locale : DOTTED_I_LOCALES) {
+                Locale.setDefault(locale);
+
+                // Set operators: adding a column to only the first branch 
changes its arity, so
+                // these have to keep being detected whatever the default 
locale is.
+                for (String sql :
+                        Arrays.asList(
+                                "select id from a union select id from b",
+                                "select id from a union all select id from b",
+                                "select id from a intersect select id from b",
+                                "select id from a minus select id from b",
+                                "select id from a except select id from b")) {
+                    Assertions.assertTrue(
+                            
SqlWhereConditionHelper.hasTopLevelSetOperator(sql),
+                            "set operator must be detected under " + locale + 
": " + sql);
+                }
+
+                // Complex clauses: auto-adding a column changes the result of 
these queries.
+                for (String sql :
+                        Arrays.asList(
+                                "select distinct id from t",
+                                "select id from t group by id",
+                                "select id from t having count(*) > 1",
+                                "select id from t limit 10",
+                                "select id from t offset 5",
+                                "select id, row_number() over (order by id) 
from t",
+                                "with x as (select id from t) select id from 
x")) {
+                    Assertions.assertTrue(
+                            SqlWhereConditionHelper.hasComplexClause(sql),
+                            "complex clause must be detected under " + locale 
+ ": " + sql);
+                }
+            }
+        } finally {
+            restoreDefaultLocales(saved);
+        }
+    }
+
+    @Test
+    public void 
testApplyWhereConditionWithWrapIsIndependentOfTheDefaultLocale() {
+        List<String> queries =
+                Arrays.asList(
+                        "select id from a union select id from b",
+                        "select distinct id from t",
+                        "select id from t limit 10",
+                        "select id from t having count(*) > 1");
+        String whereCondition = "name = 'x'";
+
+        Locale[] saved = snapshotDefaultLocales();
+        try {
+            Locale.setDefault(Locale.ROOT);
+            for (String sql : queries) {
+                String expected =
+                        SqlWhereConditionHelper.applyWhereConditionWithWrap(
+                                sql, whereCondition, true);
+                // The guard holds, so the inner query is wrapped without 
being rewritten.
+                Assertions.assertTrue(
+                        expected.contains(sql), "guarded sql must not be 
rewritten: " + expected);
+
+                for (Locale locale : DOTTED_I_LOCALES) {
+                    Locale.setDefault(locale);
+                    Assertions.assertEquals(
+                            expected,
+                            
SqlWhereConditionHelper.applyWhereConditionWithWrap(
+                                    sql, whereCondition, true),
+                            "emitted sql must not depend on the default locale 
("
+                                    + locale
+                                    + "): "
+                                    + sql);
+                }
+                Locale.setDefault(Locale.ROOT);
+            }
+        } finally {
+            restoreDefaultLocales(saved);
+        }
+    }
 }

Reply via email to