├── .gitignore
├── dbdeploy-ant
├── pom.xml
└── src
│ └── main
│ └── java
│ └── com
│ └── dbdeploy
│ ├── AntTarget.java
│ └── CreateChangeScriptTarget.java
├── dbdeploy-cli
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── dbdeploy
│ │ ├── CommandLineTarget.java
│ │ ├── DbDeployCommandLineParser.java
│ │ └── UserInputReader.java
│ └── test
│ └── java
│ └── com
│ └── dbdeploy
│ └── DbDeployCommandLineParserTest.java
├── dbdeploy-core
├── pom.xml
└── src
│ ├── it
│ └── db
│ │ ├── deltas
│ │ ├── 001_create_test_table.sql
│ │ └── 002_insert_value_into_test_table.sql
│ │ ├── high_numbers
│ │ └── 1000_high_number.sql
│ │ ├── invalid_deltas
│ │ ├── 001_create_test_table.sql
│ │ └── 002_insert_value_into_test_table.sql
│ │ └── multi_statement_deltas
│ │ ├── 001_create_test_table.sql
│ │ └── 002_insert_values_into_test_table.sql
│ ├── main
│ ├── java
│ │ └── com
│ │ │ └── dbdeploy
│ │ │ ├── AppliedChangesProvider.java
│ │ │ ├── AvailableChangeScriptsProvider.java
│ │ │ ├── ChangeScriptApplier.java
│ │ │ ├── Controller.java
│ │ │ ├── DbDeploy.java
│ │ │ ├── PrettyPrinter.java
│ │ │ ├── appliers
│ │ │ ├── ApplyMode.java
│ │ │ ├── DirectToDbApplier.java
│ │ │ ├── TemplateBasedApplier.java
│ │ │ └── UndoTemplateBasedApplier.java
│ │ │ ├── database
│ │ │ ├── DelimiterType.java
│ │ │ ├── LineEnding.java
│ │ │ ├── QueryStatementSplitter.java
│ │ │ └── changelog
│ │ │ │ ├── DatabaseSchemaVersionManager.java
│ │ │ │ └── QueryExecuter.java
│ │ │ ├── exceptions
│ │ │ ├── ChangeScriptFailedException.java
│ │ │ ├── DbDeployException.java
│ │ │ ├── DuplicateChangeScriptException.java
│ │ │ ├── SchemaVersionTrackingException.java
│ │ │ ├── UnrecognisedFilenameException.java
│ │ │ └── UsageException.java
│ │ │ └── scripts
│ │ │ ├── ChangeScript.java
│ │ │ ├── ChangeScriptCreator.java
│ │ │ ├── ChangeScriptRepository.java
│ │ │ ├── DirectoryScanner.java
│ │ │ └── FilenameParser.java
│ └── resources
│ │ ├── db2_apply.ftl
│ │ ├── db2_undo.ftl
│ │ ├── hsql_apply.ftl
│ │ ├── hsql_undo.ftl
│ │ ├── mssql_apply.ftl
│ │ ├── mssql_undo.ftl
│ │ ├── mysql_apply.ftl
│ │ ├── mysql_undo.ftl
│ │ ├── ora_apply.ftl
│ │ ├── ora_undo.ftl
│ │ ├── pgsql_apply.ftl
│ │ ├── pgsql_undo.ftl
│ │ ├── syb-ase_apply.ftl
│ │ └── syb-ase_undo.ftl
│ └── test
│ ├── java
│ └── com
│ │ └── dbdeploy
│ │ ├── ControllerTest.java
│ │ ├── DbDeployTest.java
│ │ ├── PrettyPrinterTest.java
│ │ ├── appliers
│ │ ├── DirectToDbApplierTest.java
│ │ └── TemplateBasedApplierTest.java
│ │ ├── database
│ │ ├── QueryStatementSplitterTest.java
│ │ ├── ScriptGenerationTest.java
│ │ └── changelog
│ │ │ └── DatabaseSchemaVersionManagerTest.java
│ │ ├── integration
│ │ ├── Database.java
│ │ ├── DirectToDbIntegrationTest.java
│ │ └── OutputToFileIntegrationTest.java
│ │ └── scripts
│ │ ├── ChangeScriptCreatorTest.java
│ │ ├── ChangeScriptRepositoryTest.java
│ │ ├── ChangeScriptTest.java
│ │ ├── FilenameParserTest.java
│ │ └── StubChangeScript.java
│ └── resources
│ └── com
│ └── dbdeploy
│ └── database
│ ├── db2_expected.sql
│ ├── hsql_expected.sql
│ ├── mssql_expected.sql
│ ├── mysql_expected.sql
│ ├── ora_expected.sql
│ ├── pgsql_expected.sql
│ └── syb-ase_expected.sql
├── dbdeploy-dist
├── pom.xml
└── src
│ └── main
│ ├── doc
│ ├── LICENSE
│ └── README
│ ├── example
│ ├── 001_create_table.sql
│ ├── 002_insert_data.sql
│ ├── build.xml
│ ├── cli_example.sh
│ └── pom.xml
│ ├── resources
│ └── assemblies
│ │ └── distribution.xml
│ └── scripts
│ ├── createSchemaVersionTable.db2.sql
│ ├── createSchemaVersionTable.hsql.sql
│ ├── createSchemaVersionTable.mssql.sql
│ ├── createSchemaVersionTable.mysql.sql
│ ├── createSchemaVersionTable.ora.sql
│ └── createSchemaVersionTable.syb-ase.sql
├── maven-dbdeploy-plugin
├── pom.xml
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── dbdeploy
│ │ └── mojo
│ │ ├── AbstractDbDeployMojo.java
│ │ ├── CreateChangeScriptMojo.java
│ │ ├── CreateDatabaseScriptsMojo.java
│ │ └── UpdateDatabaseMojo.java
│ └── test
│ ├── java
│ └── com
│ │ └── dbdeploy
│ │ └── mojo
│ │ ├── CreateChangeScriptMojoTest.java
│ │ ├── CreateDatabaseScriptsMojoTest.java
│ │ └── UpdateDatabaseMojoTest.java
│ └── resources
│ └── unit
│ └── test
│ ├── create-change-script-plugin-config.xml
│ ├── db-scripts-plugin-config.xml
│ └── update-plugin-config.xml
├── pom.xml
├── todo.txt
└── validate_distribution_examples.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | *.ipr
2 | *.iws
3 | *.iml
4 | target
5 | .idea
6 | *~
7 |
--------------------------------------------------------------------------------
/dbdeploy-ant/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | com.dbdeploy
6 | dbdeploy-parent
7 | 3.0-SNAPSHOT
8 |
9 |
10 | dbdeploy-ant
11 | jar
12 | dbdeploy-ant
13 | ant plugin to invoke dbdeploy
14 |
15 |
16 |
17 |
18 | org.apache.maven.plugins
19 | maven-source-plugin
20 |
21 |
22 | package
23 |
24 | jar
25 |
26 |
27 |
28 |
29 |
30 |
31 | org.apache.maven.plugins
32 | maven-shade-plugin
33 |
34 |
35 | package
36 |
37 | shade
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | ${project.groupId}
48 | dbdeploy-core
49 | ${project.version}
50 |
51 |
52 | org.apache.ant
53 | ant
54 | 1.7.0
55 | provided
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/dbdeploy-ant/src/main/java/com/dbdeploy/AntTarget.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.database.DelimiterType;
4 | import com.dbdeploy.database.LineEnding;
5 | import com.dbdeploy.exceptions.UsageException;
6 | import org.apache.tools.ant.BuildException;
7 | import org.apache.tools.ant.Task;
8 |
9 | import java.io.File;
10 |
11 | public class AntTarget extends Task {
12 | private DbDeploy dbDeploy = new DbDeploy();
13 |
14 | private static String ANT_USAGE = "\n\nDbdeploy Ant Task Usage"
15 | + "\n======================="
16 | + "\n\n\t"
32 | + "\n\n* - Indicates mandatory parameter";
33 |
34 | @Override
35 | public void execute() throws BuildException {
36 | try {
37 | dbDeploy.go();
38 | } catch (UsageException ex) {
39 | System.err.println(ANT_USAGE);
40 | throw new BuildException(ex.getMessage());
41 | } catch (Exception ex) {
42 | throw new BuildException(ex);
43 | }
44 | }
45 |
46 | public void setDir(File dir) {
47 | dbDeploy.setScriptdirectory(dir);
48 | }
49 |
50 | public void setDriver(String driver) {
51 | dbDeploy.setDriver(driver);
52 | }
53 |
54 | public void setUrl(String url) {
55 | dbDeploy.setUrl(url);
56 | }
57 |
58 | public void setPassword(String password) {
59 | dbDeploy.setPassword(password);
60 | }
61 |
62 | public void setUserid(String userid) {
63 | dbDeploy.setUserid(userid);
64 | }
65 |
66 | public void setOutputfile(File outputfile) {
67 | dbDeploy.setOutputfile(outputfile);
68 | }
69 |
70 | public void setDbms(String dbms) {
71 | dbDeploy.setDbms(dbms);
72 | }
73 |
74 | public void setLastChangeToApply(Long lastChangeToApply) {
75 | dbDeploy.setLastChangeToApply(lastChangeToApply);
76 | }
77 |
78 | public void setUndoOutputfile(File undoOutputfile) {
79 | dbDeploy.setUndoOutputfile(undoOutputfile);
80 | }
81 |
82 | public void setChangeLogTableName(String changeLogTableName) {
83 | dbDeploy.setChangeLogTableName(changeLogTableName);
84 | }
85 |
86 | public void setDelimiter(String delimiter) {
87 | dbDeploy.setDelimiter(delimiter);
88 | }
89 |
90 | public void setDelimitertype(DelimiterType delimiterType) {
91 | dbDeploy.setDelimiterType(delimiterType);
92 | }
93 |
94 | public void setTemplatedir(File templateDirectory) {
95 | dbDeploy.setTemplatedir(templateDirectory);
96 | }
97 |
98 | public void setEncoding(String encoding) {
99 | dbDeploy.setEncoding(encoding);
100 | }
101 |
102 | public void setLineEnding(LineEnding lineEnding) {
103 | dbDeploy.setLineEnding(lineEnding);
104 | }
105 | }
106 |
107 |
--------------------------------------------------------------------------------
/dbdeploy-ant/src/main/java/com/dbdeploy/CreateChangeScriptTarget.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.exceptions.UsageException;
4 | import com.dbdeploy.scripts.ChangeScriptCreator;
5 | import java.io.File;
6 | import org.apache.tools.ant.BuildException;
7 | import org.apache.tools.ant.Task;
8 |
9 | /**
10 | * Ant task that will create a new dbdeploy
11 | * change script with a timestamp and
12 | * specified file name.
13 | *
14 | * @author jbogan
15 | */
16 | public class CreateChangeScriptTarget extends Task {
17 | final ChangeScriptCreator changeScriptCreator = new ChangeScriptCreator();
18 |
19 | private static String ANT_USAGE = "\n\nDbdeploy Create Script Ant Task Usage"
20 | + "\n======================="
21 | + "\n\n\t"
25 | + "\n\n* - Indicates mandatory parameter";
26 |
27 | @Override
28 | public void execute() throws BuildException {
29 | try {
30 | changeScriptCreator.go();
31 | } catch (UsageException ex) {
32 | System.err.println(ANT_USAGE);
33 | throw new BuildException(ex.getMessage());
34 | } catch (Exception ex) {
35 | throw new BuildException(ex);
36 | }
37 | }
38 |
39 | public void setName(final String name) {
40 | changeScriptCreator.setScriptDescription(name);
41 | }
42 |
43 | public void setDir(final File dir) {
44 | changeScriptCreator.setScriptDirectory(dir);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/dbdeploy-cli/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | com.dbdeploy
6 | dbdeploy-parent
7 | 3.0-SNAPSHOT
8 |
9 |
10 | dbdeploy-cli
11 | jar
12 | dbdeploy-cli
13 | Command line interface to dbdeploy
14 |
15 |
16 |
17 |
18 |
19 |
20 | maven-assembly-plugin
21 |
22 |
23 |
24 | com.dbdeploy.CommandLineTarget
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | org.apache.maven.plugins
34 | maven-source-plugin
35 |
36 |
37 | package
38 |
39 | jar
40 |
41 |
42 |
43 |
44 |
45 |
46 | org.apache.maven.plugins
47 | maven-shade-plugin
48 |
49 |
50 | package
51 |
52 | shade
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | ${project.groupId}
64 | dbdeploy-core
65 | ${project.version}
66 |
67 |
68 | commons-cli
69 | commons-cli
70 | 1.2
71 |
72 |
73 |
74 |
75 |
--------------------------------------------------------------------------------
/dbdeploy-cli/src/main/java/com/dbdeploy/CommandLineTarget.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.exceptions.UsageException;
4 | import com.dbdeploy.DbDeploy;
5 |
6 | public class CommandLineTarget {
7 | private static final DbDeployCommandLineParser commandLineParser = new DbDeployCommandLineParser();
8 |
9 | public static void main(String[] args) {
10 | try {
11 | DbDeploy dbDeploy = new DbDeploy();
12 | commandLineParser.parse(args, dbDeploy);
13 | dbDeploy.go();
14 | } catch (UsageException ex) {
15 | System.err.println("ERROR: " + ex.getMessage());
16 | commandLineParser.printUsage();
17 | } catch (Exception ex) {
18 | System.err.println("Failed to apply changes: " + ex);
19 | ex.printStackTrace();
20 | System.exit(2);
21 | }
22 |
23 | System.exit(0);
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/dbdeploy-cli/src/main/java/com/dbdeploy/DbDeployCommandLineParser.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.database.LineEnding;
4 | import com.dbdeploy.exceptions.UsageException;
5 | import com.dbdeploy.database.DelimiterType;
6 | import org.apache.commons.cli.*;
7 |
8 | import java.beans.BeanInfo;
9 | import java.beans.Introspector;
10 | import java.beans.PropertyDescriptor;
11 | import java.io.File;
12 |
13 | public class DbDeployCommandLineParser {
14 | private final UserInputReader userInputReader;
15 |
16 | public DbDeployCommandLineParser() {
17 | this(new UserInputReader());
18 | }
19 |
20 | public DbDeployCommandLineParser(UserInputReader userInputReader) {
21 | this.userInputReader = userInputReader;
22 | }
23 |
24 | public void parse(String[] args, DbDeploy dbDeploy) throws UsageException {
25 | try {
26 | dbDeploy.setScriptdirectory(new File("."));
27 | final CommandLine commandLine = new GnuParser().parse(getOptions(), args);
28 | copyValuesFromCommandLineToDbDeployBean(dbDeploy, commandLine);
29 |
30 | if (commandLine.hasOption("password") && commandLine.getOptionValue("password") == null) {
31 | dbDeploy.setPassword(userInputReader.read("Password"));
32 | }
33 | } catch (ParseException e) {
34 | throw new UsageException(e.getMessage(), e);
35 | }
36 | }
37 |
38 | private void copyValuesFromCommandLineToDbDeployBean(DbDeploy dbDeploy, CommandLine commandLine) {
39 |
40 | try {
41 | final BeanInfo info = Introspector.getBeanInfo(dbDeploy.getClass());
42 |
43 | for (PropertyDescriptor p : info.getPropertyDescriptors()) {
44 | final String propertyName = p.getDisplayName();
45 | if (commandLine.hasOption(propertyName)) {
46 | Object value = commandLine.getOptionValue(propertyName);
47 | if (p.getPropertyType().isAssignableFrom(File.class)) {
48 | value = new File((String) value);
49 | }
50 |
51 | p.getWriteMethod().invoke(dbDeploy, value);
52 | }
53 | }
54 |
55 | if (commandLine.hasOption("delimitertype")) {
56 | dbDeploy.setDelimiterType(DelimiterType.valueOf(commandLine.getOptionValue("delimitertype")));
57 | }
58 |
59 | if (commandLine.hasOption("lineending")) {
60 | dbDeploy.setLineEnding(LineEnding.valueOf(commandLine.getOptionValue("lineending")));
61 | }
62 |
63 | } catch (Exception e) {
64 | throw new RuntimeException(e);
65 | }
66 | }
67 |
68 | public void printUsage() {
69 | HelpFormatter formatter = new HelpFormatter();
70 | formatter.printHelp("dbdeploy", getOptions());
71 |
72 | }
73 |
74 | @SuppressWarnings({"AccessStaticViaInstance"})
75 | private Options getOptions() {
76 | final Options options = new Options();
77 |
78 | options.addOption(OptionBuilder
79 | .hasArg()
80 | .withDescription("database user id")
81 | .withLongOpt("userid")
82 | .create("U"));
83 |
84 | options.addOption(OptionBuilder
85 | .hasOptionalArg()
86 | .withDescription("database password (use -P without a argument value to be prompted)")
87 | .withLongOpt("password")
88 | .create("P"));
89 |
90 | options.addOption(OptionBuilder
91 | .hasArg()
92 | .withDescription("database driver class")
93 | .withLongOpt("driver")
94 | .create("D"));
95 |
96 | options.addOption(OptionBuilder
97 | .hasArg()
98 | .withDescription("database url")
99 | .withLongOpt("url")
100 | .create("u"));
101 |
102 | options.addOption(OptionBuilder
103 | .hasArg()
104 | .withDescription("directory containing change scripts (default: .)")
105 | .withLongOpt("scriptdirectory")
106 | .create("s"));
107 |
108 | options.addOption(OptionBuilder
109 | .hasArg()
110 | .withDescription("encoding for input and output files (default: UTF-8)")
111 | .withLongOpt("encoding")
112 | .create("e"));
113 |
114 | options.addOption(OptionBuilder
115 | .hasArg()
116 | .withDescription("output file")
117 | .withLongOpt("outputfile")
118 | .create("o"));
119 |
120 | options.addOption(OptionBuilder
121 | .hasArg()
122 | .withDescription("dbms type")
123 | .withLongOpt("dbms")
124 | .create("d"));
125 |
126 | options.addOption(OptionBuilder
127 | .hasArg()
128 | .withDescription("template directory")
129 | .withLongOpt("templatedir")
130 | .create());
131 |
132 | options.addOption(OptionBuilder
133 | .hasArg()
134 | .withDescription("name of change log table to use (default: changelog)")
135 | .withLongOpt("changeLogTableName")
136 | .create("t"));
137 |
138 | options.addOption(OptionBuilder
139 | .hasArg()
140 | .withDescription("delimiter to separate sql statements")
141 | .withLongOpt("delimiter")
142 | .create());
143 |
144 | options.addOption(OptionBuilder
145 | .hasArg()
146 | .withDescription("delimiter type to separate sql statements (row or normal)")
147 | .withLongOpt("delimitertype")
148 | .create());
149 |
150 | options.addOption(OptionBuilder
151 | .hasArg()
152 | .withDescription("line ending to use when applying scripts direct to db (platform, cr, crlf, lf)")
153 | .withLongOpt("lineending")
154 | .create());
155 |
156 |
157 | return options;
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/dbdeploy-cli/src/main/java/com/dbdeploy/UserInputReader.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import java.util.Scanner;
4 |
5 | public class UserInputReader {
6 | public String read(String prompt) {
7 | System.out.print(prompt + ": ");
8 | Scanner scanner = new Scanner(System.in);
9 | return scanner.nextLine();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/dbdeploy-cli/src/test/java/com/dbdeploy/DbDeployCommandLineParserTest.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.database.DelimiterType;
4 | import com.dbdeploy.database.LineEnding;
5 | import org.junit.Test;
6 |
7 | import java.io.File;
8 |
9 | import static org.hamcrest.Matchers.is;
10 | import static org.junit.Assert.assertEquals;
11 | import static org.junit.Assert.assertThat;
12 | import static org.mockito.Mockito.*;
13 |
14 | public class DbDeployCommandLineParserTest {
15 | UserInputReader userInputReader = mock(UserInputReader.class);
16 |
17 | private final DbDeploy dbDeploy = new DbDeploy();
18 | private final DbDeployCommandLineParser parser = new DbDeployCommandLineParser(userInputReader);
19 |
20 | @Test
21 | public void canParseUserIdFromCommandLine() throws Exception {
22 | parser.parse("-U myuserid".split(" "), dbDeploy);
23 | assertEquals("myuserid", dbDeploy.getUserid());
24 | }
25 |
26 | @Test
27 | public void thisIsntReallyATestBecuaseThereIsNoAssertButItsVeryUsefulToLookAtTheResult() throws Exception {
28 | parser.printUsage();
29 | }
30 |
31 | @Test
32 | public void checkAllOfTheOtherFieldsParseOkHere() throws Exception {
33 | parser.parse(("-U userid -Ppassword --driver a.b.c --url b:c:d " +
34 | "--scriptdirectory . -o output.sql " +
35 | "--changeLogTableName my-change-log " +
36 | "--dbms ora " +
37 | "--templatedir /tmp/mytemplates " +
38 | "--delimiter \\ --delimitertype row").split(" "), dbDeploy);
39 |
40 | assertThat(dbDeploy.getUserid(), is("userid"));
41 | assertThat(dbDeploy.getPassword(), is("password"));
42 | assertThat(dbDeploy.getDriver(), is("a.b.c"));
43 | assertThat(dbDeploy.getUrl(), is("b:c:d"));
44 | assertThat(dbDeploy.getScriptdirectory().getName(), is("."));
45 | assertThat(dbDeploy.getOutputfile().getName(), is("output.sql"));
46 | assertThat(dbDeploy.getDbms(), is("ora"));
47 | assertThat(dbDeploy.getChangeLogTableName(), is("my-change-log"));
48 | assertThat(dbDeploy.getDelimiter(), is("\\"));
49 | assertThat(dbDeploy.getDelimiterType(), is(DelimiterType.row));
50 | assertThat(dbDeploy.getTemplatedir().getPath(), is(File.separator + "tmp" + File.separator + "mytemplates"));
51 | }
52 |
53 | @Test
54 | public void delimiterTypeWorksOk() throws Exception {
55 | parser.parse("--delimitertype normal".split(" "), dbDeploy);
56 | assertThat(dbDeploy.getDelimiterType(), is(DelimiterType.normal));
57 |
58 | parser.parse("--delimitertype row".split(" "), dbDeploy);
59 | assertThat(dbDeploy.getDelimiterType(), is(DelimiterType.row));
60 | }
61 |
62 | @Test
63 | public void lineEndingWorksOk() throws Exception {
64 | assertThat(dbDeploy.getLineEnding(), is(LineEnding.platform));
65 |
66 | parser.parse("--lineending cr".split(" "), dbDeploy);
67 | assertThat(dbDeploy.getLineEnding(), is(LineEnding.cr));
68 |
69 | parser.parse("--lineending crlf".split(" "), dbDeploy);
70 | assertThat(dbDeploy.getLineEnding(), is(LineEnding.crlf));
71 |
72 | parser.parse("--lineending lf".split(" "), dbDeploy);
73 | assertThat(dbDeploy.getLineEnding(), is(LineEnding.lf));
74 |
75 | parser.parse("--lineending platform".split(" "), dbDeploy);
76 | assertThat(dbDeploy.getLineEnding(), is(LineEnding.platform));
77 |
78 | }
79 |
80 | @Test
81 | public void shouldPromptFromStdinForPasswordIfPasswordParamSuppliedWithNoArg() throws Exception {
82 | when(userInputReader.read("Password")).thenReturn("user entered password");
83 |
84 | parser.parse(new String[] { "-P" }, dbDeploy);
85 |
86 | assertThat(dbDeploy.getPassword(), is("user entered password"));
87 | }
88 |
89 | @Test
90 | public void shouldNotPromptForPasswordWhenSupplied() throws Exception {
91 | parser.parse(new String[]{"-P", "password"}, dbDeploy);
92 | verifyZeroInteractions(userInputReader);
93 | }
94 |
95 | @Test
96 | public void shouldNotPromptForPasswordNotSpecifiedOnCommandLine() throws Exception {
97 | // this is important: not all databases require passwords :)
98 | parser.parse(new String[] {}, dbDeploy);
99 | verifyZeroInteractions(userInputReader);
100 | }
101 |
102 | }
103 |
104 |
--------------------------------------------------------------------------------
/dbdeploy-core/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 |
5 | com.dbdeploy
6 | dbdeploy-parent
7 | 3.0-SNAPSHOT
8 |
9 |
10 | dbdeploy-core
11 | jar
12 | dbdeploy-core
13 |
14 |
15 |
16 |
17 | org.apache.maven.plugins
18 | maven-source-plugin
19 |
20 |
21 | package
22 |
23 | jar
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | hsqldb
34 | hsqldb
35 | ${hsqldb.version}
36 | test
37 |
38 |
39 | commons-io
40 | commons-io
41 | 1.4
42 | test
43 |
44 |
45 | commons-lang
46 | commons-lang
47 | 2.4
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/it/db/deltas/001_create_test_table.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE Test (id INTEGER);
2 |
3 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/it/db/deltas/002_insert_value_into_test_table.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO Test VALUES (6);
2 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/it/db/high_numbers/1000_high_number.sql:
--------------------------------------------------------------------------------
1 | --
2 | -- This sql file has a number > 1000 - see http://code.google.com/p/dbdeploy/issues/detail?id=36
3 | --
4 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/it/db/invalid_deltas/001_create_test_table.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE Test (id INTEGER);
2 |
3 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/it/db/invalid_deltas/002_insert_value_into_test_table.sql:
--------------------------------------------------------------------------------
1 | -- this is deliberately invalid
2 | INSERT INTO Test VALUES (1,2,3,4);
3 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/it/db/multi_statement_deltas/001_create_test_table.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE Test (id INTEGER);
2 |
3 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/it/db/multi_statement_deltas/002_insert_values_into_test_table.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO Test VALUES (6);
2 | INSERT INTO Test VALUES (7);
3 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/AppliedChangesProvider.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import java.util.List;
4 |
5 | public interface AppliedChangesProvider {
6 | List getAppliedChanges();
7 | }
8 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/AvailableChangeScriptsProvider.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.scripts.ChangeScript;
4 |
5 | import java.util.List;
6 |
7 | public interface AvailableChangeScriptsProvider {
8 | List getAvailableChangeScripts();
9 | }
10 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/ChangeScriptApplier.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.scripts.ChangeScript;
4 |
5 | import java.util.List;
6 |
7 | public interface ChangeScriptApplier {
8 | void apply(List changeScript);
9 | }
10 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/Controller.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.exceptions.DbDeployException;
4 | import com.dbdeploy.scripts.ChangeScript;
5 |
6 | import java.io.IOException;
7 | import java.util.ArrayList;
8 | import java.util.Collections;
9 | import java.util.List;
10 |
11 | public class Controller {
12 |
13 | private final AvailableChangeScriptsProvider availableChangeScriptsProvider;
14 | private final AppliedChangesProvider appliedChangesProvider;
15 | private final ChangeScriptApplier changeScriptApplier;
16 | private final ChangeScriptApplier undoScriptApplier;
17 |
18 | private final PrettyPrinter prettyPrinter = new PrettyPrinter();
19 |
20 | public Controller(AvailableChangeScriptsProvider availableChangeScriptsProvider,
21 | AppliedChangesProvider appliedChangesProvider,
22 | ChangeScriptApplier changeScriptApplier, ChangeScriptApplier undoScriptApplier) {
23 | this.availableChangeScriptsProvider = availableChangeScriptsProvider;
24 | this.appliedChangesProvider = appliedChangesProvider;
25 | this.changeScriptApplier = changeScriptApplier;
26 | this.undoScriptApplier = undoScriptApplier;
27 | }
28 |
29 | public void processChangeScripts(Long lastChangeToApply) throws DbDeployException, IOException {
30 | if (lastChangeToApply != Long.MAX_VALUE) {
31 | info("Only applying changes up and including change script #" + lastChangeToApply);
32 | }
33 |
34 | List scripts = availableChangeScriptsProvider.getAvailableChangeScripts();
35 | List applied = appliedChangesProvider.getAppliedChanges();
36 | List toApply = identifyChangesToApply(lastChangeToApply, scripts, applied);
37 |
38 | logStatus(scripts, applied, toApply);
39 |
40 | changeScriptApplier.apply(Collections.unmodifiableList(toApply));
41 |
42 | if (undoScriptApplier != null) {
43 | info("Generating undo scripts...");
44 | Collections.reverse(toApply);
45 | undoScriptApplier.apply(Collections.unmodifiableList(toApply));
46 | }
47 | }
48 |
49 | private void logStatus(List scripts, List applied, List toApply) {
50 | info("Changes currently applied to database:\n " + prettyPrinter.format(applied));
51 | info("Scripts available:\n " + prettyPrinter.formatChangeScriptList(scripts));
52 | info("To be applied:\n " + prettyPrinter.formatChangeScriptList(toApply));
53 | }
54 |
55 | private List identifyChangesToApply(Long lastChangeToApply, List scripts, List applied) {
56 | List result = new ArrayList();
57 |
58 | for (ChangeScript script : scripts) {
59 | if (script.getId() > lastChangeToApply)
60 | break;
61 |
62 | if (!applied.contains(script.getId())) {
63 | result.add(script);
64 | }
65 | }
66 |
67 | return result;
68 | }
69 |
70 | private void info(String string) {
71 | System.err.println(string);
72 | }
73 | }
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/DbDeploy.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.appliers.DirectToDbApplier;
4 | import com.dbdeploy.appliers.TemplateBasedApplier;
5 | import com.dbdeploy.appliers.UndoTemplateBasedApplier;
6 | import com.dbdeploy.database.DelimiterType;
7 | import com.dbdeploy.database.LineEnding;
8 | import com.dbdeploy.database.QueryStatementSplitter;
9 | import com.dbdeploy.database.changelog.DatabaseSchemaVersionManager;
10 | import com.dbdeploy.database.changelog.QueryExecuter;
11 | import com.dbdeploy.exceptions.UsageException;
12 | import com.dbdeploy.scripts.ChangeScriptRepository;
13 | import com.dbdeploy.scripts.DirectoryScanner;
14 |
15 | import java.io.File;
16 | import java.io.PrintWriter;
17 |
18 | public class DbDeploy {
19 | private String url;
20 | private String userid;
21 | private String password;
22 | private String encoding = "UTF-8";
23 | private File scriptdirectory;
24 | private File outputfile;
25 | private File undoOutputfile;
26 | private LineEnding lineEnding = LineEnding.platform;
27 | private String dbms;
28 | private Long lastChangeToApply = Long.MAX_VALUE;
29 | private String driver;
30 | private String changeLogTableName = "changelog";
31 | private String delimiter = ";";
32 | private DelimiterType delimiterType = DelimiterType.normal;
33 | private File templatedir;
34 |
35 | public void setDriver(String driver) {
36 | this.driver = driver;
37 | }
38 |
39 | public void setUrl(String url) {
40 | this.url = url;
41 | }
42 |
43 | public void setUserid(String userid) {
44 | this.userid = userid;
45 | }
46 |
47 | public void setPassword(String password) {
48 | this.password = password;
49 | }
50 |
51 | public void setScriptdirectory(File scriptdirectory) {
52 | this.scriptdirectory = scriptdirectory;
53 | }
54 |
55 | public void setOutputfile(File outputfile) {
56 | this.outputfile = outputfile;
57 | }
58 |
59 | public void setDbms(String dbms) {
60 | this.dbms = dbms;
61 | }
62 |
63 | public void setLastChangeToApply(Long lastChangeToApply) {
64 | this.lastChangeToApply = lastChangeToApply;
65 | }
66 |
67 | public void setUndoOutputfile(File undoOutputfile) {
68 | this.undoOutputfile = undoOutputfile;
69 | }
70 |
71 | public void setChangeLogTableName(String changeLogTableName) {
72 | this.changeLogTableName = changeLogTableName;
73 | }
74 |
75 | public void setEncoding(String encoding) {
76 | this.encoding = encoding;
77 | }
78 |
79 | public void setLineEnding(LineEnding lineEnding) {
80 | this.lineEnding = lineEnding;
81 | }
82 |
83 | public void go() throws Exception {
84 | System.err.println(getWelcomeString());
85 |
86 | validate();
87 |
88 | Class.forName(driver);
89 |
90 | QueryExecuter queryExecuter = new QueryExecuter(url, userid, password);
91 |
92 | DatabaseSchemaVersionManager databaseSchemaVersionManager =
93 | new DatabaseSchemaVersionManager(queryExecuter, changeLogTableName);
94 |
95 | ChangeScriptRepository changeScriptRepository =
96 | new ChangeScriptRepository(new DirectoryScanner(encoding).getChangeScriptsForDirectory(scriptdirectory));
97 |
98 | ChangeScriptApplier doScriptApplier;
99 |
100 | if (outputfile != null) {
101 | doScriptApplier = new TemplateBasedApplier(
102 | new PrintWriter(outputfile, encoding), dbms,
103 | changeLogTableName, delimiter, delimiterType, getTemplatedir());
104 | } else {
105 | QueryStatementSplitter splitter = new QueryStatementSplitter();
106 | splitter.setDelimiter(getDelimiter());
107 | splitter.setDelimiterType(getDelimiterType());
108 | splitter.setOutputLineEnding(lineEnding);
109 | doScriptApplier = new DirectToDbApplier(queryExecuter, databaseSchemaVersionManager, splitter);
110 | }
111 |
112 | ChangeScriptApplier undoScriptApplier = null;
113 |
114 | if (undoOutputfile != null) {
115 | undoScriptApplier = new UndoTemplateBasedApplier(
116 | new PrintWriter(undoOutputfile), dbms, changeLogTableName, delimiter, delimiterType, templatedir);
117 |
118 | }
119 |
120 | Controller controller = new Controller(changeScriptRepository, databaseSchemaVersionManager, doScriptApplier, undoScriptApplier);
121 |
122 | controller.processChangeScripts(lastChangeToApply);
123 |
124 | queryExecuter.close();
125 | }
126 |
127 | private void validate() throws UsageException {
128 | checkForRequiredParameter(userid, "userid");
129 | checkForRequiredParameter(driver, "driver");
130 | checkForRequiredParameter(url, "url");
131 | checkForRequiredParameter(scriptdirectory, "dir");
132 |
133 | if (scriptdirectory == null || !scriptdirectory.isDirectory()) {
134 | throw new UsageException("Script directory must point to a valid directory");
135 | }
136 | }
137 |
138 | private void checkForRequiredParameter(String parameterValue, String parameterName) throws UsageException {
139 | if (parameterValue == null || parameterValue.length() == 0) {
140 | UsageException.throwForMissingRequiredValue(parameterName);
141 | }
142 | }
143 |
144 | private void checkForRequiredParameter(Object parameterValue, String parameterName) throws UsageException {
145 | if (parameterValue == null) {
146 | UsageException.throwForMissingRequiredValue(parameterName);
147 | }
148 | }
149 |
150 | public String getUserid() {
151 | return userid;
152 | }
153 |
154 | public String getUrl() {
155 | return url;
156 | }
157 |
158 | public String getPassword() {
159 | return password;
160 | }
161 |
162 | public File getScriptdirectory() {
163 | return scriptdirectory;
164 | }
165 |
166 | public File getOutputfile() {
167 | return outputfile;
168 | }
169 |
170 | public File getUndoOutputfile() {
171 | return undoOutputfile;
172 | }
173 |
174 | public String getDbms() {
175 | return dbms;
176 | }
177 |
178 | public Long getLastChangeToApply() {
179 | return lastChangeToApply;
180 | }
181 |
182 | public String getDriver() {
183 | return driver;
184 | }
185 |
186 | public void setTemplatedir(File templatedir) {
187 | this.templatedir = templatedir;
188 | }
189 |
190 | public File getTemplatedir() {
191 | return templatedir;
192 | }
193 |
194 | public String getChangeLogTableName() {
195 | return changeLogTableName;
196 | }
197 |
198 | public String getDelimiter() {
199 | return delimiter;
200 | }
201 |
202 | public void setDelimiter(String delimiter) {
203 | this.delimiter = delimiter;
204 | }
205 |
206 | public DelimiterType getDelimiterType() {
207 | return delimiterType;
208 | }
209 |
210 |
211 | public void setDelimiterType(DelimiterType delimiterType) {
212 | this.delimiterType = delimiterType;
213 | }
214 |
215 | public String getWelcomeString() {
216 | String version = getClass().getPackage().getImplementationVersion();
217 | return "dbdeploy " + version;
218 | }
219 |
220 | public String getEncoding() {
221 | return encoding;
222 | }
223 |
224 | public LineEnding getLineEnding() {
225 | return lineEnding;
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/PrettyPrinter.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.scripts.ChangeScript;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | public class PrettyPrinter {
9 |
10 | public String format(List appliedChanges) {
11 | if (appliedChanges.isEmpty()) {
12 | return "(none)";
13 | }
14 |
15 | StringBuilder builder = new StringBuilder();
16 |
17 | Long lastRangeStart = null;
18 | Long lastNumber = null;
19 |
20 | for (Long thisNumber : appliedChanges) {
21 | if (lastNumber == null) {
22 | // first in loop
23 | lastNumber = thisNumber;
24 | lastRangeStart = thisNumber;
25 | } else if (thisNumber == lastNumber + 1) {
26 | // continuation of current range
27 | lastNumber = thisNumber;
28 | } else {
29 | // doesn't fit into last range - so output the old range and
30 | // start a new one
31 | appendRange(builder, lastRangeStart, lastNumber);
32 | lastNumber = thisNumber;
33 | lastRangeStart = thisNumber;
34 | }
35 | }
36 |
37 | appendRange(builder, lastRangeStart, lastNumber);
38 | return builder.toString();
39 | }
40 |
41 | private void appendRange(StringBuilder builder, Long lastRangeStart, Long lastNumber) {
42 | if (lastRangeStart == lastNumber) {
43 | appendWithPossibleComma(builder, lastNumber);
44 | } else if (lastRangeStart + 1 == lastNumber) {
45 | appendWithPossibleComma(builder, lastRangeStart);
46 | appendWithPossibleComma(builder, lastNumber);
47 | } else {
48 | appendWithPossibleComma(builder, lastRangeStart + ".." + lastNumber);
49 | }
50 | }
51 |
52 | private void appendWithPossibleComma(StringBuilder builder, Object o) {
53 | if (builder.length() != 0) {
54 | builder.append(", ");
55 | }
56 | builder.append(o);
57 | }
58 |
59 | public String formatChangeScriptList(List changeScripts) {
60 | List numberList = new ArrayList(changeScripts.size());
61 |
62 | for (ChangeScript changeScript : changeScripts) {
63 | numberList.add(changeScript.getId());
64 | }
65 |
66 | return format(numberList);
67 | }
68 |
69 | }
70 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/appliers/ApplyMode.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.appliers;
2 |
3 | public enum ApplyMode {
4 | DO,
5 | UNDO,
6 | }
7 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/appliers/DirectToDbApplier.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.appliers;
2 |
3 | import com.dbdeploy.ChangeScriptApplier;
4 | import com.dbdeploy.database.QueryStatementSplitter;
5 | import com.dbdeploy.database.changelog.DatabaseSchemaVersionManager;
6 | import com.dbdeploy.database.changelog.QueryExecuter;
7 | import com.dbdeploy.exceptions.ChangeScriptFailedException;
8 | import com.dbdeploy.scripts.ChangeScript;
9 |
10 | import java.sql.SQLException;
11 | import java.util.List;
12 |
13 | public class DirectToDbApplier implements ChangeScriptApplier {
14 | private final QueryExecuter queryExecuter;
15 | private final DatabaseSchemaVersionManager schemaVersionManager;
16 | private final QueryStatementSplitter splitter;
17 |
18 | public DirectToDbApplier(QueryExecuter queryExecuter, DatabaseSchemaVersionManager schemaVersionManager, QueryStatementSplitter splitter) {
19 | this.queryExecuter = queryExecuter;
20 | this.schemaVersionManager = schemaVersionManager;
21 | this.splitter = splitter;
22 | }
23 |
24 | public void apply(List changeScript) {
25 | begin();
26 |
27 | for (ChangeScript script : changeScript) {
28 | System.err.println("Applying " + script + "...");
29 |
30 | applyChangeScript(script);
31 | insertToSchemaVersionTable(script);
32 |
33 | commitTransaction();
34 | }
35 | }
36 |
37 | public void begin() {
38 | try {
39 | queryExecuter.setAutoCommit(false);
40 | } catch (SQLException e) {
41 | throw new RuntimeException(e);
42 | }
43 | }
44 |
45 | protected void applyChangeScript(ChangeScript script) {
46 | List statements = splitter.split(script.getContent());
47 |
48 | for (int i = 0; i < statements.size(); i++) {
49 | String statement = statements.get(i);
50 | try {
51 | if (statements.size() > 1) {
52 | System.err.println(" -> statement " + (i+1) + " of " + statements.size() + "...");
53 | }
54 | queryExecuter.execute(statement);
55 | } catch (SQLException e) {
56 | throw new ChangeScriptFailedException(e, script, i+1, statement);
57 | }
58 | }
59 | }
60 |
61 | protected void insertToSchemaVersionTable(ChangeScript changeScript) {
62 | schemaVersionManager.recordScriptApplied(changeScript);
63 | }
64 |
65 | protected void commitTransaction() {
66 | try {
67 | queryExecuter.commit();
68 | } catch (SQLException e) {
69 | throw new RuntimeException();
70 | }
71 | }
72 |
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/appliers/TemplateBasedApplier.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.appliers;
2 |
3 | import com.dbdeploy.ChangeScriptApplier;
4 | import com.dbdeploy.database.DelimiterType;
5 | import com.dbdeploy.exceptions.UsageException;
6 | import com.dbdeploy.scripts.ChangeScript;
7 | import freemarker.cache.ClassTemplateLoader;
8 | import freemarker.cache.FileTemplateLoader;
9 | import freemarker.cache.MultiTemplateLoader;
10 | import freemarker.cache.TemplateLoader;
11 | import freemarker.template.Configuration;
12 | import freemarker.template.Template;
13 |
14 | import java.io.*;
15 | import java.util.HashMap;
16 | import java.util.List;
17 | import java.util.Map;
18 |
19 |
20 | public class TemplateBasedApplier implements ChangeScriptApplier {
21 | private Configuration configuration;
22 | private Writer writer;
23 | private String syntax;
24 | private String changeLogTableName;
25 | private String delimiter;
26 | private DelimiterType delimiterType;
27 |
28 | public TemplateBasedApplier(Writer writer, String syntax, String changeLogTableName, String delimiter, DelimiterType delimiterType, File templateDirectory) throws IOException {
29 | this.syntax = syntax;
30 | this.changeLogTableName = changeLogTableName;
31 | this.delimiter = delimiter;
32 | this.delimiterType = delimiterType;
33 | this.writer = writer;
34 | this.configuration = new Configuration();
35 |
36 | FileTemplateLoader fileTemplateLoader = createFileTemplateLoader(templateDirectory);
37 | this.configuration.setTemplateLoader(
38 | new MultiTemplateLoader(new TemplateLoader[]{
39 | fileTemplateLoader,
40 | new ClassTemplateLoader(getClass(), "/"),
41 | }));
42 | }
43 |
44 | private FileTemplateLoader createFileTemplateLoader(File templateDirectory) throws IOException {
45 | if (templateDirectory == null) {
46 | return new FileTemplateLoader();
47 | } else {
48 | return new FileTemplateLoader(templateDirectory, true);
49 | }
50 | }
51 |
52 | public void apply(List changeScripts) {
53 | String filename = syntax + "_" + getTemplateQualifier() + ".ftl";
54 |
55 | try {
56 | Map model = new HashMap();
57 | model.put("scripts", changeScripts);
58 | model.put("changeLogTableName", changeLogTableName);
59 | model.put("delimiter", delimiter);
60 | model.put("separator", delimiterType == DelimiterType.row ? "\n" : "");
61 |
62 | try {
63 | Template template = configuration.getTemplate(filename);
64 | template.process(model, writer);
65 | } finally {
66 | writer.close();
67 | }
68 | } catch (FileNotFoundException ex) {
69 | throw new UsageException("Could not find template named " + filename + "\n" +
70 | "Check that you have got the name of the database syntax correct.", ex);
71 | } catch (Exception e) {
72 | throw new RuntimeException(e);
73 | }
74 | }
75 |
76 | protected String getTemplateQualifier() {
77 | return "apply";
78 | }
79 |
80 | }
81 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/appliers/UndoTemplateBasedApplier.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.appliers;
2 |
3 | import com.dbdeploy.database.DelimiterType;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.io.OutputStream;
8 | import java.io.Writer;
9 |
10 | public class UndoTemplateBasedApplier extends TemplateBasedApplier {
11 | public UndoTemplateBasedApplier(Writer writer, String syntax,
12 | String changeLogTableName, String delimiter, DelimiterType delimiterType, File templateDirectory) throws IOException {
13 | super(writer, syntax, changeLogTableName, delimiter, delimiterType, templateDirectory);
14 | }
15 |
16 | @Override
17 | protected String getTemplateQualifier() {
18 | return "undo";
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/database/DelimiterType.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.database;
2 |
3 | public enum DelimiterType {
4 | /**
5 | * Delimiter is interpreted whenever it appears at the end of a line
6 | */
7 | normal {
8 | public boolean matches(String line, String delimiter) {
9 | return line.endsWith(delimiter);
10 | }
11 | },
12 |
13 | /**
14 | * Delimiter must be on a line all to itself
15 | */
16 | row {
17 | public boolean matches(String line, String delimiter) {
18 | return line.equals(delimiter);
19 | }
20 | };
21 |
22 | public abstract boolean matches(String line, String delimiter);
23 | }
24 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/database/LineEnding.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.database;
2 |
3 | import org.apache.commons.lang.SystemUtils;
4 |
5 | public enum LineEnding {
6 | platform { public String get() { return SystemUtils.LINE_SEPARATOR; } },
7 | cr { public String get() { return "\r"; } },
8 | crlf { public String get() { return "\r\n"; } },
9 | lf { public String get() { return "\n"; } };
10 |
11 | abstract public String get();
12 | }
13 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/database/QueryStatementSplitter.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.database;
2 |
3 | import org.apache.commons.lang.StringUtils;
4 | import org.apache.commons.lang.text.StrBuilder;
5 | import org.apache.commons.lang.text.StrMatcher;
6 | import org.apache.commons.lang.text.StrTokenizer;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | public class QueryStatementSplitter {
12 | private String delimiter = ";";
13 | private DelimiterType delimiterType = DelimiterType.normal;
14 | private LineEnding lineEnding = LineEnding.platform;
15 |
16 | public QueryStatementSplitter() {
17 | }
18 |
19 | public List split(String input) {
20 | List statements = new ArrayList();
21 | StrBuilder currentSql = new StrBuilder();
22 |
23 | StrTokenizer lineTokenizer = new StrTokenizer(input);
24 | lineTokenizer.setDelimiterMatcher(StrMatcher.charSetMatcher("\r\n"));
25 |
26 | for (String line : lineTokenizer.getTokenArray()) {
27 | String strippedLine = StringUtils.stripEnd(line, null);
28 | if (!currentSql.isEmpty()) {
29 | currentSql.append(lineEnding.get());
30 | }
31 |
32 | currentSql.append(strippedLine);
33 |
34 | if (delimiterType.matches(strippedLine, delimiter)) {
35 | statements.add(currentSql.substring(0, currentSql.length() - delimiter.length()));
36 | currentSql.clear();
37 | }
38 | }
39 |
40 | if (!currentSql.isEmpty()) {
41 | statements.add(currentSql.toString());
42 | }
43 |
44 | return statements;
45 | }
46 |
47 | public String getDelimiter() {
48 | return delimiter;
49 | }
50 |
51 | public void setDelimiter(String delimiter) {
52 | this.delimiter = delimiter;
53 | }
54 |
55 | public DelimiterType getDelimiterType() {
56 | return delimiterType;
57 | }
58 |
59 | public void setDelimiterType(DelimiterType delimiterType) {
60 | this.delimiterType = delimiterType;
61 | }
62 |
63 | public void setOutputLineEnding(LineEnding lineEnding) {
64 | this.lineEnding = lineEnding;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/database/changelog/DatabaseSchemaVersionManager.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.database.changelog;
2 |
3 | import com.dbdeploy.AppliedChangesProvider;
4 | import com.dbdeploy.exceptions.SchemaVersionTrackingException;
5 | import com.dbdeploy.scripts.ChangeScript;
6 |
7 | import java.sql.ResultSet;
8 | import java.sql.SQLException;
9 | import java.sql.Timestamp;
10 | import java.util.ArrayList;
11 | import java.util.Date;
12 | import java.util.List;
13 |
14 | /**
15 | * This class is responsible for all interaction with the changelog table
16 | */
17 | public class DatabaseSchemaVersionManager implements AppliedChangesProvider {
18 |
19 | private final QueryExecuter queryExecuter;
20 | private final String changeLogTableName;
21 | private CurrentTimeProvider timeProvider = new CurrentTimeProvider();
22 |
23 | public DatabaseSchemaVersionManager(QueryExecuter queryExecuter, String changeLogTableName) {
24 | this.queryExecuter = queryExecuter;
25 | this.changeLogTableName = changeLogTableName;
26 | }
27 |
28 | public List getAppliedChanges() {
29 | try {
30 | ResultSet rs = queryExecuter.executeQuery(
31 | "SELECT change_number FROM " + changeLogTableName + " ORDER BY change_number");
32 |
33 | List changeNumbers = new ArrayList();
34 |
35 | while (rs.next()) {
36 | changeNumbers.add(rs.getLong(1));
37 | }
38 |
39 | rs.close();
40 |
41 | return changeNumbers;
42 | } catch (SQLException e) {
43 | throw new SchemaVersionTrackingException("Could not retrieve change log from database because: "
44 | + e.getMessage(), e);
45 | }
46 | }
47 |
48 | public String getChangelogDeleteSql(ChangeScript script) {
49 | return String.format(
50 | "DELETE FROM " + changeLogTableName + " WHERE change_number = %d",
51 | script.getId());
52 | }
53 |
54 | public void recordScriptApplied(ChangeScript script) {
55 | try {
56 | queryExecuter.execute(
57 | "INSERT INTO " + changeLogTableName + " (change_number, complete_dt, applied_by, description)" +
58 | " VALUES (?, ?, ?, ?)",
59 | script.getId(),
60 | new Timestamp(timeProvider.now().getTime()),
61 | queryExecuter.getDatabaseUsername(),
62 | script.getDescription()
63 | );
64 | } catch (SQLException e) {
65 | throw new SchemaVersionTrackingException("Could not update change log because: "
66 | + e.getMessage(), e);
67 | }
68 | }
69 |
70 | public void setTimeProvider(CurrentTimeProvider timeProvider) {
71 | this.timeProvider = timeProvider;
72 | }
73 |
74 | public static class CurrentTimeProvider {
75 |
76 | public Date now() {
77 | return new Date();
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/database/changelog/QueryExecuter.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.database.changelog;
2 |
3 | import java.sql.*;
4 |
5 | public class QueryExecuter {
6 | private final Connection connection;
7 | private final String username;
8 |
9 | public QueryExecuter(String connectionString, String username, String password) throws SQLException {
10 | this.username = username;
11 | connection = DriverManager.getConnection(connectionString, username, password);
12 | }
13 |
14 | public ResultSet executeQuery(String sql) throws SQLException {
15 | Statement statement = connection.createStatement();
16 | return statement.executeQuery(sql);
17 | }
18 |
19 | public void execute(String sql) throws SQLException {
20 | Statement statement = connection.createStatement();
21 | try {
22 | statement.execute(sql);
23 | } finally {
24 | statement.close();
25 | }
26 | }
27 |
28 | public void execute(String sql, Object... params) throws SQLException {
29 | PreparedStatement statement = connection.prepareStatement(sql);
30 | try {
31 | for (int i = 0; i < params.length; i++) {
32 | Object param = params[i];
33 | statement.setObject(i+1, param);
34 | }
35 | statement.execute();
36 | } finally {
37 | statement.close();
38 | }
39 | }
40 |
41 | public void close() throws SQLException {
42 | connection.close();
43 | }
44 |
45 | public void setAutoCommit(boolean autoCommitMode) throws SQLException {
46 | connection.setAutoCommit(autoCommitMode);
47 | }
48 |
49 | public void commit() throws SQLException {
50 | connection.commit();
51 | }
52 |
53 | public String getDatabaseUsername() {
54 | return username;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/exceptions/ChangeScriptFailedException.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.exceptions;
2 |
3 | import com.dbdeploy.scripts.ChangeScript;
4 |
5 | import java.sql.SQLException;
6 |
7 | public class ChangeScriptFailedException extends DbDeployException {
8 | private final ChangeScript script;
9 | private final int statement;
10 | private final String executedSql;
11 |
12 | public ChangeScriptFailedException(SQLException cause, ChangeScript script,
13 | int statement, String executedSql) {
14 | super(cause);
15 | this.script = script;
16 | this.statement = statement;
17 | this.executedSql = executedSql;
18 | }
19 |
20 | public ChangeScript getScript() {
21 | return script;
22 | }
23 |
24 | public String getExecutedSql() {
25 | return executedSql;
26 | }
27 |
28 | public int getStatement() {
29 | return statement;
30 | }
31 |
32 | @Override
33 | public String getMessage() {
34 | return "change script " + script +
35 | " failed while executing statement " + statement + ":\n"
36 | + executedSql + "\n -> " + getCause().getMessage();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/exceptions/DbDeployException.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.exceptions;
2 |
3 | public class DbDeployException extends RuntimeException {
4 |
5 | private static final long serialVersionUID = 1L;
6 |
7 | public DbDeployException() {
8 | super();
9 |
10 | }
11 |
12 | public DbDeployException(String message) {
13 | super(message);
14 |
15 | }
16 |
17 | public DbDeployException(String message, Throwable cause) {
18 | super(message, cause);
19 |
20 | }
21 |
22 | public DbDeployException(Throwable cause) {
23 | super(cause);
24 |
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/exceptions/DuplicateChangeScriptException.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.exceptions;
2 |
3 | public class DuplicateChangeScriptException extends DbDeployException {
4 |
5 | private static final long serialVersionUID = 1L;
6 |
7 | public DuplicateChangeScriptException() {
8 | super();
9 | }
10 |
11 | public DuplicateChangeScriptException(String message, Throwable cause) {
12 | super(message, cause);
13 | }
14 |
15 | public DuplicateChangeScriptException(String message) {
16 | super(message);
17 | }
18 |
19 | public DuplicateChangeScriptException(Throwable cause) {
20 | super(cause);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/exceptions/SchemaVersionTrackingException.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.exceptions;
2 |
3 | public class SchemaVersionTrackingException extends DbDeployException {
4 |
5 | private static final long serialVersionUID = 1L;
6 |
7 | public SchemaVersionTrackingException() {
8 | super();
9 |
10 | }
11 |
12 | public SchemaVersionTrackingException(String message, Throwable cause) {
13 | super(message, cause);
14 |
15 | }
16 |
17 | public SchemaVersionTrackingException(String message) {
18 | super(message);
19 |
20 | }
21 |
22 | public SchemaVersionTrackingException(Throwable cause) {
23 | super(cause);
24 |
25 | }
26 |
27 |
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/exceptions/UnrecognisedFilenameException.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.exceptions;
2 |
3 | public class UnrecognisedFilenameException extends DbDeployException {
4 |
5 | private static final long serialVersionUID = 1L;
6 |
7 | public UnrecognisedFilenameException() {
8 | super();
9 |
10 | }
11 |
12 | public UnrecognisedFilenameException(String message, Throwable cause) {
13 | super(message, cause);
14 |
15 | }
16 |
17 | public UnrecognisedFilenameException(String message) {
18 | super(message);
19 |
20 | }
21 |
22 | public UnrecognisedFilenameException(Throwable cause) {
23 | super(cause);
24 |
25 | }
26 |
27 |
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/exceptions/UsageException.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.exceptions;
2 |
3 | public class UsageException extends DbDeployException {
4 |
5 | public UsageException(String message) {
6 | super(message);
7 | }
8 |
9 | public UsageException(String message, Throwable throwable) {
10 | super(message, throwable);
11 | }
12 |
13 | public static void throwForMissingRequiredValue(String valueName) throws UsageException {
14 | throw new UsageException(valueName + " required");
15 | }
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/scripts/ChangeScript.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.scripts;
2 |
3 | import com.dbdeploy.exceptions.DbDeployException;
4 |
5 | import java.io.*;
6 |
7 | public class ChangeScript implements Comparable {
8 |
9 | private final long id;
10 | private final File file;
11 | private final String description;
12 | private final String encoding;
13 | private static final String UNDO_MARKER = "--//@UNDO";
14 |
15 | public ChangeScript(long id) {
16 | this(id, "test");
17 | }
18 |
19 | public ChangeScript(long id, String description) {
20 | this.id = id;
21 | this.file = null;
22 | this.description = description;
23 | this.encoding = "UTF-8";
24 | }
25 |
26 | public ChangeScript(long id, File file, String encoding) {
27 | this.id = id;
28 | this.file = file;
29 | this.description = file.getName();
30 | this.encoding = encoding;
31 | }
32 |
33 | public File getFile() {
34 | return file;
35 | }
36 |
37 | public long getId() {
38 | return id;
39 | }
40 |
41 | public String getDescription() {
42 | return description;
43 | }
44 |
45 | public int compareTo(Object o) {
46 | ChangeScript other = (ChangeScript) o;
47 | return Long.valueOf(this.id).compareTo(other.id);
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return "#" + id + ": " + description;
53 | }
54 |
55 | public String getContent() {
56 | return getFileContents(false);
57 | }
58 |
59 | public String getUndoContent() {
60 | return getFileContents(true);
61 | }
62 |
63 | private String getFileContents(boolean onlyAfterUndoMarker) {
64 | try {
65 | StringBuilder content = new StringBuilder();
66 | boolean foundUndoMarker = false;
67 | BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), encoding));
68 |
69 | try {
70 | for (;;) {
71 | String str = reader.readLine();
72 |
73 | if (str == null)
74 | break;
75 |
76 | if (str.trim().equals(UNDO_MARKER)) {
77 | foundUndoMarker = true;
78 | continue;
79 | }
80 |
81 | if (foundUndoMarker == onlyAfterUndoMarker) {
82 | content.append(str);
83 | content.append('\n');
84 | }
85 | }
86 | } finally {
87 | reader.close();
88 | }
89 |
90 | return content.toString();
91 | } catch (IOException e) {
92 | throw new DbDeployException("Failed to read change script file", e);
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/scripts/ChangeScriptCreator.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.scripts;
2 |
3 | import com.dbdeploy.exceptions.UsageException;
4 | import java.io.File;
5 | import java.io.IOException;
6 | import java.text.DateFormat;
7 | import java.text.SimpleDateFormat;
8 | import java.util.Date;
9 |
10 | /**
11 | * Responsible for creating a new change script file
12 | * to be used by dbdeploy. This class will generate
13 | * a new change script using a timestamp as the change
14 | * script number and any supplied text as the rest
15 | * of the filename.
16 | *
17 | * @author jbogan
18 | */
19 | public class ChangeScriptCreator {
20 | private String changeScriptSuffix = ".sql";
21 | private String changeScriptTimestampFormat = "yyyyMMddHHmmss";
22 | private String scriptDescription;
23 | private File scriptDirectory;
24 | private DateFormat dateFormatter;
25 |
26 | public ChangeScriptCreator() {
27 | dateFormatter = new SimpleDateFormat(changeScriptTimestampFormat);
28 | }
29 |
30 | public File go() throws IOException {
31 | validate();
32 |
33 | return createScript();
34 | }
35 |
36 | private void validate() {
37 | if (scriptDirectory == null || !scriptDirectory.isDirectory()) {
38 | throw new UsageException("Script directory must point to a valid directory");
39 | }
40 | }
41 |
42 | public File createScript() throws IOException {
43 | final String newScriptFileName = getChangeScriptFileName();
44 | final String fullScriptPath = scriptDirectory + File.separator + newScriptFileName;
45 |
46 | final File newChangeScriptFile = new File(fullScriptPath);
47 | if (newChangeScriptFile.createNewFile()) {
48 | return newChangeScriptFile;
49 | } else {
50 | throw new IOException("Unable to create new change script " + fullScriptPath);
51 | }
52 | }
53 |
54 | private String getChangeScriptFileName() {
55 | final StringBuilder fileNameBuilder = new StringBuilder();
56 | fileNameBuilder.append(getFileTimestamp());
57 | if (scriptDescription != null && !scriptDescription.equals("")) {
58 | fileNameBuilder.append("_");
59 | fileNameBuilder.append(scriptDescription);
60 | }
61 | fileNameBuilder.append(changeScriptSuffix);
62 |
63 | return fileNameBuilder.toString();
64 | }
65 |
66 | private String getFileTimestamp() {
67 | return dateFormatter.format(new Date());
68 | }
69 |
70 | public void setScriptDescription(final String scriptDescription) {
71 | this.scriptDescription = scriptDescription;
72 | }
73 |
74 | public void setScriptDirectory(final File scriptDirectory) {
75 | this.scriptDirectory = scriptDirectory;
76 | }
77 |
78 | public static void main(String[] args) {
79 | ChangeScriptCreator creator = new ChangeScriptCreator();
80 |
81 | try {
82 | parseArguments(args, creator);
83 | creator.go();
84 | } catch (UsageException ex) {
85 | System.err.println("ERROR: " + ex.getMessage());
86 | System.err.println("Usage: java " + creator.getClass().getName() + " scriptDirectory [scriptName]");
87 | } catch (Exception ex) {
88 | System.err.println("Failed to create script: " + ex);
89 | ex.printStackTrace();
90 | System.exit(2);
91 | }
92 |
93 | System.exit(0);
94 | }
95 |
96 | private static void parseArguments(String[] args, ChangeScriptCreator creator) {
97 | if (args.length >= 1) {
98 | final String scriptDirectoryPath = args[0];
99 | creator.setScriptDirectory(new File(scriptDirectoryPath));
100 | }
101 |
102 | if (args.length >= 2) {
103 | final String scriptDescription = args[1];
104 | creator.setScriptDescription(scriptDescription);
105 | }
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/scripts/ChangeScriptRepository.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.scripts;
2 |
3 | import com.dbdeploy.exceptions.DuplicateChangeScriptException;
4 | import com.dbdeploy.AvailableChangeScriptsProvider;
5 |
6 | import java.util.Collections;
7 | import java.util.List;
8 |
9 |
10 | public class ChangeScriptRepository implements AvailableChangeScriptsProvider {
11 |
12 | private final List scripts;
13 |
14 | @SuppressWarnings("unchecked")
15 | public ChangeScriptRepository(List scripts) throws DuplicateChangeScriptException {
16 | this.scripts = scripts;
17 |
18 | Collections.sort(this.scripts);
19 |
20 | checkForDuplicateIds(scripts);
21 | }
22 |
23 | private void checkForDuplicateIds(List scripts) throws DuplicateChangeScriptException {
24 | long lastId = -1;
25 |
26 | for (ChangeScript script : scripts) {
27 | if (script.getId() == lastId) {
28 | throw new DuplicateChangeScriptException("There is more than one change script with number " + lastId);
29 | }
30 |
31 | lastId = script.getId();
32 | }
33 |
34 | }
35 |
36 | public List getOrderedListOfDoChangeScripts() {
37 | return Collections.unmodifiableList(scripts);
38 | }
39 |
40 | public List getAvailableChangeScripts() {
41 | return getOrderedListOfDoChangeScripts();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/scripts/DirectoryScanner.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.scripts;
2 |
3 | import com.dbdeploy.exceptions.UnrecognisedFilenameException;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.util.ArrayList;
8 | import java.util.List;
9 |
10 | public class DirectoryScanner {
11 |
12 | private final FilenameParser filenameParser = new FilenameParser();
13 | private final String encoding;
14 |
15 | public DirectoryScanner(String encoding) {
16 | this.encoding = encoding;
17 | }
18 |
19 | public List getChangeScriptsForDirectory(File directory) {
20 | try {
21 | System.err.println("Reading change scripts from directory " + directory.getCanonicalPath() + "...");
22 | } catch (IOException e1) {
23 | // ignore
24 | }
25 |
26 | List scripts = new ArrayList();
27 |
28 | for (File file : directory.listFiles()) {
29 | if (file.isFile()) {
30 | String filename = file.getName();
31 | try {
32 | long id = filenameParser.extractIdFromFilename(filename);
33 | scripts.add(new ChangeScript(id, file, encoding));
34 | } catch (UnrecognisedFilenameException e) {
35 | // ignore
36 | }
37 | }
38 | }
39 |
40 | return scripts;
41 |
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/java/com/dbdeploy/scripts/FilenameParser.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.scripts;
2 |
3 | import com.dbdeploy.exceptions.UnrecognisedFilenameException;
4 |
5 | import java.util.regex.Matcher;
6 | import java.util.regex.Pattern;
7 |
8 | public class FilenameParser {
9 | private final Pattern pattern;
10 |
11 | public FilenameParser() {
12 | pattern = Pattern.compile("(\\d+).*");
13 | }
14 |
15 | public long extractIdFromFilename(String filename) throws UnrecognisedFilenameException {
16 | Matcher matches = pattern.matcher(filename);
17 | if (!matches.matches() || matches.groupCount() != 1)
18 | throw new UnrecognisedFilenameException("Could not extract a change script number from filename: " + filename);
19 |
20 | return Long.parseLong(matches.group(1));
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/db2_apply.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="delimiter" type="java.lang.String" --]
4 | [#-- @ftlvariable name="separator" type="java.lang.String" --]
5 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
6 | [#list scripts as script]
7 |
8 | -- START CHANGE SCRIPT ${script}
9 |
10 | ${script.content}
11 |
12 | INSERT INTO ${changeLogTableName} (change_number, complete_dt, applied_by, description)
13 | VALUES (${script.id?c}, CURRENT TIMESTAMP, USER, '${script.description}')${separator}${delimiter}
14 |
15 | COMMIT${separator}${delimiter}
16 |
17 | -- END CHANGE SCRIPT ${script}
18 |
19 | [/#list]
20 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/db2_undo.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="delimiter" type="java.lang.String" --]
4 | [#-- @ftlvariable name="separator" type="java.lang.String" --]
5 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
6 | [#list scripts as script]
7 |
8 | -- START UNDO OF CHANGE SCRIPT ${script}
9 |
10 | ${script.undoContent}
11 |
12 | DELETE FROM ${changeLogTableName} WHERE change_number = ${script.id?c}${separator}${delimiter}
13 |
14 | COMMIT${separator}${delimiter}
15 |
16 | -- END UNDO OF CHANGE SCRIPT ${script}
17 |
18 | [/#list]
19 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/hsql_apply.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="delimiter" type="java.lang.String" --]
4 | [#-- @ftlvariable name="separator" type="java.lang.String" --]
5 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
6 | [#list scripts as script]
7 |
8 | -- START CHANGE SCRIPT ${script}
9 |
10 | ${script.content}
11 |
12 | INSERT INTO ${changeLogTableName} (change_number, complete_dt, applied_by, description)
13 | VALUES (${script.id?c}, CURRENT_TIMESTAMP, USER(), '${script.description}')${separator}${delimiter}
14 |
15 | COMMIT${separator}${delimiter}
16 |
17 | -- END CHANGE SCRIPT ${script}
18 |
19 | [/#list]
20 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/hsql_undo.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="delimiter" type="java.lang.String" --]
4 | [#-- @ftlvariable name="separator" type="java.lang.String" --]
5 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
6 | [#list scripts as script]
7 |
8 | -- START UNDO OF CHANGE SCRIPT ${script}
9 |
10 | ${script.undoContent}
11 |
12 | DELETE FROM ${changeLogTableName} WHERE change_number = ${script.id?c}${separator}${delimiter}
13 |
14 | COMMIT${separator}${delimiter}
15 |
16 | -- END UNDO OF CHANGE SCRIPT ${script}
17 |
18 | [/#list]
19 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/mssql_apply.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
4 | [#list scripts as script]
5 |
6 | -- START CHANGE SCRIPT ${script}
7 |
8 | ${script.content}
9 |
10 | INSERT INTO ${changeLogTableName} (change_number, complete_dt, applied_by, description)
11 | VALUES (${script.id?c}, getdate(), user_name(), '${script.description}')
12 | GO
13 |
14 | COMMIT
15 | GO
16 |
17 | -- END CHANGE SCRIPT ${script}
18 |
19 | [/#list]
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/mssql_undo.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
4 | [#list scripts as script]
5 |
6 | -- START UNDO OF CHANGE SCRIPT ${script}
7 |
8 | ${script.undoContent}
9 |
10 | DELETE FROM ${changeLogTableName} WHERE change_number = ${script.id?c}
11 | GO
12 |
13 | COMMIT
14 | GO
15 |
16 | -- END UNDO OF CHANGE SCRIPT ${script}
17 |
18 | [/#list]
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/mysql_apply.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="delimiter" type="java.lang.String" --]
4 | [#-- @ftlvariable name="separator" type="java.lang.String" --]
5 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
6 | [#list scripts as script]
7 |
8 | -- START CHANGE SCRIPT ${script}
9 |
10 | ${script.content}
11 |
12 | INSERT INTO ${changeLogTableName} (change_number, complete_dt, applied_by, description)
13 | VALUES (${script.id?c}, CURRENT_TIMESTAMP, USER(), '${script.description}')${separator}${delimiter}
14 |
15 | COMMIT${separator}${delimiter}
16 |
17 | -- END CHANGE SCRIPT ${script}
18 |
19 | [/#list]
20 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/mysql_undo.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="delimiter" type="java.lang.String" --]
4 | [#-- @ftlvariable name="separator" type="java.lang.String" --]
5 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
6 | [#list scripts as script]
7 |
8 | -- START UNDO OF CHANGE SCRIPT ${script}
9 |
10 | START TRANSACTION${separator}${delimiter}
11 |
12 | ${script.undoContent}
13 |
14 | DELETE FROM ${changeLogTableName} WHERE change_number = ${script.id?c}${separator}${delimiter}
15 |
16 | COMMIT${separator}${delimiter}
17 |
18 | -- END UNDO OF CHANGE SCRIPT ${script}
19 |
20 | [/#list]
21 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/ora_apply.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="delimiter" type="java.lang.String" --]
4 | [#-- @ftlvariable name="separator" type="java.lang.String" --]
5 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
6 | [#list scripts as script]
7 |
8 | -- START CHANGE SCRIPT ${script}
9 |
10 | ${script.content}
11 |
12 | INSERT INTO ${changeLogTableName} (change_number, complete_dt, applied_by, description)
13 | VALUES (${script.id?c}, CURRENT_TIMESTAMP, USER, '${script.description}')${separator}${delimiter}
14 |
15 | COMMIT${separator}${delimiter}
16 |
17 | -- END CHANGE SCRIPT ${script}
18 |
19 | [/#list]
20 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/ora_undo.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="delimiter" type="java.lang.String" --]
4 | [#-- @ftlvariable name="separator" type="java.lang.String" --]
5 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
6 | [#list scripts as script]
7 |
8 | -- START UNDO OF CHANGE SCRIPT ${script}
9 |
10 | ${script.undoContent}
11 |
12 | DELETE FROM ${changeLogTableName} WHERE change_number = ${script.id?c}${separator}${delimiter}
13 |
14 | COMMIT${separator}${delimiter}
15 |
16 | -- END UNDO OF CHANGE SCRIPT ${script}
17 |
18 | [/#list]
19 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/pgsql_apply.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="delimiter" type="java.lang.String" --]
4 | [#-- @ftlvariable name="separator" type="java.lang.String" --]
5 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
6 | [#list scripts as script]
7 |
8 | -- START CHANGE SCRIPT ${script}
9 |
10 | ${script.content}
11 |
12 | INSERT INTO ${changeLogTableName} (change_number, complete_dt, applied_by, description)
13 | VALUES (${script.id?c}, current_timestamp, current_user, '${script.description}')${separator}${delimiter}
14 |
15 | COMMIT${separator}${delimiter}
16 |
17 | -- END CHANGE SCRIPT ${script}
18 |
19 | [/#list]
20 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/pgsql_undo.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="delimiter" type="java.lang.String" --]
4 | [#-- @ftlvariable name="separator" type="java.lang.String" --]
5 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
6 | [#list scripts as script]
7 |
8 | -- START UNDO OF CHANGE SCRIPT ${script}
9 |
10 | ${script.undoContent}
11 |
12 | DELETE FROM ${changeLogTableName} WHERE change_number = ${script.id?c}${separator}${delimiter}
13 |
14 | COMMIT${separator}${delimiter}
15 |
16 | -- END UNDO OF CHANGE SCRIPT ${script}
17 |
18 | [/#list]
19 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/syb-ase_apply.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
4 | [#list scripts as script]
5 |
6 | -- START CHANGE SCRIPT ${script}
7 |
8 | ${script.content}
9 |
10 | INSERT INTO ${changeLogTableName} (change_number, complete_dt, applied_by, description)
11 | VALUES (${script.id?c}, getdate(), user_name(), '${script.description}')
12 | GO
13 |
14 | COMMIT
15 | GO
16 |
17 | -- END CHANGE SCRIPT ${script}
18 |
19 | [/#list]
--------------------------------------------------------------------------------
/dbdeploy-core/src/main/resources/syb-ase_undo.ftl:
--------------------------------------------------------------------------------
1 | [#ftl]
2 | [#-- @ftlvariable name="changeLogTableName" type="java.lang.String" --]
3 | [#-- @ftlvariable name="scripts" type="java.util.List" --]
4 | [#list scripts as script]
5 |
6 | -- START UNDO OF CHANGE SCRIPT ${script}
7 |
8 | ${script.undoContent}
9 |
10 | DELETE FROM ${changeLogTableName} WHERE change_number = ${script.id?c}
11 | GO
12 |
13 | COMMIT
14 | GO
15 |
16 | -- END UNDO OF CHANGE SCRIPT ${script}
17 |
18 | [/#list]
--------------------------------------------------------------------------------
/dbdeploy-core/src/test/java/com/dbdeploy/ControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.scripts.ChangeScript;
4 | import static org.hamcrest.Matchers.is;
5 | import static org.junit.Assert.assertThat;
6 | import org.junit.Before;
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 | import org.mockito.Mock;
10 | import static org.mockito.Mockito.when;
11 | import org.mockito.runners.MockitoJUnit44Runner;
12 |
13 | import java.util.ArrayList;
14 | import java.util.Arrays;
15 | import java.util.Collections;
16 | import java.util.List;
17 |
18 | @RunWith(MockitoJUnit44Runner.class)
19 | public class ControllerTest {
20 |
21 | @Mock private AvailableChangeScriptsProvider availableChangeScriptsProvider;
22 | @Mock private AppliedChangesProvider appliedChangesProvider;
23 | private Controller controller;
24 | private ChangeScript change1;
25 | private ChangeScript change2;
26 | private ChangeScript change3;
27 |
28 | private StubChangeScriptApplier applier = new StubChangeScriptApplier();
29 | private StubChangeScriptApplier undoApplier = new StubChangeScriptApplier();
30 |
31 | @Before
32 | public void setUp() {
33 | controller = new Controller(availableChangeScriptsProvider, appliedChangesProvider, applier, undoApplier);
34 |
35 | change1 = new ChangeScript(1);
36 | change2 = new ChangeScript(2);
37 | change3 = new ChangeScript(3);
38 |
39 | when(availableChangeScriptsProvider.getAvailableChangeScripts())
40 | .thenReturn(Arrays.asList(change1, change2, change3));
41 | }
42 |
43 | @Test
44 | public void shouldApplyChangeScriptsInOrder() throws Exception {
45 | when(appliedChangesProvider.getAppliedChanges()).thenReturn(Collections.emptyList());
46 |
47 | controller.processChangeScripts(Long.MAX_VALUE);
48 |
49 | assertThat(applier.changeScripts.size(), is(3));
50 | assertThat(applier.changeScripts.get(0), is(change1));
51 | assertThat(applier.changeScripts.get(1), is(change2));
52 | assertThat(applier.changeScripts.get(2), is(change3));
53 | }
54 |
55 | @Test
56 | public void shouldNotCrashWhenPassedANullUndoApplier() throws Exception {
57 | controller = new Controller(availableChangeScriptsProvider, appliedChangesProvider, applier, null);
58 |
59 | when(appliedChangesProvider.getAppliedChanges()).thenReturn(Collections.emptyList());
60 |
61 | controller.processChangeScripts(Long.MAX_VALUE);
62 | }
63 |
64 | @Test
65 | public void shouldApplyUndoScriptsInReverseOrder() throws Exception {
66 | when(appliedChangesProvider.getAppliedChanges()).thenReturn(Collections.emptyList());
67 |
68 | controller.processChangeScripts(Long.MAX_VALUE);
69 |
70 | assertThat(undoApplier.changeScripts.size(), is(3));
71 | assertThat(undoApplier.changeScripts.get(0), is(change3));
72 | assertThat(undoApplier.changeScripts.get(1), is(change2));
73 | assertThat(undoApplier.changeScripts.get(2), is(change1));
74 | }
75 |
76 |
77 | @Test
78 | public void shouldIgnoreChangesAlreadyAppliedToTheDatabase() throws Exception {
79 | when(appliedChangesProvider.getAppliedChanges()).thenReturn(Arrays.asList(1L));
80 |
81 | controller.processChangeScripts(Long.MAX_VALUE);
82 |
83 | assertThat(applier.changeScripts.size(), is(2));
84 | assertThat(applier.changeScripts.get(0), is(change2));
85 | assertThat(applier.changeScripts.get(1), is(change3));
86 | }
87 |
88 | @Test
89 | public void shouldNotApplyChangesGreaterThanTheMaxChangeToApply() throws Exception {
90 | when(appliedChangesProvider.getAppliedChanges()).thenReturn(Collections.emptyList());
91 |
92 | controller.processChangeScripts(2L);
93 |
94 | assertThat(applier.changeScripts.size(), is(2));
95 | assertThat(applier.changeScripts.get(0), is(change1));
96 | assertThat(applier.changeScripts.get(1), is(change2));
97 | }
98 |
99 |
100 |
101 | private class StubChangeScriptApplier implements ChangeScriptApplier {
102 | private List changeScripts;
103 |
104 | public void apply(List changeScripts) {
105 | this.changeScripts = new ArrayList(changeScripts);
106 | }
107 |
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/test/java/com/dbdeploy/DbDeployTest.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.exceptions.UsageException;
4 | import static org.hamcrest.Matchers.startsWith;
5 | import static org.junit.Assert.*;
6 | import org.junit.Before;
7 | import org.junit.Test;
8 |
9 | import java.io.File;
10 |
11 | public class DbDeployTest {
12 | private final DbDeploy dbDeploy = new DbDeploy();
13 |
14 | @Before
15 | public void setSensibleDefaultValuesForAllParameters() {
16 | dbDeploy.setDriver(getClass().getName());
17 |
18 | dbDeploy.setUserid("someUser");
19 |
20 | dbDeploy.setDbms("hsql");
21 | dbDeploy.setUrl("jdbc:hsqldb:mem:dbdeploy");
22 |
23 | dbDeploy.setScriptdirectory(new File("."));
24 | dbDeploy.setOutputfile(new File("a.txt"));
25 | }
26 |
27 | @Test(expected = ClassNotFoundException.class)
28 | public void shouldThrowIfInvalidDriverClassNameSpecified() throws Exception {
29 | dbDeploy.setDriver("some.class.that.will.not.be.Found");
30 | dbDeploy.go();
31 | }
32 |
33 | @Test(expected = UsageException.class)
34 | public void shouldThrowIfUserIdNotSpecified() throws Exception {
35 | dbDeploy.setUserid(null);
36 | dbDeploy.go();
37 | }
38 |
39 | @Test(expected = UsageException.class)
40 | public void shouldThrowIfDriverNotSpecified() throws Exception {
41 | dbDeploy.setDriver(null);
42 | dbDeploy.go();
43 | }
44 |
45 | @Test(expected = UsageException.class)
46 | public void shouldThrowIfUrlNotSpecified() throws Exception {
47 | dbDeploy.setUrl(null);
48 | dbDeploy.go();
49 | }
50 |
51 | @Test
52 | public void shouldThrowIfScriptDirectoryIsNotAValidDirectory() throws Exception {
53 | dbDeploy.setScriptdirectory(new File("fileThatDoesntExist.txt"));
54 | try {
55 | dbDeploy.go();
56 | fail("exception expected");
57 | } catch (UsageException e) {
58 | assertEquals("Script directory must point to a valid directory", e.getMessage());
59 | }
60 | }
61 |
62 | @Test
63 | public void shouldReportVersionNumberWithoutCrashing() {
64 | assertThat(dbDeploy.getWelcomeString(), startsWith("dbdeploy"));
65 | }
66 |
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/test/java/com/dbdeploy/PrettyPrinterTest.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy;
2 |
3 | import com.dbdeploy.scripts.ChangeScript;
4 | import static org.junit.Assert.*;
5 | import org.junit.Test;
6 |
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 |
10 | import com.dbdeploy.PrettyPrinter;
11 |
12 | public class PrettyPrinterTest {
13 | PrettyPrinter prettyPrinter = new PrettyPrinter();
14 |
15 | @Test
16 | public void shouldDisplayNonRangedNumbersAsSeperateEntities() throws Exception {
17 | assertEquals("1, 3, 5", prettyPrinter.format(Arrays.asList(1L, 3L, 5L)));
18 | }
19 |
20 | @Test
21 | public void shouldDisplayARangeAsSuch() throws Exception {
22 | assertEquals("1..5", prettyPrinter.format(Arrays.asList(1L, 2L, 3L, 4L, 5L)));
23 | }
24 |
25 | @Test
26 | public void rangesOfTwoAreNotDisplayedAsARange() throws Exception {
27 | assertEquals("1, 2", prettyPrinter.format(Arrays.asList(1L, 2L)));
28 | }
29 |
30 | @Test
31 | public void shouldReturnNoneWithAnEmptyList() throws Exception {
32 | assertEquals("(none)", prettyPrinter.format(new ArrayList()));
33 | }
34 |
35 | @Test
36 | public void canDealWithMixtureOfRangesAndNonRanges() throws Exception {
37 | assertEquals("1, 2, 4, 7..10, 12", prettyPrinter.format(Arrays.asList(1L, 2L, 4L, 7L, 8L, 9L, 10L, 12L)));
38 | }
39 |
40 | @Test
41 | public void canFormatAChangeScriptList() throws Exception {
42 | ChangeScript change1 = new ChangeScript(1);
43 | ChangeScript change3 = new ChangeScript(3);
44 | assertEquals("1, 3", prettyPrinter.formatChangeScriptList(Arrays.asList(change1, change3)));
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/test/java/com/dbdeploy/appliers/DirectToDbApplierTest.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.appliers;
2 |
3 | import com.dbdeploy.database.QueryStatementSplitter;
4 | import com.dbdeploy.database.changelog.DatabaseSchemaVersionManager;
5 | import com.dbdeploy.database.changelog.QueryExecuter;
6 | import com.dbdeploy.exceptions.ChangeScriptFailedException;
7 | import com.dbdeploy.scripts.ChangeScript;
8 | import com.dbdeploy.scripts.StubChangeScript;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.mockito.Mock;
13 | import org.mockito.runners.MockitoJUnit44Runner;
14 |
15 | import java.sql.SQLException;
16 | import java.util.Arrays;
17 |
18 | import static org.hamcrest.Matchers.*;
19 | import static org.junit.Assert.assertThat;
20 | import static org.junit.Assert.fail;
21 | import static org.mockito.Mockito.*;
22 |
23 | @RunWith(MockitoJUnit44Runner.class)
24 | public class DirectToDbApplierTest {
25 | @Mock private QueryExecuter queryExecuter;
26 | @Mock private DatabaseSchemaVersionManager schemaVersionManager;
27 | @Mock private QueryStatementSplitter splitter;
28 | private DirectToDbApplier applier;
29 |
30 | @Before
31 | public void setUp() {
32 | applier = new DirectToDbApplier(queryExecuter, schemaVersionManager, splitter);
33 | }
34 |
35 | @Test
36 | public void shouldSetConnectionToManualCommitModeAtStart() throws Exception {
37 | applier.begin();
38 |
39 | verify(queryExecuter).setAutoCommit(false);
40 | }
41 |
42 | @Test
43 | public void shouldApplyChangeScriptBySplittingContentUsingTheSplitter() throws Exception {
44 | when(splitter.split("split; content")).thenReturn(Arrays.asList("split", "content"));
45 |
46 | applier.applyChangeScript(new StubChangeScript(1, "script", "split; content"));
47 |
48 | verify(queryExecuter).execute("split");
49 | verify(queryExecuter).execute("content");
50 | }
51 |
52 | @Test
53 | public void shouldRethrowSqlExceptionsWithInformationAboutWhatStringFailed() throws Exception {
54 | when(splitter.split("split; content")).thenReturn(Arrays.asList("split", "content"));
55 | ChangeScript script = new StubChangeScript(1, "script", "split; content");
56 |
57 | doThrow(new SQLException("dummy exception")).when(queryExecuter).execute("split");
58 |
59 | try {
60 | applier.applyChangeScript(script);
61 | fail("exception expected");
62 | } catch (ChangeScriptFailedException e) {
63 | assertThat(e.getExecutedSql(), is("split"));
64 | assertThat(e.getScript(), is(script));
65 | }
66 |
67 | verify(queryExecuter, never()).execute("content");
68 | }
69 |
70 | @Test
71 | public void shouldInsertToSchemaVersionTable() throws Exception {
72 | ChangeScript changeScript = new ChangeScript(1, "script.sql");
73 |
74 | applier.insertToSchemaVersionTable(changeScript);
75 |
76 | verify(schemaVersionManager).recordScriptApplied(changeScript);
77 |
78 | }
79 |
80 | @Test
81 | public void shouldCommitTransactionOnErrrCommitTransaction() throws Exception {
82 | applier.commitTransaction();
83 |
84 | verify(queryExecuter).commit();
85 | }
86 |
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/test/java/com/dbdeploy/appliers/TemplateBasedApplierTest.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.appliers;
2 |
3 | import com.dbdeploy.database.DelimiterType;
4 | import com.dbdeploy.exceptions.UsageException;
5 | import org.apache.commons.io.output.NullWriter;
6 | import org.junit.Assert;
7 | import org.junit.Test;
8 |
9 | import static org.hamcrest.Matchers.is;
10 | import static org.junit.Assert.assertThat;
11 |
12 | public class TemplateBasedApplierTest {
13 |
14 | @Test
15 | public void shouldThrowUsageExceptionWhenTemplateNotFound() throws Exception {
16 | TemplateBasedApplier applier = new TemplateBasedApplier(new NullWriter(), "some_complete_rubbish", null, ";", DelimiterType.normal, null);
17 | try {
18 | applier.apply(null);
19 | Assert.fail("expected exception");
20 | } catch (UsageException e) {
21 | assertThat(e.getMessage(), is("Could not find template named some_complete_rubbish_apply.ftl\n" +
22 | "Check that you have got the name of the database syntax correct."));
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/test/java/com/dbdeploy/database/QueryStatementSplitterTest.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.database;
2 |
3 | import org.apache.commons.lang.SystemUtils;
4 | import org.junit.Before;
5 | import org.junit.Test;
6 |
7 | import java.util.List;
8 |
9 | import static org.hamcrest.Matchers.*;
10 | import static org.junit.Assert.assertThat;
11 |
12 | public class QueryStatementSplitterTest {
13 | private QueryStatementSplitter splitter;
14 |
15 | @Before
16 | public void setUp() throws Exception {
17 | splitter = new QueryStatementSplitter();
18 | }
19 |
20 |
21 | @Test
22 | public void shouldNotSplitStatementsThatHaveNoDelimter() throws Exception {
23 | List result = splitter.split("SELECT 1");
24 | assertThat(result, hasItem("SELECT 1"));
25 | assertThat(result.size(), is(1));
26 | }
27 |
28 | @Test
29 | public void shouldIgnoreSemicolonsInTheMiddleOfALine() throws Exception {
30 | List result = splitter.split("SELECT ';'");
31 | assertThat(result, hasItem("SELECT ';'"));
32 | assertThat(result.size(), is(1));
33 | }
34 |
35 | @Test
36 | public void shouldSplitStatementsOnASemicolonAtTheEndOfALine() throws Exception {
37 | List result = splitter.split("SELECT 1;\nSELECT 2;");
38 | assertThat(result, hasItems("SELECT 1", "SELECT 2"));
39 | assertThat(result.size(), is(2));
40 | }
41 |
42 | @Test
43 | public void shouldSplitStatementsOnASemicolonAtTheEndOfALineEvenWithWindowsLineEndings() throws Exception {
44 | List result = splitter.split("SELECT 1;\r\nSELECT 2;");
45 | assertThat(result, hasItems("SELECT 1", "SELECT 2"));
46 | assertThat(result.size(), is(2));
47 | }
48 |
49 | @Test
50 | public void shouldSplitStatementsOnASemicolonAtTheEndOfALineIgnoringWhitespace() throws Exception {
51 | List result = splitter.split("SELECT 1; \nSELECT 2; ");
52 | assertThat(result, hasItems("SELECT 1", "SELECT 2"));
53 | assertThat(result.size(), is(2));
54 | }
55 |
56 | @Test
57 | public void shouldLeaveLineBreaksAlone() throws Exception {
58 | assertThat(splitter.split("SELECT\n1"), hasItems("SELECT" + SystemUtils.LINE_SEPARATOR + "1"));
59 | assertThat(splitter.split("SELECT\r\n1"), hasItems("SELECT" + SystemUtils.LINE_SEPARATOR + "1"));
60 | }
61 |
62 | @Test
63 | public void shouldSupportRowStyleTerminators() throws Exception {
64 | splitter.setDelimiter("/");
65 | splitter.setDelimiterType(DelimiterType.row);
66 |
67 | List result = splitter.split("SHOULD IGNORE /\nAT THE END OF A LINE\n/\nSELECT BLAH FROM DUAL");
68 | assertThat(result, hasItems("SHOULD IGNORE /" + SystemUtils.LINE_SEPARATOR + "AT THE END OF A LINE" + SystemUtils.LINE_SEPARATOR, "SELECT BLAH FROM DUAL"));
69 | assertThat(result.size(), is(2));
70 | }
71 |
72 | @Test
73 | public void shouldSupportDefinedNewLineCharacters() throws Exception {
74 | splitter.setOutputLineEnding(LineEnding.crlf);
75 | assertThat(splitter.split("SELECT\n1"), hasItems("SELECT\r\n1"));
76 | assertThat(splitter.split("SELECT\r\n1"), hasItems("SELECT\r\n1"));
77 |
78 | splitter.setOutputLineEnding(LineEnding.cr);
79 | assertThat(splitter.split("SELECT\n1"), hasItems("SELECT\r1"));
80 | assertThat(splitter.split("SELECT\r\n1"), hasItems("SELECT\r1"));
81 |
82 |
83 | splitter.setOutputLineEnding(LineEnding.lf);
84 | assertThat(splitter.split("SELECT\n1"), hasItems("SELECT\n1"));
85 | assertThat(splitter.split("SELECT\r\n1"), hasItems("SELECT\n1"));
86 |
87 |
88 | splitter.setOutputLineEnding(LineEnding.platform);
89 | assertThat(splitter.split("SELECT\n1"), hasItems("SELECT" + SystemUtils.LINE_SEPARATOR + "1"));
90 | assertThat(splitter.split("SELECT\r\n1"), hasItems("SELECT" + SystemUtils.LINE_SEPARATOR + "1"));
91 | }
92 |
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/test/java/com/dbdeploy/database/ScriptGenerationTest.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.database;
2 |
3 | import com.dbdeploy.ChangeScriptApplier;
4 | import com.dbdeploy.Controller;
5 | import com.dbdeploy.appliers.TemplateBasedApplier;
6 | import com.dbdeploy.database.changelog.DatabaseSchemaVersionManager;
7 | import com.dbdeploy.exceptions.SchemaVersionTrackingException;
8 | import com.dbdeploy.scripts.ChangeScript;
9 | import com.dbdeploy.scripts.ChangeScriptRepository;
10 | import com.dbdeploy.scripts.StubChangeScript;
11 | import org.junit.Test;
12 |
13 | import java.io.*;
14 | import java.util.Arrays;
15 | import java.util.Collections;
16 | import java.util.List;
17 |
18 | import static org.junit.Assert.assertEquals;
19 |
20 | public class ScriptGenerationTest {
21 |
22 | @Test
23 | public void generateConsolidatedChangesScriptForAllDatabasesAndCompareAgainstTemplate() throws Exception {
24 | for (String syntax : Arrays.asList("hsql", "mssql", "mysql", "ora", "syb-ase", "db2", "pgsql")) {
25 | try {
26 | System.out.printf("Testing syntax %s\n", syntax);
27 | runIntegratedTestAndConfirmOutputResults(syntax);
28 | } catch (Exception e) {
29 | throw new RuntimeException("Failed while testing syntax " + syntax, e);
30 | }
31 | }
32 | }
33 |
34 | private void runIntegratedTestAndConfirmOutputResults(String syntaxName) throws Exception {
35 |
36 | StringWriter writer = new StringWriter();
37 |
38 | ChangeScript changeOne = new StubChangeScript(1, "001_change.sql", "-- contents of change script 1");
39 | ChangeScript changeTwo = new StubChangeScript(2, "002_change.sql", "-- contents of change script 2");
40 |
41 | List changeScripts = Arrays.asList(changeOne, changeTwo);
42 | ChangeScriptRepository changeScriptRepository = new ChangeScriptRepository(changeScripts);
43 |
44 |
45 |
46 | final StubSchemaManager schemaManager = new StubSchemaManager();
47 | ChangeScriptApplier applier = new TemplateBasedApplier(writer, syntaxName, "changelog", ";", DelimiterType.normal, null);
48 | Controller controller = new Controller(changeScriptRepository, schemaManager, applier, null);
49 |
50 | controller.processChangeScripts(Long.MAX_VALUE);
51 |
52 | assertEquals(readExpectedFileContents(getExpectedFilename(syntaxName)), writer.toString());
53 | }
54 |
55 | private String getExpectedFilename(String dbSyntaxName) {
56 | return dbSyntaxName + "_expected.sql";
57 | }
58 |
59 | private String readExpectedFileContents(String expectedFilename) throws IOException {
60 | final InputStream stream = getClass().getResourceAsStream(expectedFilename);
61 | BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
62 | try {
63 | return readEntireStreamIntoAStringWithConversionToSystemDependantLineTerminators(reader);
64 | } finally {
65 | reader.close();
66 | }
67 | }
68 |
69 | private String readEntireStreamIntoAStringWithConversionToSystemDependantLineTerminators(BufferedReader reader) throws IOException {
70 | StringWriter contentWithSystemDependentLineTerminators = new StringWriter();
71 | PrintWriter newLineConvertingContentWriter = new PrintWriter(contentWithSystemDependentLineTerminators);
72 | try {
73 | String line;
74 | while ((line = reader.readLine()) != null) {
75 | newLineConvertingContentWriter.println(line);
76 | }
77 | newLineConvertingContentWriter.flush();
78 | return contentWithSystemDependentLineTerminators.toString();
79 | } finally {
80 | newLineConvertingContentWriter.close();
81 | }
82 | }
83 |
84 |
85 | private class StubSchemaManager extends DatabaseSchemaVersionManager {
86 | public StubSchemaManager() {
87 | super(null, "changelog");
88 | }
89 |
90 | @Override
91 | public List getAppliedChanges() throws SchemaVersionTrackingException {
92 | return Collections.emptyList();
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/test/java/com/dbdeploy/database/changelog/DatabaseSchemaVersionManagerTest.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.database.changelog;
2 |
3 | import com.dbdeploy.scripts.ChangeScript;
4 | import org.hamcrest.Matchers;
5 | import static org.hamcrest.Matchers.equalToIgnoringWhiteSpace;
6 | import static org.hamcrest.Matchers.hasItems;
7 | import static org.junit.Assert.assertThat;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import static org.mockito.Matchers.anyString;
11 | import static org.mockito.Matchers.startsWith;
12 | import org.mockito.Mock;
13 | import static org.mockito.Mockito.verify;
14 | import static org.mockito.Mockito.when;
15 | import org.mockito.MockitoAnnotations;
16 |
17 | import java.sql.ResultSet;
18 | import java.sql.SQLException;
19 | import java.sql.Timestamp;
20 | import java.util.Date;
21 | import java.util.List;
22 |
23 | public class DatabaseSchemaVersionManagerTest {
24 | private final ChangeScript script = new ChangeScript(99, "Some Description");
25 |
26 | private DatabaseSchemaVersionManager schemaVersionManager;
27 |
28 | @Mock private ResultSet expectedResultSet;
29 | @Mock private QueryExecuter queryExecuter;
30 | @Mock private DatabaseSchemaVersionManager.CurrentTimeProvider timeProvider;
31 |
32 | @Before
33 | public void setUp() throws SQLException {
34 | MockitoAnnotations.initMocks(this);
35 |
36 | when(queryExecuter.executeQuery(anyString())).thenReturn(expectedResultSet);
37 |
38 | schemaVersionManager = new DatabaseSchemaVersionManager(queryExecuter, "changelog");
39 | schemaVersionManager.setTimeProvider(timeProvider);
40 | }
41 |
42 | @Test
43 | public void shouldUseQueryExecuterToReadInformationFromTheChangelogTable() throws Exception {
44 | when(expectedResultSet.next()).thenReturn(true, true, true, false);
45 | when(expectedResultSet.getLong(1)).thenReturn(5L, 9L, 12L);
46 |
47 | final List numbers = schemaVersionManager.getAppliedChanges();
48 | assertThat(numbers, hasItems(5L, 9L, 12L));
49 | }
50 |
51 |
52 | @Test
53 | public void shouldUpdateChangelogTable() throws Exception {
54 | Date now = new Date();
55 |
56 | when(queryExecuter.getDatabaseUsername()).thenReturn("DBUSER");
57 | when(timeProvider.now()).thenReturn(now);
58 |
59 | schemaVersionManager.recordScriptApplied(script);
60 | String expected =
61 | "INSERT INTO changelog (change_number, complete_dt, applied_by, description) " +
62 | "VALUES (?, ?, ?, ?)";
63 |
64 | verify(queryExecuter).execute(expected, script.getId(),
65 | new Timestamp(now.getTime()), "DBUSER", script.getDescription());
66 | }
67 |
68 | @Test
69 | public void shouldGenerateSqlStringToDeleteChangelogTableAfterUndoScriptApplication() throws Exception {
70 | String sql = schemaVersionManager.getChangelogDeleteSql(script);
71 | String expected =
72 | "DELETE FROM changelog WHERE change_number = 99";
73 | assertThat(sql, equalToIgnoringWhiteSpace(expected));
74 | }
75 |
76 | @Test
77 | public void shouldGetAppliedChangesFromSpecifiedChangelogTableName() throws SQLException {
78 | DatabaseSchemaVersionManager schemaVersionManagerWithDifferentTableName =
79 | new DatabaseSchemaVersionManager(queryExecuter,
80 | "user_specified_changelog");
81 |
82 | schemaVersionManagerWithDifferentTableName.getAppliedChanges();
83 |
84 | verify(queryExecuter).executeQuery(startsWith("SELECT change_number FROM user_specified_changelog "));
85 | }
86 |
87 | @Test
88 | public void shouldGenerateSqlStringContainingSpecifiedChangelogTableNameOnDelete() {
89 | DatabaseSchemaVersionManager schemaVersionManagerWithDifferentTableName =
90 | new DatabaseSchemaVersionManager(queryExecuter,
91 | "user_specified_changelog");
92 |
93 | String updateSql = schemaVersionManagerWithDifferentTableName.getChangelogDeleteSql(script);
94 |
95 | assertThat(updateSql, Matchers.startsWith("DELETE FROM user_specified_changelog "));
96 | }
97 | }
98 |
99 |
--------------------------------------------------------------------------------
/dbdeploy-core/src/test/java/com/dbdeploy/integration/Database.java:
--------------------------------------------------------------------------------
1 | package com.dbdeploy.integration;
2 |
3 | import com.dbdeploy.DbDeploy;
4 | import com.dbdeploy.database.changelog.DatabaseSchemaVersionManager;
5 | import com.dbdeploy.database.changelog.QueryExecuter;
6 | import com.dbdeploy.exceptions.SchemaVersionTrackingException;
7 | import org.apache.commons.io.FileUtils;
8 |
9 | import java.io.File;
10 | import java.io.IOException;
11 | import java.sql.*;
12 | import java.util.ArrayList;
13 | import java.util.List;
14 |
15 | public class Database {
16 | String connectionString;
17 | Connection connection;
18 | private final String changeLogTableName;
19 |
20 | private static final String DATABASE_SYNTAX = "hsql";
21 | private static final String DATABASE_DRIVER = "org.hsqldb.jdbcDriver";
22 | private static final String DATABASE_USERNAME = "sa";
23 | private static final String DATABASE_PASSWORD = "";
24 |
25 | public Database(String databaseName) throws ClassNotFoundException, SQLException {
26 | this(databaseName, "changelog");
27 | }
28 |
29 | public Database(String databaseName, String changeLogTableName) throws ClassNotFoundException, SQLException {
30 | this.changeLogTableName = changeLogTableName;
31 | connectionString = "jdbc:hsqldb:mem:" + databaseName;
32 | connection = openConnection();
33 | }
34 |
35 | private Connection openConnection() throws ClassNotFoundException, SQLException {
36 | Class.forName(DATABASE_DRIVER);
37 | return DriverManager.getConnection(connectionString, DATABASE_USERNAME, DATABASE_PASSWORD);
38 | }
39 |
40 | public void createSchemaVersionTable() throws SQLException {
41 | execute("CREATE TABLE " + changeLogTableName +
42 | " ( " +
43 | " change_number INTEGER NOT NULL, " +
44 | " complete_dt TIMESTAMP NOT NULL, " +
45 | " applied_by VARCHAR(100) NOT NULL, " +
46 | " description VARCHAR(500) NOT NULL " +
47 | ")");
48 |
49 | execute("ALTER TABLE " + changeLogTableName +
50 | " ADD CONSTRAINT Pkchangelog PRIMARY KEY (change_number)");
51 | }
52 |
53 | private void execute(String sql) throws SQLException {
54 | final Statement statement = connection.createStatement();
55 | statement.execute(sql);
56 | statement.close();
57 | }
58 |
59 | public void applyDatabaseSettingsTo(DbDeploy dbDeploy) {
60 | dbDeploy.setDbms(DATABASE_SYNTAX);
61 | dbDeploy.setDriver(DATABASE_DRIVER);
62 | dbDeploy.setUrl(connectionString);
63 | dbDeploy.setUserid(DATABASE_USERNAME);
64 | dbDeploy.setPassword(DATABASE_PASSWORD);
65 | }
66 |
67 | public void applyScript(File sqlFile) throws SQLException, IOException {
68 | String sql = FileUtils.readFileToString(sqlFile);
69 |
70 | final String[] statements = sql.split(";");
71 |
72 | for (String statement : statements) {
73 | execute(statement);
74 | }
75 | }
76 |
77 | public List