提交 7e8d5029 authored 作者: andrei's avatar andrei

Merge branch 'master' of https://github.com/h2database/h2database into issue_435

...@@ -244,20 +244,20 @@ public class AlterTableAddConstraint extends SchemaCommand { ...@@ -244,20 +244,20 @@ public class AlterTableAddConstraint extends SchemaCommand {
} }
int id = getObjectId(); int id = getObjectId();
String name = generateConstraintName(table); String name = generateConstraintName(table);
ConstraintReferential ref = new ConstraintReferential(getSchema(), ConstraintReferential refConstraint = new ConstraintReferential(getSchema(),
id, name, table); id, name, table);
ref.setColumns(indexColumns); refConstraint.setColumns(indexColumns);
ref.setIndex(index, isOwner); refConstraint.setIndex(index, isOwner);
ref.setRefTable(refTable); refConstraint.setRefTable(refTable);
ref.setRefColumns(refIndexColumns); refConstraint.setRefColumns(refIndexColumns);
ref.setRefIndex(refIndex, isRefOwner); refConstraint.setRefIndex(refIndex, isRefOwner);
if (checkExisting) { if (checkExisting) {
ref.checkExistingData(session); refConstraint.checkExistingData(session);
} }
constraint = ref; refTable.addConstraint(refConstraint);
refTable.addConstraint(constraint); refConstraint.setDeleteAction(deleteAction);
ref.setDeleteAction(deleteAction); refConstraint.setUpdateAction(updateAction);
ref.setUpdateAction(updateAction); constraint = refConstraint;
break; break;
} }
default: default:
......
...@@ -30,9 +30,11 @@ import java.util.ArrayList; ...@@ -30,9 +30,11 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface; import org.h2.command.CommandInterface;
import org.h2.engine.ConnectionInfo; import org.h2.engine.ConnectionInfo;
...@@ -1738,6 +1740,12 @@ public class JdbcConnection extends TraceObject ...@@ -1738,6 +1740,12 @@ public class JdbcConnection extends TraceObject
} }
checkClosed(); checkClosed();
// no change to property: Ignore call. This early exit fixes a problem with websphere liberty
// resetting the client info of a pooled connection to its initial values.
if (Objects.equals(value, getClientInfo(name))) {
return;
}
if (isInternalProperty(name)) { if (isInternalProperty(name)) {
throw new SQLClientInfoException( throw new SQLClientInfoException(
"Property name '" + name + " is used internally by H2.", "Property name '" + name + " is used internally by H2.",
......
...@@ -13,14 +13,15 @@ import java.sql.DriverManager; ...@@ -13,14 +13,15 @@ import java.sql.DriverManager;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import org.h2.api.ErrorCode; import java.util.concurrent.ExecutionException;
import org.h2.engine.Constants; import java.util.concurrent.ExecutorService;
import org.h2.util.IOUtils; import java.util.concurrent.Executors;
import org.h2.util.JdbcUtils; import java.util.concurrent.Future;
import org.h2.util.Tool; import org.h2.util.Tool;
/** /**
* Creates a cluster from a standalone database. * Creates a cluster from a stand-alone database.
* <br /> * <br />
* Copies a database to another location if required. * Copies a database to another location if required.
* @h2.resource * @h2.resource
...@@ -100,116 +101,98 @@ public class CreateCluster extends Tool { ...@@ -100,116 +101,98 @@ public class CreateCluster extends Tool {
private static void process(String urlSource, String urlTarget, private static void process(String urlSource, String urlTarget,
String user, String password, String serverList) throws SQLException { String user, String password, String serverList) throws SQLException {
Connection connSource = null, connTarget = null;
Statement statSource = null, statTarget = null;
PipedReader pipeReader = null;
try {
org.h2.Driver.load(); org.h2.Driver.load();
// verify that the database doesn't exist, try (Connection connSource = DriverManager.getConnection(
// or if it exists (an old cluster instance), it is deleted
boolean exists = true;
try {
connTarget = DriverManager.getConnection(urlTarget +
";IFEXISTS=TRUE;CLUSTER=" + Constants.CLUSTERING_ENABLED,
user, password);
Statement stat = connTarget.createStatement();
stat.execute("DROP ALL OBJECTS DELETE FILES");
stat.close();
exists = false;
connTarget.close();
} catch (SQLException e) {
if (e.getErrorCode() == ErrorCode.DATABASE_NOT_FOUND_1) {
// database does not exists yet - ok
exists = false;
} else {
throw e;
}
}
if (exists) {
throw new SQLException(
"Target database must not yet exist. Please delete it first: " +
urlTarget);
}
// use cluster='' so connecting is possible // use cluster='' so connecting is possible
// even if the cluster is enabled // even if the cluster is enabled
connSource = DriverManager.getConnection(urlSource + urlSource + ";CLUSTER=''", user, password);
";CLUSTER=''", user, password); Statement statSource = connSource.createStatement())
statSource = connSource.createStatement(); {
// enable the exclusive mode and close other connections, // enable the exclusive mode and close other connections,
// so that data can't change while restoring the second database // so that data can't change while restoring the second database
statSource.execute("SET EXCLUSIVE 2"); statSource.execute("SET EXCLUSIVE 2");
pipeReader = new PipedReader();
try { try {
/* performTransfer(statSource, urlTarget, user, password, serverList);
* Pipe writer is used + closed in the inner class, in a } finally {
* separate thread (needs to be final). It should be initialized // switch back to the regular mode
* within try{} so an exception could be caught if creation statSource.execute("SET EXCLUSIVE FALSE");
* fails. In that scenario, the the writer should be null and }
* needs no closing, and the main goal is that finally{} should }
* bring the source DB out of exclusive mode, and close the }
* reader.
*/
final PipedWriter pipeWriter = new PipedWriter(pipeReader);
// Backup data from source database in script form. private static void performTransfer(Statement statSource, String urlTarget,
// Start writing to pipe writer in separate thread. String user, String password, String serverList) throws SQLException {
final ResultSet rs = statSource.executeQuery("SCRIPT");
// Delete the target database first. // Delete the target database first.
connTarget = DriverManager.getConnection( try (Connection connTarget = DriverManager.getConnection(
urlTarget + ";CLUSTER=''", user, password); urlTarget + ";CLUSTER=''", user, password);
statTarget = connTarget.createStatement(); Statement statTarget = connTarget.createStatement())
{
statTarget.execute("DROP ALL OBJECTS DELETE FILES"); statTarget.execute("DROP ALL OBJECTS DELETE FILES");
connTarget.close(); }
try (PipedReader pipeReader = new PipedReader()) {
Future<?> threadFuture = startWriter(pipeReader, statSource);
new Thread( // Read data from pipe reader, restore on target.
new Runnable(){ try (Connection connTarget = DriverManager.getConnection(
@Override urlTarget, user, password);
public void run() { Statement statTarget = connTarget.createStatement())
{
RunScript.execute(connTarget, pipeReader);
// Check if the writer encountered any exception
try { try {
while (rs.next()) { threadFuture.get();
pipeWriter.write(rs.getString(1) + "\n"); } catch (ExecutionException ex) {
} throw new SQLException(ex.getCause());
} catch (SQLException ex) { } catch (InterruptedException ex) {
throw new IllegalStateException("Producing script from the source DB is failing.",ex); throw new SQLException(ex);
} catch (IOException ex) {
throw new IllegalStateException("Producing script from the source DB is failing.",ex);
} finally {
IOUtils.closeSilently(pipeWriter);
}
}
} }
).start();
// Read data from pipe reader, restore on target.
connTarget = DriverManager.getConnection(urlTarget, user, password);
RunScript.execute(connTarget,pipeReader);
statTarget = connTarget.createStatement();
// set the cluster to the serverList on both databases // set the cluster to the serverList on both databases
statSource.executeUpdate("SET CLUSTER '" + serverList + "'"); statSource.executeUpdate("SET CLUSTER '" + serverList + "'");
statTarget.executeUpdate("SET CLUSTER '" + serverList + "'"); statTarget.executeUpdate("SET CLUSTER '" + serverList + "'");
}
} catch (IOException ex) { } catch (IOException ex) {
throw new SQLException(ex); throw new SQLException(ex);
} finally {
// switch back to the regular mode
statSource.execute("SET EXCLUSIVE FALSE");
} }
} finally {
IOUtils.closeSilently(pipeReader);
JdbcUtils.closeSilently(statSource);
JdbcUtils.closeSilently(statTarget);
JdbcUtils.closeSilently(connSource);
JdbcUtils.closeSilently(connTarget);
} }
private static Future<?> startWriter(final PipedReader pipeReader,
final Statement statSource) throws SQLException, IOException {
final ExecutorService thread = Executors.newFixedThreadPool(1);
// 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 (final PipedWriter pipeWriter = new PipedWriter(pipeReader);
final ResultSet rs = statSource.executeQuery("SCRIPT"))
{
while (rs.next()) {
pipeWriter.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;
} }
} }
...@@ -13,6 +13,8 @@ import java.sql.SQLException; ...@@ -13,6 +13,8 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.Properties; import java.util.Properties;
/** /**
* Tests the client info * Tests the client info
*/ */
...@@ -35,6 +37,7 @@ public class TestConnection extends TestBase { ...@@ -35,6 +37,7 @@ public class TestConnection extends TestBase {
testSetSupportedClientInfoProperties(); testSetSupportedClientInfoProperties();
testSetUnsupportedClientInfoProperties(); testSetUnsupportedClientInfoProperties();
testSetInternalProperty(); testSetInternalProperty();
testSetInternalPropertyToInitialValue();
testSetGetSchema(); testSetGetSchema();
} }
...@@ -48,6 +51,29 @@ public class TestConnection extends TestBase { ...@@ -48,6 +51,29 @@ public class TestConnection extends TestBase {
conn.close(); conn.close();
} }
/**
* Test that no exception is thrown if the client info of a connection managed in a connection pool is reset
* to its initial values.
*
* This is needed when using h2 in websphere liberty.
*/
private void testSetInternalPropertyToInitialValue() throws SQLException {
// Use MySQL-mode since this allows all property names
// (apart from h2 internal names).
Connection conn = getConnection("clientInfoMySQL;MODE=MySQL");
String numServersPropertyName = "numServers";
String numServers = conn.getClientInfo(numServersPropertyName);
conn.setClientInfo(numServersPropertyName, numServers);
assertEquals(conn.getClientInfo(numServersPropertyName), numServers);
conn.close();
}
private void testSetUnsupportedClientInfoProperties() throws SQLException { private void testSetUnsupportedClientInfoProperties() throws SQLException {
Connection conn = getConnection("clientInfo"); Connection conn = getConnection("clientInfo");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论