3030import org .springframework .dao .DataAccessException ;
3131import org .springframework .dao .DataAccessResourceFailureException ;
3232import org .springframework .jdbc .core .JdbcTemplate ;
33+ import org .springframework .jdbc .datasource .init .ResourceDatabasePopulator ;
3334import org .springframework .util .StringUtils ;
3435
3536/**
@@ -47,6 +48,10 @@ public class JdbcTestUtils {
4748
4849 private static final Log logger = LogFactory .getLog (JdbcTestUtils .class );
4950
51+ private static final String DEFAULT_COMMENT_PREFIX = "--" ;
52+
53+ private static final char DEFAULT_STATEMENT_SEPARATOR = ';' ;
54+
5055
5156 /**
5257 * Count the rows in the given table.
@@ -124,10 +129,10 @@ public static void dropTables(JdbcTemplate jdbcTemplate, String... tableNames) {
124129 * exception in the event of an error
125130 * @throws DataAccessException if there is an error executing a statement
126131 * and {@code continueOnError} is {@code false}
132+ * @see ResourceDatabasePopulator
127133 */
128134 public static void executeSqlScript (JdbcTemplate jdbcTemplate , ResourceLoader resourceLoader ,
129135 String sqlResourcePath , boolean continueOnError ) throws DataAccessException {
130-
131136 Resource resource = resourceLoader .getResource (sqlResourcePath );
132137 executeSqlScript (jdbcTemplate , resource , continueOnError );
133138 }
@@ -145,10 +150,10 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, ResourceLoader re
145150 * exception in the event of an error
146151 * @throws DataAccessException if there is an error executing a statement
147152 * and {@code continueOnError} is {@code false}
153+ * @see ResourceDatabasePopulator
148154 */
149155 public static void executeSqlScript (JdbcTemplate jdbcTemplate , Resource resource , boolean continueOnError )
150156 throws DataAccessException {
151-
152157 executeSqlScript (jdbcTemplate , new EncodedResource (resource ), continueOnError );
153158 }
154159
@@ -164,6 +169,7 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, Resource resource
164169 * exception in the event of an error
165170 * @throws DataAccessException if there is an error executing a statement
166171 * and {@code continueOnError} is {@code false}
172+ * @see ResourceDatabasePopulator
167173 */
168174 public static void executeSqlScript (JdbcTemplate jdbcTemplate , EncodedResource resource , boolean continueOnError )
169175 throws DataAccessException {
@@ -177,12 +183,14 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource r
177183 try {
178184 reader = new LineNumberReader (resource .getReader ());
179185 String script = readScript (reader );
180- char delimiter = ';' ;
186+ char delimiter = DEFAULT_STATEMENT_SEPARATOR ;
181187 if (!containsSqlScriptDelimiters (script , delimiter )) {
182188 delimiter = '\n' ;
183189 }
184190 splitSqlScript (script , delimiter , statements );
191+ int lineNumber = 0 ;
185192 for (String statement : statements ) {
193+ lineNumber ++;
186194 try {
187195 int rowsAffected = jdbcTemplate .update (statement );
188196 if (logger .isDebugEnabled ()) {
@@ -192,7 +200,8 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource r
192200 catch (DataAccessException ex ) {
193201 if (continueOnError ) {
194202 if (logger .isWarnEnabled ()) {
195- logger .warn ("SQL statement [" + statement + "] failed" , ex );
203+ logger .warn ("Failed to execute SQL script statement at line " + lineNumber
204+ + " of resource " + resource + ": " + statement , ex );
196205 }
197206 }
198207 else {
@@ -213,24 +222,40 @@ public static void executeSqlScript(JdbcTemplate jdbcTemplate, EncodedResource r
213222 if (reader != null ) {
214223 reader .close ();
215224 }
216- } catch (IOException ex ) {
225+ }
226+ catch (IOException ex ) {
217227 // ignore
218228 }
219229 }
220230 }
221231
222232 /**
223- * Read a script from the provided {@code LineNumberReader} and build a
224- * {@code String} containing the lines.
233+ * Read a script from the provided {@code LineNumberReader}, using
234+ * "{@code --}" as the comment prefix, and build a {@code String} containing
235+ * the lines.
225236 * @param lineNumberReader the {@code LineNumberReader} containing the script
226237 * to be processed
227238 * @return a {@code String} containing the script lines
239+ * @see #readScript(LineNumberReader, String)
228240 */
229241 public static String readScript (LineNumberReader lineNumberReader ) throws IOException {
242+ return readScript (lineNumberReader , DEFAULT_COMMENT_PREFIX );
243+ }
244+
245+ /**
246+ * Read a script from the provided {@code LineNumberReader}, using the supplied
247+ * comment prefix, and build a {@code String} containing the lines.
248+ * @param lineNumberReader the {@code LineNumberReader} containing the script
249+ * to be processed
250+ * @param commentPrefix the line prefix that identifies comments in the SQL script
251+ * @return a {@code String} containing the script lines
252+ */
253+ public static String readScript (LineNumberReader lineNumberReader , String commentPrefix ) throws IOException {
230254 String currentStatement = lineNumberReader .readLine ();
231255 StringBuilder scriptBuilder = new StringBuilder ();
232256 while (currentStatement != null ) {
233- if (StringUtils .hasText (currentStatement )) {
257+ if (StringUtils .hasText (currentStatement )
258+ && (commentPrefix != null && !currentStatement .startsWith (commentPrefix ))) {
234259 if (scriptBuilder .length () > 0 ) {
235260 scriptBuilder .append ('\n' );
236261 }
@@ -270,26 +295,71 @@ public static boolean containsSqlScriptDelimiters(String script, char delim) {
270295 * @param statements the list that will contain the individual statements
271296 */
272297 public static void splitSqlScript (String script , char delim , List <String > statements ) {
298+ splitSqlScript (script , "" + delim , statements );
299+ }
300+
301+ /**
302+ * Split an SQL script into separate statements delimited with the provided delimiter
303+ * character. Each individual statement will be added to the provided {@code List}.
304+ * @param script the SQL script
305+ * @param delim character delimiting each statement — typically a ';' character
306+ * @param statements the List that will contain the individual statements
307+ */
308+ private static void splitSqlScript (String script , String delim , List <String > statements ) {
273309 StringBuilder sb = new StringBuilder ();
274310 boolean inLiteral = false ;
311+ boolean inEscape = false ;
275312 char [] content = script .toCharArray ();
276313 for (int i = 0 ; i < script .length (); i ++) {
277- if (content [i ] == '\'' ) {
314+ char c = content [i ];
315+ if (inEscape ) {
316+ inEscape = false ;
317+ sb .append (c );
318+ continue ;
319+ }
320+ // MySQL style escapes
321+ if (c == '\\' ) {
322+ inEscape = true ;
323+ sb .append (c );
324+ continue ;
325+ }
326+ if (c == '\'' ) {
278327 inLiteral = !inLiteral ;
279328 }
280- if (content [i ] == delim && !inLiteral ) {
281- if (sb .length () > 0 ) {
282- statements .add (sb .toString ());
283- sb = new StringBuilder ();
329+ if (!inLiteral ) {
330+ if (startsWithDelimiter (script , i , delim )) {
331+ if (sb .length () > 0 ) {
332+ statements .add (sb .toString ());
333+ sb = new StringBuilder ();
334+ }
335+ i += delim .length () - 1 ;
336+ continue ;
337+ }
338+ else if (c == '\n' || c == '\t' ) {
339+ c = ' ' ;
284340 }
285341 }
286- else {
287- sb .append (content [i ]);
288- }
342+ sb .append (c );
289343 }
290- if (sb . length () > 0 ) {
344+ if (StringUtils . hasText ( sb ) ) {
291345 statements .add (sb .toString ());
292346 }
293347 }
294348
349+ /**
350+ * Return whether the substring of a given source {@link String} starting at the
351+ * given index starts with the given delimiter.
352+ * @param source the source {@link String} to inspect
353+ * @param startIndex the index to look for the delimiter
354+ * @param delim the delimiter to look for
355+ */
356+ private static boolean startsWithDelimiter (String source , int startIndex , String delim ) {
357+ int endIndex = startIndex + delim .length ();
358+ if (source .length () < endIndex ) {
359+ // String is too short to contain the delimiter
360+ return false ;
361+ }
362+ return source .substring (startIndex , endIndex ).equals (delim );
363+ }
364+
295365}
0 commit comments