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

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


The following commit(s) were added to refs/heads/master by this push:
     new ec75aacab75 Fix PostgreSQL external parser input boundaries (#40202)
ec75aacab75 is described below

commit ec75aacab754f81abe0889586cae1e3261a8b0cf
Author: ZhangCheng <[email protected]>
AuthorDate: Fri Oct 16 19:23:53 2026 +0800

    Fix PostgreSQL external parser input boundaries (#40202)
---
 ...ostgreSQLExternalTestParameterLoadTemplate.java | 37 ++++++++++++++++++++--
 ...reSQLExternalTestParameterLoadTemplateTest.java | 33 +++++++++++++++++++
 2 files changed, 68 insertions(+), 2 deletions(-)

diff --git 
a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/dialect/PostgreSQLExternalTestParameterLoadTemplate.java
 
b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/dialect/PostgreSQLExternalTestParameterLoadTemplate.java
index 233f56d812f..7bf8a1043c4 100644
--- 
a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/dialect/PostgreSQLExternalTestParameterLoadTemplate.java
+++ 
b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/dialect/PostgreSQLExternalTestParameterLoadTemplate.java
@@ -176,6 +176,8 @@ public final class 
PostgreSQLExternalTestParameterLoadTemplate implements Extern
         
         private boolean clientStatement;
         
+        private boolean emptyQuotedIdentifier;
+        
         private boolean echoAll = true;
         
         private boolean clientStateKnown = true;
@@ -210,6 +212,9 @@ public final class 
PostgreSQLExternalTestParameterLoadTemplate implements Extern
                     continue;
                 }
                 String kind = 
lexer.getVocabulary().getSymbolicName(token.getType());
+                if ("DQ_".equals(kind) || "IDENTIFIER_".equals(kind) && '"' == 
input.LA(1) && '"' == input.LA(token.getStartIndex() - input.index())) {
+                    readQuotedIdentifier(token);
+                }
                 if ("BACKSLASH_".equals(kind) && 
readCommand(token.getStartIndex())) {
                     continue;
                 }
@@ -244,6 +249,33 @@ public final class 
PostgreSQLExternalTestParameterLoadTemplate implements Extern
             }
         }
         
+        private void readQuotedIdentifier(final Token token) {
+            clientStateKnown = false;
+            input.seek(token.getStartIndex() + 1);
+            boolean empty = true;
+            boolean closed = false;
+            while (Token.EOF != input.LA(1)) {
+                int character = input.LA(1);
+                input.consume();
+                if ('"' == character) {
+                    if ('"' != input.LA(1)) {
+                        closed = true;
+                        break;
+                    }
+                    input.consume();
+                }
+                empty = false;
+            }
+            if (closed && empty) {
+                emptyQuotedIdentifier = true;
+            }
+            int lastLine = lineAt(input.index() - 1);
+            for (int i = token.getLine(); i <= lastLine; i++) {
+                quotedLines[i] = true;
+            }
+            seek(input.index());
+        }
+        
         private List<String> getEcho(final int firstLine, final int lastLine) {
             return IntStream.rangeClosed(firstLine, lastLine).filter(i -> 
!lines.get(i).isEmpty() || 
quotedLines[i]).mapToObj(lines::get).collect(Collectors.toList());
         }
