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