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);
+ }
+ }
}