View Javadoc
1   /*
2    *    Copyright 2009-2023 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.jdbc;
17  
18  import static org.junit.jupiter.api.Assertions.assertEquals;
19  import static org.junit.jupiter.api.Assertions.assertSame;
20  import static org.junit.jupiter.api.Assertions.assertTrue;
21  import static org.junit.jupiter.api.Assertions.fail;
22  import static org.mockito.ArgumentMatchers.eq;
23  import static org.mockito.Mockito.mock;
24  import static org.mockito.Mockito.verify;
25  import static org.mockito.Mockito.when;
26  
27  import java.io.IOException;
28  import java.io.PrintWriter;
29  import java.io.Reader;
30  import java.io.StringReader;
31  import java.io.StringWriter;
32  import java.sql.Connection;
33  import java.sql.SQLException;
34  import java.sql.Statement;
35  import java.util.List;
36  import java.util.Map;
37  import java.util.Properties;
38  
39  import javax.sql.DataSource;
40  
41  import org.apache.ibatis.BaseDataTest;
42  import org.apache.ibatis.datasource.pooled.PooledDataSource;
43  import org.apache.ibatis.datasource.unpooled.UnpooledDataSource;
44  import org.apache.ibatis.io.Resources;
45  import org.junit.jupiter.api.Disabled;
46  import org.junit.jupiter.api.Test;
47  import org.mockito.Mockito;
48  
49  class ScriptRunnerTest extends BaseDataTest {
50  
51    private static final String LINE_SEPARATOR = System.lineSeparator();
52  
53    @Test
54    @Disabled("This fails with HSQLDB 2.0 due to the create index statements in the schema script")
55    void shouldRunScriptsBySendingFullScriptAtOnce() throws Exception {
56      DataSource ds = createUnpooledDataSource(JPETSTORE_PROPERTIES);
57      Connection conn = ds.getConnection();
58      ScriptRunner runner = new ScriptRunner(conn);
59      runner.setSendFullScript(true);
60      runner.setAutoCommit(true);
61      runner.setStopOnError(false);
62      runner.setErrorLogWriter(null);
63      runner.setLogWriter(null);
64      conn.close();
65      runJPetStoreScripts(runner);
66      assertProductsTableExistsAndLoaded();
67    }
68  
69    @Test
70    void shouldRunScriptsUsingConnection() throws Exception {
71      DataSource ds = createUnpooledDataSource(JPETSTORE_PROPERTIES);
72      try (Connection conn = ds.getConnection()) {
73        ScriptRunner runner = new ScriptRunner(conn);
74        runner.setAutoCommit(true);
75        runner.setStopOnError(false);
76        runner.setErrorLogWriter(null);
77        runner.setLogWriter(null);
78        runJPetStoreScripts(runner);
79      }
80      assertProductsTableExistsAndLoaded();
81    }
82  
83    @Test
84    void shouldRunScriptsUsingProperties() throws Exception {
85      Properties props = Resources.getResourceAsProperties(JPETSTORE_PROPERTIES);
86      DataSource dataSource = new UnpooledDataSource(props.getProperty("driver"), props.getProperty("url"),
87          props.getProperty("username"), props.getProperty("password"));
88      ScriptRunner runner = new ScriptRunner(dataSource.getConnection());
89      runner.setAutoCommit(true);
90      runner.setStopOnError(false);
91      runner.setErrorLogWriter(null);
92      runner.setLogWriter(null);
93      runJPetStoreScripts(runner);
94      assertProductsTableExistsAndLoaded();
95    }
96  
97    @Test
98    void shouldReturnWarningIfEndOfLineTerminatorNotFound() throws Exception {
99      DataSource ds = createUnpooledDataSource(JPETSTORE_PROPERTIES);
100     String resource = "org/apache/ibatis/jdbc/ScriptMissingEOLTerminator.sql";
101     try (Connection conn = ds.getConnection(); Reader reader = Resources.getResourceAsReader(resource)) {
102       ScriptRunner runner = new ScriptRunner(conn);
103       runner.setAutoCommit(true);
104       runner.setStopOnError(false);
105       runner.setErrorLogWriter(null);
106       runner.setLogWriter(null);
107 
108       try {
109         runner.runScript(reader);
110         fail("Expected script runner to fail due to missing end of line terminator.");
111       } catch (Exception e) {
112         assertTrue(e.getMessage().contains("end-of-line terminator"));
113       }
114     }
115   }
116 
117   @Test
118   void commentAferStatementDelimiterShouldNotCauseRunnerFail() throws Exception {
119     DataSource ds = createUnpooledDataSource(JPETSTORE_PROPERTIES);
120     String resource = "org/apache/ibatis/jdbc/ScriptCommentAfterEOLTerminator.sql";
121     try (Connection conn = ds.getConnection(); Reader reader = Resources.getResourceAsReader(resource)) {
122       ScriptRunner runner = new ScriptRunner(conn);
123       runner.setAutoCommit(true);
124       runner.setStopOnError(true);
125       runner.setErrorLogWriter(null);
126       runner.setLogWriter(null);
127       runJPetStoreScripts(runner);
128       runner.runScript(reader);
129     }
130   }
131 
132   @Test
133   void shouldReturnWarningIfNotTheCurrentDelimiterUsed() throws Exception {
134     DataSource ds = createUnpooledDataSource(JPETSTORE_PROPERTIES);
135     String resource = "org/apache/ibatis/jdbc/ScriptChangingDelimiterMissingDelimiter.sql";
136     try (Connection conn = ds.getConnection(); Reader reader = Resources.getResourceAsReader(resource)) {
137       ScriptRunner runner = new ScriptRunner(conn);
138       runner.setAutoCommit(false);
139       runner.setStopOnError(true);
140       runner.setErrorLogWriter(null);
141       runner.setLogWriter(null);
142       try {
143         runner.runScript(reader);
144         fail("Expected script runner to fail due to the usage of invalid delimiter.");
145       } catch (Exception e) {
146         assertTrue(e.getMessage().contains("end-of-line terminator"));
147       }
148     }
149   }
150 
151   @Test
152   void changingDelimiterShouldNotCauseRunnerFail() throws Exception {
153     DataSource ds = createUnpooledDataSource(JPETSTORE_PROPERTIES);
154     String resource = "org/apache/ibatis/jdbc/ScriptChangingDelimiter.sql";
155     try (Connection conn = ds.getConnection(); Reader reader = Resources.getResourceAsReader(resource)) {
156       ScriptRunner runner = new ScriptRunner(conn);
157       runner.setAutoCommit(false);
158       runner.setStopOnError(true);
159       runner.setErrorLogWriter(null);
160       runner.setLogWriter(null);
161       runJPetStoreScripts(runner);
162       runner.runScript(reader);
163     }
164   }
165 
166   @Test
167   void testLogging() throws Exception {
168     DataSource ds = createUnpooledDataSource(JPETSTORE_PROPERTIES);
169     try (Connection conn = ds.getConnection()) {
170       ScriptRunner runner = new ScriptRunner(conn);
171       runner.setAutoCommit(true);
172       runner.setStopOnError(false);
173       runner.setErrorLogWriter(null);
174       runner.setSendFullScript(false);
175       StringWriter sw = new StringWriter();
176       PrintWriter logWriter = new PrintWriter(sw);
177       runner.setLogWriter(logWriter);
178 
179       Reader reader = new StringReader("select userid from account where userid = 'j2ee';");
180       runner.runScript(reader);
181 
182       assertEquals("select userid from account where userid = 'j2ee'" + LINE_SEPARATOR + LINE_SEPARATOR + "USERID\t"
183           + LINE_SEPARATOR + "j2ee\t" + LINE_SEPARATOR, sw.toString());
184     }
185   }
186 
187   @Test
188   void testLoggingFullScipt() throws Exception {
189     DataSource ds = createUnpooledDataSource(JPETSTORE_PROPERTIES);
190     try (Connection conn = ds.getConnection()) {
191       ScriptRunner runner = new ScriptRunner(conn);
192       runner.setAutoCommit(true);
193       runner.setStopOnError(false);
194       runner.setErrorLogWriter(null);
195       runner.setSendFullScript(true);
196       StringWriter sw = new StringWriter();
197       PrintWriter logWriter = new PrintWriter(sw);
198       runner.setLogWriter(logWriter);
199 
200       Reader reader = new StringReader("select userid from account where userid = 'j2ee';");
201       runner.runScript(reader);
202 
203       assertEquals("select userid from account where userid = 'j2ee';" + LINE_SEPARATOR + LINE_SEPARATOR + "USERID\t"
204           + LINE_SEPARATOR + "j2ee\t" + LINE_SEPARATOR, sw.toString());
205     }
206   }
207 
208   private void runJPetStoreScripts(ScriptRunner runner) throws IOException, SQLException {
209     runScript(runner, JPETSTORE_DDL);
210     runScript(runner, JPETSTORE_DATA);
211   }
212 
213   private void assertProductsTableExistsAndLoaded() throws IOException, SQLException {
214     PooledDataSource ds = createPooledDataSource(JPETSTORE_PROPERTIES);
215     try (Connection conn = ds.getConnection()) {
216       SqlRunner executor = new SqlRunner(conn);
217       List<Map<String, Object>> products = executor.selectAll("SELECT * FROM PRODUCT");
218       assertEquals(16, products.size());
219     } finally {
220       ds.forceCloseAll();
221     }
222   }
223 
224   @Test
225   void shouldAcceptDelimiterVariations() throws Exception {
226     Connection conn = mock(Connection.class);
227     Statement stmt = mock(Statement.class);
228     when(conn.createStatement()).thenReturn(stmt);
229     when(stmt.getUpdateCount()).thenReturn(-1);
230     ScriptRunner runner = new ScriptRunner(conn);
231 
232     String sql = "-- @DELIMITER | \n" + "line 1;\n" + "line 2;\n" + "|\n" + "//  @DELIMITER  ;\n" + "line 3; \n"
233         + "-- //@deLimiTer $  blah\n" + "line 4$\n" + "// //@DELIMITER %\n" + "line 5%\n";
234     Reader reader = new StringReader(sql);
235     runner.runScript(reader);
236 
237     verify(stmt, Mockito.times(1))
238         .execute(eq("line 1;" + LINE_SEPARATOR + "line 2;" + LINE_SEPARATOR + LINE_SEPARATOR));
239     verify(stmt, Mockito.times(1)).execute(eq("line 3" + LINE_SEPARATOR));
240     verify(stmt, Mockito.times(1)).execute(eq("line 4" + LINE_SEPARATOR));
241     verify(stmt, Mockito.times(1)).execute(eq("line 5" + LINE_SEPARATOR));
242   }
243 
244   @Test
245   void test() {
246     StringBuilder sb = new StringBuilder();
247     StringBuilder sb2 = y(sb);
248     assertSame(sb, sb2);
249   }
250 
251   private StringBuilder y(StringBuilder sb) {
252     sb.append("ABC");
253     return sb;
254   }
255 
256   @Test
257   void shouldAcceptMultiCharDelimiter() throws Exception {
258     Connection conn = mock(Connection.class);
259     Statement stmt = mock(Statement.class);
260     when(conn.createStatement()).thenReturn(stmt);
261     when(stmt.getUpdateCount()).thenReturn(-1);
262     ScriptRunner runner = new ScriptRunner(conn);
263 
264     String sql = "-- @DELIMITER || \n" + "line 1;\n" + "line 2;\n" + "||\n" + "//  @DELIMITER  ;\n" + "line 3; \n";
265     Reader reader = new StringReader(sql);
266     runner.runScript(reader);
267 
268     verify(stmt, Mockito.times(1))
269         .execute(eq("line 1;" + LINE_SEPARATOR + "line 2;" + LINE_SEPARATOR + LINE_SEPARATOR));
270     verify(stmt, Mockito.times(1)).execute(eq("line 3" + LINE_SEPARATOR));
271   }
272 }