diff --git a/.gitignore b/.gitignore index 76aa705e6f1..bcec0f67eb8 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,5 @@ derby.log /bin/ .mvn/wrapper/maven-wrapper.jar +ibderby/ .sts4-cache/ diff --git a/src/main/java/org/apache/ibatis/jdbc/ScriptRunner.java b/src/main/java/org/apache/ibatis/jdbc/ScriptRunner.java index 80d45187961..f5c57a1c7df 100644 --- a/src/main/java/org/apache/ibatis/jdbc/ScriptRunner.java +++ b/src/main/java/org/apache/ibatis/jdbc/ScriptRunner.java @@ -15,6 +15,9 @@ */ package org.apache.ibatis.jdbc; +import org.apache.ibatis.jdbc.handler.DefaultDelimiterHandler; +import org.apache.ibatis.jdbc.handler.DelimiterHandler; + import java.io.BufferedReader; import java.io.PrintWriter; import java.io.Reader; @@ -24,8 +27,6 @@ import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.Statement; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * @author Clinton Begin @@ -36,8 +37,6 @@ public class ScriptRunner { private static final String DEFAULT_DELIMITER = ";"; - private static final Pattern DELIMITER_PATTERN = Pattern.compile("^\\s*((--)|(//))?\\s*(//)?\\s*@DELIMITER\\s+([^\\s]+)", Pattern.CASE_INSENSITIVE); - private final Connection connection; private boolean stopOnError; @@ -53,6 +52,8 @@ public class ScriptRunner { private String delimiter = DEFAULT_DELIMITER; private boolean fullLineDelimiter; + private DelimiterHandler delimiterHandler = new DefaultDelimiterHandler(); + public ScriptRunner(Connection connection) { this.connection = connection; } @@ -104,6 +105,10 @@ public void setFullLineDelimiter(boolean fullLineDelimiter) { this.fullLineDelimiter = fullLineDelimiter; } + public void setDelimiterHandler(DelimiterHandler delimiterHandler) { + this.delimiterHandler = delimiterHandler; + } + public void runScript(Reader reader) { setAutoCommit(); @@ -205,11 +210,9 @@ private void checkForMissingLineTerminator(StringBuilder command) { private void handleLine(StringBuilder command, String line) throws SQLException { String trimmedLine = line.trim(); - if (lineIsComment(trimmedLine)) { - Matcher matcher = DELIMITER_PATTERN.matcher(trimmedLine); - if (matcher.find()) { - delimiter = matcher.group(5); - } + if (delimiterHandler.resetDelimiter(this, trimmedLine)) { + println(trimmedLine); + } else if (lineIsComment(trimmedLine)) { println(trimmedLine); } else if (commandReadyToExecute(trimmedLine)) { command.append(line, 0, line.lastIndexOf(delimiter)); @@ -224,7 +227,7 @@ private void handleLine(StringBuilder command, String line) throws SQLException } private boolean lineIsComment(String trimmedLine) { - return trimmedLine.startsWith("//") || trimmedLine.startsWith("--"); + return trimmedLine.startsWith("//") || trimmedLine.startsWith("--") || trimmedLine.startsWith("/*"); } private boolean commandReadyToExecute(String trimmedLine) { diff --git a/src/main/java/org/apache/ibatis/jdbc/handler/DefaultDelimiterHandler.java b/src/main/java/org/apache/ibatis/jdbc/handler/DefaultDelimiterHandler.java new file mode 100644 index 00000000000..8716a85af60 --- /dev/null +++ b/src/main/java/org/apache/ibatis/jdbc/handler/DefaultDelimiterHandler.java @@ -0,0 +1,30 @@ +package org.apache.ibatis.jdbc.handler; + +import org.apache.ibatis.jdbc.ScriptRunner; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * comment marked delimiter + * @author James + */ +public class DefaultDelimiterHandler implements DelimiterHandler { + public static final Pattern DELIMITER_PATTERN = Pattern.compile("^\\s*((--)|(//))?\\s*(//)?\\s*@?DELIMITER\\s+([^\\s]+)", Pattern.CASE_INSENSITIVE); + + @Override + public boolean resetDelimiter(ScriptRunner scriptRunner, String trimmedLine) { + if (lineIsDelimiterMark(trimmedLine)) { + Matcher matcher = DELIMITER_PATTERN.matcher(trimmedLine); + if (matcher.find()) { + scriptRunner.setDelimiter(matcher.group(5)); + return true; + } + } + return false; + } + + private boolean lineIsDelimiterMark(String trimmedLine) { + return trimmedLine.startsWith("//") || trimmedLine.startsWith("--"); + } +} diff --git a/src/main/java/org/apache/ibatis/jdbc/handler/DelimiterHandler.java b/src/main/java/org/apache/ibatis/jdbc/handler/DelimiterHandler.java new file mode 100644 index 00000000000..99206e02e6d --- /dev/null +++ b/src/main/java/org/apache/ibatis/jdbc/handler/DelimiterHandler.java @@ -0,0 +1,10 @@ +package org.apache.ibatis.jdbc.handler; + +import org.apache.ibatis.jdbc.ScriptRunner; + +/** + * @author James + */ +public interface DelimiterHandler { + boolean resetDelimiter(ScriptRunner scriptRunner, String line); +} diff --git a/src/test/java/org/apache/ibatis/jdbc/NoCommentDelimiterHandler.java b/src/test/java/org/apache/ibatis/jdbc/NoCommentDelimiterHandler.java new file mode 100644 index 00000000000..548b238fc6f --- /dev/null +++ b/src/test/java/org/apache/ibatis/jdbc/NoCommentDelimiterHandler.java @@ -0,0 +1,33 @@ +package org.apache.ibatis.jdbc; + +import org.apache.ibatis.jdbc.handler.DelimiterHandler; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author James + */ +public class NoCommentDelimiterHandler implements DelimiterHandler { + + private static final String DELIMITER_NAME = "DELIMITER"; + + private boolean lineStartWithDelimiter(String trimmedLine) { + if (trimmedLine.length() <= DELIMITER_NAME.length()) { + return false; + } + return DELIMITER_NAME.equalsIgnoreCase(trimmedLine.substring(0, DELIMITER_NAME.length())); + } + + @Override + public boolean resetDelimiter(ScriptRunner scriptRunner, String trimmedLine) { + if (lineStartWithDelimiter(trimmedLine)) { + String delimiter = trimmedLine.substring(DELIMITER_NAME.length()).trim(); + if (delimiter.length() > 0) { + scriptRunner.setDelimiter(delimiter); + return true; + } + } + return false; + } +} diff --git a/src/test/java/org/apache/ibatis/jdbc/ScriptRunnerTest.java b/src/test/java/org/apache/ibatis/jdbc/ScriptRunnerTest.java index 9850a023703..f3de8caef5c 100644 --- a/src/test/java/org/apache/ibatis/jdbc/ScriptRunnerTest.java +++ b/src/test/java/org/apache/ibatis/jdbc/ScriptRunnerTest.java @@ -29,6 +29,8 @@ import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.sql.DataSource; @@ -36,6 +38,7 @@ import org.apache.ibatis.datasource.pooled.PooledDataSource; import org.apache.ibatis.datasource.unpooled.UnpooledDataSource; import org.apache.ibatis.io.Resources; +import org.junit.Assert; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -44,6 +47,9 @@ class ScriptRunnerTest extends BaseDataTest { private static final String LINE_SEPARATOR = System.lineSeparator(); + private static final Pattern DELIMITER_PATTERN = Pattern.compile("^\\s*((--)|(//))?\\s*(//)?\\s*@?DELIMITER\\s+([^\\s]+)", Pattern.CASE_INSENSITIVE); + + @Test @Disabled("This fails with HSQLDB 2.0 due to the create index statements in the schema script") void shouldRunScriptsBySendingFullScriptAtOnce() throws Exception { @@ -285,4 +291,84 @@ void shouldAcceptMultiCharDelimiter() throws Exception { verify(stmt, Mockito.times(1)).execute(eq("line 1;" + LINE_SEPARATOR + "line 2;" + LINE_SEPARATOR + LINE_SEPARATOR)); verify(stmt, Mockito.times(1)).execute(eq("line 3" + LINE_SEPARATOR)); } + + public String findDelimiter(Pattern pattern, String inputStr) { + Matcher matcher = pattern.matcher(inputStr); + if (matcher.find()) { + return matcher.group(5); + } + return ""; + } + + @Test + public void testDelimiter() { + String testStr1 = "delimiter ;;"; + String testStr2 = "delimiter ;"; + Assert.assertEquals(";;", findDelimiter(DELIMITER_PATTERN, testStr1)); + Assert.assertEquals(";", findDelimiter(DELIMITER_PATTERN, testStr2)); + } + + @Test + public void testSubDelimiter() { + String testStr1 = "delimiter ;;"; + String testStr2 = "delimiter ;"; + String testStr3 = "sdelimiter ;"; + Assert.assertTrue("DELIMITER".equalsIgnoreCase(testStr1.substring(0, "DELIMITER".length()))); + Assert.assertTrue("DELIMITER".equalsIgnoreCase(testStr2.substring(0, "DELIMITER".length()))); + Assert.assertFalse("DELIMITER".equalsIgnoreCase(testStr3.substring(0, "DELIMITER".length()))); + } + + private String regionMatchDelimiter(String inputLine) { + String trimLine = inputLine.trim(); + if (trimLine.regionMatches(true, 0, + "Delimiter", 0, "Delimiter".length())) { + return inputLine.substring("Delimiter".length()).trim(); + } + return ""; + } + + @Test + public void testRegionMatch() { + String testStr1 = "Delimiter ;;"; + Assert.assertTrue(regionMatchDelimiter(testStr1).length() > 0); + Assert.assertEquals(";;", regionMatchDelimiter(testStr1)); + String testStr2 = "Delimiter "; + Assert.assertFalse(regionMatchDelimiter(testStr2).length() > 0); + } + + /** + * eg: dbeaver export + * DELIMITER ;; + * CREATE DEFINER=`chengdu`@`localhost` PROCEDURE `insert1`() + * begin + * declare i int; + * set i = 1; + * while i < 10 do + * set i = i + 1; + * end while; + * end ;; + * DELIMITER ; + * DROP TABLE IF EXISTS `testdata_1`; + * @throws Exception + */ + @Test + void shouldNoCommentDelimiter() throws Exception { + Connection conn = mock(Connection.class); + Statement stmt = mock(Statement.class); + when(conn.createStatement()).thenReturn(stmt); + when(stmt.getUpdateCount()).thenReturn(-1); + ScriptRunner runner = new ScriptRunner(conn); + runner.setDelimiterHandler(new NoCommentDelimiterHandler()); + String sql = " DELIMITER ;; \n" + + "line 1;\n" + + "line 2;\n" + + ";;\n" + + "DELIMITER ;\n" + + "line 3; \n"; + Reader reader = new StringReader(sql); + runner.runScript(reader); + + verify(stmt, Mockito.times(1)).execute(eq("line 1;" + LINE_SEPARATOR + "line 2;" + LINE_SEPARATOR + LINE_SEPARATOR)); + verify(stmt, Mockito.times(1)).execute(eq("line 3" + LINE_SEPARATOR)); + } }