Skip to content

Commit 17914fc

Browse files
philwebbsbrannen
authored andcommitted
Add multi-prefix comment support for SQL scripts
Update `ResourceDatabasePopulator` and `ScriptUtils` so that more than one comment prefix can be used when processing SQL scripts. This feature is particularly useful when dealing with scripts provided by Quartz since they often use a mix `--` and `#`. Closes gh-23289
1 parent 1ff29b0 commit 17914fc

File tree

4 files changed

+173
-16
lines changed

4 files changed

+173
-16
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ResourceDatabasePopulator.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {
6060

6161
private String separator = ScriptUtils.DEFAULT_STATEMENT_SEPARATOR;
6262

63-
private String commentPrefix = ScriptUtils.DEFAULT_COMMENT_PREFIX;
63+
private String[] commentPrefixes = ScriptUtils.DEFAULT_COMMENT_PREFIXES;
6464

6565
private String blockCommentStartDelimiter = ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER;
6666

@@ -171,9 +171,22 @@ public void setSeparator(String separator) {
171171
* Set the prefix that identifies single-line comments within the SQL scripts.
172172
* <p>Defaults to {@code "--"}.
173173
* @param commentPrefix the prefix for single-line comments
174+
* @see #setCommentPrefixes(String...)
174175
*/
175176
public void setCommentPrefix(String commentPrefix) {
176-
this.commentPrefix = commentPrefix;
177+
Assert.hasText(commentPrefix, "CommentPrefix must not be null or empty");
178+
this.commentPrefixes = new String[] { commentPrefix };
179+
}
180+
181+
/**
182+
* Set the prefixes that identify single-line comments within the SQL scripts.
183+
* <p>Defaults to {@code "--"}.
184+
* @param commentPrefixes the prefixes for single-line comments
185+
* @since 5.2
186+
*/
187+
public void setCommentPrefixes(String... commentPrefixes) {
188+
Assert.notNull(commentPrefixes, "CommentPrefixes must not be null");
189+
this.commentPrefixes = commentPrefixes;
177190
}
178191

179192
/**
@@ -236,7 +249,7 @@ public void populate(Connection connection) throws ScriptException {
236249
for (Resource script : this.scripts) {
237250
EncodedResource encodedScript = new EncodedResource(script, this.sqlScriptEncoding);
238251
ScriptUtils.executeSqlScript(connection, encodedScript, this.continueOnError, this.ignoreFailedDrops,
239-
this.commentPrefix, this.separator, this.blockCommentStartDelimiter, this.blockCommentEndDelimiter);
252+
this.commentPrefixes, this.separator, this.blockCommentStartDelimiter, this.blockCommentEndDelimiter);
240253
}
241254
}
242255

spring-jdbc/src/main/java/org/springframework/jdbc/datasource/init/ScriptUtils.java

Lines changed: 125 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ public abstract class ScriptUtils {
8080
*/
8181
public static final String DEFAULT_COMMENT_PREFIX = "--";
8282

83+
/**
84+
* Default prefixes for single-line comments within SQL scripts: {@code ["--"]}.
85+
* @since 5.2
86+
*/
87+
public static final String[] DEFAULT_COMMENT_PREFIXES = { DEFAULT_COMMENT_PREFIX };
88+
8389
/**
8490
* Default start delimiter for block comments within SQL scripts: {@code "/*"}.
8591
*/
@@ -170,9 +176,46 @@ public static void splitSqlScript(@Nullable EncodedResource resource, String scr
170176
String separator, String commentPrefix, String blockCommentStartDelimiter,
171177
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
172178

179+
Assert.hasText(commentPrefix, "'commentPrefix' must not be null or empty");
180+
splitSqlScript(resource, script, separator, new String[] { commentPrefix },
181+
blockCommentStartDelimiter, blockCommentEndDelimiter, statements);
182+
}
183+
184+
/**
185+
* Split an SQL script into separate statements delimited by the provided
186+
* separator string. Each individual statement will be added to the provided
187+
* {@code List}.
188+
* <p>Within the script, the provided {@code commentPrefix} will be honored:
189+
* any text beginning with the comment prefix and extending to the end of the
190+
* line will be omitted from the output. Similarly, the provided
191+
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
192+
* delimiters will be honored: any text enclosed in a block comment will be
193+
* omitted from the output. In addition, multiple adjacent whitespace characters
194+
* will be collapsed into a single space.
195+
* @param resource the resource from which the script was read
196+
* @param script the SQL script
197+
* @param separator text separating each statement
198+
* (typically a ';' or newline character)
199+
* @param commentPrefixes the prefixes that identify SQL line comments
200+
* (typically "--")
201+
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
202+
* never {@code null} or empty
203+
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
204+
* never {@code null} or empty
205+
* @param statements the list that will contain the individual statements
206+
* @throws ScriptException if an error occurred while splitting the SQL script
207+
* @since 5.2
208+
*/
209+
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
210+
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
211+
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {
212+
173213
Assert.hasText(script, "'script' must not be null or empty");
174214
Assert.notNull(separator, "'separator' must not be null");
175-
Assert.hasText(commentPrefix, "'commentPrefix' must not be null or empty");
215+
Assert.notNull(commentPrefixes, "'commentPrefixes' must not be null");
216+
for (int i = 0; i < commentPrefixes.length; i++) {
217+
Assert.hasText(commentPrefixes[i], "'commentPrefixes' must not contain null or empty elements");
218+
}
176219
Assert.hasText(blockCommentStartDelimiter, "'blockCommentStartDelimiter' must not be null or empty");
177220
Assert.hasText(blockCommentEndDelimiter, "'blockCommentEndDelimiter' must not be null or empty");
178221

@@ -210,7 +253,7 @@ else if (!inSingleQuote && (c == '"')) {
210253
i += separator.length() - 1;
211254
continue;
212255
}
213-
else if (script.startsWith(commentPrefix, i)) {
256+
else if (startsWithAny(script, commentPrefixes, i)) {
214257
// Skip over any content from the start of the comment to the EOL
215258
int indexOfNextNewline = script.indexOf('\n', i);
216259
if (indexOfNextNewline > i) {
@@ -260,7 +303,7 @@ else if (c == ' ' || c == '\r' || c == '\n' || c == '\t') {
260303
* @throws IOException in case of I/O errors
261304
*/
262305
static String readScript(EncodedResource resource) throws IOException {
263-
return readScript(resource, DEFAULT_COMMENT_PREFIX, DEFAULT_STATEMENT_SEPARATOR, DEFAULT_BLOCK_COMMENT_END_DELIMITER);
306+
return readScript(resource, DEFAULT_COMMENT_PREFIXES, DEFAULT_STATEMENT_SEPARATOR, DEFAULT_BLOCK_COMMENT_END_DELIMITER);
264307
}
265308

266309
/**
@@ -271,19 +314,19 @@ static String readScript(EncodedResource resource) throws IOException {
271314
* a statement &mdash; will be included in the results.
272315
* @param resource the {@code EncodedResource} containing the script
273316
* to be processed
274-
* @param commentPrefix the prefix that identifies comments in the SQL script
317+
* @param commentPrefixes the prefix that identifies comments in the SQL script
275318
* (typically "--")
276319
* @param separator the statement separator in the SQL script (typically ";")
277320
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
278321
* @return a {@code String} containing the script lines
279322
* @throws IOException in case of I/O errors
280323
*/
281-
private static String readScript(EncodedResource resource, @Nullable String commentPrefix,
324+
private static String readScript(EncodedResource resource, @Nullable String[] commentPrefixes,
282325
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
283326

284327
LineNumberReader lnr = new LineNumberReader(resource.getReader());
285328
try {
286-
return readScript(lnr, commentPrefix, separator, blockCommentEndDelimiter);
329+
return readScript(lnr, commentPrefixes, separator, blockCommentEndDelimiter);
287330
}
288331
finally {
289332
lnr.close();
@@ -309,11 +352,35 @@ private static String readScript(EncodedResource resource, @Nullable String comm
309352
public static String readScript(LineNumberReader lineNumberReader, @Nullable String lineCommentPrefix,
310353
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
311354

355+
String[] lineCommentPrefixes = (lineCommentPrefix != null) ? new String[] { lineCommentPrefix } : null;
356+
return readScript(lineNumberReader, lineCommentPrefixes, separator, blockCommentEndDelimiter);
357+
}
358+
359+
/**
360+
* Read a script from the provided {@code LineNumberReader}, using the supplied
361+
* comment prefix and statement separator, and build a {@code String} containing
362+
* the lines.
363+
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
364+
* results; however, line comments anywhere else &mdash; for example, within
365+
* a statement &mdash; will be included in the results.
366+
* @param lineNumberReader the {@code LineNumberReader} containing the script
367+
* to be processed
368+
* @param lineCommentPrefixes the prefixes that identify comments in the SQL script
369+
* (typically "--")
370+
* @param separator the statement separator in the SQL script (typically ";")
371+
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
372+
* @return a {@code String} containing the script lines
373+
* @throws IOException in case of I/O errors
374+
* @since 5.2
375+
*/
376+
public static String readScript(LineNumberReader lineNumberReader, @Nullable String[] lineCommentPrefixes,
377+
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {
378+
312379
String currentStatement = lineNumberReader.readLine();
313380
StringBuilder scriptBuilder = new StringBuilder();
314381
while (currentStatement != null) {
315382
if ((blockCommentEndDelimiter != null && currentStatement.contains(blockCommentEndDelimiter)) ||
316-
(lineCommentPrefix != null && !currentStatement.startsWith(lineCommentPrefix))) {
383+
(lineCommentPrefixes != null && !startsWithAny(currentStatement, lineCommentPrefixes, 0))) {
317384
if (scriptBuilder.length() > 0) {
318385
scriptBuilder.append('\n');
319386
}
@@ -340,6 +407,15 @@ private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuild
340407
}
341408
}
342409

410+
private static boolean startsWithAny(String script, String[] prefixes, int toffset) {
411+
for (String prefix : prefixes) {
412+
if (script.startsWith(prefix, toffset)) {
413+
return true;
414+
}
415+
}
416+
return false;
417+
}
418+
343419
/**
344420
* Does the provided SQL script contain the specified delimiter?
345421
* @param script the SQL script
@@ -454,6 +530,46 @@ public static void executeSqlScript(Connection connection, EncodedResource resou
454530
boolean ignoreFailedDrops, String commentPrefix, @Nullable String separator,
455531
String blockCommentStartDelimiter, String blockCommentEndDelimiter) throws ScriptException {
456532

533+
executeSqlScript(connection, resource, continueOnError, ignoreFailedDrops,
534+
new String[] { commentPrefix }, separator, blockCommentStartDelimiter,
535+
blockCommentEndDelimiter);
536+
}
537+
538+
/**
539+
* Execute the given SQL script.
540+
* <p>Statement separators and comments will be removed before executing
541+
* individual statements within the supplied script.
542+
* <p><strong>Warning</strong>: this method does <em>not</em> release the
543+
* provided {@link Connection}.
544+
* @param connection the JDBC connection to use to execute the script; already
545+
* configured and ready to use
546+
* @param resource the resource (potentially associated with a specific encoding)
547+
* to load the SQL script from
548+
* @param continueOnError whether or not to continue without throwing an exception
549+
* in the event of an error
550+
* @param ignoreFailedDrops whether or not to continue in the event of specifically
551+
* an error on a {@code DROP} statement
552+
* @param commentPrefixes the prefixes that identify single-line comments in the
553+
* SQL script (typically "--")
554+
* @param separator the script statement separator; defaults to
555+
* {@value #DEFAULT_STATEMENT_SEPARATOR} if not specified and falls back to
556+
* {@value #FALLBACK_STATEMENT_SEPARATOR} as a last resort; may be set to
557+
* {@value #EOF_STATEMENT_SEPARATOR} to signal that the script contains a
558+
* single statement without a separator
559+
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
560+
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
561+
* @throws ScriptException if an error occurred while executing the SQL script
562+
* @since 5.2
563+
* @see #DEFAULT_STATEMENT_SEPARATOR
564+
* @see #FALLBACK_STATEMENT_SEPARATOR
565+
* @see #EOF_STATEMENT_SEPARATOR
566+
* @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
567+
* @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
568+
*/
569+
public static void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError,
570+
boolean ignoreFailedDrops, String[] commentPrefixes, @Nullable String separator,
571+
String blockCommentStartDelimiter, String blockCommentEndDelimiter) throws ScriptException {
572+
457573
try {
458574
if (logger.isDebugEnabled()) {
459575
logger.debug("Executing SQL script from " + resource);
@@ -462,7 +578,7 @@ public static void executeSqlScript(Connection connection, EncodedResource resou
462578

463579
String script;
464580
try {
465-
script = readScript(resource, commentPrefix, separator, blockCommentEndDelimiter);
581+
script = readScript(resource, commentPrefixes, separator, blockCommentEndDelimiter);
466582
}
467583
catch (IOException ex) {
468584
throw new CannotReadScriptException(resource, ex);
@@ -476,7 +592,7 @@ public static void executeSqlScript(Connection connection, EncodedResource resou
476592
}
477593

478594
List<String> statements = new ArrayList<>();
479-
splitSqlScript(resource, script, separator, commentPrefix, blockCommentStartDelimiter,
595+
splitSqlScript(resource, script, separator, commentPrefixes, blockCommentStartDelimiter,
480596
blockCommentEndDelimiter, statements);
481597

482598
int stmtNumber = 0;

spring-jdbc/src/test/java/org/springframework/jdbc/datasource/init/ScriptUtilsUnitTests.java

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
import org.springframework.core.io.support.EncodedResource;
2626

2727
import static org.assertj.core.api.Assertions.assertThat;
28+
import static org.springframework.jdbc.datasource.init.ScriptUtils.DEFAULT_BLOCK_COMMENT_END_DELIMITER;
29+
import static org.springframework.jdbc.datasource.init.ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER;
30+
import static org.springframework.jdbc.datasource.init.ScriptUtils.DEFAULT_COMMENT_PREFIXES;
2831
import static org.springframework.jdbc.datasource.init.ScriptUtils.DEFAULT_STATEMENT_SEPARATOR;
2932
import static org.springframework.jdbc.datasource.init.ScriptUtils.containsSqlScriptDelimiters;
3033
import static org.springframework.jdbc.datasource.init.ScriptUtils.splitSqlScript;
@@ -117,18 +120,25 @@ public void readAndSplitScriptWithMultipleNewlinesAsSeparator() throws Exception
117120
@Test
118121
public void readAndSplitScriptContainingComments() throws Exception {
119122
String script = readScript("test-data-with-comments.sql");
120-
splitScriptContainingComments(script);
123+
splitScriptContainingComments(script, DEFAULT_COMMENT_PREFIXES);
121124
}
122125

123126
@Test
124127
public void readAndSplitScriptContainingCommentsWithWindowsLineEnding() throws Exception {
125128
String script = readScript("test-data-with-comments.sql").replaceAll("\n", "\r\n");
126-
splitScriptContainingComments(script);
129+
splitScriptContainingComments(script, DEFAULT_COMMENT_PREFIXES);
127130
}
128131

129-
private void splitScriptContainingComments(String script) throws Exception {
132+
@Test
133+
public void readAndSplitScriptContainingCommentsWithMultiplePrefixes() throws Exception {
134+
String script = readScript("test-data-with-multi-prefix-comments.sql");
135+
splitScriptContainingComments(script, "--", "#", "^");
136+
}
137+
138+
private void splitScriptContainingComments(String script, String... commentPrefixes) throws Exception {
130139
List<String> statements = new ArrayList<>();
131-
splitSqlScript(script, ';', statements);
140+
splitSqlScript(null, script, ";", commentPrefixes, DEFAULT_BLOCK_COMMENT_START_DELIMITER,
141+
DEFAULT_BLOCK_COMMENT_END_DELIMITER, statements);
132142

133143
String statement1 = "insert into customer (id, name) values (1, 'Rod; Johnson'), (2, 'Adrian Collier')";
134144
String statement2 = "insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2)";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
-- The next comment line has no text after the '--' prefix.
2+
--
3+
-- The next comment line starts with a space.
4+
-- x, y, z...
5+
6+
insert into customer (id, name)
7+
values (1, 'Rod; Johnson'), (2, 'Adrian Collier');
8+
-- This is also a comment.
9+
insert into orders(id, order_date, customer_id)
10+
values (1, '2008-01-02', 2);
11+
# A comment with a different prefix
12+
insert into orders(id, order_date, customer_id) values (1, '2008-01-02', 2);
13+
INSERT INTO persons( person_id--
14+
, name)
15+
^ A comment with yet another different prefix
16+
VALUES( 1 -- person_id
17+
, 'Name' --name
18+
);--

0 commit comments

Comments
 (0)