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

terrymanu 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 15e55e398f1 Fix PostgreSQL external regression script loading (#40126)
15e55e398f1 is described below

commit 15e55e398f10e860a7833d31151a6fd9b60925cb
Author: Liang Zhang <[email protected]>
AuthorDate: Sat Oct 10 13:41:43 2026 +0800

    Fix PostgreSQL external regression script loading (#40126)
    
    * Suppress routine Jev usage feedback in task reports
    
    * Fix PostgreSQL external regression script loading
---
 ...ostgreSQLExternalTestParameterLoadTemplate.java | 398 +++++++++++++++++++++
 .../StandardExternalTestParameterLoadTemplate.java |  81 -----
 ...reSQLExternalTestParameterLoadTemplateTest.java | 187 ++++++++++
 .../postgresql/ExternalPostgreSQLParserIT.java     |   4 +-
 4 files changed, 587 insertions(+), 83 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
new file mode 100644
index 00000000000..6c1f48507d7
--- /dev/null
+++ 
b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/dialect/PostgreSQLExternalTestParameterLoadTemplate.java
@@ -0,0 +1,398 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 
org.apache.shardingsphere.test.it.sql.parser.external.loader.template.dialect;
+
+import lombok.RequiredArgsConstructor;
+import lombok.SneakyThrows;
+import org.antlr.v4.runtime.BaseErrorListener;
+import org.antlr.v4.runtime.CharStream;
+import org.antlr.v4.runtime.CodePointBuffer;
+import org.antlr.v4.runtime.CodePointCharStream;
+import org.antlr.v4.runtime.ConsoleErrorListener;
+import org.antlr.v4.runtime.Lexer;
+import org.antlr.v4.runtime.LexerNoViableAltException;
+import org.antlr.v4.runtime.RecognitionException;
+import org.antlr.v4.runtime.Recognizer;
+import org.antlr.v4.runtime.Token;
+import org.antlr.v4.runtime.misc.Interval;
+import 
org.apache.shardingsphere.database.connector.core.spi.DatabaseTypedSPILoader;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import org.apache.shardingsphere.sql.parser.spi.DialectSQLParserFacade;
+import 
org.apache.shardingsphere.test.it.sql.parser.external.ExternalSQLTestParameter;
+import 
org.apache.shardingsphere.test.it.sql.parser.external.loader.template.ExternalTestParameterLoadTemplate;
+
+import java.nio.CharBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+/**
+ * External test parameter load template for PostgreSQL.
+ */
+public final class PostgreSQLExternalTestParameterLoadTemplate implements 
ExternalTestParameterLoadTemplate {
+    
+    @Override
+    public Collection<ExternalSQLTestParameter> load(final String 
sqlCaseFileName, final List<String> sqlCaseFileContent,
+                                                     final List<String> 
resultFileContent, final String databaseType, final String reportType) {
+        List<ScriptStatement> statements = new 
ScriptReader(sqlCaseFileContent, databaseType).read();
+        int[] echoes = findEchoes(statements, resultFileContent);
+        Collection<ExternalSQLTestParameter> result = new 
ArrayList<>(statements.size());
+        for (int i = 0; i < statements.size(); i++) {
+            ScriptStatement statement = statements.get(i);
+            if (!statement.sql.isEmpty() && !hasNativeError(statement, i, 
echoes, resultFileContent)) {
+                result.add(new ExternalSQLTestParameter(sqlCaseFileName + 
(result.size() + 1), databaseType, statement.sql, reportType));
+            }
+        }
+        return result;
+    }
+    
+    private int[] findEchoes(final List<ScriptStatement> statements, final 
List<String> expectedLines) {
+        Map<String, List<Integer>> linePositions = IntStream.range(0, 
expectedLines.size()).boxed().collect(Collectors.groupingBy(expectedLines::get));
+        Map<List<String>, Integer> occurrences = 
statements.stream().collect(Collectors.toMap(each -> each.echo, each -> 1, 
Integer::sum));
+        Map<List<String>, Iterator<Integer>> matches = 
occurrences.entrySet().stream()
+                .collect(Collectors.toMap(Entry::getKey, entry -> 
matchEcho(entry.getKey(), linePositions, expectedLines, 
entry.getValue()).iterator()));
+        int[] result = new int[statements.size()];
+        Arrays.fill(result, -1);
+        int cursor = 0;
+        for (int i = 0; i < statements.size(); i++) {
+            List<String> echo = statements.get(i).echo;
+            Iterator<Integer> positions = matches.get(echo);
+            int position = positions.hasNext() ? positions.next() : -1;
+            if (position >= cursor) {
+                result[i] = position;
+                cursor = result[i] + echo.size();
+            }
+        }
+        return result;
+    }
+    
+    private List<Integer> matchEcho(final List<String> echo, final Map<String, 
List<Integer>> linePositions, final List<String> expectedLines, final int 
occurrences) {
+        Collection<Integer> positions = 
linePositions.getOrDefault(echo.get(0), Collections.emptyList());
+        List<Integer> result = positions.stream().mapToInt(each -> each)
+                .filter(each -> each + echo.size() <= expectedLines.size() && 
expectedLines.subList(each, each + 
echo.size()).equals(echo)).boxed().collect(Collectors.toList());
+        return result.size() == occurrences ? result : Collections.emptyList();
+    }
+    
+    private boolean hasNativeError(final ScriptStatement statement, final int 
index, final int[] echoes, final List<String> expectedLines) {
+        if (!statement.filterable || echoes[index] < 0 || index + 1 < 
echoes.length && echoes[index + 1] < 0) {
+            return false;
+        }
+        int end = index + 1 == echoes.length ? expectedLines.size() : 
echoes[index + 1];
+        for (int i = echoes[index] + statement.echo.size(); i < end; i++) {
+            String line = expectedLines.get(i);
+            if (line.startsWith("NOTICE:") || line.startsWith("WARNING:") || 
line.startsWith("INFO:") || line.startsWith("LOG:") || 
line.startsWith("DEBUG:")) {
+                return false;
+            }
+            if (line.startsWith("ERROR:")) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    @RequiredArgsConstructor
+    private static final class ScriptStatement {
+        
+        private final String sql;
+        
+        private final List<String> echo;
+        
+        private final boolean filterable;
+    }
+    
+    private static final class ScriptReader extends BaseErrorListener {
+        
+        private final List<String> lines;
+        
+        private final DatabaseType databaseType;
+        
+        private final CharStream input;
+        
+        private final Lexer lexer;
+        
+        private final int[] lineStarts;
+        
+        private final boolean[] quotedLines;
+        
+        private final List<ScriptStatement> statements = new ArrayList<>();
+        
+        private int start = -1;
+        
+        private int parentheses;
+        
+        private int atomicBlocks;
+        
+        private int caseBlocks;
+        
+        private String previousKind = "";
+        
+        private boolean firstToken = true;
+        
+        private boolean copyStatement;
+        
+        private boolean copyOutput;
+        
+        private boolean pendingCopy;
+        
+        private boolean unsafe;
+        
+        private boolean clientBuffer;
+        
+        private boolean echoAll = true;
+        
+        private boolean clientStateKnown = true;
+        
+        private ScriptReader(final List<String> lines, final String 
databaseType) {
+            this.lines = lines;
+            this.databaseType = TypedSPILoader.getService(DatabaseType.class, 
databaseType);
+            input = 
CodePointCharStream.fromBuffer(CodePointBuffer.withChars(CharBuffer.wrap(String.join("\n",
 lines).toCharArray())));
+            lexer = createLexer(input);
+            lexer.addErrorListener(this);
+            lineStarts = new int[lines.size()];
+            quotedLines = new boolean[lines.size()];
+            for (int i = 1; i < lineStarts.length; i++) {
+                lineStarts[i] = lineStarts[i - 1] + lines.get(i - 1).length() 
+ 1;
+            }
+        }
+        
+        @SneakyThrows(ReflectiveOperationException.class)
+        private Lexer createLexer(final CharStream characters) {
+            Lexer result = (Lexer) 
DatabaseTypedSPILoader.getService(DialectSQLParserFacade.class, 
databaseType).getLexerClass().getConstructor(CharStream.class).newInstance(characters);
+            result.removeErrorListener(ConsoleErrorListener.INSTANCE);
+            return result;
+        }
+        
+        private List<ScriptStatement> read() {
+            for (Token token = lexer.nextToken(); Token.EOF != 
token.getType(); token = lexer.nextToken()) {
+                markQuotedLines(token);
+                if (Token.DEFAULT_CHANNEL != token.getChannel()) {
+                    if (start < 0) {
+                        statements.add(new ScriptStatement("", 
getEcho(lineAt(token.getStartIndex()), lineAt(token.getStopIndex())), false));
+                    }
+                    continue;
+                }
+                String kind = 
lexer.getVocabulary().getSymbolicName(token.getType());
+                if ("BACKSLASH_".equals(kind) && 
readCommand(token.getStartIndex())) {
+                    continue;
+                }
+                if (start < 0) {
+                    start = token.getStartIndex();
+                }
+                readBlock(kind, token);
+                readCopy(kind);
+                previousKind = kind;
+                if ("COLON_".equals(kind)) {
+                    unsafe = true;
+                }
+                if ("SEMI_".equals(kind) && (clientBuffer || 0 == parentheses 
&& 0 == atomicBlocks)) {
+                    submit(token.getStopIndex() + 1, token.getStopIndex() + 1, 
true);
+                }
+            }
+            if (start >= 0) {
+                submit(input.size(), input.size(), 0 == parentheses && 0 == 
atomicBlocks);
+            }
+            return statements;
+        }
+        
+        private void markQuotedLines(final Token token) {
+            if (lexer.getLine() > token.getLine()) {
+                int lastLine = lineAt(token.getStopIndex());
+                for (int i = token.getLine(); i <= lastLine; i++) {
+                    quotedLines[i] = true;
+                }
+            }
+        }
+        
+        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());
+        }
+        
+        private boolean readCommand(final int offset) {
+            int line = lineAt(offset);
+            int end = lineStarts[line] + lines.get(line).length();
+            String command = input.getText(Interval.of(offset + 1, end - 1));
+            if (command.startsWith(";")) {
+                unsafe = true;
+                clientBuffer = true;
+                firstToken = true;
+                previousKind = "";
+                seek(offset + 2);
+                return true;
+            }
+            String name = command.split("\\s|\\\\", 2)[0];
+            if (command.indexOf('\\') >= 0) {
+                unsafe = true;
+                clientBuffer = true;
+                return false;
+            }
+            if (Arrays.asList("g", "gx", "gset", "gexec").contains(name)) {
+                boolean copy = pendingCopy;
+                if (start >= 0) {
+                    submit(offset, end, "g".equals(command.trim()) || 
"gset".equals(command.trim()));
+                } else {
+                    statements.add(new ScriptStatement("", lines.subList(line, 
line + 1), false));
+                }
+                if (!copy) {
+                    seek(Math.min(input.size(), end + 1));
+                }
+                return true;
+            }
+            if (start >= 0) {
+                unsafe = true;
+                clientBuffer = true;
+                return false;
+            }
+            if ("copy".equals(name)) {
+                start = offset;
+                unsafe = true;
+                boolean copy = isCopyFromStdin(command);
+                pendingCopy = copy;
+                submit(end, end, false);
+                if (!copy) {
+                    seek(Math.min(input.size(), end + 1));
+                }
+                return true;
+            }
+            statements.add(new ScriptStatement("", lines.subList(line, line + 
1), false));
+            if (command.startsWith("set ECHO ")) {
+                echoAll = "all".equals(command.substring("set ECHO 
".length()).trim());
+            } else if (!Arrays.asList("echo", "qecho").contains(name)) {
+                clientStateKnown = false;
+            }
+            seek(Math.min(input.size(), end + 1));
+            return true;
+        }
+        
+        private boolean isCopyFromStdin(final String command) {
+            Lexer header = 
createLexer(CodePointCharStream.fromBuffer(CodePointBuffer.withChars(CharBuffer.wrap(command.toCharArray()))));
+            int depth = 0;
+            boolean from = false;
+            for (Token token = header.nextToken(); Token.EOF != 
token.getType(); token = header.nextToken()) {
+                if (Token.DEFAULT_CHANNEL != token.getChannel()) {
+                    continue;
+                }
+                String kind = 
header.getVocabulary().getSymbolicName(token.getType());
+                if ("LP_".equals(kind)) {
+                    depth++;
+                } else if ("RP_".equals(kind)) {
+                    depth--;
+                } else if (0 == depth && "STDIN".equals(kind) && from) {
+                    return true;
+                }
+                from = "FROM".equals(kind);
+            }
+            return false;
+        }
+        
+        private void seek(final int offset) {
+            input.seek(offset);
+            if (offset < input.size()) {
+                int line = lineAt(offset);
+                lexer.setLine(line + 1);
+                lexer.setCharPositionInLine(offset - lineStarts[line]);
+            }
+        }
+        
+        private int lineAt(final int offset) {
+            int index = Arrays.binarySearch(lineStarts, offset);
+            return index >= 0 ? index : -index - 2;
+        }
+        
+        private void readBlock(final String kind, final Token token) {
+            if ("LP_".equals(kind)) {
+                parentheses++;
+            } else if ("RP_".equals(kind) && parentheses > 0) {
+                parentheses--;
+            } else if ("BEGIN".equals(previousKind) && 
"ATOMIC".equalsIgnoreCase(token.getText())) {
+                atomicBlocks++;
+            } else if (atomicBlocks > 0 && "CASE".equals(kind)) {
+                caseBlocks++;
+            } else if (atomicBlocks > 0 && "END".equals(kind)) {
+                if (caseBlocks > 0) {
+                    caseBlocks--;
+                } else {
+                    atomicBlocks--;
+                }
+            }
+        }
+        
+        private void readCopy(final String kind) {
+            if (firstToken) {
+                copyStatement = "COPY".equals(kind);
+                firstToken = false;
+            }
+            if (copyStatement && 0 == parentheses && 
"FROM".equals(previousKind) && "STDIN".equals(kind)) {
+                pendingCopy = true;
+            }
+            if (copyStatement && 0 == parentheses && "TO".equals(previousKind) 
&& "STDOUT".equals(kind)) {
+                copyOutput = true;
+            }
+        }
+        
+        private void submit(final int sqlEnd, final int echoEnd, final boolean 
filterable) {
+            int end = sqlEnd;
+            int firstLine = lineAt(start);
+            int lastLine = lineAt(echoEnd - 1);
+            boolean exclusive = 
input.getText(Interval.of(lineStarts[firstLine], start - 1)).trim().isEmpty()
+                    && input.getText(Interval.of(echoEnd, lineStarts[lastLine] 
+ lines.get(lastLine).length() - 1)).trim().isEmpty();
+            if (pendingCopy) {
+                int marker = IntStream.range(lastLine + 1, 
lines.size()).filter(i -> "\\.".equals(lines.get(i))).findFirst().orElse(-1);
+                if (marker < 0) {
+                    end = input.size();
+                    unsafe = true;
+                    seek(input.size());
+                } else {
+                    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));
+            reset();
+        }
+        
+        private void reset() {
+            start = -1;
+            parentheses = 0;
+            atomicBlocks = 0;
+            caseBlocks = 0;
+            previousKind = "";
+            firstToken = true;
+            copyStatement = false;
+            copyOutput = false;
+            pendingCopy = false;
+            unsafe = false;
+            clientBuffer = false;
+        }
+        
+        @Override
+        public void syntaxError(final Recognizer<?, ?> recognizer, final 
Object offendingSymbol, final int line, final int charPositionInLine, final 
String msg, final RecognitionException ex) {
+            unsafe = true;
+            if (start < 0) {
+                start = ((LexerNoViableAltException) ex).getStartIndex();
+            }
+        }
+    }
+}
diff --git 
a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/type/StandardExternalTestParameterLoadTemplate.java
 
b/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/type/StandardExternalTestParameterLoadTemplate.java
deleted file mode 100644
index b9231c99ec4..00000000000
--- 
a/test/it/parser/src/main/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/type/StandardExternalTestParameterLoadTemplate.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package 
org.apache.shardingsphere.test.it.sql.parser.external.loader.template.type;
-
-import 
org.apache.shardingsphere.test.it.sql.parser.external.ExternalSQLTestParameter;
-import 
org.apache.shardingsphere.test.it.sql.parser.external.loader.SQLLineComment;
-import 
org.apache.shardingsphere.test.it.sql.parser.external.loader.template.ExternalTestParameterLoadTemplate;
-
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-
-/**
- * Standard external test parameter load template.
- */
-public final class StandardExternalTestParameterLoadTemplate implements 
ExternalTestParameterLoadTemplate {
-    
-    @Override
-    public Collection<ExternalSQLTestParameter> load(final String 
sqlCaseFileName, final List<String> sqlCaseFileContent,
-                                                     final List<String> 
resultFileContent, final String databaseType, final String reportType) {
-        Collection<ExternalSQLTestParameter> result = new LinkedList<>();
-        String completedSQL = "";
-        int sqlCaseEnum = 1;
-        int statementLines = 0;
-        int resultIndex = 0;
-        boolean inProcedure = false;
-        for (String each : sqlCaseFileContent) {
-            inProcedure = isInProcedure(inProcedure, each.trim());
-            completedSQL = getStatement(completedSQL, each.trim(), 
inProcedure);
-            statementLines = completedSQL.isEmpty() ? 0 : statementLines + 1;
-            if (completedSQL.contains(";") && !inProcedure) {
-                resultIndex = searchInResultContent(resultIndex, 
resultFileContent, completedSQL, statementLines);
-                if (resultIndex >= resultFileContent.size() || 
!resultFileContent.get(resultIndex).contains("ERROR")) {
-                    String sqlCaseId = sqlCaseFileName + sqlCaseEnum;
-                    result.add(new ExternalSQLTestParameter(sqlCaseId, 
databaseType, completedSQL, reportType));
-                    sqlCaseEnum++;
-                }
-                completedSQL = "";
-                statementLines = 0;
-            }
-        }
-        return result;
-    }
-    
-    private boolean isInProcedure(final boolean inProcedure, final String 
statementLines) {
-        if (statementLines.contains("{") && statementLines.contains("}")) {
-            return inProcedure;
-        }
-        return (statementLines.contains("{") || statementLines.contains("}") 
|| statementLines.contains("$$")) != inProcedure;
-    }
-    
-    private String getStatement(final String completedSQL, final String 
rawSQLLine, final boolean inProcedure) {
-        return (rawSQLLine.isEmpty() || SQLLineComment.isComment(rawSQLLine)) 
&& !inProcedure ? "" : completedSQL + rawSQLLine + " ";
-    }
-    
-    private int searchInResultContent(final int resultIndex, final 
List<String> resultLines, final String completedSQL, final int statementLines) {
-        int index = resultIndex;
-        while (index < resultLines.size() && 
!completedSQL.startsWith(resultLines.get(index).trim())) {
-            index++;
-        }
-        if (index != resultLines.size()) {
-            return index + statementLines;
-        }
-        return resultIndex;
-    }
-}
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
new file mode 100644
index 00000000000..f442f4c3da7
--- /dev/null
+++ 
b/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/external/loader/template/dialect/PostgreSQLExternalTestParameterLoadTemplateTest.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 
org.apache.shardingsphere.test.it.sql.parser.external.loader.template.dialect;
+
+import 
org.apache.shardingsphere.test.it.sql.parser.external.ExternalSQLTestParameter;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class PostgreSQLExternalTestParameterLoadTemplateTest {
+    
+    private final PostgreSQLExternalTestParameterLoadTemplate template = new 
PostgreSQLExternalTestParameterLoadTemplate();
+    
+    @ParameterizedTest(name = "{0}")
+    @MethodSource({"sqlCases", "expectedCases"})
+    void assertLoadSQL(final String name, final String source, final String 
expectedEcho, final Collection<String> expectedSQL) {
+        List<String> expectedFileContent = expectedEcho.isEmpty() ? 
Collections.emptyList() : Arrays.asList(expectedEcho.split("\n", -1));
+        Collection<ExternalSQLTestParameter> actual = template.load("sample", 
Arrays.asList(source.split("\n", -1)), expectedFileContent, "PostgreSQL", 
"CSV");
+        
assertThat(actual.stream().map(ExternalSQLTestParameter::getSql).collect(Collectors.toList()),
 is(expectedSQL));
+        
assertThat(actual.stream().map(ExternalSQLTestParameter::getSqlCaseId).collect(Collectors.toList()),
+                is(IntStream.rangeClosed(1, expectedSQL.size()).mapToObj(index 
-> "sample" + index).collect(Collectors.toList())));
+    }
+    
+    private static Stream<Arguments> sqlCases() {
+        return Stream.of(
+                Arguments.of("tagged dollar body", "DO $x$\nDECLARE\n  foo 
integer;\nBEGIN\n  foo := 1;\nEND;\n$x$;\nSELECT 2;", "",
+                        Arrays.asList("DO $x$\nDECLARE\n  foo 
integer;\nBEGIN\n  foo := 1;\nEND;\n$x$;", "SELECT 2;")),
+                Arguments.of("nested dollar text", "DO $x$ BEGIN PERFORM 
$y$foo;bar$y$; END $x$; SELECT 2;", "", Arrays.asList("DO $x$ BEGIN PERFORM 
$y$foo;bar$y$; END $x$;", "SELECT 2;")),
+                Arguments.of("untagged dollar body", "CREATE FUNCTION foo() 
RETURNS integer AS $$ BEGIN RETURN 1; END; $$ LANGUAGE plpgsql; SELECT 2;", "",
+                        Arrays.asList("CREATE FUNCTION foo() RETURNS integer 
AS $$ BEGIN RETURN 1; END; $$ LANGUAGE plpgsql;", "SELECT 2;")),
+                Arguments.of("single quoted body", "CREATE FUNCTION foo() 
RETURNS integer AS 'BEGIN\nRETURN 1;\nEND' LANGUAGE plpgsql;\nSELECT 2;", "",
+                        Arrays.asList("CREATE FUNCTION foo() RETURNS integer 
AS 'BEGIN\nRETURN 1;\nEND' LANGUAGE plpgsql;", "SELECT 2;")),
+                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("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;")),
+                Arguments.of("blank lines in quoted text", "SELECT 
'foo\n\nbar', \"foo\n\nbar\", $x$foo\n\nbar$x$ /*foo\n\nbar*/;\nSELECT 2;", "",
+                        Arrays.asList("SELECT 'foo\n\nbar', \"foo\n\nbar\", 
$x$foo\n\nbar$x$ /*foo\n\nbar*/;", "SELECT 2;")),
+                Arguments.of("leading comments", "-- foo;\n/* bar; */\nSELECT 
1;", "", Collections.singletonList("SELECT 1;")),
+                Arguments.of("same line statements", "SELECT 1; SELECT 2; 
SELECT 3;", "", Arrays.asList("SELECT 1;", "SELECT 2;", "SELECT 3;")),
+                Arguments.of("atomic body with case", "CREATE FUNCTION foo() 
RETURNS integer BEGIN ATOMIC SELECT CASE WHEN true THEN 1 ELSE 2 END; SELECT 3; 
END; SELECT 4;", "",
+                        Arrays.asList("CREATE FUNCTION foo() RETURNS integer 
BEGIN ATOMIC SELECT CASE WHEN true THEN 1 ELSE 2 END; SELECT 3; END;", "SELECT 
4;")),
+                Arguments.of("transaction begin", "BEGIN; SELECT 1; COMMIT;", 
"", Arrays.asList("BEGIN;", "SELECT 1;", "COMMIT;")),
+                Arguments.of("unmatched closing parenthesis", "SELECT 
);\nSELECT 2;", "", Arrays.asList("SELECT );", "SELECT 2;")),
+                Arguments.of("rule action parentheses", "CREATE RULE foo AS ON 
INSERT TO bar DO ALSO (INSERT INTO baz VALUES (1); INSERT INTO baz VALUES (2)); 
SELECT 3;", "",
+                        Arrays.asList("CREATE RULE foo AS ON INSERT TO bar DO 
ALSO (INSERT INTO baz VALUES (1); INSERT INTO baz VALUES (2));", "SELECT 3;")),
+                Arguments.of("submit g", "SELECT 1 \\g\nSELECT 2;", "", 
Arrays.asList("SELECT 1", "SELECT 2;")),
+                Arguments.of("submit gx", "SELECT 1 \\gx\nSELECT 2;", "", 
Arrays.asList("SELECT 1", "SELECT 2;")),
+                Arguments.of("submit gset", "SELECT 1 \\gset\nSELECT 2;", "", 
Arrays.asList("SELECT 1", "SELECT 2;")),
+                Arguments.of("submit gexec", "SELECT 1 \\gexec\nSELECT 2;", 
"", Arrays.asList("SELECT 1", "SELECT 2;")),
+                Arguments.of("multiline submit", "SELECT\n  1\n\\gset\nSELECT 
2;", "", Arrays.asList("SELECT\n  1", "SELECT 2;")),
+                Arguments.of("copy payload", "COPY foo FROM 
STDIN;\nfoo';\n$x$;\n\\.\nSELECT 2;", "", Arrays.asList("COPY foo FROM STDIN;", 
"SELECT 2;")),
+                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 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$;")),
+                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("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;", "",
+                        Collections.singletonList("CREATE FUNCTION foo() 
RETURNS integer BEGIN ATOMIC SELECT 1;\nSELECT 2;")),
+                Arguments.of("unknown lexical character", "SELECT 1 " + (char) 
1 + "; SELECT 2;", "", Arrays.asList("SELECT 1 " + (char) 1 + ";", "SELECT 
2;")),
+                Arguments.of("unknown leading lexical character", (char) 127 + 
"SELECT 1;\nSELECT 2;", "", Arrays.asList((char) 127 + "SELECT 1;", "SELECT 
2;")),
+                Arguments.of("unicode offsets", "SELECT 'foo😀bar;'; SELECT 
2;", "", Arrays.asList("SELECT 'foo😀bar;';", "SELECT 2;")),
+                Arguments.of("literal brace and dollar", "SELECT '{foo', 
'}bar', '$$;'; SELECT 2;", "", Arrays.asList("SELECT '{foo', '}bar', '$$;';", 
"SELECT 2;")));
+    }
+    
+    private static Stream<Arguments> expectedCases() {
+        return Stream.of(
+                Arguments.of("blank is not echo anchor", "SELECT 1;\nSELECT 
2;", "\nSELECT 1;\nERROR: foo\nSELECT 2;", Collections.singletonList("SELECT 
2;")),
+                Arguments.of("notice before error", "DO $x$\nBEGIN\n  RAISE 
NOTICE 'foo';\n  RAISE EXCEPTION 'bar';\nEND $x$;\nSELECT 2;",
+                        "DO $x$\nBEGIN\n  RAISE NOTICE 'foo';\n  RAISE 
EXCEPTION 'bar';\nEND $x$;\nNOTICE: foo\nERROR: bar\nSELECT 2;",
+                        Arrays.asList("DO $x$\nBEGIN\n  RAISE NOTICE 'foo';\n  
RAISE EXCEPTION 'bar';\nEND $x$;", "SELECT 2;")),
+                Arguments.of("notice continuation with error text", "DO $$ 
BEGIN RAISE NOTICE E'foo\\nERROR: fake'; END $$;\nSELECT 2;",
+                        "DO $$ BEGIN RAISE NOTICE E'foo\\nERROR: fake'; END 
$$;\nNOTICE: foo\nERROR: fake\nSELECT 2;",
+                        Arrays.asList("DO $$ BEGIN RAISE NOTICE E'foo\\nERROR: 
fake'; END $$;", "SELECT 2;")),
+                Arguments.of("warning continuation with error text", "SELECT 
1;\nSELECT 2;", "SELECT 1;\nWARNING: foo\nERROR: fake\nSELECT 2;", 
Arrays.asList("SELECT 1;", "SELECT 2;")),
+                Arguments.of("info continuation with error text", "SELECT 
1;\nSELECT 2;", "SELECT 1;\nINFO: foo\nERROR: fake\nSELECT 2;", 
Arrays.asList("SELECT 1;", "SELECT 2;")),
+                Arguments.of("log continuation with error text", "SELECT 
1;\nSELECT 2;", "SELECT 1;\nLOG: foo\nERROR: fake\nSELECT 2;", 
Arrays.asList("SELECT 1;", "SELECT 2;")),
+                Arguments.of("debug continuation with error text", "SELECT 
1;\nSELECT 2;", "SELECT 1;\nDEBUG: foo\nERROR: fake\nSELECT 2;", 
Arrays.asList("SELECT 1;", "SELECT 2;")),
+                Arguments.of("multiline echo", "SELECT\n\n  -- foo\n  
1;\nSELECT 2;", "SELECT\n  -- foo\n  1;\nERROR: bar\nSELECT 2;", 
Collections.singletonList("SELECT 2;")),
+                Arguments.of("space only line in echo", "SELECT\n  \n  
1;\nSELECT 2;", "SELECT\n  \n  1;\nERROR: foo\nSELECT 2;", 
Collections.singletonList("SELECT 2;")),
+                Arguments.of("blank line in string echo", "SELECT 
'foo\n\nbar';\nSELECT 2;", "SELECT 'foo\n\nbar';\nERROR: foo\nSELECT 2;", 
Collections.singletonList("SELECT 2;")),
+                Arguments.of("blank line in identifier echo", "SELECT 
\"foo\n\nbar\";\nSELECT 2;", "SELECT \"foo\n\nbar\";\nERROR: foo\nSELECT 2;", 
Collections.singletonList("SELECT 2;")),
+                Arguments.of("blank line in comment echo", "SELECT 
/*foo\n\nbar*/ 1;\nSELECT 2;", "SELECT /*foo\n\nbar*/ 1;\nERROR: foo\nSELECT 
2;", Collections.singletonList("SELECT 2;")),
+                Arguments.of("blank line in dollar echo", "SELECT 
$x$foo\n\nbar$x$;\nSELECT 2;", "SELECT $x$foo\n\nbar$x$;\nERROR: foo\nSELECT 
2;", Collections.singletonList("SELECT 2;")),
+                Arguments.of("error in output literal", "SELECT 'ERROR' AS 
foo;\nSELECT 2;", "SELECT 'ERROR' AS foo;\n foo\n-----\n ERROR\n(1 row)\nSELECT 
2;",
+                        Arrays.asList("SELECT 'ERROR' AS foo;", "SELECT 2;")),
+                Arguments.of("error in echoed string", "SELECT 'foo\nERROR: 
bar\nbaz';\nSELECT 2;", "SELECT 'foo\nERROR: bar\nbaz';\n foo\nSELECT 2;",
+                        Arrays.asList("SELECT 'foo\nERROR: bar\nbaz';", 
"SELECT 2;")),
+                Arguments.of("copy output error literal", "COPY (SELECT 
'ERROR: client text') TO STDOUT;\nSELECT 2;",
+                        "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;")),
+                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;")),
+                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;")),
+                Arguments.of("different multiline echo", "SELECT\n  1;\nSELECT 
2;", "SELECT\n  2;\nERROR: foo\nSELECT 2;", Arrays.asList("SELECT\n  1;", 
"SELECT 2;")),
+                Arguments.of("missing next echo", "SELECT 1;\nSELECT 2;", 
"SELECT 1;\nERROR: foo", Arrays.asList("SELECT 1;", "SELECT 2;")),
+                Arguments.of("echo prefix mismatch", "SELECT 12;\nSELECT 2;", 
"SELECT 1;\nERROR: foo\nSELECT 2;", Arrays.asList("SELECT 12;", "SELECT 2;")),
+                Arguments.of("missing duplicate echo", "SELECT 1;\nSELECT 1;", 
"SELECT 1;\nERROR: foo", Arrays.asList("SELECT 1;", "SELECT 1;")),
+                Arguments.of("shared echo line", "SELECT 1; SELECT 2;", 
"SELECT 1; SELECT 2;\nERROR: foo", Arrays.asList("SELECT 1;", "SELECT 2;")),
+                Arguments.of("repeated echo with context", "SELECT 1;\nSELECT 
2;\nSELECT 1;", "SELECT 1;\n foo\nSELECT 2;\n bar\nSELECT 1;\nERROR: baz", 
Arrays.asList("SELECT 1;", "SELECT 2;")),
+                Arguments.of("repeated echo after rejected position", "SELECT 
1;\nSELECT 2;\nSELECT 1;\nSELECT 1;", "SELECT 1;\nSELECT 1;\nSELECT 2;\nSELECT 
1;\nERROR: foo",
+                        Arrays.asList("SELECT 1;", "SELECT 2;", "SELECT 1;")),
+                Arguments.of("command response boundary", "SELECT 1;\n\\echo 
foo\nSELECT 2;", "SELECT 1;\n\\echo foo\nERROR: client text\nSELECT 2;", 
Arrays.asList("SELECT 1;", "SELECT 2;")),
+                Arguments.of("empty query buffer submission", "SELECT 
1;\n\\g\nSELECT 2;", "SELECT 1;\n\\g\nERROR: client text\nSELECT 2;", 
Arrays.asList("SELECT 1;", "SELECT 2;")),
+                Arguments.of("echo errors mode", "\\set ECHO errors\nSELECT 
1;\n\\set ECHO all\nSELECT 2;", "\\set ECHO errors\nERROR: foo\nSTATEMENT: 
SELECT 1;\nSELECT 2;",
+                        Arrays.asList("SELECT 1;", "SELECT 2;")),
+                Arguments.of("unaligned format after echo all", "\\pset format 
unaligned\n\\set ECHO all\nSELECT 'ERROR: client text';",
+                        "\\pset format unaligned\nOutput format is 
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("printed query buffer", "SELECT 1;\n\\p\nSELECT 
2;", "SELECT 1;\n foo\n\\p\nSELECT 1;\nERROR: client text\nSELECT 2;", 
Arrays.asList("SELECT 1;", "SELECT 2;")),
+                Arguments.of("expanded field with error text", "SELECT 1 AS 
\"ERROR:\" \\gx\nSELECT 2;", "SELECT 1 AS \"ERROR:\" \\gx\n-[ RECORD 1 
]-\nERROR: | 1\nSELECT 2;",
+                        Arrays.asList("SELECT 1 AS \"ERROR:\"", "SELECT 2;")),
+                Arguments.of("unaligned output with error text", "SELECT 
'ERROR: literal' \\g (format=unaligned)\nSELECT 2;",
+                        "SELECT 'ERROR: literal' \\g 
(format=unaligned)\n?column?\nERROR: literal\n(1 row)\nSELECT 2;", 
Arrays.asList("SELECT 'ERROR: literal'", "SELECT 2;")),
+                Arguments.of("g native error", "SELECT 1 \\g\nSELECT 2;", 
"SELECT 1 \\g\nERROR: foo\nSELECT 2;", Collections.singletonList("SELECT 2;")),
+                Arguments.of("gset native error", "SELECT 1 \\gset\nSELECT 
2;", "SELECT 1 \\gset\nERROR: foo\nSELECT 2;", 
Collections.singletonList("SELECT 2;")),
+                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("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;")));
+    }
+    
+    @Test
+    void assertLoadNativeError() {
+        Collection<ExternalSQLTestParameter> actual = template.load("sample", 
Collections.singletonList("SELECT 1;"), Arrays.asList("SELECT 1;", "ERROR: 
foo"), "PostgreSQL", "CSV");
+        assertTrue(actual.isEmpty());
+    }
+    
+    @Test
+    void assertLoadEmptySource() {
+        Collection<ExternalSQLTestParameter> actual = template.load("sample", 
Collections.emptyList(), Collections.emptyList(), "PostgreSQL", "CSV");
+        assertTrue(actual.isEmpty());
+    }
+}
diff --git 
a/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/postgresql/ExternalPostgreSQLParserIT.java
 
b/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/postgresql/ExternalPostgreSQLParserIT.java
index d3f9e15a343..2eddc569320 100644
--- 
a/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/postgresql/ExternalPostgreSQLParserIT.java
+++ 
b/test/it/parser/src/test/java/org/apache/shardingsphere/test/it/sql/parser/postgresql/ExternalPostgreSQLParserIT.java
@@ -19,10 +19,10 @@ package 
org.apache.shardingsphere.test.it.sql.parser.postgresql;
 
 import 
org.apache.shardingsphere.test.it.sql.parser.external.ExternalCaseSettings;
 import 
org.apache.shardingsphere.test.it.sql.parser.external.ExternalSQLParserIT;
-import 
org.apache.shardingsphere.test.it.sql.parser.external.loader.template.type.StandardExternalTestParameterLoadTemplate;
+import 
org.apache.shardingsphere.test.it.sql.parser.external.loader.template.dialect.PostgreSQLExternalTestParameterLoadTemplate;
 
 @ExternalCaseSettings(value = "PostgreSQL", caseURL = 
ExternalPostgreSQLParserIT.CASE_URL, resultURL = 
ExternalPostgreSQLParserIT.RESULT_URL,
-        template = StandardExternalTestParameterLoadTemplate.class)
+        template = PostgreSQLExternalTestParameterLoadTemplate.class)
 class ExternalPostgreSQLParserIT extends ExternalSQLParserIT {
     
     static final String CASE_URL = 
"https://github.com/postgres/postgres/tree/master/src/test/regress/sql";;

Reply via email to