Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multi-prefix comment support for SQL scripts #23289

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ public class ResourceDatabasePopulator implements DatabasePopulator {

private String separator = ScriptUtils.DEFAULT_STATEMENT_SEPARATOR;

private String commentPrefix = ScriptUtils.DEFAULT_COMMENT_PREFIX;
private String[] commentPrefixes = ScriptUtils.DEFAULT_COMMENT_PREFIXES;

private String blockCommentStartDelimiter = ScriptUtils.DEFAULT_BLOCK_COMMENT_START_DELIMITER;

Expand Down Expand Up @@ -171,9 +171,22 @@ public void setSeparator(String separator) {
* Set the prefix that identifies single-line comments within the SQL scripts.
* <p>Defaults to {@code "--"}.
* @param commentPrefix the prefix for single-line comments
* @see #setCommentPrefixes(String...)
*/
public void setCommentPrefix(String commentPrefix) {
this.commentPrefix = commentPrefix;
Assert.hasText(commentPrefix, "CommentPrefix must not be null or empty");
philwebb marked this conversation as resolved.
Show resolved Hide resolved
this.commentPrefixes = new String[] { commentPrefix };
}

/**
* Set the prefixes that identify single-line comments within the SQL scripts.
* <p>Defaults to {@code "--"}.
* @param commentPrefixes the prefixes for single-line comments
* @since 5.2
*/
public void setCommentPrefixes(String... commentPrefixes) {
Assert.notNull(commentPrefixes, "CommentPrefixes must not be null");
philwebb marked this conversation as resolved.
Show resolved Hide resolved
this.commentPrefixes = commentPrefixes;
}

/**
Expand Down Expand Up @@ -236,7 +249,7 @@ public void populate(Connection connection) throws ScriptException {
for (Resource script : this.scripts) {
EncodedResource encodedScript = new EncodedResource(script, this.sqlScriptEncoding);
ScriptUtils.executeSqlScript(connection, encodedScript, this.continueOnError, this.ignoreFailedDrops,
this.commentPrefix, this.separator, this.blockCommentStartDelimiter, this.blockCommentEndDelimiter);
this.commentPrefixes, this.separator, this.blockCommentStartDelimiter, this.blockCommentEndDelimiter);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@ public abstract class ScriptUtils {
*/
public static final String DEFAULT_COMMENT_PREFIX = "--";

/**
* Default prefixes for single-line comments within SQL scripts: {@code ["--"]}.
* @since 5.2
*/
public static final String[] DEFAULT_COMMENT_PREFIXES = { DEFAULT_COMMENT_PREFIX };

/**
* Default start delimiter for block comments within SQL scripts: {@code "/*"}.
*/
Expand Down Expand Up @@ -170,9 +176,46 @@ public static void splitSqlScript(@Nullable EncodedResource resource, String scr
String separator, String commentPrefix, String blockCommentStartDelimiter,
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {

Assert.hasText(commentPrefix, "'commentPrefix' must not be null or empty");
splitSqlScript(resource, script, separator, new String[] { commentPrefix },
blockCommentStartDelimiter, blockCommentEndDelimiter, statements);
}

/**
* Split an SQL script into separate statements delimited by the provided
* separator string. Each individual statement will be added to the provided
* {@code List}.
* <p>Within the script, the provided {@code commentPrefix} will be honored:
philwebb marked this conversation as resolved.
Show resolved Hide resolved
* any text beginning with the comment prefix and extending to the end of the
philwebb marked this conversation as resolved.
Show resolved Hide resolved
* line will be omitted from the output. Similarly, the provided
* {@code blockCommentStartDelimiter} and {@code blockCommentEndDelimiter}
* delimiters will be honored: any text enclosed in a block comment will be
* omitted from the output. In addition, multiple adjacent whitespace characters
* will be collapsed into a single space.
* @param resource the resource from which the script was read
* @param script the SQL script
* @param separator text separating each statement
* (typically a ';' or newline character)
* @param commentPrefixes the prefixes that identify SQL line comments
* (typically "--")
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter;
* never {@code null} or empty
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter;
* never {@code null} or empty
* @param statements the list that will contain the individual statements
* @throws ScriptException if an error occurred while splitting the SQL script
* @since 5.2
*/
public static void splitSqlScript(@Nullable EncodedResource resource, String script,
String separator, String[] commentPrefixes, String blockCommentStartDelimiter,
String blockCommentEndDelimiter, List<String> statements) throws ScriptException {

Assert.hasText(script, "'script' must not be null or empty");
Assert.notNull(separator, "'separator' must not be null");
Assert.hasText(commentPrefix, "'commentPrefix' must not be null or empty");
Assert.notNull(commentPrefixes, "'commentPrefixes' must not be null");
for (int i = 0; i < commentPrefixes.length; i++) {
Assert.hasText(commentPrefixes[i], "'commentPrefixes' must not contain null or empty elements");
}
Assert.hasText(blockCommentStartDelimiter, "'blockCommentStartDelimiter' must not be null or empty");
Assert.hasText(blockCommentEndDelimiter, "'blockCommentEndDelimiter' must not be null or empty");

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

/**
Expand All @@ -271,19 +314,19 @@ static String readScript(EncodedResource resource) throws IOException {
* a statement &mdash; will be included in the results.
* @param resource the {@code EncodedResource} containing the script
* to be processed
* @param commentPrefix the prefix that identifies comments in the SQL script
* @param commentPrefixes the prefix that identifies comments in the SQL script
philwebb marked this conversation as resolved.
Show resolved Hide resolved
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
*/
private static String readScript(EncodedResource resource, @Nullable String commentPrefix,
private static String readScript(EncodedResource resource, @Nullable String[] commentPrefixes,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {

LineNumberReader lnr = new LineNumberReader(resource.getReader());
try {
return readScript(lnr, commentPrefix, separator, blockCommentEndDelimiter);
return readScript(lnr, commentPrefixes, separator, blockCommentEndDelimiter);
}
finally {
lnr.close();
Expand All @@ -309,11 +352,35 @@ private static String readScript(EncodedResource resource, @Nullable String comm
public static String readScript(LineNumberReader lineNumberReader, @Nullable String lineCommentPrefix,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {

String[] lineCommentPrefixes = (lineCommentPrefix != null) ? new String[] { lineCommentPrefix } : null;
return readScript(lineNumberReader, lineCommentPrefixes, separator, blockCommentEndDelimiter);
}

/**
* Read a script from the provided {@code LineNumberReader}, using the supplied
* comment prefix and statement separator, and build a {@code String} containing
philwebb marked this conversation as resolved.
Show resolved Hide resolved
* the lines.
* <p>Lines <em>beginning</em> with the comment prefix are excluded from the
philwebb marked this conversation as resolved.
Show resolved Hide resolved
* results; however, line comments anywhere else &mdash; for example, within
* a statement &mdash; will be included in the results.
* @param lineNumberReader the {@code LineNumberReader} containing the script
* to be processed
* @param lineCommentPrefixes the prefixes that identify comments in the SQL script
* (typically "--")
* @param separator the statement separator in the SQL script (typically ";")
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @return a {@code String} containing the script lines
* @throws IOException in case of I/O errors
* @since 5.2
*/
public static String readScript(LineNumberReader lineNumberReader, @Nullable String[] lineCommentPrefixes,
@Nullable String separator, @Nullable String blockCommentEndDelimiter) throws IOException {

String currentStatement = lineNumberReader.readLine();
StringBuilder scriptBuilder = new StringBuilder();
while (currentStatement != null) {
if ((blockCommentEndDelimiter != null && currentStatement.contains(blockCommentEndDelimiter)) ||
(lineCommentPrefix != null && !currentStatement.startsWith(lineCommentPrefix))) {
(lineCommentPrefixes != null && !startsWithAny(currentStatement, lineCommentPrefixes, 0))) {
if (scriptBuilder.length() > 0) {
scriptBuilder.append('\n');
}
Expand All @@ -340,6 +407,15 @@ private static void appendSeparatorToScriptIfNecessary(StringBuilder scriptBuild
}
}

private static boolean startsWithAny(String script, String[] prefixes, int toffset) {
philwebb marked this conversation as resolved.
Show resolved Hide resolved
for (String prefix : prefixes) {
if (script.startsWith(prefix, toffset)) {
philwebb marked this conversation as resolved.
Show resolved Hide resolved
return true;
}
}
return false;
}

/**
* Does the provided SQL script contain the specified delimiter?
* @param script the SQL script
Expand Down Expand Up @@ -454,6 +530,46 @@ public static void executeSqlScript(Connection connection, EncodedResource resou
boolean ignoreFailedDrops, String commentPrefix, @Nullable String separator,
String blockCommentStartDelimiter, String blockCommentEndDelimiter) throws ScriptException {

executeSqlScript(connection, resource, continueOnError, ignoreFailedDrops,
new String[] { commentPrefix }, separator, blockCommentStartDelimiter,
blockCommentEndDelimiter);
}

/**
* Execute the given SQL script.
* <p>Statement separators and comments will be removed before executing
* individual statements within the supplied script.
* <p><strong>Warning</strong>: this method does <em>not</em> release the
* provided {@link Connection}.
* @param connection the JDBC connection to use to execute the script; already
* configured and ready to use
* @param resource the resource (potentially associated with a specific encoding)
* to load the SQL script from
* @param continueOnError whether or not to continue without throwing an exception
* in the event of an error
* @param ignoreFailedDrops whether or not to continue in the event of specifically
* an error on a {@code DROP} statement
* @param commentPrefixes the prefixes that identify single-line comments in the
* SQL script (typically "--")
* @param separator the script statement separator; defaults to
* {@value #DEFAULT_STATEMENT_SEPARATOR} if not specified and falls back to
* {@value #FALLBACK_STATEMENT_SEPARATOR} as a last resort; may be set to
* {@value #EOF_STATEMENT_SEPARATOR} to signal that the script contains a
* single statement without a separator
* @param blockCommentStartDelimiter the <em>start</em> block comment delimiter
* @param blockCommentEndDelimiter the <em>end</em> block comment delimiter
* @throws ScriptException if an error occurred while executing the SQL script
* @since 5.2
* @see #DEFAULT_STATEMENT_SEPARATOR
* @see #FALLBACK_STATEMENT_SEPARATOR
* @see #EOF_STATEMENT_SEPARATOR
* @see org.springframework.jdbc.datasource.DataSourceUtils#getConnection
* @see org.springframework.jdbc.datasource.DataSourceUtils#releaseConnection
*/
public static void executeSqlScript(Connection connection, EncodedResource resource, boolean continueOnError,
boolean ignoreFailedDrops, String[] commentPrefixes, @Nullable String separator,
String blockCommentStartDelimiter, String blockCommentEndDelimiter) throws ScriptException {

try {
if (logger.isDebugEnabled()) {
logger.debug("Executing SQL script from " + resource);
Expand All @@ -462,7 +578,7 @@ public static void executeSqlScript(Connection connection, EncodedResource resou

String script;
try {
script = readScript(resource, commentPrefix, separator, blockCommentEndDelimiter);
script = readScript(resource, commentPrefixes, separator, blockCommentEndDelimiter);
}
catch (IOException ex) {
throw new CannotReadScriptException(resource, ex);
Expand All @@ -476,7 +592,7 @@ public static void executeSqlScript(Connection connection, EncodedResource resou
}

List<String> statements = new ArrayList<>();
splitSqlScript(resource, script, separator, commentPrefix, blockCommentStartDelimiter,
splitSqlScript(resource, script, separator, commentPrefixes, blockCommentStartDelimiter,
blockCommentEndDelimiter, statements);

int stmtNumber = 0;
Expand Down
Loading