提交 27293b7c authored 作者: Noel Grandin's avatar Noel Grandin

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

...@@ -212,7 +212,7 @@ public class AlterTableAddConstraint extends SchemaCommand { ...@@ -212,7 +212,7 @@ public class AlterTableAddConstraint extends SchemaCommand {
isOwner = true; isOwner = true;
index.getIndexType().setBelongsToConstraint(true); index.getIndexType().setBelongsToConstraint(true);
} else { } else {
index = getIndex(table, indexColumns, true); index = getIndex(table, indexColumns, false);
if (index == null) { if (index == null) {
index = createIndex(table, indexColumns, false); index = createIndex(table, indexColumns, false);
isOwner = true; isOwner = true;
...@@ -330,27 +330,28 @@ public class AlterTableAddConstraint extends SchemaCommand { ...@@ -330,27 +330,28 @@ public class AlterTableAddConstraint extends SchemaCommand {
return null; return null;
} }
// all cols must be in the index key, the order doesn't matter and there must be no other fields in the index key
private static boolean canUseUniqueIndex(Index idx, Table table, private static boolean canUseUniqueIndex(Index idx, Table table,
IndexColumn[] cols) { IndexColumn[] cols) {
if (idx.getTable() != table || !idx.getIndexType().isUnique()) { if (idx.getTable() != table || !idx.getIndexType().isUnique()) {
return false; return false;
} }
Column[] indexCols = idx.getColumns(); Column[] indexCols = idx.getColumns();
if (indexCols.length > cols.length) {
return false; HashSet<Column> indexColsSet = New.hashSet();
}
HashSet<Column> set = New.hashSet();
for (IndexColumn c : cols) {
set.add(c.column);
}
for (Column c : indexCols) { for (Column c : indexCols) {
// all columns of the index must be part of the list, indexColsSet.add(c);
// but not all columns of the list need to be part of the index
if (!set.contains(c)) {
return false;
} }
HashSet<Column> colsSet = New.hashSet();
for (IndexColumn c : cols) {
colsSet.add(c.column);
} }
return true;
return colsSet.equals(indexColsSet);
} }
private static boolean canUseIndex(Index existingIndex, Table table, private static boolean canUseIndex(Index existingIndex, Table table,
......
...@@ -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.",
......
...@@ -197,7 +197,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -197,7 +197,7 @@ public class MVPrimaryIndex extends BaseIndex {
} }
} }
TransactionMap<Value, Value> map = getMap(session); TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.entryIterator(min), max); return new MVStoreCursor(session, map.entryIterator(min, max));
} }
@Override @Override
...@@ -270,13 +270,13 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -270,13 +270,13 @@ public class MVPrimaryIndex extends BaseIndex {
TransactionMap<Value, Value> map = getMap(session); TransactionMap<Value, Value> map = getMap(session);
ValueLong v = (ValueLong) (first ? map.firstKey() : map.lastKey()); ValueLong v = (ValueLong) (first ? map.firstKey() : map.lastKey());
if (v == null) { if (v == null) {
return new MVStoreCursor(session, Collections return new MVStoreCursor(session,
.<Entry<Value, Value>> emptyList().iterator(), null); Collections.<Entry<Value, Value>> emptyList().iterator());
} }
Value value = map.get(v); Value value = map.get(v);
Entry<Value, Value> e = new DataUtils.MapEntry<Value, Value>(v, value); Entry<Value, Value> e = new DataUtils.MapEntry<Value, Value>(v, value);
List<Entry<Value, Value>> list = Arrays.asList(e); List<Entry<Value, Value>> list = Collections.singletonList(e);
MVStoreCursor c = new MVStoreCursor(session, list.iterator(), v); MVStoreCursor c = new MVStoreCursor(session, list.iterator());
c.next(); c.next();
return c; return c;
} }
...@@ -356,7 +356,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -356,7 +356,7 @@ public class MVPrimaryIndex extends BaseIndex {
*/ */
Cursor find(Session session, ValueLong first, ValueLong last) { Cursor find(Session session, ValueLong first, ValueLong last) {
TransactionMap<Value, Value> map = getMap(session); TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.entryIterator(first), last); return new MVStoreCursor(session, map.entryIterator(first, last));
} }
@Override @Override
...@@ -385,14 +385,12 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -385,14 +385,12 @@ public class MVPrimaryIndex extends BaseIndex {
private final Session session; private final Session session;
private final Iterator<Entry<Value, Value>> it; private final Iterator<Entry<Value, Value>> it;
private final ValueLong last;
private Entry<Value, Value> current; private Entry<Value, Value> current;
private Row row; private Row row;
public MVStoreCursor(Session session, Iterator<Entry<Value, Value>> it, ValueLong last) { public MVStoreCursor(Session session, Iterator<Entry<Value, Value>> it) {
this.session = session; this.session = session;
this.it = it; this.it = it;
this.last = last;
} }
@Override @Override
...@@ -415,9 +413,6 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -415,9 +413,6 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public boolean next() { public boolean next() {
current = it.hasNext() ? it.next() : null; current = it.hasNext() ? it.next() : null;
if (current != null && current.getKey().getLong() > last.getLong()) {
current = null;
}
row = null; row = null;
return current != null; return current != null;
} }
......
...@@ -1470,7 +1470,7 @@ public class TransactionStore { ...@@ -1470,7 +1470,7 @@ public class TransactionStore {
* @param from the first key to return * @param from the first key to return
* @return the iterator * @return the iterator
*/ */
public Iterator<Entry<K, V>> entryIterator(final K from) { public Iterator<Entry<K, V>> entryIterator(final K from, final K to) {
return new Iterator<Entry<K, V>>() { return new Iterator<Entry<K, V>>() {
private Entry<K, V> current; private Entry<K, V> current;
private K currentKey = from; private K currentKey = from;
...@@ -1507,6 +1507,9 @@ public class TransactionStore { ...@@ -1507,6 +1507,9 @@ public class TransactionStore {
} }
} }
final K key = k; final K key = k;
if (to != null && map.getKeyType().compare(k, to) > 0) {
break;
}
VersionedValue data = cursor.getValue(); VersionedValue data = cursor.getValue();
data = getValue(key, readLogId, data); data = getValue(key, readLogId, data);
if (data != null && data.value != null) { if (data != null && data.value != null) {
......
...@@ -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;
} }
} }
...@@ -150,18 +150,6 @@ public class TestCases extends TestBase { ...@@ -150,18 +150,6 @@ public class TestCases extends TestBase {
"foreign key(a_id) references a(id)"); "foreign key(a_id) references a(id)");
stat.execute("update a set x=200"); stat.execute("update a set x=200");
stat.execute("drop table if exists a, b"); stat.execute("drop table if exists a, b");
stat.execute("drop all objects");
stat.execute("create table parent(id int primary key)");
stat.execute("create table child(id int, parent_id int, x int)");
stat.execute("create index y on child(parent_id, x)");
stat.execute("alter table child add constraint z " +
"foreign key(parent_id) references parent(id)");
ResultSet rs = stat.executeQuery(
"select * from information_schema.indexes where table_name = 'CHILD'");
while (rs.next()) {
assertEquals("Y", rs.getString("index_name"));
}
conn.close(); conn.close();
} }
......
...@@ -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");
......
...@@ -77,7 +77,9 @@ public class TestScript extends TestBase { ...@@ -77,7 +77,9 @@ public class TestScript extends TestBase {
return; return;
} }
reconnectOften = !config.memory && config.big; reconnectOften = !config.memory && config.big;
testScript("testScript.sql"); testScript("testScript.sql");
testScript("altertable-index-reuse.sql");
testScript("query-optimisations.sql"); testScript("query-optimisations.sql");
testScript("commands-dml-script.sql"); testScript("commands-dml-script.sql");
testScript("commands-dml-create-view.sql"); testScript("commands-dml-create-view.sql");
......
-- Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
-- and the EPL 1.0 (http://h2database.com/html/license.html).
-- Initial Developer: H2 Group
--
CREATE TABLE "domains" ("id" bigint NOT NULL auto_increment PRIMARY KEY);
> ok
CREATE TABLE "users" ("id" bigint NOT NULL auto_increment PRIMARY KEY,"username" varchar_ignorecase(255),"domain" bigint,"desc" varchar_ignorecase(255));
> ok
-- adds constraint on (domain,username) and generates unique index domainusername_key_INDEX_xxx
ALTER TABLE "users" ADD CONSTRAINT "domainusername_key" UNIQUE ("domain","username");
> ok
-- adds foreign key on domain - if domainusername_key didn't exist it would create unique index on domain, but it reuses the existing index
ALTER TABLE "users" ADD CONSTRAINT "udomain_fkey" FOREIGN KEY ("domain") REFERENCES "domains"("id") ON DELETE RESTRICT;
> ok
-- now we drop the domainusername_key, but domainusername_key_INDEX_xxx is used by udomain_fkey and was not being dropped
-- this was an issue, because it's a unique index and still enforcing constraint on (domain,username)
ALTER TABLE "users" DROP CONSTRAINT "domainusername_key";
> ok
insert into "domains" ("id") VALUES (1);
> update count: 1
insert into "users" ("username","domain","desc") VALUES ('test',1,'first user');
> update count: 1
-- should work,because we dropped domainusername_key, but failed: Unique index or primary key violation
INSERT INTO "users" ("username","domain","desc") VALUES ('test',1,'second user');
> update count: 1
...@@ -18,6 +18,7 @@ import java.sql.ResultSet; ...@@ -18,6 +18,7 @@ import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Savepoint; import java.sql.Savepoint;
import java.sql.Statement; import java.sql.Statement;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
...@@ -85,6 +86,7 @@ public class TestMVTableEngine extends TestBase { ...@@ -85,6 +86,7 @@ public class TestMVTableEngine extends TestBase {
testDataTypes(); testDataTypes();
testLocking(); testLocking();
testSimple(); testSimple();
testReverseDeletePerformance();
} }
private void testLobCopy() throws Exception { private void testLobCopy() throws Exception {
...@@ -1478,4 +1480,32 @@ public class TestMVTableEngine extends TestBase { ...@@ -1478,4 +1480,32 @@ public class TestMVTableEngine extends TestBase {
conn.close(); conn.close();
} }
private void testReverseDeletePerformance() throws Exception {
long direct = 0;
long reverse = 0;
for (int i = 0; i < 5; i++) {
reverse += testReverseDeletePerformance(true);
direct += testReverseDeletePerformance(false);
}
assertTrue("direct: " + direct + ", reverse: " + reverse, 2 * Math.abs(reverse - direct) < reverse + direct);
}
private long testReverseDeletePerformance(boolean reverse) throws Exception {
deleteDb(getTestName());
String dbName = getTestName() + ";MV_STORE=TRUE";
try (Connection conn = getConnection(dbName)) {
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE test(id INT PRIMARY KEY, name VARCHAR) AS SELECT x, x || space(1024) || x FROM system_range(1, 1000)");
conn.setAutoCommit(false);
PreparedStatement prep = conn.prepareStatement("DELETE FROM test WHERE id = ?");
long start = System.nanoTime();
for (int i = 0; i < 1000; i++) {
prep.setInt(1, reverse ? 1000 - i : i);
prep.execute();
}
long end = System.nanoTime();
conn.commit();
return TimeUnit.NANOSECONDS.toMillis(end - start);
}
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论