提交 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 {
isOwner = true;
index.getIndexType().setBelongsToConstraint(true);
} else {
index = getIndex(table, indexColumns, true);
index = getIndex(table, indexColumns, false);
if (index == null) {
index = createIndex(table, indexColumns, false);
isOwner = true;
......@@ -329,29 +329,30 @@ public class AlterTableAddConstraint extends SchemaCommand {
}
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,
IndexColumn[] cols) {
IndexColumn[] cols) {
if (idx.getTable() != table || !idx.getIndexType().isUnique()) {
return false;
}
Column[] indexCols = idx.getColumns();
if (indexCols.length > cols.length) {
return false;
HashSet<Column> indexColsSet = New.hashSet();
for (Column c : indexCols) {
indexColsSet.add(c);
}
HashSet<Column> set = New.hashSet();
HashSet<Column> colsSet = New.hashSet();
for (IndexColumn c : cols) {
set.add(c.column);
colsSet.add(c.column);
}
for (Column c : indexCols) {
// all columns of the index must be part of the list,
// but not all columns of the list need to be part of the index
if (!set.contains(c)) {
return false;
}
}
return true;
}
return colsSet.equals(indexColsSet);
}
private static boolean canUseIndex(Index existingIndex, Table table,
IndexColumn[] cols, boolean moreColumnsOk) {
......
......@@ -30,9 +30,11 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.regex.Pattern;
import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface;
import org.h2.engine.ConnectionInfo;
......@@ -1737,6 +1739,12 @@ public class JdbcConnection extends TraceObject
+ ");");
}
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)) {
throw new SQLClientInfoException(
......
......@@ -197,7 +197,7 @@ public class MVPrimaryIndex extends BaseIndex {
}
}
TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.entryIterator(min), max);
return new MVStoreCursor(session, map.entryIterator(min, max));
}
@Override
......@@ -270,13 +270,13 @@ public class MVPrimaryIndex extends BaseIndex {
TransactionMap<Value, Value> map = getMap(session);
ValueLong v = (ValueLong) (first ? map.firstKey() : map.lastKey());
if (v == null) {
return new MVStoreCursor(session, Collections
.<Entry<Value, Value>> emptyList().iterator(), null);
return new MVStoreCursor(session,
Collections.<Entry<Value, Value>> emptyList().iterator());
}
Value value = map.get(v);
Entry<Value, Value> e = new DataUtils.MapEntry<Value, Value>(v, value);
List<Entry<Value, Value>> list = Arrays.asList(e);
MVStoreCursor c = new MVStoreCursor(session, list.iterator(), v);
List<Entry<Value, Value>> list = Collections.singletonList(e);
MVStoreCursor c = new MVStoreCursor(session, list.iterator());
c.next();
return c;
}
......@@ -356,7 +356,7 @@ public class MVPrimaryIndex extends BaseIndex {
*/
Cursor find(Session session, ValueLong first, ValueLong last) {
TransactionMap<Value, Value> map = getMap(session);
return new MVStoreCursor(session, map.entryIterator(first), last);
return new MVStoreCursor(session, map.entryIterator(first, last));
}
@Override
......@@ -385,14 +385,12 @@ public class MVPrimaryIndex extends BaseIndex {
private final Session session;
private final Iterator<Entry<Value, Value>> it;
private final ValueLong last;
private Entry<Value, Value> current;
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.it = it;
this.last = last;
}
@Override
......@@ -415,9 +413,6 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public boolean next() {
current = it.hasNext() ? it.next() : null;
if (current != null && current.getKey().getLong() > last.getLong()) {
current = null;
}
row = null;
return current != null;
}
......
......@@ -1470,7 +1470,7 @@ public class TransactionStore {
* @param from the first key to return
* @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>>() {
private Entry<K, V> current;
private K currentKey = from;
......@@ -1507,6 +1507,9 @@ public class TransactionStore {
}
}
final K key = k;
if (to != null && map.getKeyType().compare(k, to) > 0) {
break;
}
VersionedValue data = cursor.getValue();
data = getValue(key, readLogId, data);
if (data != null && data.value != null) {
......
......@@ -13,14 +13,15 @@ import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
import org.h2.util.IOUtils;
import org.h2.util.JdbcUtils;
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 standalone database.
* Creates a cluster from a stand-alone database.
* <br />
* Copies a database to another location if required.
* @h2.resource
......@@ -100,116 +101,98 @@ public class CreateCluster extends Tool {
private static void process(String urlSource, String urlTarget,
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();
// verify that the database doesn't exist,
// 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
// even if the cluster is enabled
connSource = DriverManager.getConnection(urlSource +
";CLUSTER=''", user, password);
statSource = connSource.createStatement();
org.h2.Driver.load();
try (Connection connSource = DriverManager.getConnection(
// use cluster='' so connecting is possible
// even if the cluster is enabled
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");
pipeReader = new PipedReader();
try {
/*
* Pipe writer is used + closed in the inner class, in a
* separate thread (needs to be final). It should be initialized
* within try{} so an exception could be caught if creation
* 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.
// Start writing to pipe writer in separate thread.
final ResultSet rs = statSource.executeQuery("SCRIPT");
// Delete the target database first.
connTarget = DriverManager.getConnection(
urlTarget + ";CLUSTER=''", user, password);
statTarget = connTarget.createStatement();
statTarget.execute("DROP ALL OBJECTS DELETE FILES");
connTarget.close();
new Thread(
new Runnable(){
@Override
public void run() {
try {
while (rs.next()) {
pipeWriter.write(rs.getString(1) + "\n");
}
} catch (SQLException ex) {
throw new IllegalStateException("Producing script from the source DB is failing.",ex);
} catch (IOException ex) {
throw new IllegalStateException("Producing script from the source DB is failing.",ex);
} finally {
IOUtils.closeSilently(pipeWriter);
}
}
}
).start();
performTransfer(statSource, urlTarget, user, password, serverList);
} finally {
// switch back to the regular mode
statSource.execute("SET EXCLUSIVE FALSE");
}
}
}
// Read data from pipe reader, restore on target.
connTarget = DriverManager.getConnection(urlTarget, user, password);
RunScript.execute(connTarget,pipeReader);
statTarget = connTarget.createStatement();
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);
} 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);
} catch (IOException ex) {
throw new SQLException(ex);
}
}
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 {
"foreign key(a_id) references a(id)");
stat.execute("update a set x=200");
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();
}
......
......@@ -13,6 +13,8 @@ import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
/**
* Tests the client info
*/
......@@ -35,6 +37,7 @@ public class TestConnection extends TestBase {
testSetSupportedClientInfoProperties();
testSetUnsupportedClientInfoProperties();
testSetInternalProperty();
testSetInternalPropertyToInitialValue();
testSetGetSchema();
}
......@@ -47,6 +50,29 @@ public class TestConnection extends TestBase {
assertThrows(SQLClientInfoException.class, conn).setClientInfo("server23", "SomeValue");
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 {
Connection conn = getConnection("clientInfo");
......
......@@ -77,7 +77,9 @@ public class TestScript extends TestBase {
return;
}
reconnectOften = !config.memory && config.big;
testScript("testScript.sql");
testScript("altertable-index-reuse.sql");
testScript("query-optimisations.sql");
testScript("commands-dml-script.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;
import java.sql.SQLException;
import java.sql.Savepoint;
import java.sql.Statement;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.h2.api.ErrorCode;
import org.h2.engine.Constants;
......@@ -85,6 +86,7 @@ public class TestMVTableEngine extends TestBase {
testDataTypes();
testLocking();
testSimple();
testReverseDeletePerformance();
}
private void testLobCopy() throws Exception {
......@@ -1478,4 +1480,32 @@ public class TestMVTableEngine extends TestBase {
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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论