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

Improve splitting of SQL scripts into statements #1627

Merged
merged 14 commits into from
Jul 23, 2019
Merged
2 changes: 2 additions & 0 deletions modules/database-commons/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ description = "Testcontainers :: Database-Commons"

dependencies {
compile project(':testcontainers')

testCompile 'org.assertj:assertj-core:3.12.2'
}
Original file line number Diff line number Diff line change
Expand Up @@ -163,21 +163,18 @@ public static void splitSqlScript(String resource, String script, String separat
}
final boolean inComment = inLineComment || inBlockComment;

if (!inLiteral && !inComment && containsSubstringAtOffset(lowerCaseScriptContent, "BEGIN", i)) {
if (!inLiteral && !inComment && containsKeywordsAtOffset(lowerCaseScriptContent, "BEGIN", i, separator, commentPrefix, blockCommentStartDelimiter)) {
compoundStatementDepth++;
}
if (!inLiteral && !inComment && containsSubstringAtOffset(lowerCaseScriptContent, "END", i)) {
if (!inLiteral && !inComment && containsKeywordsAtOffset(lowerCaseScriptContent, "END", i, separator, commentPrefix, blockCommentStartDelimiter)) {
compoundStatementDepth--;
}
final boolean inCompoundStatement = compoundStatementDepth != 0;

if (!inLiteral && !inCompoundStatement) {
if (script.startsWith(separator, i)) {
// we've reached the end of the current statement
if (sb.length() > 0) {
statements.add(sb.toString());
sb = new StringBuilder();
}
sb = flushStringBuilder(sb, statements);
i += separator.length() - 1;
continue;
}
Expand All @@ -199,6 +196,7 @@ else if (script.startsWith(blockCommentStartDelimiter, i)) {
int indexOfCommentEnd = script.indexOf(blockCommentEndDelimiter, i);
if (indexOfCommentEnd > i) {
i = indexOfCommentEnd + blockCommentEndDelimiter.length() - 1;
inBlockComment = false;
continue;
}
else {
Expand All @@ -218,24 +216,57 @@ else if (c == ' ' || c == '\n' || c == '\t' || c == '\r') {
}
sb.append(c);
}
if (StringUtils.isNotEmpty(sb.toString())) {
statements.add(sb.toString());
flushStringBuilder(sb, statements);
}

private static StringBuilder flushStringBuilder(StringBuilder sb, List<String> statements) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! 👍

if (sb.length() == 0) {
return sb;
}

final String s = sb.toString().trim();
if (StringUtils.isNotEmpty(s)) {
statements.add(s);
}

return new StringBuilder();
}

private static boolean isSeperator(char c, String separator, String commentPrefix,
String blockCommentStartDelimiter) {
return c == ' ' || c == '\r' || c == '\n' || c == '\t' ||
c == separator.charAt(0) || c == separator.charAt(separator.length() - 1) ||
c == commentPrefix.charAt(0) || c == blockCommentStartDelimiter.charAt(0) ||
c == blockCommentStartDelimiter.charAt(blockCommentStartDelimiter.length() - 1);
}

private static boolean containsSubstringAtOffset(String lowercaseString, String substring, int offset) {
String lowercaseSubstring = substring.toLowerCase();

return lowercaseString.startsWith(lowercaseSubstring, offset);
}

private static boolean containsKeywordsAtOffset(String lowercaseString, String keywords, int offset,
String separator, String commentPrefix,
String blockCommentStartDelimiter) {
String lowercaseKeywords = keywords.toLowerCase();

boolean backSeperated = (offset == 0) || isSeperator(lowercaseString.charAt(offset - 1),
separator, commentPrefix, blockCommentStartDelimiter);
boolean frontSeperated = (offset >= (lowercaseString.length() - keywords.length())) ||
isSeperator(lowercaseString.charAt(offset + keywords.length()),
separator, commentPrefix, blockCommentStartDelimiter);

return backSeperated && frontSeperated && lowercaseString.startsWith(lowercaseKeywords, offset);
}

private static void checkArgument(boolean expression, String errorMessage) {
if (!expression) {
throw new IllegalArgumentException(errorMessage);
}
}

/**
/**
* Does the provided SQL script contain the specified delimiter?
* @param script the SQL script
* @param delim String delimiting each statement - typically a ';' character
Expand Down Expand Up @@ -356,7 +387,7 @@ public ScriptLoadException(String message, Throwable cause) {
}
}

private static class ScriptParseException extends RuntimeException {
public static class ScriptParseException extends RuntimeException {
public ScriptParseException(String format, String scriptPath) {
super(String.format(format, scriptPath));
}
Expand Down