├── .gitignore
├── .travis.yml
├── README.md
├── pom.xml
└── src
├── main
└── java
│ └── net
│ └── moznion
│ └── mysql
│ └── diff
│ ├── App.java
│ ├── DiffExtractor.java
│ ├── MySqlConnectionInfo.java
│ ├── MySqlConnectionUri.java
│ ├── SchemaDumper.java
│ ├── SchemaParser.java
│ └── model
│ ├── Column.java
│ ├── OrdinaryKey.java
│ ├── Table.java
│ └── UniqueKey.java
└── test
└── java
└── net
└── moznion
└── mysql
└── diff
├── AppTest.java
├── ConnectionInfoTest.java
├── DiffExtractorTest.java
└── SchemaDumperTest.java
/.gitignore:
--------------------------------------------------------------------------------
1 | # Created by https://www.gitignore.io
2 |
3 | ### Java ###
4 | *.class
5 |
6 | # Mobile Tools for Java (J2ME)
7 | .mtj.tmp/
8 |
9 | # Package Files #
10 | *.jar
11 | *.war
12 | *.ear
13 |
14 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
15 | hs_err_pid*
16 |
17 | /target/
18 |
19 | /.checkstyle
20 | /.classpath
21 | /.project
22 | /.settings/
23 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: java
2 | jdk:
3 | - oraclejdk8
4 | install: mvn install -DskipTests=true -Dgpg.skip=true
5 | before_deploy:
6 | - mvn -P fatjar clean package -DskipTests=true -Dproject.finalName=$TRAVIS_TAG
7 | deploy:
8 | provider: releases
9 | api_key:
10 | secure: tBjY19aiHZ02GsCj9W+B0iscsZM8X+geHuErZdQ82wrhsf9DwxwBqPDYlfVtf19VhbnfZy/JjqMqv+R8T6lIYZxsgvbTaT41IA7dR9g49W6LfPZStY4blYF8cR+WB7tlS7bJ+9KhIzZ/5+DxAqZ78PfDENhMseWMNR39g/d3Dmw=
11 | file: target/$TRAVIS_TAG.jar
12 | skip_cleanup: true
13 | on:
14 | repo: moznion/java-mysql-diff
15 | all_branches: true
16 | tags: true
17 |
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | mysql-diff [](https://travis-ci.org/moznion/java-mysql-diff) [](https://maven-badges.herokuapp.com/maven-central/net.moznion/mysql-diff) [](https://javadocio-badges.herokuapp.com/net.moznion/mysql-diff)
2 | ==
3 |
4 | Detect and extract diff between two table declarations from schema of MySQL.
5 |
6 | Synopsis
7 | --
8 |
9 | ### Use as CLI application
10 |
11 | Executable fat-jar is available at [here](https://github.com/moznion/java-mysql-diff/releases).
12 |
13 | ```
14 | $ java -jar [old_database] [new_database]
15 | ```
16 |
17 | If you want more details, please run this command with `--help` option.
18 |
19 | ### Programmatically
20 |
21 | ```java
22 | import net.moznion.mysql.diff.DiffExtractor;
23 | import net.moznion.mysql.diff.MySqlConnectionInfo;
24 | import net.moznion.mysql.diff.SchemaDumper;
25 | import net.moznion.mysql.diff.SchemaParser;
26 |
27 | MySqlConnectionInfo localMySqlConnectionInfo = MySqlConnectionInfo.builder()
28 | // .host("your-host") // "localhost" is the default value
29 | // .user("your-name") // "root" is the default value
30 | // .pass("your-password") // "" is the default value
31 | // .url("jdbc:mysql://your-host:3306?cacheServerConfiguration=true") // or you can specify host, port and properties by a URL
32 | .build();
33 | SchemaDumper schemaDumper = new SchemaDumper(localMySqlConnectionInfo);
34 |
35 | final String oldSql = "CREATE TABLE `sample` (\n"
36 | + " `id` INTEGER(10) NOT NULL AUTO_INCREMENT,\n"
37 | + " PRIMARY KEY (`id`)\n"
38 | + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;\n";
39 | final String newSql = "CREATE TABLE `sample` (\n"
40 | + " `id` INTEGER(10) NOT NULL AUTO_INCREMENT,\n"
41 | + " `name` VARCHAR(32) NOT NULL,\n"
42 | + " PRIMARY KEY (`id`)\n"
43 | + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;\n";
44 |
45 | String oldSchema = schemaDumper.dump(oldSql);
46 | String newSchema = schemaDumper.dump(newSql);
47 |
48 | List
oldTables = SchemaParser.parse(oldSchema);
49 | List newTables = SchemaParser.parse(newSchema);
50 |
51 | String diff = DiffExtractor.extractDiff(oldTables, newTables);
52 | ```
53 |
54 | Description
55 | --
56 |
57 | This package provides a function to detect and extract diff between two table declarations from schema.
58 |
59 | Extraction processing flow is following;
60 |
61 | 1. Dump normalized schema by creating new database according to input and dump it out by using `mysqldump`
62 | 2. Parse normalized schema
63 | 3. Take diff between parsed structure
64 |
65 | This package is port of onishi-san's [mysqldiff](https://github.com/onishi/mysqldiff) from Perl to Java.
66 |
67 | Dependencies
68 | --
69 |
70 | - Java 8 or later
71 | - MySQL 5 or later (`mysqld` must be upped when this program is executed)
72 | - mysqldump
73 |
74 | How to build fat-jar
75 | --
76 |
77 | If you want to build an executable standalone jar,
78 | please run following command;
79 |
80 | ```
81 | $ mvn -P fatjar clean package
82 | ```
83 |
84 | And now, generated runnable fat-jar file has been available on [GitHub Releases](https://github.com/moznion/java-mysql-diff/releases).
85 |
86 | See Also
87 | --
88 |
89 | - [mysqldiff](https://github.com/onishi/mysqldiff)
90 |
91 | Author
92 | --
93 |
94 | moznion ()
95 |
96 | License
97 | --
98 |
99 | ```
100 | The MIT License (MIT)
101 | Copyright © 2014 moznion, http://moznion.net/
102 |
103 | Permission is hereby granted, free of charge, to any person obtaining a copy
104 | of this software and associated documentation files (the “Software”), to deal
105 | in the Software without restriction, including without limitation the rights
106 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
107 | copies of the Software, and to permit persons to whom the Software is
108 | furnished to do so, subject to the following conditions:
109 |
110 | The above copyright notice and this permission notice shall be included in
111 | all copies or substantial portions of the Software.
112 |
113 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
114 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
115 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
116 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
117 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
118 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
119 | THE SOFTWARE.
120 | ```
121 |
122 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 | 4.0.0
3 |
4 | net.moznion
5 | mysql-diff
6 | 1.1.1-SNAPSHOT
7 | jar
8 |
9 | mysql-diff
10 | Detect and extract diff between two table declarations from schema of MySQL
11 | https://github.com/moznion/java-mysql-diff
12 |
13 |
14 | UTF-8
15 | UTF-8
16 | ${project.artifactId}-${project.version}
17 |
18 |
19 |
20 |
21 | mysql
22 | mysql-connector-java
23 | [8.0.21,)
24 |
25 |
26 | args4j
27 | args4j
28 | 2.0.29
29 |
30 |
31 | junit
32 | junit
33 | 4.13.1
34 | test
35 |
36 |
37 | org.projectlombok
38 | lombok
39 | 1.14.8
40 | provided
41 |
42 |
43 | com.google.code.findbugs
44 | findbugs
45 | 3.0.0
46 | test
47 |
48 |
49 |
50 |
51 |
52 |
53 | org.apache.maven.plugins
54 | maven-compiler-plugin
55 | 3.1
56 |
57 | 1.8
58 | 1.8
59 | 1.8
60 | UTF-8
61 |
62 |
63 |
64 |
65 | org.apache.maven.plugins
66 | maven-jar-plugin
67 | 2.5
68 |
69 |
70 |
71 | net.moznion.mysql.diff.App
72 | true
73 | true
74 |
75 |
76 | ${project.finalName}
77 |
78 |
79 |
80 |
81 | org.apache.maven.plugins
82 | maven-release-plugin
83 | 2.5.1
84 |
85 |
86 | org.apache.maven.plugins
87 | maven-source-plugin
88 | 2.4
89 |
90 |
91 | attach-sources
92 |
93 | jar
94 |
95 |
96 |
97 |
98 |
99 | org.apache.maven.plugins
100 | maven-javadoc-plugin
101 | 2.10.1
102 |
103 |
104 | attach-javadocs
105 |
106 | jar
107 |
108 |
109 |
110 |
111 |
112 | org.apache.maven.plugins
113 | maven-surefire-plugin
114 | 2.18
115 |
116 |
117 | org.slf4j.simpleLogger.defaultLogLevel
118 | debug
119 |
120 |
121 |
122 |
123 | org.sonatype.plugins
124 | nexus-staging-maven-plugin
125 | 1.6.5
126 | true
127 |
128 | ossrh
129 | https://oss.sonatype.org/
130 | true
131 |
132 |
133 |
134 | org.apache.maven.plugins
135 | maven-gpg-plugin
136 | 1.5
137 |
138 |
139 | sign-artifacts
140 | verify
141 |
142 | sign
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | fatjar
153 |
154 |
155 |
156 | org.apache.maven.plugins
157 | maven-assembly-plugin
158 | 2.5.3
159 |
160 |
161 | jar-with-dependencies
162 |
163 | false
164 |
165 |
166 | net.moznion.mysql.diff.App
167 | true
168 | true
169 |
170 |
171 | ${project.finalName}
172 |
173 |
174 |
175 | make-assembly
176 | package
177 |
178 | single
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 | ${project.artifactId}
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 | org.codehaus.mojo
195 | findbugs-maven-plugin
196 | 3.0.0
197 |
198 |
199 |
200 |
201 |
202 |
203 | ossrh
204 | https://oss.sonatype.org/content/repositories/snapshots
205 |
206 |
207 | ossrh
208 | https://oss.sonatype.org/service/local/staging/deploy/maven2/
209 |
210 |
211 |
212 |
213 | https://github.com/moznion/java-mysql-diff
214 | scm:git:git@github.com:moznion/java-mysql-diff.git
215 | scm:git:git@github.com:moznion/java-mysql-diff.git
216 | HEAD
217 |
218 |
219 |
220 |
221 | MIT License
222 | http://www.opensource.org/licenses/mit-license.php
223 | repo
224 |
225 |
226 |
227 |
228 |
229 | moznion
230 | Taiki Kawakami
231 | moznion@gmail.com
232 | http://moznion.net
233 |
234 |
235 |
236 |
--------------------------------------------------------------------------------
/src/main/java/net/moznion/mysql/diff/App.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff;
2 |
3 | import lombok.Getter;
4 |
5 | import net.moznion.mysql.diff.model.Table;
6 |
7 | import org.kohsuke.args4j.Argument;
8 | import org.kohsuke.args4j.CmdLineException;
9 | import org.kohsuke.args4j.CmdLineParser;
10 | import org.kohsuke.args4j.Option;
11 | import org.kohsuke.args4j.spi.StringArrayOptionHandler;
12 |
13 | import java.io.File;
14 | import java.io.IOException;
15 | import java.sql.SQLException;
16 | import java.util.ArrayList;
17 | import java.util.Arrays;
18 | import java.util.List;
19 | import java.util.Optional;
20 |
21 | /**
22 | * Class for CLI application.
23 | *
24 | * @author moznion
25 | *
26 | */
27 | public class App {
28 | @Option(name = "-v", aliases = "--version", usage = "print version")
29 | private boolean showVersion;
30 |
31 | @Option(name = "-h", aliases = "--help", usage = "print usage message and exit")
32 | private boolean showUsage;
33 |
34 | @Argument(index = 0, metaVar = "arguments...", handler = StringArrayOptionHandler.class)
35 | private String[] arguments;
36 |
37 | /**
38 | * Class to parse options for remote DB connection information.
39 | */
40 | @Getter
41 | private class RemoteDbArg {
42 | @Option(name = "-h", aliases = "--host", metaVar = "host", usage = "specify host")
43 | private String host;
44 |
45 | @Option(name = "-u", aliases = "--user", metaVar = "user", usage = "specify user")
46 | private String user;
47 |
48 | @Option(name = "-p", aliases = "--password", metaVar = "pass", usage = "specify password")
49 | private String pass;
50 |
51 | @Argument(index = 0, metaVar = "dbName")
52 | private String dbName;
53 | }
54 |
55 | /**
56 | * Entry point of CLI application.
57 | *
58 | *
59 | * [Usage]
60 | * java -jar [old_database] [new_database]
61 | * [Examples]
62 | * Take diff between createtable1.sql and createtable2.sql (both of SQL files on your machine)
63 | * java -jar createtable1.sql createtable2.sql
64 | * Take diff between dbname1 and dbname2 (both of databases on the local MySQL)
65 | * java -jar dbname1 dbname2
66 | * Take diff between dbname1 and dbname2 (both of databases on remote MySQL)
67 | * java -jar '-uroot -hlocalhost dbname1' '-uroot -hlocalhost dbname2'
68 | * [Options]
69 | * -h, --help: Show usage
70 | * -v, --version: Show version
71 | *
72 | *
73 | * @param args Options, or target of database arguments.
74 | * @throws IOException Throw if mysqldump command is failed.
75 | * @throws SQLException Throw if invalid SQL is given.
76 | * @throws InterruptedException Throw if mysqldump command is failed.
77 | */
78 | public static void main(String[] args) throws IOException, SQLException, InterruptedException {
79 | App app = new App();
80 |
81 | CmdLineParser parser = new CmdLineParser(app);
82 | try {
83 | parser.parseArgument(args);
84 | } catch (CmdLineException e) {
85 | throw new IllegalArgumentException("Invalid arguments are detected: " + Arrays.asList(args));
86 | }
87 |
88 | if (app.showVersion) {
89 | System.out.println(Optional.ofNullable(App.class.getPackage().getImplementationVersion())
90 | .orElse("Missing Version")); // XXX "Missing Version" maybe used by testing only...
91 | return;
92 | }
93 |
94 | if (app.showUsage) {
95 | System.out.println(getUsageMessage());
96 | return;
97 | }
98 |
99 | List coreArgs = Arrays.asList(Optional.ofNullable(args).orElse(new String[0]));
100 | int numOfArgs = coreArgs.size();
101 | if (numOfArgs != 2) {
102 | if (numOfArgs < 2) {
103 | System.err.println("[ERROR] Too few command line arguments");
104 | } else {
105 | System.err.println("[ERROR] Too many command line arguments");
106 | }
107 | System.err.println();
108 | System.err.println(getUsageMessage());
109 | System.exit(1);
110 | }
111 |
112 | List> parsed = new ArrayList<>();
113 | for (String arg : coreArgs) {
114 | String schema;
115 | SchemaDumper schemaDumper = new SchemaDumper(); // TODO should be more configurable
116 |
117 | File file = new File(arg);
118 | if (file.exists()) {
119 | // for file
120 | schema = schemaDumper.dump(file);
121 | } else if (arg.contains(" ")) {
122 | // for remote server
123 | RemoteDbArg remoteDbArg = new App().new RemoteDbArg();
124 | CmdLineParser remoteDbArgParser = new CmdLineParser(remoteDbArg);
125 | try {
126 | remoteDbArgParser.parseArgument(arg.substring(1, arg.length() - 1).split(" "));
127 | } catch (CmdLineException e) {
128 | throw new IllegalArgumentException("Invalid remote DB argument is detected: " + arg);
129 | }
130 |
131 | if (remoteDbArg.dbName == null || remoteDbArg.dbName.isEmpty()) {
132 | throw new IllegalArgumentException("Invalid remote DB argument is detected: " + arg);
133 | }
134 |
135 | MySqlConnectionInfo.Builder mysqlConnectionInfoBuilder = MySqlConnectionInfo.builder();
136 |
137 | if (remoteDbArg.host != null) {
138 | mysqlConnectionInfoBuilder.host(remoteDbArg.host);
139 | }
140 |
141 | if (remoteDbArg.user != null) {
142 | mysqlConnectionInfoBuilder.user(remoteDbArg.user);
143 | }
144 |
145 | if (remoteDbArg.pass != null) {
146 | mysqlConnectionInfoBuilder.pass(remoteDbArg.pass);
147 | }
148 |
149 | schema =
150 | schemaDumper.dumpFromRemoteDb(remoteDbArg.dbName, mysqlConnectionInfoBuilder.build());
151 | } else {
152 | // for local server
153 | schema = schemaDumper.dumpFromLocalDb(arg);
154 | }
155 |
156 | parsed.add(SchemaParser.parse(schema));
157 | }
158 |
159 | String diff = DiffExtractor.extractDiff(parsed.get(0), parsed.get(1));
160 | System.out.println(diff);
161 | }
162 |
163 | private static String getUsageMessage() {
164 | return "[Usage]\n"
165 | + " java -jar [old_database] [new_database]\n"
166 | + "[Examples]\n"
167 | + "* Take diff between createtable1.sql and createtable2.sql "
168 | + "(both of SQL files on your machine)\n"
169 | + " java -jar createtable1.sql createtable2.sql\n"
170 | + "* Take diff between dbname1 and dbname2 "
171 | + "(both of databases on the local MySQL)\n"
172 | + " java -jar dbname1 dbname2\n"
173 | + "* Take diff between dbname1 and dbname2 "
174 | + "(both of databases on remote MySQL)\n"
175 | + " java -jar '-u root -h localhost dbname1' '-u root -h localhost dbname2'"
176 | + "\n"
177 | + "[Options]\n"
178 | + " -h, --help: Show usage\n"
179 | + " -v, --version: Show version";
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/src/main/java/net/moznion/mysql/diff/DiffExtractor.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff;
2 |
3 | import net.moznion.mysql.diff.model.Column;
4 | import net.moznion.mysql.diff.model.OrdinaryKey;
5 | import net.moznion.mysql.diff.model.Table;
6 | import net.moznion.mysql.diff.model.UniqueKey;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.HashMap;
11 | import java.util.List;
12 | import java.util.Map;
13 | import java.util.Map.Entry;
14 | import java.util.Set;
15 | import java.util.stream.Collectors;
16 |
17 | /**
18 | * Diff extractor for table definition of schema.
19 | *
20 | * @author moznion
21 | *
22 | */
23 | public class DiffExtractor {
24 | /**
25 | * Extract diff between two schemas.
26 | *
27 | * @param oldTables tables of old schema.
28 | * @param newTables tables of new schema.
29 | * @return Diff string.
30 | */
31 | public static String extractDiff(List oldTables, List newTables) {
32 | StringBuilder diffStringBuilder = new StringBuilder();
33 |
34 | List newTableNames = newTables.stream()
35 | .map(table -> table.getTableName())
36 | .sorted()
37 | .collect(Collectors.toList());
38 |
39 | Map oldTableMap = oldTables.stream()
40 | .collect(Collectors.toMap(Table::getTableName, t -> t));
41 | Map newTableMap = newTables.stream()
42 | .collect(Collectors.toMap(Table::getTableName, t -> t));
43 |
44 | for (String tableName : newTableNames) {
45 | Table newTable = newTableMap.get(tableName);
46 | if (oldTableMap.containsKey(tableName)) {
47 | Table oldTable = oldTableMap.get(tableName);
48 | diffStringBuilder.append(extractTableDiff(tableName, oldTable, newTable));
49 | } else {
50 | diffStringBuilder.append(newTable.getContent()).append(";\n\n");
51 | }
52 | }
53 |
54 | return diffStringBuilder.toString();
55 | }
56 |
57 | private static String extractTableDiff(String tableName, Table oldTable, Table newTable) {
58 | List changes = extractColumnDiff(oldTable, newTable);
59 | changes.addAll(extractKeyDiff(oldTable, newTable));
60 |
61 | if (changes.isEmpty()) {
62 | return "";
63 | }
64 |
65 | return new StringBuilder()
66 | .append("ALTER TABLE `")
67 | .append(tableName)
68 | .append("` ")
69 | .append(String.join(", ", changes))
70 | .append(";\n\n")
71 | .toString();
72 | }
73 |
74 | private static List extractColumnDiff(Table oldTable, Table newTable) {
75 | List oldColumns = oldTable.getColumns();
76 | List newColumns = newTable.getColumns();
77 |
78 | Map oldColumnMap = oldColumns.stream()
79 | .collect(Collectors.toMap(Column::getName, c -> c));
80 | Map newColumnMap = newColumns.stream()
81 | .collect(Collectors.toMap(Column::getName, c -> c));
82 |
83 | Map allColumnMap = new HashMap<>();
84 | allColumnMap.putAll(oldColumnMap);
85 | allColumnMap.putAll(newColumnMap);
86 |
87 | List changes = new ArrayList<>();
88 | for (Entry column : allColumnMap.entrySet()) {
89 | String columnName = column.getKey();
90 |
91 | if (!oldColumnMap.containsKey(columnName)) {
92 | changes.add(new StringBuilder()
93 | .append("ADD `")
94 | .append(columnName)
95 | .append("` ")
96 | .append(newColumnMap.get(columnName).getDefinition())
97 | .toString());
98 | continue;
99 | }
100 |
101 | if (!newColumnMap.containsKey(columnName)) {
102 | changes.add(new StringBuilder()
103 | .append("DROP `")
104 | .append(columnName)
105 | .append("`")
106 | .toString());
107 | continue;
108 | }
109 |
110 | String oldDefinition = oldColumnMap.get(columnName).getDefinition();
111 | String newDefinition = newColumnMap.get(columnName).getDefinition();
112 | if (!oldDefinition.equals(newDefinition)) {
113 | changes.add(new StringBuilder()
114 | .append("MODIFY `")
115 | .append(columnName)
116 | .append("` ")
117 | .append(newDefinition)
118 | .toString());
119 | continue;
120 | }
121 | }
122 |
123 | return changes;
124 | }
125 |
126 | private static List extractKeyDiff(Table oldTable, Table newTable) {
127 | List changes = new ArrayList<>();
128 |
129 | // For ordinary key
130 | changes.addAll(extractOrdinaryKeyDiff(oldTable, newTable));
131 |
132 | // For unique key
133 | changes.addAll(extractUniqueKeyDiff(oldTable, newTable));
134 |
135 | return changes;
136 | }
137 |
138 | private static List extractOrdinaryKeyDiff(Table oldTable, Table newTable) {
139 | List changes = new ArrayList<>();
140 |
141 | List oldKeys = oldTable.getKeys();
142 | List newKeys = newTable.getKeys();
143 |
144 | Set oldKeysAttendance = oldKeys.stream()
145 | .map(OrdinaryKey::getColumn)
146 | .collect(Collectors.toSet());
147 | Set newKeysAttendance = newKeys.stream()
148 | .map(OrdinaryKey::getColumn)
149 | .collect(Collectors.toSet());
150 |
151 | // add key
152 | for (OrdinaryKey key : newKeys) {
153 | String column = key.getColumn();
154 | if (oldKeysAttendance.contains(column)) {
155 | continue;
156 | }
157 |
158 | String name = String.join("_",
159 | Arrays.stream(column.split(","))
160 | .map(col -> col.replaceAll("[`()]", ""))
161 | .collect(Collectors.toList()));
162 |
163 | changes.add(
164 | new StringBuilder()
165 | .append("ADD INDEX `")
166 | .append(name)
167 | .append("` (")
168 | .append(column)
169 | .append(")")
170 | .toString());
171 | }
172 |
173 | // drop key
174 | for (OrdinaryKey key : oldKeys) {
175 | String column = key.getColumn();
176 | if (newKeysAttendance.contains(column)) {
177 | continue;
178 | }
179 |
180 | changes.add(
181 | new StringBuilder()
182 | .append("DROP INDEX `")
183 | .append(key.getName())
184 | .append("`")
185 | .toString());
186 | }
187 |
188 | return changes;
189 | }
190 |
191 | private static List extractUniqueKeyDiff(Table oldTable, Table newTable) {
192 | List changes = new ArrayList<>();
193 |
194 | List oldKeys = oldTable.getUniqueKeys();
195 | List newKeys = newTable.getUniqueKeys();
196 |
197 | Set oldKeysAtendance = oldKeys.stream()
198 | .map(OrdinaryKey::getColumn)
199 | .collect(Collectors.toSet());
200 | Set newKeysAtendance = newKeys.stream()
201 | .map(OrdinaryKey::getColumn)
202 | .collect(Collectors.toSet());
203 |
204 | // add key
205 | for (UniqueKey key : newKeys) {
206 | String column = key.getColumn();
207 | if (oldKeysAtendance.contains(column)) {
208 | continue;
209 | }
210 |
211 | String name = String.join("_",
212 | Arrays.asList(column.split(",")).stream()
213 | .map(col -> col.replaceAll("[`()]", ""))
214 | .collect(Collectors.toList()));
215 |
216 | changes.add(
217 | new StringBuilder()
218 | .append("ADD UNIQUE INDEX `")
219 | .append(name)
220 | .append("` (")
221 | .append(column)
222 | .append(")")
223 | .toString());
224 | }
225 |
226 | // drop key
227 | for (OrdinaryKey key : oldKeys) {
228 | String column = key.getColumn();
229 | if (newKeysAtendance.contains(column)) {
230 | continue;
231 | }
232 |
233 | changes.add(
234 | new StringBuilder()
235 | .append("DROP INDEX `")
236 | .append(key.getName())
237 | .append("`")
238 | .toString());
239 | }
240 |
241 | return changes;
242 | }
243 | }
244 |
--------------------------------------------------------------------------------
/src/main/java/net/moznion/mysql/diff/MySqlConnectionInfo.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import lombok.experimental.Accessors;
6 |
7 | import java.net.URISyntaxException;
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.List;
11 |
12 | /**
13 | * Representation of connection information for MySQL.
14 | *
15 | * @author moznion
16 | *
17 | */
18 | @Getter
19 | public class MySqlConnectionInfo {
20 | private final String host;
21 | private final String user;
22 | private final String pass;
23 | private final String jdbcUrl;
24 |
25 | /**
26 | * Builder class of MySqlConnectionInfo.
27 | *
28 | *
29 | * This class provides following setters;
30 | *
31 | *
32 | * host(String hostName) // default value: "localhost"
33 | * port(int portNumber) // default value: 3306
34 | * user(String userName) // default value: "root"
35 | * host(String password) // default value: ""
36 | *
37 | */
38 | @Accessors(fluent = true)
39 | public static class Builder {
40 | @Setter
41 | private String host = "localhost";
42 | @Setter
43 | private int port = 3306;
44 | @Setter
45 | private String user = "root";
46 | @Setter
47 | private String pass = "";
48 |
49 | private List properties = new ArrayList<>(Arrays.asList("allowMultiQueries=true"));
50 |
51 | /**
52 | * Add a property.
53 | *
54 | * @param property
55 | */
56 | public void addProperty(String property) {
57 | properties.add(property);
58 | }
59 |
60 | /**
61 | * Add properties.
62 | *
63 | * @param properties
64 | */
65 | public void addProperties(List properties) {
66 | this.properties.addAll(properties);
67 | }
68 |
69 | /**
70 | * Set host name, port number and options by URL of a remote MySQL.
71 | *
72 | * @param url URL of a remote MySQL (e.g.
73 | * jdbc:mysql://localhost:8888/something_table?cacheServerConfiguration=true)
74 | * @throws URISyntaxException
75 | */
76 | public Builder url(String url) throws URISyntaxException {
77 | MySqlConnectionUri connectionUri = new MySqlConnectionUri(url);
78 |
79 | host = connectionUri.getHost();
80 | port = connectionUri.getPort();
81 | addProperties(connectionUri.getQueries());
82 |
83 | return this;
84 | }
85 |
86 | /**
87 | * Builds MySqlConnectionInfo.
88 | *
89 | * @return New MySqlConnectionInfo instance.
90 | */
91 | public MySqlConnectionInfo build() {
92 | return new MySqlConnectionInfo(this);
93 | }
94 | }
95 |
96 | /**
97 | * Dispenses a new builder of MySqlConnectionInfo.
98 | *
99 | * @return Builder of MySqlConnectionInfo.
100 | */
101 | public static Builder builder() {
102 | return new Builder();
103 | }
104 |
105 | private MySqlConnectionInfo(Builder builder) {
106 | host = builder.host;
107 | user = builder.user;
108 | pass = builder.pass;
109 | jdbcUrl = new StringBuilder()
110 | .append("jdbc:mysql://")
111 | .append(builder.host)
112 | .append(":")
113 | .append(builder.port)
114 | .append("?")
115 | .append(String.join("&", builder.properties))
116 | .toString();
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/net/moznion/mysql/diff/MySqlConnectionUri.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff;
2 |
3 | import lombok.Getter;
4 |
5 | import java.net.URI;
6 | import java.net.URISyntaxException;
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 | import java.util.List;
10 | import java.util.regex.Matcher;
11 | import java.util.regex.Pattern;
12 |
13 | @Getter
14 | class MySqlConnectionUri {
15 | private String host;
16 | private int port;
17 | private List queries;
18 |
19 | private static final int DEFAULT_PORT = 3306;
20 | private static final Pattern URL_PATTERN = Pattern.compile("^([^/]*:)[^:/]+:");
21 |
22 | public MySqlConnectionUri(String url) throws URISyntaxException {
23 | Matcher urlMatcher = URL_PATTERN.matcher(url);
24 | if (!urlMatcher.find()) {
25 | throw new URISyntaxException(url, "It doesn't contain connection protocol schema of jdbc");
26 | }
27 |
28 | String cleanUrl = url.substring(urlMatcher.group(1).length());
29 | URI uri = new URI(cleanUrl);
30 |
31 | host = uri.getHost();
32 |
33 | port = uri.getPort();
34 | if (port < 0) {
35 | port = DEFAULT_PORT;
36 | }
37 |
38 | queries = parseQueryString(uri.getQuery());
39 | }
40 |
41 | private List parseQueryString(String queryString) {
42 | ArrayList queries = new ArrayList<>();
43 |
44 | if (queryString != null) {
45 | Arrays.asList(queryString.split("&")).forEach(property -> {
46 | queries.add(property);
47 | });
48 | }
49 |
50 | return queries;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/net/moznion/mysql/diff/SchemaDumper.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff;
2 |
3 | import java.io.BufferedReader;
4 | import java.io.File;
5 | import java.io.InputStream;
6 | import java.io.InputStreamReader;
7 | import java.io.IOException;
8 | import java.nio.charset.Charset;
9 | import java.nio.charset.StandardCharsets;
10 | import java.nio.file.Files;
11 | import java.nio.file.Paths;
12 | import java.sql.Connection;
13 | import java.sql.DriverManager;
14 | import java.sql.SQLException;
15 | import java.sql.Statement;
16 | import java.util.ArrayList;
17 | import java.util.Arrays;
18 | import java.util.List;
19 | import java.util.UUID;
20 |
21 | /**
22 | * Dumper for SQL table definition.
23 | *
24 | * @author moznion
25 | *
26 | */
27 | public class SchemaDumper {
28 | private static final String LINE_SEPARATOR = System.lineSeparator();
29 |
30 | private final MySqlConnectionInfo localMySqlConnectionInfo;
31 | private final String mysqldumpPath;
32 |
33 | /**
34 | * Instantiate SchemaDumper.
35 | *
36 | * @param localMySqlConnectionInfo Connection information of MySQL which is on your local
37 | * environment.
38 | * @param mysqldumpPath Path for mysqldump command.
39 | */
40 | public SchemaDumper(MySqlConnectionInfo localMySqlConnectionInfo, String mysqldumpPath) {
41 | if (localMySqlConnectionInfo == null) {
42 | throw new IllegalArgumentException("mysqlConnectionInfo must not be null");
43 | }
44 |
45 | if (mysqldumpPath == null) {
46 | throw new IllegalArgumentException("mysqldumpPath must not be null");
47 | }
48 |
49 | this.localMySqlConnectionInfo = localMySqlConnectionInfo;
50 | this.mysqldumpPath = mysqldumpPath;
51 | }
52 |
53 | /**
54 | * Instantiate SchemaDumper.
55 | *
56 | *
57 | * Path of mysqldump will be used default as "mysqldump".
58 | *
59 | *
60 | * @param localMySqlConnectionInfo Connection information of MySQL which is on your local
61 | * environment.
62 | */
63 | public SchemaDumper(MySqlConnectionInfo localMySqlConnectionInfo) {
64 | this(localMySqlConnectionInfo, "mysqldump");
65 | }
66 |
67 | /**
68 | * Instantiate SchemaDumper.
69 | *
70 | *
71 | * Connection information of local MySQL will be used default as "-h localhost -u root".
72 | *
73 | *
74 | * @param mysqldumpPath Path for mysqldump command.
75 | */
76 | public SchemaDumper(String mysqldumpPath) {
77 | this(MySqlConnectionInfo.builder().build(), mysqldumpPath);
78 | }
79 |
80 | /**
81 | * Instantiate SchemaDumper.
82 | *
83 | *
84 | * Path of mysqldump will be used default as "mysqldump".
85 | * Connection information of local MySQL will be used default as "-h localhost -u root".
86 | *
87 | */
88 | public SchemaDumper() {
89 | this(MySqlConnectionInfo.builder().build());
90 | }
91 |
92 | /**
93 | * Dump schema from SQL string.
94 | *
95 | * @param sql SQL string which is a target to dump.
96 | * @return Result of dumping.
97 | * @throws SQLException Throw if invalid SQL is given.
98 | * @throws IOException Throw if mysqldump command is failed.
99 | * @throws InterruptedException Throw if mysqldump command is failed.
100 | */
101 | public String dump(String sql) throws SQLException, IOException, InterruptedException {
102 | String tempDbName = new StringBuilder()
103 | .append("tmp_")
104 | .append(UUID.randomUUID().toString().replaceAll("-", ""))
105 | .toString();
106 |
107 | String mysqlUrl = localMySqlConnectionInfo.getJdbcUrl();
108 | String mysqlUser = localMySqlConnectionInfo.getUser();
109 | String mysqlPass = localMySqlConnectionInfo.getPass();
110 | try (Connection connection = DriverManager.getConnection(mysqlUrl, mysqlUser, mysqlPass)) {
111 | try (Statement stmt = connection.createStatement()) {
112 | stmt.executeUpdate("CREATE DATABASE " + tempDbName);
113 | }
114 |
115 | try (Statement stmt = connection.createStatement()) {
116 | stmt.execute(new StringBuilder()
117 | .append("USE ")
118 | .append(tempDbName)
119 | .append("; ")
120 | .append(sql)
121 | .toString());
122 | }
123 |
124 | return fetchSchemaViaMysqldump(tempDbName);
125 | } catch (Exception e) {
126 | throw e;
127 | } finally {
128 | try (Connection connection = DriverManager.getConnection(mysqlUrl, mysqlUser, mysqlPass)) {
129 | try (Statement stmt = connection.createStatement()) {
130 | stmt.executeUpdate("DROP DATABASE " + tempDbName);
131 | }
132 | }
133 | }
134 | }
135 |
136 | /**
137 | * Dump schema from SQL file.
138 | *
139 | * @param sqlFile SQL file.
140 | * @param charset Character set of SQL file.
141 | * @return Result of dumping.
142 | * @throws SQLException Throw if invalid SQL is given.
143 | * @throws IOException Throw if mysqldump command is failed.
144 | * @throws InterruptedException Throw if mysqldump command is failed.
145 | */
146 | public String dump(File sqlFile, Charset charset)
147 | throws IOException, SQLException, InterruptedException {
148 | String sqlString =
149 | new String(Files.readAllBytes(Paths.get(sqlFile.getAbsolutePath())), charset);
150 | return dump(sqlString);
151 | }
152 |
153 | /**
154 | * Dump schema from SQL file which is written by UTF-8.
155 | *
156 | * @param sqlFile SQL file (written by UTF-8).
157 | * @return Result of dumping.
158 | * @throws SQLException Throw if invalid SQL is given.
159 | * @throws IOException Throw if mysqldump command is failed.
160 | * @throws InterruptedException Throw if mysqldump command is failed.
161 | */
162 | public String dump(File sqlFile) throws IOException, SQLException, InterruptedException {
163 | return dump(sqlFile, StandardCharsets.UTF_8);
164 | }
165 |
166 | /**
167 | * Dump schema from DB name which is in local MySQL.
168 | *
169 | * @param dbName DB name which is in local MySQL.
170 | * @return Result of dumping.
171 | * @throws SQLException Throw if invalid SQL is given.
172 | * @throws IOException Throw if mysqldump command is failed.
173 | * @throws InterruptedException Throw if mysqldump command is failed.
174 | */
175 | public String dumpFromLocalDb(String dbName)
176 | throws IOException, InterruptedException, SQLException {
177 | return fetchSchemaViaMysqldump(dbName);
178 | }
179 |
180 | /**
181 | * Dump schema from DB name which is in remote MySQL.
182 | *
183 | * @param dbName DB name which is in remote MySQL.
184 | * @param mysqlConnectionInfo Connection information of remote MySQL.
185 | * @return Result of dumping.
186 | * @throws SQLException Throw if invalid SQL is given.
187 | * @throws IOException Throw if mysqldump command is failed.
188 | * @throws InterruptedException Throw if mysqldump command is failed.
189 | */
190 | public String dumpFromRemoteDb(String dbName, MySqlConnectionInfo mysqlConnectionInfo)
191 | throws IOException, InterruptedException, SQLException {
192 | String schema = fetchSchemaViaMysqldump(dbName, mysqlConnectionInfo);
193 | return dump(schema);
194 | }
195 |
196 | private String fetchSchemaViaMysqldump(String dbName, MySqlConnectionInfo mysqlConnectionInfo)
197 | throws IOException, InterruptedException {
198 | String schema;
199 | List mysqldumpCommand = new ArrayList<>(Arrays.asList(
200 | mysqldumpPath,
201 | "--no-data=true",
202 | dbName));
203 |
204 | String mysqlUser = mysqlConnectionInfo.getUser();
205 | if (!mysqlUser.isEmpty()) {
206 | mysqldumpCommand.add(new StringBuilder().append("-u").append(mysqlUser).toString());
207 | }
208 |
209 | String mysqlHost = mysqlConnectionInfo.getHost();
210 | if (!mysqlHost.isEmpty()) {
211 | mysqldumpCommand.add(new StringBuilder().append("-h").append(mysqlHost).toString());
212 | }
213 |
214 | ProcessBuilder processBuilder = new ProcessBuilder(mysqldumpCommand);
215 |
216 | Process process = processBuilder.start();
217 | try (InputStream inputStream = process.getInputStream()) {
218 | StringBuilder stdoutStringBuilder = new StringBuilder();
219 | try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
220 | String line;
221 | while ((line = bufferedReader.readLine()) != null) {
222 | stdoutStringBuilder.append(line).append(LINE_SEPARATOR);
223 | }
224 | }
225 | schema = stdoutStringBuilder.toString();
226 | }
227 |
228 | if (process.waitFor() != 0) {
229 | throw new RuntimeException(
230 | new StringBuilder()
231 | .append("Failed to execute `mysqldump` command.\n")
232 | .append("[command] >>>\n")
233 | .append(String.join(" ", mysqldumpCommand))
234 | .append("\n<<<\n")
235 | .append("[output] >>>\n")
236 | .append(schema)
237 | .append("\n<<<\n")
238 | .toString());
239 | }
240 |
241 | return schema;
242 | }
243 |
244 | private String fetchSchemaViaMysqldump(String dbName)
245 | throws IOException, InterruptedException, SQLException {
246 | return fetchSchemaViaMysqldump(dbName, localMySqlConnectionInfo);
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/main/java/net/moznion/mysql/diff/SchemaParser.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff;
2 |
3 | import net.moznion.mysql.diff.model.Column;
4 | import net.moznion.mysql.diff.model.OrdinaryKey;
5 | import net.moznion.mysql.diff.model.Table;
6 | import net.moznion.mysql.diff.model.UniqueKey;
7 |
8 | import java.util.ArrayList;
9 | import java.util.List;
10 | import java.util.regex.Matcher;
11 | import java.util.regex.Pattern;
12 |
13 | /**
14 | * Dumper for SQL table definition.
15 | *
16 | * @author moznion
17 | *
18 | */
19 | public class SchemaParser {
20 | private static final Pattern TABLA_BLOCK_PATTERN = Pattern.compile(
21 | "CREATE TABLE .*? ENGINE[^;]*", Pattern.MULTILINE | Pattern.DOTALL);
22 |
23 | private static final Pattern TABLE_NAME_PATTERN = Pattern.compile("`(.*?)`");
24 |
25 | private static final Pattern PRIMARY_KEY_PATTERN = Pattern.compile(
26 | "^\\s*PRIMARY KEY\\s+\\((.*)\\)");
27 |
28 | private static final Pattern UNIQUE_KEY_PATTERN = Pattern.compile(
29 | "^\\s*UNIQUE KEY\\s+`(.*)`\\s+\\((.*)\\)");
30 |
31 | private static final Pattern ORDINARY_KEY_PATTERN = Pattern.compile(
32 | "^\\s*KEY\\s+`(.*)`\\s+\\((.*)\\)");
33 |
34 | private static final Pattern COLUMN_PATTERN = Pattern.compile(
35 | "^\\s*`(.*?)`\\s+(.+?)[\n,]?$");
36 |
37 | /**
38 | * Parse and output table definition of given schema.
39 | *
40 | * @param schema Schema which is generated by SchemaDumper.
41 | * @return Table definition of given schema.
42 | */
43 | public static List parse(String schema) {
44 | Matcher blockMatcher = TABLA_BLOCK_PATTERN.matcher(schema);
45 |
46 | List tables = new ArrayList<>();
47 |
48 | while (blockMatcher.find()) {
49 | String content = blockMatcher.group();
50 |
51 | Matcher tableNameMatcher = TABLE_NAME_PATTERN.matcher(content);
52 | if (!tableNameMatcher.find()) {
53 | continue;
54 | }
55 | String tableName = tableNameMatcher.group(1);
56 |
57 | List primaryKeys = new ArrayList<>();
58 | List uniqueKeys = new ArrayList<>();
59 | List keys = new ArrayList<>();
60 | List columns = new ArrayList<>();
61 |
62 | for (String line : content.split("\r?\n")) {
63 | if (line.matches("^CREATE") || line.matches("^\\)")) {
64 | continue;
65 | }
66 |
67 | Matcher primaryKeyMatcher = PRIMARY_KEY_PATTERN.matcher(line);
68 | if (primaryKeyMatcher.find()) {
69 | primaryKeys.add(primaryKeyMatcher.group(1));
70 | continue;
71 | }
72 |
73 | Matcher uniqueKeyMatcher = UNIQUE_KEY_PATTERN.matcher(line);
74 | if (uniqueKeyMatcher.find()) {
75 | uniqueKeys.add(new UniqueKey(uniqueKeyMatcher.group(1), uniqueKeyMatcher.group(2)));
76 | continue;
77 | }
78 |
79 | Matcher ordinaryKeyMatcher = ORDINARY_KEY_PATTERN.matcher(line);
80 | if (ordinaryKeyMatcher.find()) {
81 | keys.add(new OrdinaryKey(ordinaryKeyMatcher.group(1), ordinaryKeyMatcher.group(2)));
82 | continue;
83 | }
84 |
85 | Matcher columnMatcher = COLUMN_PATTERN.matcher(line);
86 | if (columnMatcher.find()) {
87 | columns.add(new Column(columnMatcher.group(1), columnMatcher.group(2)));
88 | continue;
89 | }
90 |
91 | // Match nothing if reach here
92 | }
93 |
94 | tables.add(Table.builder()
95 | .tableName(tableName)
96 | .primaryKeys(primaryKeys)
97 | .keys(keys)
98 | .uniqueKeys(uniqueKeys)
99 | .columns(columns)
100 | .content(content)
101 | .build());
102 | }
103 |
104 | return tables;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/main/java/net/moznion/mysql/diff/model/Column.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff.model;
2 |
3 | import lombok.Getter;
4 |
5 | @Getter
6 | public class Column {
7 | private final String name;
8 | private final String definition;
9 |
10 | public Column(String name, String definition) {
11 | this.name = name;
12 | this.definition = definition;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/net/moznion/mysql/diff/model/OrdinaryKey.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff.model;
2 |
3 | import lombok.Getter;
4 |
5 | @Getter
6 | public class OrdinaryKey {
7 | private final String name;
8 | private final String column;
9 |
10 | public OrdinaryKey(String name, String column) {
11 | this.name = name;
12 | this.column = column;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/net/moznion/mysql/diff/model/Table.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff.model;
2 |
3 | import lombok.Getter;
4 | import lombok.Setter;
5 | import lombok.experimental.Accessors;
6 |
7 | import java.util.ArrayList;
8 | import java.util.List;
9 | import java.util.Optional;
10 |
11 | @Getter
12 | public class Table {
13 | private final String tableName;
14 | private final List primaryKeys;
15 | private final List uniqueKeys;
16 | private final List keys;
17 | private final List columns;
18 | private final String content;
19 |
20 | @Setter
21 | @Accessors(fluent = true)
22 | public static class Builder {
23 | private String tableName;
24 | private List primaryKeys;
25 | private List uniqueKeys;
26 | private List keys;
27 | private List columns;
28 | private String content;
29 |
30 | public Builder() {}
31 |
32 | public Table build() {
33 | return new Table(this);
34 | }
35 | }
36 |
37 | public static Builder builder() {
38 | return new Builder();
39 | }
40 |
41 | private Table(Builder builder) {
42 | tableName = Optional.ofNullable(builder.tableName)
43 | .orElseThrow(() -> new IllegalArgumentException("Missing table name"));
44 | primaryKeys = Optional.ofNullable(builder.primaryKeys).orElse(new ArrayList<>());
45 | uniqueKeys = Optional.ofNullable(builder.uniqueKeys).orElse(new ArrayList<>());
46 | keys = Optional.ofNullable(builder.keys).orElse(new ArrayList<>());
47 | columns = Optional.ofNullable(builder.columns).orElse(new ArrayList<>());
48 | content = Optional.ofNullable(builder.content).orElse("");
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/main/java/net/moznion/mysql/diff/model/UniqueKey.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff.model;
2 |
3 | public class UniqueKey extends OrdinaryKey {
4 | public UniqueKey(String name, String column) {
5 | super(name, column);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/test/java/net/moznion/mysql/diff/AppTest.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff;
2 |
3 | import com.mysql.cj.jdbc.exceptions.CommunicationsException;
4 | import java.io.BufferedWriter;
5 | import java.io.ByteArrayOutputStream;
6 | import java.io.File;
7 | import java.io.FileDescriptor;
8 | import java.io.FileOutputStream;
9 | import java.io.IOException;
10 | import java.io.OutputStreamWriter;
11 | import java.io.PrintStream;
12 | import java.nio.charset.Charset;
13 | import java.sql.Connection;
14 | import java.sql.DriverManager;
15 | import java.sql.SQLException;
16 | import java.sql.Statement;
17 | import java.util.Arrays;
18 | import java.util.UUID;
19 | import org.junit.Test;
20 |
21 | import static org.junit.Assert.assertEquals;
22 | import static org.junit.Assert.assertTrue;
23 | import static org.junit.Assume.assumeTrue;
24 |
25 | public class AppTest {
26 | private static final String SQL_FOR_TEST = "CREATE TABLE `sample` (\n"
27 | + " `id` int(10) NOT NULL AUTO_INCREMENT,\n"
28 | + " PRIMARY KEY (`id`)\n"
29 | + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;\n";
30 |
31 | @Test
32 | public void shouldShowVersion() throws IOException, SQLException, InterruptedException {
33 | // For short option
34 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
35 | System.setOut(new PrintStream(baos));
36 |
37 | String[] argsForShortOpt = {"-v"};
38 | App.main(argsForShortOpt);
39 |
40 | String versionString = baos.toString();
41 | System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
42 | assertEquals("Missing Version\n", versionString);
43 |
44 | // For long option
45 | baos = new ByteArrayOutputStream();
46 | System.setOut(new PrintStream(baos));
47 |
48 | String[] argsForLongOpt = {"--version"};
49 | App.main(argsForLongOpt);
50 |
51 | versionString = baos.toString();
52 | System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
53 | assertEquals("Missing Version\n", versionString);
54 | }
55 |
56 | @Test
57 | public void shouldShowUsageByOption() throws IOException, SQLException, InterruptedException {
58 | // For short option
59 | ByteArrayOutputStream baos = new ByteArrayOutputStream();
60 | System.setOut(new PrintStream(baos));
61 |
62 | String[] argsForShortOpt = {"-h"};
63 | App.main(argsForShortOpt);
64 |
65 | String usageString = baos.toString();
66 |
67 | String expectedUsageString = "[Usage]\n"
68 | + " java -jar [old_database] [new_database]\n"
69 | + "[Examples]\n"
70 | + "* Take diff between createtable1.sql and createtable2.sql "
71 | + "(both of SQL files on your machine)\n"
72 | + " java -jar createtable1.sql createtable2.sql\n"
73 | + "* Take diff between dbname1 and dbname2 "
74 | + "(both of databases on the local MySQL)\n"
75 | + " java -jar dbname1 dbname2\n"
76 | + "* Take diff between dbname1 and dbname2 "
77 | + "(both of databases on remote MySQL)\n"
78 | + " java -jar '-u root -h localhost dbname1' '-u root -h localhost dbname2'"
79 | + "\n"
80 | + "[Options]\n"
81 | + " -h, --help: Show usage\n"
82 | + " -v, --version: Show version\n";
83 |
84 | System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
85 | assertEquals(expectedUsageString, usageString);
86 |
87 | // For long option
88 | baos = new ByteArrayOutputStream();
89 | System.setOut(new PrintStream(baos));
90 |
91 | String[] argsForLongOpt = {"--help"};
92 | App.main(argsForLongOpt);
93 |
94 | usageString = baos.toString();
95 |
96 | System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out)));
97 | assertEquals(expectedUsageString, usageString);
98 | }
99 |
100 | @Test
101 | public void shouldTakeDiffBetweenFiles() throws IOException, SQLException, InterruptedException {
102 | File sqlFile1 = File.createTempFile("tempsql1", ".sql");
103 | File sqlFile2 = File.createTempFile("tempsql2", ".sql");
104 |
105 | for (File sqlFile : Arrays.asList(sqlFile1, sqlFile2)) {
106 | try (BufferedWriter bufferedWriter =
107 | new BufferedWriter(new OutputStreamWriter(new FileOutputStream(sqlFile),
108 | Charset.forName("UTF-8")))) {
109 | bufferedWriter.write(SQL_FOR_TEST);
110 | }
111 | }
112 |
113 | String[] args = {sqlFile1.getAbsolutePath(), sqlFile2.getAbsolutePath()};
114 | try {
115 | App.main(args);
116 | } catch (CommunicationsException e) {
117 | assumeTrue("MySQL maybe not launched", false);
118 | }
119 |
120 | assertTrue(true);
121 |
122 | sqlFile1.delete();
123 | sqlFile2.delete();
124 | }
125 |
126 | @Test
127 | @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(
128 | value = "SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE")
129 | public void shouldTakeDiffBetweenLocalDb()
130 | throws IOException, SQLException, InterruptedException {
131 | MySqlConnectionInfo connInfo = MySqlConnectionInfo.builder().build();
132 |
133 | String tempDbName1 = new StringBuilder()
134 | .append("tmp_")
135 | .append(UUID.randomUUID().toString().replaceAll("-", ""))
136 | .toString();
137 | String tempDbName2 = new StringBuilder()
138 | .append("tmp_")
139 | .append(UUID.randomUUID().toString().replaceAll("-", ""))
140 | .toString();
141 |
142 | String mysqlUrl = connInfo.getJdbcUrl();
143 | String user = connInfo.getUser();
144 | String pass = connInfo.getPass();
145 |
146 | try (Connection connection = DriverManager.getConnection(mysqlUrl, user, pass)) {
147 | for (String dbName : Arrays.asList(tempDbName1, tempDbName2)) {
148 | try (Statement stmt = connection.createStatement()) {
149 | stmt.executeUpdate("CREATE DATABASE " + dbName);
150 | }
151 | try (Statement stmt = connection.createStatement()) {
152 | stmt.executeUpdate("USE " + dbName + "; " + SQL_FOR_TEST);
153 | }
154 | }
155 |
156 | String[] args = {tempDbName1, tempDbName2};
157 | App.main(args);
158 | } catch (CommunicationsException e) {
159 | assumeTrue("MySQL maybe not launched", false);
160 | return;
161 | } catch (Exception e) {
162 | assertTrue(false);
163 | throw e;
164 | } finally {
165 | try (Connection connectionToTeardown = DriverManager.getConnection(mysqlUrl, user, pass)) {
166 | for (String dbName : Arrays.asList(tempDbName1, tempDbName2)) {
167 | try (Statement stmt = connectionToTeardown.createStatement()) {
168 | stmt.executeUpdate("DROP DATABASE " + dbName);
169 | }
170 | }
171 | } catch (CommunicationsException e) {
172 | assumeTrue("MySQL maybe not launched", false);
173 | }
174 | }
175 |
176 | assertTrue(true);
177 | }
178 |
179 | @Test
180 | @edu.umd.cs.findbugs.annotations.SuppressFBWarnings(
181 | value = "SQL_NONCONSTANT_STRING_PASSED_TO_EXECUTE")
182 | public void shouldTakeDiffBetweenRemoteDb() throws IOException, SQLException,
183 | InterruptedException {
184 | MySqlConnectionInfo connInfo = MySqlConnectionInfo.builder().build();
185 |
186 | final String tempDbName1 = new StringBuilder()
187 | .append("tmp_")
188 | .append(UUID.randomUUID().toString().replaceAll("-", ""))
189 | .toString();
190 | final String tempDbName2 = new StringBuilder()
191 | .append("tmp_")
192 | .append(UUID.randomUUID().toString().replaceAll("-", ""))
193 | .toString();
194 |
195 | String mysqlUrl = connInfo.getJdbcUrl();
196 | String user = connInfo.getUser();
197 | String pass = connInfo.getPass();
198 |
199 | try (Connection connection = DriverManager.getConnection(mysqlUrl, user, pass)) {
200 | for (String dbName : Arrays.asList(tempDbName1, tempDbName2)) {
201 | try (Statement stmt = connection.createStatement()) {
202 | stmt.executeUpdate("CREATE DATABASE " + dbName);
203 | }
204 | try (Statement stmt = connection.createStatement()) {
205 | stmt.executeUpdate("USE " + dbName + "; " + SQL_FOR_TEST);
206 | }
207 | }
208 |
209 | String[] args = {
210 | "'-u root -h localhost " + tempDbName1 + "'",
211 | "'-u root -h localhost " + tempDbName2 + "'"
212 | };
213 | App.main(args);
214 | } catch (CommunicationsException e) {
215 | assumeTrue("MySQL maybe not launched", false);
216 | return;
217 | } catch (Exception e) {
218 | throw e;
219 | } finally {
220 | try (Connection connectionToTeardown = DriverManager.getConnection(mysqlUrl, user, pass)) {
221 | for (String dbName : Arrays.asList(tempDbName1, tempDbName2)) {
222 | try (Statement stmt = connectionToTeardown.createStatement()) {
223 | stmt.executeUpdate("DROP DATABASE " + dbName);
224 | }
225 | }
226 | } catch (CommunicationsException e) {
227 | assumeTrue("MySQL maybe not launched", false);
228 | }
229 | }
230 |
231 | assertTrue(true);
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/src/test/java/net/moznion/mysql/diff/ConnectionInfoTest.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff;
2 |
3 | import static org.junit.Assert.assertTrue;
4 |
5 | import org.junit.Test;
6 |
7 | import java.net.URISyntaxException;
8 |
9 | public class ConnectionInfoTest {
10 | @Test
11 | public void shouldConnectSuccessfullyThroughFullyUrl() {
12 | try {
13 | new SchemaDumper(
14 | MySqlConnectionInfo
15 | .builder()
16 | .url(
17 | "jdbc:log4j:mysql://localhost:3306/something_table?"
18 | + "cacheServerConfiguration=true&useLocalSessionState=true")
19 | .build());
20 | assertTrue(true);
21 | } catch (Exception e) {
22 | assertTrue(false);
23 | }
24 | }
25 |
26 | @Test
27 | public void shouldConnectSuccessfullyThroughUrlWithoutPort() {
28 | try {
29 | new SchemaDumper(
30 | MySqlConnectionInfo
31 | .builder()
32 | .url(
33 | "jdbc:log4j:mysql://localhost/something_table?"
34 | + "cacheServerConfiguration=true&useLocalSessionState=true")
35 | .build());
36 | assertTrue(true);
37 | } catch (Exception e) {
38 | assertTrue(false);
39 | }
40 | }
41 |
42 | @Test
43 | public void shouldConnectSuccessfullyThroughUrlWithoutOptions() {
44 | try {
45 | new SchemaDumper(MySqlConnectionInfo.builder()
46 | .url("jdbc:log4j:mysql://localhost/something_table")
47 | .build());
48 | assertTrue(true);
49 | } catch (Exception e) {
50 | assertTrue(false);
51 | }
52 | }
53 |
54 | @Test
55 | public void shouldConnectSuccessfullyThroughUrlWithoutConnectionPrefix() {
56 | try {
57 | new SchemaDumper(
58 | MySqlConnectionInfo
59 | .builder()
60 | .url("localhost/something_table")
61 | .build());
62 | assertTrue(false);
63 | } catch (URISyntaxException e) {
64 | assertTrue(true);
65 | } catch (Exception e) {
66 | assertTrue(false);
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/test/java/net/moznion/mysql/diff/DiffExtractorTest.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff;
2 |
3 | import com.mysql.cj.jdbc.exceptions.CommunicationsException;
4 | import java.io.IOException;
5 | import java.sql.SQLException;
6 | import java.util.Arrays;
7 | import java.util.List;
8 | import java.util.Set;
9 | import java.util.stream.Collectors;
10 | import java.util.stream.Stream;
11 | import net.moznion.mysql.diff.model.Table;
12 | import org.junit.Test;
13 | import org.junit.experimental.runners.Enclosed;
14 | import org.junit.runner.RunWith;
15 |
16 | import static org.junit.Assert.assertEquals;
17 | import static org.junit.Assert.assertTrue;
18 | import static org.junit.Assume.assumeTrue;
19 |
20 | @RunWith(Enclosed.class)
21 | public class DiffExtractorTest {
22 | private static final SchemaDumper SCHEMA_DUMPER = new SchemaDumper();
23 |
24 | public static class ForTableDiff {
25 | private String getAlterTableDiffRightly(String oldSql, String newSql) throws SQLException,
26 | IOException, InterruptedException {
27 | String oldSchema = SCHEMA_DUMPER.dump(oldSql);
28 | String newSchema = SCHEMA_DUMPER.dump(newSql);
29 |
30 | List oldTables = SchemaParser.parse(oldSchema);
31 | List newTables = SchemaParser.parse(newSchema);
32 |
33 | String diff = DiffExtractor.extractDiff(oldTables, newTables);
34 | diff = diff.replaceAll("\r?\n", "");
35 |
36 | assertTrue(diff.startsWith("ALTER TABLE `sample` "));
37 | assertTrue(diff.endsWith(";"));
38 | diff = diff.replaceFirst("^ALTER TABLE `sample` ", "");
39 | diff = diff.replaceFirst(";$", "");
40 |
41 | return diff;
42 | }
43 |
44 | @Test
45 | public void shouldDetectColumnDiffRightly() throws SQLException, IOException,
46 | InterruptedException {
47 | String oldSql = "CREATE TABLE `sample` ("
48 | + " `id` int(10) NOT NULL AUTO_INCREMENT,"
49 | + " `title` varchar(64) NOT NULL,"
50 | + " `created_on` int(10) unsigned NOT NULL,"
51 | + " PRIMARY KEY (`id`)"
52 | + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
53 | String newSql = "CREATE TABLE `sample` ("
54 | + " `id` int(10) NOT NULL AUTO_INCREMENT,"
55 | + " `title` varchar(64) DEFAULT NULL,"
56 | + " `updated_on` int(10) unsigned NOT NULL,"
57 | + " PRIMARY KEY (`id`)"
58 | + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
59 |
60 | try {
61 | String diff = getAlterTableDiffRightly(oldSql, newSql);
62 | Set modifiers = Arrays.stream(diff.split(", ")).collect(Collectors.toSet());
63 | Set expected = Stream.of(
64 | "DROP `created_on`",
65 | "MODIFY `title` varchar(64) DEFAULT NULL",
66 | "ADD `updated_on` int(10) unsigned NOT NULL"
67 | ).collect(Collectors.toSet());
68 | assertEquals(modifiers, expected);
69 | } catch (CommunicationsException e) {
70 | assumeTrue("MySQL maybe not launched", false);
71 | } catch (Exception e) {
72 | assertTrue(false);
73 | }
74 | }
75 |
76 | @Test
77 | public void shouldDetectKeyDiffRightly()
78 | throws SQLException, IOException, InterruptedException {
79 | String oldSql = "CREATE TABLE `sample` ("
80 | + " `id` int(10) NOT NULL AUTO_INCREMENT,"
81 | + " `name` varchar(16) NOT NULL,"
82 | + " `email` varchar(16) NOT NULL,"
83 | + " `title` varchar(64) NOT NULL,"
84 | + " `created_on` int (10) unsigned NOT NULL,"
85 | + " `updated_on` int (10) unsigned NOT NULL,"
86 | + " PRIMARY KEY (`id`),"
87 | + " UNIQUE KEY `name` (`name`),"
88 | + " KEY `updated_on` (`updated_on`)"
89 | + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
90 | String newSql = "CREATE TABLE `sample` ("
91 | + " `id` int(10) NOT NULL AUTO_INCREMENT,"
92 | + " `name` varchar(16) NOT NULL,"
93 | + " `email` varchar(16) NOT NULL,"
94 | + " `title` varchar(64) NOT NULL,"
95 | + " `created_on` int (10) unsigned NOT NULL,"
96 | + " `updated_on` int (10) unsigned NOT NULL,"
97 | + " PRIMARY KEY (`id`),"
98 | + " UNIQUE KEY `identifier` (`email`,`name`),"
99 | + " KEY `timestamp` (`created_on`,`updated_on`)"
100 | + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
101 |
102 | try {
103 | String diff = getAlterTableDiffRightly(oldSql, newSql);
104 | Set modifiers = Arrays.stream(diff.split(", ")).collect(Collectors.toSet());
105 | Set expected = Stream.of(
106 | "ADD INDEX `created_on_updated_on` (`created_on`,`updated_on`)",
107 | "DROP INDEX `updated_on`",
108 | "ADD UNIQUE INDEX `email_name` (`email`,`name`)",
109 | "DROP INDEX `name`"
110 | ).collect(Collectors.toSet());
111 | assertEquals(modifiers, expected);
112 | } catch (CommunicationsException e) {
113 | assumeTrue("MySQL maybe not launched", false);
114 | } catch (Exception e) {
115 | assertTrue(false);
116 | }
117 | }
118 | }
119 |
120 | public static class ForMissingTable {
121 | @Test
122 | public void shouldDetectMissingTableRightly() throws SQLException, IOException,
123 | InterruptedException {
124 | String oldSql = "CREATE TABLE `sample` ("
125 | + " `id` int(10) NOT NULL AUTO_INCREMENT,"
126 | + " PRIMARY KEY (`id`)"
127 | + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
128 | String newSql = "CREATE TABLE `new_one` ("
129 | + " `id` int(10) NOT NULL AUTO_INCREMENT,"
130 | + " PRIMARY KEY (`id`)"
131 | + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;";
132 |
133 | try {
134 | String oldSchema = SCHEMA_DUMPER.dump(oldSql);
135 | String newSchema = SCHEMA_DUMPER.dump(newSql);
136 |
137 | List oldTables = SchemaParser.parse(oldSchema);
138 | List newTables = SchemaParser.parse(newSchema);
139 |
140 | String diff = DiffExtractor.extractDiff(oldTables, newTables);
141 | List got = Arrays.stream(diff.split("\r?\n"))
142 | .map(line -> line.trim())
143 | .collect(Collectors.toList());
144 | List expected = Arrays.asList(
145 | "CREATE TABLE `new_one` (",
146 | "`id` int(10) NOT NULL AUTO_INCREMENT,",
147 | "PRIMARY KEY (`id`)",
148 | ") ENGINE=InnoDB DEFAULT CHARSET=utf8;");
149 | assertEquals(got, expected);
150 | } catch (CommunicationsException e) {
151 | assumeTrue("MySQL maybe not launched", false);
152 | } catch (Exception e) {
153 | assertTrue(false);
154 | }
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/test/java/net/moznion/mysql/diff/SchemaDumperTest.java:
--------------------------------------------------------------------------------
1 | package net.moznion.mysql.diff;
2 |
3 | import com.mysql.cj.jdbc.exceptions.CommunicationsException;
4 | import java.io.BufferedWriter;
5 | import java.io.File;
6 | import java.io.FileOutputStream;
7 | import java.io.IOException;
8 | import java.io.OutputStreamWriter;
9 | import java.nio.charset.Charset;
10 | import java.sql.Connection;
11 | import java.sql.DriverManager;
12 | import java.sql.SQLException;
13 | import java.sql.Statement;
14 | import java.util.UUID;
15 | import org.junit.Test;
16 | import org.junit.experimental.runners.Enclosed;
17 | import org.junit.runner.RunWith;
18 |
19 | import static org.junit.Assert.assertTrue;
20 | import static org.junit.Assume.assumeTrue;
21 |
22 | @RunWith(Enclosed.class)
23 | public class SchemaDumperTest {
24 | private static final String SQL_FOR_TEST = "CREATE TABLE `sample` (\n"
25 | + " `id` int(10) NOT NULL AUTO_INCREMENT,\n"
26 | + " PRIMARY KEY (`id`)\n"
27 | + ") ENGINE=InnoDB DEFAULT CHARSET=utf8;\n";
28 |
29 | public static class ForConstructors {
30 | @Test
31 | public void shouldInstantiateAsDefault()
32 | throws SQLException, IOException, InterruptedException {
33 | SchemaDumper schemaDumper = new SchemaDumper();
34 | try {
35 | schemaDumper.dump(SQL_FOR_TEST);
36 | assertTrue(true);
37 | } catch (CommunicationsException e) {
38 | assumeTrue("MySQL maybe not launched", false);
39 | } catch (Exception e) {
40 | assertTrue(false);
41 | }
42 | }
43 |
44 | @Test
45 | public void shouldInstantiateByMysqldumpPath() throws SQLException, IOException,
46 | InterruptedException {
47 | SchemaDumper schemaDumper = new SchemaDumper("mysqldump");
48 | try {
49 | schemaDumper.dump(SQL_FOR_TEST);
50 | assertTrue(true);
51 | } catch (CommunicationsException e) {
52 | assumeTrue("MySQL maybe not launched", false);
53 | } catch (Exception e) {
54 | assertTrue(false);
55 | }
56 | }
57 |
58 | @Test
59 | public void shouldInstantiateByLocalMySqlConnectionInfo() throws SQLException, IOException,
60 | InterruptedException {
61 | SchemaDumper schemaDumper = new SchemaDumper(MySqlConnectionInfo.builder().build());
62 | try {
63 | schemaDumper.dump(SQL_FOR_TEST);
64 | assertTrue(true);
65 | } catch (CommunicationsException e) {
66 | assumeTrue("MySQL maybe not launched", false);
67 | } catch (Exception e) {
68 | assertTrue(false);
69 | }
70 | }
71 |
72 | @Test
73 | public void shouldInstantiateByAllArgs()
74 | throws SQLException, IOException, InterruptedException {
75 | SchemaDumper schemaDumper =
76 | new SchemaDumper(MySqlConnectionInfo.builder().build(), "mysqldump");
77 | try {
78 | schemaDumper.dump(SQL_FOR_TEST);
79 | assertTrue(true);
80 | } catch (CommunicationsException e) {
81 | assumeTrue("MySQL maybe not launched", false);
82 | } catch (Exception e) {
83 | assertTrue(false);
84 | }
85 | }
86 |
87 | @Test
88 | public void shouldRaiseIllegalArgumentExceptionByNullConnectionInfo() {
89 | try {
90 | new SchemaDumper(null, "mysqldump");
91 | } catch (IllegalArgumentException e) {
92 | assertTrue(true);
93 | return;
94 | } catch (Exception e) {
95 | assertTrue(false);
96 | }
97 | assertTrue(false);
98 | }
99 |
100 | @Test
101 | public void shouldRaiseIllegalArgumentExceptionByNullMysqldumpPath() {
102 | try {
103 | new SchemaDumper(MySqlConnectionInfo.builder().build(), null);
104 | } catch (IllegalArgumentException e) {
105 | assertTrue(true);
106 | return;
107 | } catch (Exception e) {
108 | assertTrue(false);
109 | }
110 | assertTrue(false);
111 | }
112 | }
113 |
114 | public static class ForDumpMethods {
115 | private SchemaDumper schemaDumper = new SchemaDumper();
116 |
117 | @Test
118 | public void shouldDumpBySqlString() throws SQLException, IOException, InterruptedException {
119 | try {
120 | schemaDumper.dump(SQL_FOR_TEST);
121 | assertTrue(true);
122 | } catch (CommunicationsException e) {
123 | assumeTrue("MySQL maybe not launched", false);
124 | } catch (Exception e) {
125 | assertTrue(false);
126 | }
127 | }
128 |
129 | @Test
130 | public void shouldDumpBySqlFileWithDefaultCharset()
131 | throws IOException, SQLException, InterruptedException {
132 | File sqlFile = File.createTempFile("tempsql", ".sql");
133 |
134 | try (BufferedWriter bufferedWriter =
135 | new BufferedWriter(new OutputStreamWriter(new FileOutputStream(sqlFile),
136 | Charset.forName("UTF-8")))) {
137 | bufferedWriter.write(SQL_FOR_TEST);
138 | }
139 |
140 | try {
141 | schemaDumper.dump(sqlFile);
142 | assertTrue(true);
143 | } catch (CommunicationsException e) {
144 | assumeTrue("MySQL maybe not launched", false);
145 | } catch (Exception e) {
146 | assertTrue(false);
147 | } finally {
148 | sqlFile.delete();
149 | }
150 | }
151 |
152 | @Test
153 | public void shouldDumpBySqlFileWithSpecifiedCharset()
154 | throws IOException, SQLException, InterruptedException {
155 | File sqlFile = File.createTempFile("tempsql", ".sql");
156 |
157 | try (BufferedWriter bufferedWriter =
158 | new BufferedWriter(new OutputStreamWriter(new FileOutputStream(sqlFile),
159 | Charset.forName("EUC-JP")))) {
160 | bufferedWriter.write(SQL_FOR_TEST);
161 | }
162 |
163 | try {
164 | schemaDumper.dump(sqlFile, Charset.forName("EUC-JP"));
165 | assertTrue(true);
166 | } catch (CommunicationsException e) {
167 | assumeTrue("MySQL maybe not launched", false);
168 | } catch (Exception e) {
169 | assertTrue(false);
170 | } finally {
171 | sqlFile.delete();
172 | }
173 |
174 | }
175 |
176 | @Test
177 | public void shouldDumpFromLocalMySql()
178 | throws SQLException, IOException, InterruptedException {
179 | MySqlConnectionInfo connInfo = MySqlConnectionInfo.builder().build();
180 |
181 | String tempDbName = new StringBuilder()
182 | .append("tmp_")
183 | .append(UUID.randomUUID().toString().replaceAll("-", ""))
184 | .toString();
185 | String mysqlUrl = connInfo.getJdbcUrl();
186 | String user = connInfo.getUser();
187 | String pass = connInfo.getPass();
188 |
189 | try (Connection connection = DriverManager.getConnection(mysqlUrl, user, pass)) {
190 | try (Statement stmt = connection.createStatement()) {
191 | stmt.executeUpdate("CREATE DATABASE " + tempDbName);
192 | }
193 | try (Statement stmt = connection.createStatement()) {
194 | stmt.executeUpdate("USE " + tempDbName + "; " + SQL_FOR_TEST);
195 | }
196 |
197 | schemaDumper.dumpFromLocalDb(tempDbName);
198 | } catch (Exception e) {
199 | throw e;
200 | } finally {
201 | try (Connection connectionToTeardown = DriverManager.getConnection(mysqlUrl, user, pass)) {
202 | try (Statement stmt = connectionToTeardown.createStatement()) {
203 | stmt.executeUpdate("DROP DATABASE " + tempDbName);
204 | }
205 | } catch (CommunicationsException e) {
206 | assumeTrue("MySQL maybe not launched", false);
207 | }
208 | }
209 | assertTrue(true);
210 | }
211 |
212 | @Test
213 | public void shouldDumpFromRemoteMySql() throws SQLException, IOException, InterruptedException {
214 | MySqlConnectionInfo connInfo = MySqlConnectionInfo.builder().build();
215 |
216 | String tempDbName = new StringBuilder()
217 | .append("tmp_")
218 | .append(UUID.randomUUID().toString().replaceAll("-", ""))
219 | .toString();
220 | String mysqlUrl = connInfo.getJdbcUrl();
221 | String user = connInfo.getUser();
222 | String pass = connInfo.getPass();
223 |
224 | try (Connection connection = DriverManager.getConnection(mysqlUrl, user, pass)) {
225 | try (Statement stmt = connection.createStatement()) {
226 | stmt.executeUpdate("CREATE DATABASE " + tempDbName);
227 | }
228 | try (Statement stmt = connection.createStatement()) {
229 | stmt.executeUpdate("USE " + tempDbName + "; " + SQL_FOR_TEST);
230 | }
231 | schemaDumper.dumpFromRemoteDb(tempDbName, connInfo);
232 | } catch (Exception e) {
233 | throw e;
234 | } finally {
235 | try (Connection connectionToTeardown = DriverManager.getConnection(mysqlUrl, user, pass)) {
236 | try (Statement stmt = connectionToTeardown.createStatement()) {
237 | stmt.executeUpdate("DROP DATABASE " + tempDbName);
238 | }
239 | } catch (CommunicationsException e) {
240 | assumeTrue("MySQL maybe not launched", false);
241 | }
242 | }
243 | assertTrue(true);
244 | }
245 | }
246 | }
247 |
--------------------------------------------------------------------------------