org.h2.tools.CreateCluster Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of h2-mvstore Show documentation
Show all versions of h2-mvstore Show documentation
Fork of h2database to maintain Java 8 compatibility
The newest version!
/*
* Copyright 2004-2023 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (https://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.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.jdbc.JdbcConnection;
import org.h2.util.Tool;
/**
* Creates a cluster from a stand-alone database.
*
* Copies a database to another location if required.
*/
public class CreateCluster extends Tool {
/**
* Options are case sensitive.
*
* Supported options
* [-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
*
*
* @param args the command line arguments
* @throws SQLException on failure
*/
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.
*
* @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
* @throws SQLException on failure
*/
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 {
// use cluster='' so connecting is possible
// even if the cluster is enabled
try (JdbcConnection connSource = new JdbcConnection(urlSource + ";CLUSTER=''", null, user, password, false);
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 (JdbcConnection connTarget = new JdbcConnection(urlTarget + ";CLUSTER=''", null, user, password, false);
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 (JdbcConnection connTarget = new JdbcConnection(urlTarget, null, user, password, false);
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(() -> {
// 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;
}
}