/*
* Copyright 2004-2019 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.tools;
import java.io.IOException;
import java.io.PipedReader;
import java.io.PipedWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.h2.util.Tool;
Creates a cluster from a stand-alone database.
Copies a database to another location if required.
@h2.resource
/**
* Creates a cluster from a stand-alone database.
* <br />
* Copies a database to another location if required.
* @h2.resource
*/
public class CreateCluster extends Tool {
Options are case sensitive. Supported options are:
[-help] or [-?]
Print the list of options
[-urlSource "<url>"]
The database URL of the source database (jdbc:h2:...)
[-urlTarget "<url>"]
The database URL of the target database (jdbc:h2:...)
[-user <user>]
The user name (default: sa)
[-password <pwd>]
The password
[-serverList <list>]
The comma separated list of host names or IP addresses
Params: - args – the command line arguments
@h2.resource
/**
* Options are case sensitive. Supported options are:
* <table>
* <tr><td>[-help] or [-?]</td>
* <td>Print the list of options</td></tr>
* <tr><td>[-urlSource "<url>"]</td>
* <td>The database URL of the source database (jdbc:h2:...)</td></tr>
* <tr><td>[-urlTarget "<url>"]</td>
* <td>The database URL of the target database (jdbc:h2:...)</td></tr>
* <tr><td>[-user <user>]</td>
* <td>The user name (default: sa)</td></tr>
* <tr><td>[-password <pwd>]</td>
* <td>The password</td></tr>
* <tr><td>[-serverList <list>]</td>
* <td>The comma separated list of host names or IP addresses</td></tr>
* </table>
* @h2.resource
*
* @param args the command line arguments
*/
public static void main(String... args) throws SQLException {
new CreateCluster().runTool(args);
}
@Override
public void runTool(String... args) throws SQLException {
String urlSource = null;
String urlTarget = null;
String user = "";
String password = "";
String serverList = null;
for (int i = 0; args != null && i < args.length; i++) {
String arg = args[i];
if (arg.equals("-urlSource")) {
urlSource = args[++i];
} else if (arg.equals("-urlTarget")) {
urlTarget = args[++i];
} else if (arg.equals("-user")) {
user = args[++i];
} else if (arg.equals("-password")) {
password = args[++i];
} else if (arg.equals("-serverList")) {
serverList = args[++i];
} else if (arg.equals("-help") || arg.equals("-?")) {
showUsage();
return;
} else {
showUsageAndThrowUnsupportedOption(arg);
}
}
if (urlSource == null || urlTarget == null || serverList == null) {
showUsage();
throw new SQLException("Source URL, target URL, or server list not set");
}
process(urlSource, urlTarget, user, password, serverList);
}
Creates a cluster.
Params: - urlSource – the database URL of the original database
- urlTarget – the database URL of the copy
- user – the user name
- password – the password
- serverList – the server list
/**
* Creates a cluster.
*
* @param urlSource the database URL of the original database
* @param urlTarget the database URL of the copy
* @param user the user name
* @param password the password
* @param serverList the server list
*/
public void execute(String urlSource, String urlTarget,
String user, String password, String serverList) throws SQLException {
process(urlSource, urlTarget, user, password, serverList);
}
private static void process(String urlSource, String urlTarget,
String user, String password, String serverList) throws SQLException {
org.h2.Driver.load();
// use cluster='' so connecting is possible
// even if the cluster is enabled
try (Connection connSource = DriverManager.getConnection(urlSource + ";CLUSTER=''", user, password);
Statement statSource = connSource.createStatement()) {
// enable the exclusive mode and close other connections,
// so that data can't change while restoring the second database
statSource.execute("SET EXCLUSIVE 2");
try {
performTransfer(statSource, urlTarget, user, password, serverList);
} finally {
// switch back to the regular mode
statSource.execute("SET EXCLUSIVE FALSE");
}
}
}
private static void performTransfer(Statement statSource, String urlTarget, String user, String password,
String serverList) throws SQLException {
// Delete the target database first.
try (Connection connTarget = DriverManager.getConnection(urlTarget + ";CLUSTER=''", user, password);
Statement statTarget = connTarget.createStatement()) {
statTarget.execute("DROP ALL OBJECTS DELETE FILES");
}
try (PipedReader pipeReader = new PipedReader()) {
Future<?> threadFuture = startWriter(pipeReader, statSource);
// Read data from pipe reader, restore on target.
try (Connection connTarget = DriverManager.getConnection(urlTarget, user, password);
Statement statTarget = connTarget.createStatement()) {
RunScript.execute(connTarget, pipeReader);
// Check if the writer encountered any exception
try {
threadFuture.get();
} catch (ExecutionException ex) {
throw new SQLException(ex.getCause());
} catch (InterruptedException ex) {
throw new SQLException(ex);
}
// set the cluster to the serverList on both databases
statSource.executeUpdate("SET CLUSTER '" + serverList + "'");
statTarget.executeUpdate("SET CLUSTER '" + serverList + "'");
}
} catch (IOException ex) {
throw new SQLException(ex);
}
}
private static Future<?> startWriter(final PipedReader pipeReader,
final Statement statSource) throws IOException {
final ExecutorService thread = Executors.newFixedThreadPool(1);
final PipedWriter pipeWriter = new PipedWriter(pipeReader);
// Since exceptions cannot be thrown across thread boundaries, return
// the task's future so we can check manually
Future<?> threadFuture = thread.submit(new Runnable() {
@Override
public void run() {
// If the creation of the piped writer fails, the reader will
// throw an IOException as soon as read() is called: IOException
// - if the pipe is broken, unconnected, closed, or an I/O error
// occurs. The reader's IOException will then trigger the
// finally{} that releases exclusive mode on the source DB.
try (PipedWriter writer = pipeWriter;
final ResultSet rs = statSource.executeQuery("SCRIPT")) {
while (rs.next()) {
writer.write(rs.getString(1) + "\n");
}
} catch (SQLException | IOException ex) {
throw new IllegalStateException("Producing script from the source DB is failing.", ex);
}
}
});
thread.shutdown();
return threadFuture;
}
}