Skip to content
Draft
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 @@ -28,6 +28,8 @@
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.UnaryOperator;
import java.util.regex.Pattern;

import javax.script.ScriptException;

Expand Down Expand Up @@ -69,6 +71,15 @@ public abstract class ScriptUtils {
*/
public static final String DEFAULT_BLOCK_COMMENT_END_DELIMITER = "*/";

/**
* T-SQL batch separator used by Microsoft SQL Server tooling such as {@code sqlcmd} and SSMS.
* Pass this as the {@code separator} argument when executing scripts that use {@code GO} as a batch
* delimiter instead of {@code ;}.
*/
public static final String GO_STATEMENT_SEPARATOR = "GO";

private static final Pattern GO_SEPARATOR_PATTERN = Pattern.compile("(?im)^[ \\t]*GO[ \\t]*$");

/**
* Prevent instantiation of this utility class.
*/
Expand Down Expand Up @@ -191,6 +202,52 @@ public static boolean containsSqlScriptDelimiters(
return false;
}

/**
* Replaces standalone {@code GO} batch separators with {@code ;} so that T-SQL scripts produced
* by tools such as {@code sqlcmd} or SSMS can be fed to the standard script executor.
*
* @param script the raw SQL script content
* @return the script with {@code GO} separators replaced by {@code ;}
*/
public static String normalizeGoSeparator(String script) {
return GO_SEPARATOR_PATTERN.matcher(script).replaceAll(";");
}

/**
* Load script from classpath, apply a preprocessor, and execute it against the given database.
*
* @param databaseDelegate database delegate for script execution
* @param initScriptPath the resource to load the init script from
* @param scriptPreprocessor function applied to the raw script content before execution
*/
public static void runInitScript(
DatabaseDelegate databaseDelegate,
String initScriptPath,
UnaryOperator<String> scriptPreprocessor
) {
try {
URL resource = Thread.currentThread().getContextClassLoader().getResource(initScriptPath);
if (resource == null) {
resource = ScriptUtils.class.getClassLoader().getResource(initScriptPath);
if (resource == null) {
LOGGER.warn("Could not load classpath init script: {}", initScriptPath);
throw new ScriptLoadException(
"Could not load classpath init script: " + initScriptPath + ". Resource not found."
);
}
}
String scripts = IOUtils.toString(resource, StandardCharsets.UTF_8);
scripts = scriptPreprocessor.apply(scripts);
executeDatabaseScript(databaseDelegate, initScriptPath, scripts);
} catch (IOException e) {
LOGGER.warn("Could not load classpath init script: {}", initScriptPath);
throw new ScriptLoadException("Could not load classpath init script: " + initScriptPath, e);
} catch (ScriptException e) {
LOGGER.error("Error while executing init script: {}", initScriptPath, e);
throw new UncategorizedScriptException("Error while executing init script: " + initScriptPath, e);
}
}

/**
* Load script from classpath and apply it to the given database
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -478,4 +478,36 @@ void testIgnoreDelimitersInLiteralsAndComments() {
void testContainsDelimiters() {
assertThat(ScriptUtils.containsSqlScriptDelimiters("'@' /*@*/ @ \"@\" --@", "@")).isTrue();
}

@Test
void testNormalizeGoSeparatorBasic() {
String script = "SELECT 1\nGO\nSELECT 2\nGO\n";
String normalized = ScriptUtils.normalizeGoSeparator(script);
List<String> statements = doSplit(normalized, ScriptUtils.DEFAULT_STATEMENT_SEPARATOR);
assertThat(statements).containsExactly("SELECT 1", "SELECT 2");
}

@Test
void testNormalizeGoSeparatorCaseInsensitive() {
String script = "SELECT 1\ngo\nSELECT 2\nGo\n";
String normalized = ScriptUtils.normalizeGoSeparator(script);
List<String> statements = doSplit(normalized, ScriptUtils.DEFAULT_STATEMENT_SEPARATOR);
assertThat(statements).containsExactly("SELECT 1", "SELECT 2");
}

@Test
void testNormalizeGoSeparatorWithLeadingWhitespace() {
String script = "SELECT 1\n GO\nSELECT 2\n\tGO\n";
String normalized = ScriptUtils.normalizeGoSeparator(script);
List<String> statements = doSplit(normalized, ScriptUtils.DEFAULT_STATEMENT_SEPARATOR);
assertThat(statements).containsExactly("SELECT 1", "SELECT 2");
}

@Test
void testNormalizeGoSeparatorDoesNotMatchInlineGo() {
String script = "SELECT GOOD, GOTO_COL\nGO\n";
String normalized = ScriptUtils.normalizeGoSeparator(script);
List<String> statements = doSplit(normalized, ScriptUtils.DEFAULT_STATEMENT_SEPARATOR);
assertThat(statements).containsExactly("SELECT GOOD, GOTO_COL");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -358,14 +358,25 @@ protected void optionallyMapResourceParameterAsVolume(
}
}

/**
* Override to preprocess the raw SQL script content before execution.
* The default implementation returns the script unchanged.
*
* @param script the raw script content
* @return the preprocessed script content
*/
protected String preprocessInitScript(String script) {
return script;
}

/**
* Load init script content and apply it to the database if initScriptPath is set
*/
protected void runInitScriptIfRequired() {
initScriptPaths
.stream()
.filter(Objects::nonNull)
.forEach(path -> ScriptUtils.runInitScript(getDatabaseDelegate(), path));
.forEach(path -> ScriptUtils.runInitScript(getDatabaseDelegate(), path, this::preprocessInitScript));
}

public void setParameters(Map<String, String> parameters) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package org.testcontainers.containers;

import org.testcontainers.ext.ScriptUtils;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.LicenseAcceptance;

Expand Down Expand Up @@ -166,4 +167,9 @@ private void checkPasswordStrength(String password) {
);
}
}

@Override
protected String preprocessInitScript(String script) {
return ScriptUtils.normalizeGoSeparator(script);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.testcontainers.mssqlserver;

import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.ext.ScriptUtils;
import org.testcontainers.utility.DockerImageName;
import org.testcontainers.utility.LicenseAcceptance;

Expand Down Expand Up @@ -153,4 +154,9 @@ private void checkPasswordStrength(String password) {
);
}
}

@Override
protected String preprocessInitScript(String script) {
return ScriptUtils.normalizeGoSeparator(script);
}
}