@@ -370,7 +402,7 @@ public final class 
PostgreSQLExternalTestParameterLoadTemplate implements Extern
         private void readParentheses(final String kind) {
             if ("LP_".equals(kind)) {
                 parentheses++;
-                if (0 == jsonObjectParentheses && 
"JSON_OBJECT".equals(previousKind)) {
+                if (0 == jsonObjectParentheses && 
("JSON_OBJECT".equals(previousKind) || "JSON_OBJECTAGG".equals(previousKind))) {
                     jsonObjectParentheses = parentheses;
                 }
             } else if (parentheses > 0) {
@@ -448,7 +480,7 @@ public final class 
PostgreSQLExternalTestParameterLoadTemplate implements Extern
         }
         
         private String getSQL(final int end) {
-            if (clientStatement) {
+            if (clientStatement || emptyQuotedIdentifier) {
                 return "";
             }
             if (variableEnd < 0) {
@@ -472,6 +504,7 @@ public final class 
PostgreSQLExternalTestParameterLoadTemplate implements Extern
             copyOutput = false;
             pendingCopy = false;
             unsafe = false;
+            emptyQuotedIdentifier = false;
             clientBuffer = false;
             clientStatement = false;
         }
diff --git 
a/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/dialect/PostgreSQLExternalTestParameterLoadTemplateTest.java
 
b/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/dialect/PostgreSQLExternalTestParameterLoadTemplateTest.java
index 0a6c04086bc..1ab3ff51225 100644
--- 
a/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/dialect/PostgreSQLExternalTestParameterLoadTemplateTest.java
+++ 
b/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/dialect/PostgreSQLExternalTestParameterLoadTemplateTest.java
@@ -61,6 +61,34 @@ class PostgreSQLExternalTestParameterLoadTemplateTest {
                 Arguments.of("escaped single quote", "SELECT 'foo'';bar'; 
SELECT 2;", "", Arrays.asList("SELECT 'foo'';bar';", "SELECT 2;")),
                 Arguments.of("escape string", "SELECT E'foo\\';bar'; SELECT 
2;", "", Arrays.asList("SELECT E'foo\\';bar';", "SELECT 2;")),
                 Arguments.of("quoted identifier", "SELECT \"foo;\"\"bar\"; 
SELECT 2;", "", Arrays.asList("SELECT \"foo;\"\"bar\";", "SELECT 2;")),
+                Arguments.of("empty identifier preserves following quoted 
SQL", "CREATE TABLE foo() USING \"\"; SELECT 1; SELECT 2 AS \"bar\"; SELECT 
3;", "",
+                        Arrays.asList("SELECT 1;", "SELECT 2 AS \"bar\";", 
"SELECT 3;")),
+                Arguments.of("empty identifier at eof", "SELECT \"\"", "",
+                        Collections.emptyList()),
+                Arguments.of("empty identifier in atomic body", "CREATE 
FUNCTION foo() RETURNS integer BEGIN ATOMIC SELECT \"\"; END; SELECT 3;", "",
+                        Collections.singletonList("SELECT 3;")),
+                Arguments.of("empty identifier between atomic keywords", 
"SELECT begin \"\" ATOMIC; SELECT 2;", "",
+                        Collections.singletonList("SELECT 2;")),
+                Arguments.of("escaped quote identifier", "SELECT \"\"\"\"; 
SELECT 2;", "",
+                        Arrays.asList("SELECT \"\"\"\";", "SELECT 2;")),
+                Arguments.of("empty quotes in string and json", "SELECT 
'\"\"', '{\"foo\":\"\"}'::json; SELECT 2;", "",
+                        Arrays.asList("SELECT '\"\"', 
'{\"foo\":\"\"}'::json;", "SELECT 2;")),
+                Arguments.of("empty quotes in dollar body", "DO $$BEGIN 
PERFORM \"\"; END$$; SELECT 2;", "",
+                        Arrays.asList("DO $$BEGIN PERFORM \"\"; END$$;", 
"SELECT 2;")),
+                Arguments.of("empty quotes in comment", "SELECT /* \"\" */ 1; 
SELECT 2;", "",
+                        Arrays.asList("SELECT /* \"\" */ 1;", "SELECT 2;")),
+                Arguments.of("unfinished nonempty identifier", "SELECT \"foo", 
"",
+                        Collections.singletonList("SELECT \"foo")),
+                Arguments.of("leading escaped quote identifier", "SELECT 
\"\"\"foo;bar\"; SELECT 2;", "",
+                        Arrays.asList("SELECT \"\"\"foo;bar\";", "SELECT 2;")),
+                Arguments.of("trailing escaped quote identifier", "SELECT 
\"foo;bar\"\"\"; SELECT 2;", "",
+                        Arrays.asList("SELECT \"foo;bar\"\"\";", "SELECT 2;")),
+                Arguments.of("quoted alias adjacent to unquoted column", 
"SELECT foo\"bar\"; SELECT 2;", "",
+                        Arrays.asList("SELECT foo\"bar\";", "SELECT 2;")),
+                Arguments.of("lone unfinished quote", "SELECT \"", "",
+                        Collections.singletonList("SELECT \"")),
+                Arguments.of("empty identifier remains invalid before escaped 
name", "SELECT \"\", \"a\"\"b\"; SELECT 2;", "",
+                        Collections.singletonList("SELECT 2;")),
                 Arguments.of("line comment in statement", 
"SELECT\n--foo;bar\n1;\nSELECT 2;", "", Arrays.asList("SELECT\n--foo;bar\n1;", 
"SELECT 2;")),
                 Arguments.of("nested block comment", "SELECT /* foo; /* bar; 
*/ baz; */ 1; SELECT 2;", "", Arrays.asList("SELECT /* foo; /* bar; */ baz; */ 
1;", "SELECT 2;")),
                 Arguments.of("blank line in statement", "SELECT\n\n  -- foo\n  
1;\nSELECT 2;", "", Arrays.asList("SELECT\n\n  -- foo\n  1;", "SELECT 2;")),
@@ -228,6 +256,11 @@ class PostgreSQLExternalTestParameterLoadTemplateTest {
                 Arguments.of("multiline g native error", "SELECT\n  
1\n\\g\nSELECT 2;", "SELECT\n  1\n\\g\nERROR: foo\nSELECT 2;", 
Collections.singletonList("SELECT 2;")),
                 Arguments.of("gexec generated query", "SELECT 'SELECT 2;' 
\\gexec\nSELECT 3;", "SELECT 'SELECT 2;' \\gexec\nSELECT 2;\nERROR: foo\nSELECT 
3;",
                         Arrays.asList("SELECT 'SELECT 2;'", "SELECT 3;")),
+                Arguments.of("native error before multiline escaped 
identifier", "SELECT +;\nSELECT \"foo\"\"\n\nbar\";\nSELECT 2;",
+                        "SELECT +;\nERROR: syntax error at or near 
\";\"\nSELECT \"foo\"\"\n\nbar\";\nSELECT 2;", Arrays.asList("SELECT 
\"foo\"\"\n\nbar\";", "SELECT 2;")),
+                Arguments.of("recovered SQL with native execution error", 
"CREATE TABLE foo() USING \"\";\nCREATE TABLE foo() USING missing_am;\nSELECT 
2;",
+                        "CREATE TABLE foo() USING \"\";\nERROR: zero-length 
delimited identifier\nCREATE TABLE foo() USING missing_am;\nERROR: access 
method \"missing_am\" does not exist\nSELECT 2;",
+                        Arrays.asList("CREATE TABLE foo() USING missing_am;", 
"SELECT 2;")),
                 Arguments.of("copy error without payload echo", "COPY foo FROM 
STDIN;\nfoo';\n$x$;\n\\.\nSELECT 2;", "COPY foo FROM STDIN;\nERROR: 
bar\nCONTEXT: COPY foo\nSELECT 2;",
                         Collections.singletonList("SELECT 2;")));
     }

Reply via email to