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 fec1f87710d Fix PostgreSQL external parser input loading (#40192)
fec1f87710d is described below
commit fec1f87710d658e43eac95815e223a5108341dff
Author: ZhangCheng <[email protected]>
AuthorDate: Fri Oct 16 08:57:37 2026 +0800
Fix PostgreSQL external parser input loading (#40192)
* Fix PostgreSQL external parser input loading
* Treat plain psql variables as opaque input
---
...ostgreSQLExternalTestParameterLoadTemplate.java | 105 +++++++++++++++++++--
...reSQLExternalTestParameterLoadTemplateTest.java | 83 +++++++++++++---
2 files changed, 168 insertions(+), 20 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 6c1f48507d7..233f56d812f 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
@@ -46,6 +46,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -125,6 +127,9 @@ public final class
PostgreSQLExternalTestParameterLoadTemplate implements Extern
private static final class ScriptReader extends BaseErrorListener {
+ private static final Pattern PSQL_VARIABLE_PATTERN =
+
Pattern.compile(":(?:[A-Za-z_0-9\\x80-\\uFFFF]+|'[A-Za-z_0-9\\x80-\\uFFFF]+'|\"[A-Za-z_0-9\\x80-\\uFFFF]+\"|\\{\\?[A-Za-z_0-9\\x80-\\uFFFF]+\\})");
+
private final List<String> lines;
private final DatabaseType databaseType;
@@ -139,10 +144,18 @@ public final class
PostgreSQLExternalTestParameterLoadTemplate implements Extern
private final List<ScriptStatement> statements = new ArrayList<>();
+ private final StringBuilder normalizedSQL = new StringBuilder();
+
private int start = -1;
+ private int variableEnd = -1;
+
private int parentheses;
+ private int jsonObjectParentheses;
+
+ private int brackets;
+
private int atomicBlocks;
private int caseBlocks;
@@ -161,6 +174,8 @@ public final class
PostgreSQLExternalTestParameterLoadTemplate implements Extern
private boolean clientBuffer;
+ private boolean clientStatement;
+
private boolean echoAll = true;
private boolean clientStateKnown = true;
@@ -203,9 +218,12 @@ public final class
PostgreSQLExternalTestParameterLoadTemplate implements Extern
}
readBlock(kind, token);
readCopy(kind);
- previousKind = kind;
+ previousKind = "IDENTIFIER_".equals(kind) &&
isJsonObjectConstructor(token) ? "JSON_OBJECT" : kind;
if ("COLON_".equals(kind)) {
unsafe = true;
+ if (0 == brackets && 0 == jsonObjectParentheses) {
+ readClientVariable(token.getStartIndex());
+ }
}
if ("SEMI_".equals(kind) && (clientBuffer || 0 == parentheses
&& 0 == atomicBlocks)) {
submit(token.getStopIndex() + 1, token.getStopIndex() + 1,
true);
@@ -237,17 +255,23 @@ public final class
PostgreSQLExternalTestParameterLoadTemplate implements Extern
if (command.startsWith(";")) {
unsafe = true;
clientBuffer = true;
+ clientStatement = true;
firstToken = true;
previousKind = "";
seek(offset + 2);
return true;
}
String name = command.split("\\s|\\\\", 2)[0];
- if (command.indexOf('\\') >= 0) {
+ boolean compoundCommand = command.indexOf('\\') >= 0;
+ if (start >= 0 && compoundCommand) {
unsafe = true;
clientBuffer = true;
+ clientStatement = true;
return false;
}
+ if (compoundCommand && !"copy".equals(name)) {
+ clientStateKnown = false;
+ }
if (Arrays.asList("g", "gx", "gset", "gexec").contains(name)) {
boolean copy = pendingCopy;
if (start >= 0) {
@@ -263,11 +287,13 @@ public final class
PostgreSQLExternalTestParameterLoadTemplate implements Extern
if (start >= 0) {
unsafe = true;
clientBuffer = true;
+ clientStatement = true;
return false;
}
if ("copy".equals(name)) {
start = offset;
unsafe = true;
+ clientStatement = true;
boolean copy = isCopyFromStdin(command);
pendingCopy = copy;
submit(end, end, false);
@@ -322,10 +348,12 @@ public final class
PostgreSQLExternalTestParameterLoadTemplate implements Extern
}
private void readBlock(final String kind, final Token token) {
- if ("LP_".equals(kind)) {
- parentheses++;
- } else if ("RP_".equals(kind) && parentheses > 0) {
- parentheses--;
+ if ("LP_".equals(kind) || "RP_".equals(kind)) {
+ readParentheses(kind);
+ } else if ("LBT_".equals(kind)) {
+ brackets++;
+ } else if ("RBT_".equals(kind) && brackets > 0) {
+ brackets--;
} else if ("BEGIN".equals(previousKind) &&
"ATOMIC".equalsIgnoreCase(token.getText())) {
atomicBlocks++;
} else if (atomicBlocks > 0 && "CASE".equals(kind)) {
@@ -339,6 +367,20 @@ public final class
PostgreSQLExternalTestParameterLoadTemplate implements Extern
}
}
+ private void readParentheses(final String kind) {
+ if ("LP_".equals(kind)) {
+ parentheses++;
+ if (0 == jsonObjectParentheses &&
"JSON_OBJECT".equals(previousKind)) {
+ jsonObjectParentheses = parentheses;
+ }
+ } else if (parentheses > 0) {
+ if (jsonObjectParentheses == parentheses) {
+ jsonObjectParentheses = 0;
+ }
+ parentheses--;
+ }
+ }
+
private void readCopy(final String kind) {
if (firstToken) {
copyStatement = "COPY".equals(kind);
@@ -352,6 +394,39 @@ public final class
PostgreSQLExternalTestParameterLoadTemplate implements Extern
}
}
+ private boolean isJsonObjectConstructor(final Token token) {
+ String identifier = token.getText();
+ return "JSON_OBJECT".equalsIgnoreCase(identifier) ||
"JSON_OBJECTAGG".equalsIgnoreCase(identifier);
+ }
+
+ private void readClientVariable(final int offset) {
+ int line = lineAt(offset);
+ Matcher matcher =
PSQL_VARIABLE_PATTERN.matcher(lines.get(line)).region(offset -
lineStarts[line], lines.get(line).length());
+ if (!matcher.lookingAt()) {
+ return;
+ }
+ String variable = matcher.group();
+ String replacement;
+ switch (variable.charAt(1)) {
+ case '\'':
+ replacement = "'foo'";
+ break;
+ case '"':
+ replacement = "\"foo\"";
+ break;
+ case '{':
+ replacement = "TRUE";
+ break;
+ default:
+ clientStatement = true;
+ seek(offset + variable.length());
+ return;
+ }
+ normalizedSQL.append(input.getText(Interval.of(variableEnd < 0 ?
start : variableEnd, offset - 1))).append(replacement);
+ variableEnd = offset + variable.length();
+ seek(variableEnd);
+ }
+
private void submit(final int sqlEnd, final int echoEnd, final boolean
filterable) {
int end = sqlEnd;
int firstLine = lineAt(start);
@@ -368,14 +443,27 @@ public final class
PostgreSQLExternalTestParameterLoadTemplate implements Extern
seek(Math.min(input.size(), lineStarts[marker] +
lines.get(marker).length() + 1));
}
}
- statements.add(new
ScriptStatement(input.getText(Interval.of(start, end - 1)).trim(),
getEcho(firstLine, lastLine),
- filterable && exclusive && !unsafe && !copyOutput &&
echoAll && clientStateKnown));
+ statements.add(new ScriptStatement(getSQL(end), getEcho(firstLine,
lastLine), filterable && exclusive && !unsafe && !copyOutput && echoAll &&
clientStateKnown));
reset();
}
+ private String getSQL(final int end) {
+ if (clientStatement) {
+ return "";
+ }
+ if (variableEnd < 0) {
+ return input.getText(Interval.of(start, end - 1)).trim();
+ }
+ return normalizedSQL.append(input.getText(Interval.of(variableEnd,
end - 1))).toString().trim();
+ }
+
private void reset() {
start = -1;
+ variableEnd = -1;
+ normalizedSQL.setLength(0);
parentheses = 0;
+ jsonObjectParentheses = 0;
+ brackets = 0;
atomicBlocks = 0;
caseBlocks = 0;
previousKind = "";
@@ -385,6 +473,7 @@ public final class
PostgreSQLExternalTestParameterLoadTemplate implements Extern
pendingCopy = false;
unsafe = false;
clientBuffer = false;
+ clientStatement = false;
}
@Override
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 f442f4c3da7..0a6c04086bc 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
@@ -83,19 +83,64 @@ class PostgreSQLExternalTestParameterLoadTemplateTest {
Arguments.of("copy payload until eof", "COPY foo FROM
STDIN;\nfoo';\n$x$;", "", Collections.singletonList("COPY foo FROM
STDIN;\nfoo';\n$x$;")),
Arguments.of("copy submitted with g", "COPY foo FROM STDIN
\\g\nfoo';\n$x$;\n\\.\nSELECT 2;", "", Arrays.asList("COPY foo FROM STDIN",
"SELECT 2;")),
Arguments.of("copy submitted with g until eof", "COPY foo FROM
STDIN \\g\nfoo';\n$x$;", "", Collections.singletonList("COPY foo FROM STDIN
\\g\nfoo';\n$x$;")),
- Arguments.of("client copy payload", "\\copy foo FROM
STDIN\nfoo';\n$x$;\n\\.\nSELECT 2;", "", Arrays.asList("\\copy foo FROM STDIN",
"SELECT 2;")),
- Arguments.of("client copy payload until eof", "\\copy foo FROM
STDIN\nfoo';\n$x$;", "", Collections.singletonList("\\copy foo FROM
STDIN\nfoo';\n$x$;")),
+ Arguments.of("client copy payload", "\\copy foo FROM
STDIN\nfoo';\n$x$;\n\\.\nSELECT 2;", "", Collections.singletonList("SELECT
2;")),
+ Arguments.of("client copy payload until eof", "\\copy foo FROM
STDIN\nfoo';\n$x$;", "", Collections.emptyList()),
Arguments.of("client copy query with stdin table", "\\copy
(SELECT * FROM stdin /*comment*/) TO STDOUT;\nSELECT 2;", "",
- Arrays.asList("\\copy (SELECT * FROM stdin
/*comment*/) TO STDOUT;", "SELECT 2;")),
- Arguments.of("client copy stdin table", "\\copy stdin TO
STDOUT\nSELECT 2;", "", Arrays.asList("\\copy stdin TO STDOUT", "SELECT 2;")),
- Arguments.of("copy in multiple query buffer", "SELECT 1 \\;
COPY foo FROM STDIN;\nfoo';\n$x$;\n\\.\nSELECT 2;", "", Arrays.asList("SELECT 1
\\; COPY foo FROM STDIN;", "SELECT 2;")),
- Arguments.of("copy in multiple query buffer until eof",
"SELECT 1 \\; COPY foo FROM STDIN;\nfoo';\n$x$;", "",
- Collections.singletonList("SELECT 1 \\; COPY foo FROM
STDIN;\nfoo';\n$x$;")),
+ Collections.singletonList("SELECT 2;")),
+ Arguments.of("client copy stdin table", "\\copy stdin TO
STDOUT\nSELECT 2;", "", Collections.singletonList("SELECT 2;")),
+ Arguments.of("client copy escaped option", "\\copy foo TO
STDOUT (ESCAPE E'\\\\')\n\\copy foo TO STDOUT\nSELECT 2;", "",
Collections.singletonList("SELECT 2;")),
+ Arguments.of("client copy escaped stdin option", "\\copy foo
FROM STDIN (NULL E'\\\\N')\nfoo';\n\\.\nSELECT 2;", "",
Collections.singletonList("SELECT 2;")),
+ Arguments.of("copy in multiple query buffer", "SELECT 1 \\;
COPY foo FROM STDIN;\nfoo';\n$x$;\n\\.\nSELECT 2;", "",
Collections.singletonList("SELECT 2;")),
+ Arguments.of("copy in multiple query buffer until eof",
"SELECT 1 \\; COPY foo FROM STDIN;\nfoo';\n$x$;", "", Collections.emptyList()),
Arguments.of("stdin table in copy query", "COPY (SELECT * FROM
stdin) TO STDOUT;\nSELECT 2;", "", Arrays.asList("COPY (SELECT * FROM stdin) TO
STDOUT;", "SELECT 2;")),
Arguments.of("copy file paths", "COPY foo FROM
'/tmp/in';\nCOPY foo TO '/tmp/out';\nSELECT 3;", "", Arrays.asList("COPY foo
FROM '/tmp/in';", "COPY foo TO '/tmp/out';", "SELECT 3;")),
- Arguments.of("psql variables", "SELECT :foo, :'bar', :\"baz\",
:{?qux}, $1, 1::integer;", "", Collections.singletonList("SELECT :foo, :'bar',
:\"baz\", :{?qux}, $1, 1::integer;")),
- Arguments.of("unknown command in buffer", "SELECT 1\n\\unknown
foo\nSELECT 2;", "", Collections.singletonList("SELECT 1\n\\unknown foo\nSELECT
2;")),
- Arguments.of("mixed commands in buffer", "SELECT 1 \\g \\echo
foo;\nSELECT 2;", "", Arrays.asList("SELECT 1 \\g \\echo foo;", "SELECT 2;")),
+ Arguments.of("psql variables", "SELECT :foo, :'bar', :\"baz\",
:{?qux}, $1, 1::integer;", "", Collections.emptyList()),
+ Arguments.of("unquoted psql variable", "SELECT :foo; SELECT
2;", "", Collections.singletonList("SELECT 2;")),
+ Arguments.of("quoted psql variable", "SELECT :'foo'; SELECT
2;", "", Arrays.asList("SELECT 'foo';", "SELECT 2;")),
+ Arguments.of("identifier psql variable", "SELECT :\"foo\";
SELECT 2;", "", Arrays.asList("SELECT \"foo\";", "SELECT 2;")),
+ Arguments.of("psql variable existence", "SELECT :{?foo};
SELECT 2;", "", Arrays.asList("SELECT TRUE;", "SELECT 2;")),
+ Arguments.of("unicode psql variables", "SELECT :变量, :'变量',
:\"变量\", :{?变量}; SELECT 2;", "", Collections.singletonList("SELECT 2;")),
+ Arguments.of("quoted unicode variables", "SELECT '😀', :'变量',
:\"变量\", :{?变量};", "", Collections.singletonList("SELECT '😀', 'foo', \"foo\",
TRUE;")),
+ Arguments.of("multiple typed variables", "SELECT :'bar',
:\"baz\", :{?qux}, $1::integer;", "", Collections.singletonList("SELECT 'foo',
\"foo\", TRUE, $1::integer;")),
+ Arguments.of("adjacent typed variables", "SELECT
:'foo'||:'bar';", "", Collections.singletonList("SELECT 'foo'||'foo';")),
+ Arguments.of("multiline typed variables", "SELECT\n :'foo',\n
:\"bar\";", "", Collections.singletonList("SELECT\n 'foo',\n \"foo\";")),
+ Arguments.of("typed variable at eof", "SELECT :'foo'", "",
Collections.singletonList("SELECT 'foo'")),
+ Arguments.of("typed variable submitted with g", "SELECT :'foo'
\\g\nSELECT 2;", "", Arrays.asList("SELECT 'foo'", "SELECT 2;")),
+ Arguments.of("typed variable in copy filename", "COPY foo FROM
:'filename'; SELECT 2;", "", Arrays.asList("COPY foo FROM 'foo';", "SELECT
2;")),
+ Arguments.of("typed identifier in table name", "SELECT * FROM
:\"table\";", "", Collections.singletonList("SELECT * FROM \"foo\";")),
+ Arguments.of("typed variable followed by raw variable",
"SELECT :'foo', :bar; SELECT 2;", "", Collections.singletonList("SELECT 2;")),
+ Arguments.of("typed variable after raw statement", "SELECT
:foo; SELECT :'bar';", "", Collections.singletonList("SELECT 'foo';")),
+ Arguments.of("typed variable in client buffer", "SELECT :'foo'
\\g \\echo bar;\nSELECT 2;", "", Collections.singletonList("SELECT 2;")),
+ Arguments.of("typed variable in atomic body", "CREATE FUNCTION
foo() RETURNS boolean BEGIN ATOMIC SELECT :{?CASE}; END; SELECT 2;", "",
+ Arrays.asList("CREATE FUNCTION foo() RETURNS boolean
BEGIN ATOMIC SELECT TRUE; END;", "SELECT 2;")),
+ Arguments.of("raw case variable in atomic body", "CREATE
FUNCTION foo() RETURNS integer LANGUAGE SQL BEGIN ATOMIC SELECT :case; END;
SELECT 3;", "",
+ Collections.singletonList("SELECT 3;")),
+ Arguments.of("uppercase case variable in atomic body", "CREATE
FUNCTION foo() RETURNS integer LANGUAGE SQL BEGIN ATOMIC SELECT :CASE; END;
SELECT 3;", "",
+ Collections.singletonList("SELECT 3;")),
+ Arguments.of("raw end variable in atomic body", "CREATE
FUNCTION foo() RETURNS integer LANGUAGE SQL BEGIN ATOMIC SELECT :end; END;
SELECT 3;", "",
+ Collections.singletonList("SELECT 3;")),
+ Arguments.of("uppercase end variable in atomic body", "CREATE
FUNCTION foo() RETURNS integer LANGUAGE SQL BEGIN ATOMIC SELECT :END; END;
SELECT 3;", "",
+ Collections.singletonList("SELECT 3;")),
+ Arguments.of("raw variable before atomic alias", "SELECT
:begin ATOMIC; SELECT 3;", "", Collections.singletonList("SELECT 3;")),
+ Arguments.of("raw variable in atomic case expression", "CREATE
FUNCTION foo() RETURNS integer LANGUAGE SQL BEGIN ATOMIC SELECT CASE WHEN TRUE
THEN :end ELSE 2 END; END; SELECT 3;", "",
+ Collections.singletonList("SELECT 3;")),
+ Arguments.of("literal psql variable", "SELECT ':foo',
$$:'foo'$$, \":foo\";", "", Collections.singletonList("SELECT ':foo',
$$:'foo'$$, \":foo\";")),
+ Arguments.of("comment psql variable", "SELECT /* :foo */ 1; --
:bar\nSELECT 2;", "", Arrays.asList("SELECT /* :foo */ 1;", "SELECT 2;")),
+ Arguments.of("array slices", "SELECT a[1:2], a[foo:bar],
a[:bar], a[1:2][3:4]; SELECT $1::integer;", "",
+ Arrays.asList("SELECT a[1:2], a[foo:bar], a[:bar],
a[1:2][3:4];", "SELECT $1::integer;")),
+ Arguments.of("array slice literal separator", "SELECT
a[1:'bar']; SELECT :'baz';", "", Arrays.asList("SELECT a[1:'bar'];", "SELECT
'foo';")),
+ Arguments.of("variable after array slice", "SELECT a[1:2];
SELECT :foo; SELECT 3;", "", Arrays.asList("SELECT a[1:2];", "SELECT 3;")),
+ Arguments.of("json object separator", "SELECT
JSON_OBJECT('foo':1, 'bar':value);", "", Collections.singletonList("SELECT
JSON_OBJECT('foo':1, 'bar':value);")),
+ Arguments.of("json literal separator and typed variable",
"SELECT JSON_OBJECT('foo':'bar'), :'baz';", "",
Collections.singletonList("SELECT JSON_OBJECT('foo':'bar'), 'foo';")),
+ Arguments.of("json object aggregate separator", "SELECT
JSON_OBJECTAGG(foo:bar) FROM baz;", "", Collections.singletonList("SELECT
JSON_OBJECTAGG(foo:bar) FROM baz;")),
+ Arguments.of("nested json object separator", "SELECT
json_object('foo':json_object('bar':1), 'baz':2);", "",
+ Collections.singletonList("SELECT
json_object('foo':json_object('bar':1), 'baz':2);")),
+ Arguments.of("variable after json object", "SELECT
JSON_OBJECT('foo':1); SELECT :foo; SELECT 3;", "", Arrays.asList("SELECT
JSON_OBJECT('foo':1);", "SELECT 3;")),
+ Arguments.of("variable after json object expression", "SELECT
JSON_OBJECT('foo':1), :foo; SELECT 2;", "", Collections.singletonList("SELECT
2;")),
+ Arguments.of("unknown command in buffer", "SELECT 1\n\\unknown
foo\nSELECT 2;", "", Collections.emptyList()),
+ Arguments.of("mixed commands in buffer", "SELECT 1 \\g \\echo
foo;\nSELECT 2;", "", Collections.singletonList("SELECT 2;")),
+ Arguments.of("standalone escaped command", "\\echo
foo\\bar\nSELECT 2;", "", Collections.singletonList("SELECT 2;")),
+ Arguments.of("standalone mixed commands", "\\g \\echo
foo;\nSELECT 2;", "", Collections.singletonList("SELECT 2;")),
Arguments.of("unterminated sql", "SELECT\n 1", "",
Collections.singletonList("SELECT\n 1")),
Arguments.of("unclosed parenthesis at eof", "SELECT
(1;\nSELECT 2;", "", Collections.singletonList("SELECT (1;\nSELECT 2;")),
Arguments.of("unclosed atomic body at eof", "CREATE FUNCTION
foo() RETURNS integer BEGIN ATOMIC SELECT 1;\nSELECT 2;", "",
@@ -133,14 +178,28 @@ class PostgreSQLExternalTestParameterLoadTemplateTest {
"COPY (SELECT 'ERROR: client text') TO STDOUT;\nERROR:
client text\nSELECT 2;", Arrays.asList("COPY (SELECT 'ERROR: client text') TO
STDOUT;", "SELECT 2;")),
Arguments.of("error in leading block comment", "SELECT
1;\n/*\nERROR: literal\n*/\nSELECT 2;", "SELECT 1;\n/*\nERROR:
literal\n*/\nSELECT 2;", Arrays.asList("SELECT 1;", "SELECT 2;")),
Arguments.of("unresolved variables with error", "SELECT :foo,
:'foo', :\"foo\", :{?foo};\nSELECT 2;", "SELECT :foo, :'foo', :\"foo\",
:{?foo};\nERROR: bar\nSELECT 2;",
- Arrays.asList("SELECT :foo, :'foo', :\"foo\",
:{?foo};", "SELECT 2;")),
+ Collections.singletonList("SELECT 2;")),
+ Arguments.of("typed variable original echo boundary", "SELECT
1;\nSELECT :'bar';\nSELECT 3;",
+ "SELECT 1;\nERROR: foo\nSELECT :'bar';\nERROR:
bar\nSELECT 3;", Arrays.asList("SELECT 'foo';", "SELECT 3;")),
+ Arguments.of("typed variable error state reset", "SELECT
:'bar';\nSELECT 2;\nSELECT 3;",
+ "SELECT :'bar';\nERROR: foo\nSELECT 2;\nERROR:
bar\nSELECT 3;", Arrays.asList("SELECT 'foo';", "SELECT 3;")),
+ Arguments.of("client statement echo boundary", "SELECT
1;\n\\copy foo TO STDOUT\nSELECT 2;", "SELECT 1;\n\\copy foo TO STDOUT\nERROR:
client text\nSELECT 2;",
+ Arrays.asList("SELECT 1;", "SELECT 2;")),
+ Arguments.of("standalone mixed format command", "\\g \\pset
format unaligned\nSELECT 'ERROR: client text';",
+ "\\g \\pset format unaligned\nSELECT 'ERROR: client
text';\n?column?\nERROR: client text\n(1 row)",
Collections.singletonList("SELECT 'ERROR: client text';")),
+ Arguments.of("mixed echo format command", "\\echo foo \\pset
format unaligned\n\\set ECHO all\nSELECT 'ERROR: client text';",
+ "\\echo foo \\pset format unaligned\n\\set ECHO
all\nSELECT 'ERROR: client text';\n?column?\nERROR: client text\n(1 row)",
+ Collections.singletonList("SELECT 'ERROR: client
text';")),
Arguments.of("cast and parameter native error", "SELECT
$1::integer;\nSELECT 2;", "SELECT $1::integer;\nERROR: foo\nSELECT 2;",
Collections.singletonList("SELECT 2;")),
Arguments.of("array slice in rule action", "CREATE RULE foo AS
ON INSERT TO bar DO ALSO (SELECT a[1:2] FROM baz; SELECT 2);\nSELECT 3;",
"CREATE RULE foo AS ON INSERT TO bar DO ALSO (SELECT
a[1:2] FROM baz; SELECT 2);\nERROR: foo\nSELECT 3;",
Arrays.asList("CREATE RULE foo AS ON INSERT TO bar DO
ALSO (SELECT a[1:2] FROM baz; SELECT 2);", "SELECT 3;")),
Arguments.of("variable in atomic body", "CREATE FUNCTION foo()
RETURNS integer BEGIN ATOMIC SELECT :foo; SELECT 2; END;\nSELECT 3;",
"CREATE FUNCTION foo() RETURNS integer BEGIN ATOMIC
SELECT :foo; SELECT 2; END;\nERROR: foo\nSELECT 3;",
- Arrays.asList("CREATE FUNCTION foo() RETURNS integer
BEGIN ATOMIC SELECT :foo; SELECT 2; END;", "SELECT 3;")),
+ Collections.singletonList("SELECT 3;")),
+ Arguments.of("raw keyword variable echo boundary", "CREATE
FUNCTION foo() RETURNS integer LANGUAGE SQL BEGIN ATOMIC SELECT :case;
END;\nSELECT 2;\nSELECT 3;",
+ "CREATE FUNCTION foo() RETURNS integer LANGUAGE SQL
BEGIN ATOMIC SELECT :case; END;\nERROR: foo\nSELECT 2;\nERROR: bar\nSELECT 3;",
+ Collections.singletonList("SELECT 3;")),
Arguments.of("missing echo", "SELECT 1;\nSELECT 2;", "ERROR:
foo\nSELECT 2;", Arrays.asList("SELECT 1;", "SELECT 2;")),
Arguments.of("reversed echo order", "SELECT 1;\nSELECT 2;",
"SELECT 2;\nERROR: foo\nSELECT 1;\nERROR: bar", Arrays.asList("SELECT 1;",
"SELECT 2;")),
Arguments.of("incomplete multiline echo", "SELECT\n
1;\nSELECT 2;", "SELECT", Arrays.asList("SELECT\n 1;", "SELECT 2;")),