提交 344f2633 authored 作者: Thomas Mueller's avatar Thomas Mueller

MVTableEngine

上级 27d8e115
......@@ -25,7 +25,8 @@ Change Log
is no longer supported. This is to simplify the MVTableEngine.
</li><li>New column "information_schema.tables.row_count_estimate".
</li><li>Issue 468: trunc(timestamp) could return the wrong value.
</li><li><li>Fix deadlock when updating LOB's concurrently. See TestLob.testDeadlock2().
</li><li>Fixed a deadlock when updating LOB's concurrently. See TestLob.testDeadlock2().
</li><li>Fixed a deadlock related to very large temporary result sets.
</li></ul>
<h2>Version 1.3.172 (2013-05-25)</h2>
......
......@@ -100,6 +100,7 @@ public class Insert extends Prepared implements ResultTarget {
if (listSize > 0) {
int columnLen = columns.length;
for (int x = 0; x < listSize; x++) {
session.startStatementWithinTransaction();
Row newRow = table.getTemplateRow();
Expression[] expr = list.get(x);
setCurrentRowNumber(x + 1);
......
......@@ -7,7 +7,6 @@
package org.h2.engine;
import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
......@@ -646,6 +645,9 @@ public class Database implements DataHandler {
for (MetaRecord rec : records) {
rec.execute(this, systemSession, eventListener);
}
if (mvStore != null) {
mvStore.rollback();
}
recompileInvalidViews(systemSession);
starting = false;
if (!readOnly) {
......@@ -1797,11 +1799,16 @@ public class Database implements DataHandler {
* Flush all pending changes to the transaction log.
*/
public synchronized void flush() {
if (readOnly || pageStore == null) {
if (readOnly) {
return;
}
if (pageStore != null) {
pageStore.flushLog();
}
if (mvStore != null) {
mvStore.store();
}
}
public void setEventListener(DatabaseEventListener eventListener) {
this.eventListener = eventListener;
......@@ -2276,6 +2283,9 @@ public class Database implements DataHandler {
pageStore.checkpoint();
}
}
if (mvStore != null) {
mvStore.store();
}
}
getTempFileDeleter().deleteUnused();
}
......
......@@ -67,6 +67,20 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
}
}
/**
* Check that the index columns are not CLOB or BLOB.
*
* @param columns the columns
*/
protected static void checkIndexColumnTypes(IndexColumn[] columns) {
for (IndexColumn c : columns) {
int type = c.column.getType();
if (type == Value.CLOB || type == Value.BLOB) {
throw DbException.get(ErrorCode.FEATURE_NOT_SUPPORTED_1, "Index on BLOB or CLOB column: " + c.column.getCreateSQL());
}
}
}
@Override
public String getDropSQL() {
return null;
......
......@@ -75,15 +75,6 @@ public class PageBtreeIndex extends PageIndex {
memoryPerPage = (Constants.MEMORY_PAGE_BTREE + store.getPageSize()) >> 2;
}
private static void checkIndexColumnTypes(IndexColumn[] columns) {
for (IndexColumn c : columns) {
int type = c.column.getType();
if (type == Value.CLOB || type == Value.BLOB) {
throw DbException.get(ErrorCode.FEATURE_NOT_SUPPORTED_1, "Index on BLOB or CLOB column: " + c.column.getCreateSQL());
}
}
}
@Override
public void add(Session session, Row row) {
if (trace.isDebugEnabled()) {
......
......@@ -31,6 +31,9 @@ public class TreeIndex extends BaseIndex {
public TreeIndex(RegularTable table, int id, String indexName, IndexColumn[] columns, IndexType indexType) {
initBaseIndex(table, id, indexName, columns, indexType);
tableData = table;
if (!database.isStarting()) {
checkIndexColumnTypes(columns);
}
}
@Override
......
......@@ -535,7 +535,7 @@ public class MVStore {
}
} catch (Exception e) {
try {
close(false);
closeFile(false);
} catch (Exception e2) {
// ignore
}
......@@ -668,10 +668,6 @@ public class MVStore {
* written but not committed, this is rolled back. All open maps are closed.
*/
public void close() {
close(true);
}
private void close(boolean shrinkIfPossible) {
if (closed) {
return;
}
......@@ -681,6 +677,20 @@ public class MVStore {
store(false);
}
}
closeFile(true);
}
/**
* Close the file and the store, without writing anything.
*/
public void closeImmediately() {
closeFile(false);
}
private void closeFile(boolean shrinkIfPossible) {
if (closed) {
return;
}
closed = true;
if (file == null) {
return;
......
......@@ -329,8 +329,7 @@ public class MVPrimaryIndex extends BaseIndex {
return dataMap;
}
Transaction t = mvTable.getTransaction(session);
long savepoint = session.getStatementSavepoint();
return dataMap.getInstance(t, savepoint);
return dataMap.getInstance(t, Long.MAX_VALUE);
}
/**
......
......@@ -69,15 +69,6 @@ public class MVSecondaryIndex extends BaseIndex {
dataMap = mvTable.getTransaction(null).openMap(mapName, mapBuilder);
}
private static void checkIndexColumnTypes(IndexColumn[] columns) {
for (IndexColumn c : columns) {
int type = c.column.getType();
if (type == Value.CLOB || type == Value.BLOB) {
throw DbException.get(ErrorCode.FEATURE_NOT_SUPPORTED_1, "Index on BLOB or CLOB column: " + c.column.getCreateSQL());
}
}
}
@Override
public void close(Session session) {
// ok
......@@ -98,7 +89,7 @@ public class MVSecondaryIndex extends BaseIndex {
ValueArray array = getKey(row);
if (indexType.isUnique()) {
array.getList()[keyColumns - 1] = ValueLong.get(Long.MIN_VALUE);
ValueArray key = (ValueArray) map.ceilingKey(array);
ValueArray key = (ValueArray) map.getLatestCeilingKey(array);
if (key != null) {
SearchRow r2 = getRow(key.getList());
if (compareRows(row, r2) == 0) {
......@@ -253,8 +244,7 @@ public class MVSecondaryIndex extends BaseIndex {
return dataMap;
}
Transaction t = mvTable.getTransaction(session);
long savepoint = session.getStatementSavepoint();
return dataMap.getInstance(t, savepoint);
return dataMap.getInstance(t, Long.MAX_VALUE);
}
/**
......
......@@ -6,8 +6,6 @@
*/
package org.h2.mvstore.db;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
......@@ -15,8 +13,9 @@ import org.h2.api.TableEngine;
import org.h2.command.ddl.CreateTableData;
import org.h2.engine.Constants;
import org.h2.engine.Database;
import org.h2.message.DbException;
import org.h2.mvstore.MVStore;
import org.h2.mvstore.db.TransactionStore.Transaction;
import org.h2.table.RegularTable;
import org.h2.table.TableBase;
import org.h2.util.New;
......@@ -28,6 +27,9 @@ public class MVTableEngine implements TableEngine {
@Override
public TableBase createTable(CreateTableData data) {
Database db = data.session.getDatabase();
if (!data.persistData || (data.temporary && !data.persistIndexes)) {
return new RegularTable(data);
}
Store store = db.getMvStore();
if (store == null) {
byte[] key = db.getFilePasswordHash();
......@@ -86,7 +88,7 @@ public class MVTableEngine implements TableEngine {
this.db = db;
this.store = store;
this.transactionStore = new TransactionStore(store,
new ValueDataType(null, null, null));
new ValueDataType(null, db, null));
}
public MVStore getStore() {
......@@ -128,14 +130,7 @@ public class MVTableEngine implements TableEngine {
if (store.isClosed()) {
return;
}
FileChannel f = store.getFile();
if (f != null) {
try {
f.close();
} catch (IOException e) {
throw DbException.convertIOException(e, "Closing file");
}
}
store.closeImmediately();
}
/**
......@@ -154,6 +149,13 @@ public class MVTableEngine implements TableEngine {
store.setWriteDelay(value);
}
public void rollback() {
List<Transaction> list = transactionStore.getOpenTransactions();
for (Transaction t : list) {
t.rollback();
}
}
}
}
......@@ -61,6 +61,8 @@ public class TransactionStore {
*/
private final MVMap<String, String> settings;
private final DataType dataType;
private long lastTransactionIdStored;
private long lastTransactionId;
......@@ -80,23 +82,24 @@ public class TransactionStore {
* Create a new transaction store.
*
* @param store the store
* @param keyType the data type for map keys
* @param dataType the data type for map keys and values
*/
public TransactionStore(MVStore store, DataType keyType) {
public TransactionStore(MVStore store, DataType dataType) {
this.store = store;
this.dataType = dataType;
settings = store.openMap("settings");
preparedTransactions = store.openMap("openTransactions",
new MVMap.Builder<Long, Object[]>());
// TODO commit of larger transaction could be faster if we have one undo
// log per transaction, or a range delete operation for maps
VersionedValueType oldValueType = new VersionedValueType(keyType);
ArrayType valueType = new ArrayType(new DataType[]{
new ObjectDataType(), new ObjectDataType(), keyType,
VersionedValueType oldValueType = new VersionedValueType(dataType);
ArrayType undoLogValueType = new ArrayType(new DataType[]{
new ObjectDataType(), new ObjectDataType(), dataType,
oldValueType
});
MVMap.Builder<long[], Object[]> builder =
new MVMap.Builder<long[], Object[]>().
valueType(valueType);
valueType(undoLogValueType);
// TODO escape other map names, to avoid conflicts
undoLog = store.openMap("undoLog", builder);
init();
......@@ -229,10 +232,7 @@ public class TransactionStore {
int opType = (Integer) op[0];
if (opType == Transaction.OP_REMOVE) {
int mapId = (Integer) op[1];
Map<String, String> meta = store.getMetaMap();
String m = meta.get("map." + mapId);
String mapName = DataUtils.parseMap(m).get("name");
MVMap<Object, VersionedValue> map = store.openMap(mapName);
MVMap<Object, VersionedValue> map = openMap(mapId);
Object key = op[2];
VersionedValue value = map.get(key);
// possibly the entry was added later on
......@@ -247,6 +247,23 @@ public class TransactionStore {
endTransaction(t);
}
private MVMap<Object, VersionedValue> openMap(int mapId) {
// TODO open map by id if possible
Map<String, String> meta = store.getMetaMap();
String m = meta.get("map." + mapId);
if (m == null) {
// the map was removed later on
return null;
}
String mapName = DataUtils.parseMap(m).get("name");
VersionedValueType vt = new VersionedValueType(dataType);
MVMap.Builder<Object, VersionedValue> mapBuilder =
new MVMap.Builder<Object, VersionedValue>().
keyType(dataType).valueType(vt);
MVMap<Object, VersionedValue> map = store.openMap(mapName, mapBuilder);
return map;
}
/**
* Check whether the given transaction id is still open and contains log
* entries.
......@@ -304,11 +321,8 @@ public class TransactionStore {
long[] undoKey = new long[] { t.getId(), logId };
Object[] op = undoLog.get(undoKey);
int mapId = ((Integer) op[1]).intValue();
// TODO open map by id if possible
Map<String, String> meta = store.getMetaMap();
String m = meta.get("map." + mapId);
String mapName = DataUtils.parseMap(m).get("name");
MVMap<Object, VersionedValue> map = store.openMap(mapName);
MVMap<Object, VersionedValue> map = openMap(mapId);
if (map != null) {
Object key = op[2];
VersionedValue oldValue = (VersionedValue) op[3];
if (oldValue == null) {
......@@ -318,6 +332,7 @@ public class TransactionStore {
// this transaction updated the value
map.put(key, oldValue);
}
}
undoLog.remove(undoKey);
}
}
......@@ -339,9 +354,13 @@ public class TransactionStore {
// TODO open map by id if possible
Map<String, String> meta = store.getMetaMap();
String m = meta.get("map." + mapId);
if (m == null) {
// map was removed later on
} else {
String mapName = DataUtils.parseMap(m).get("name");
set.add(mapName);
}
}
return set;
}
......@@ -940,6 +959,68 @@ public class TransactionStore {
return map.lastKey();
}
/**
* Get the most recent smallest key that is larger or equal to this key.
*
* @param key the key (may not be null)
* @return the result
*/
public K getLatestCeilingKey(K key) {
Cursor<K> cursor = map.keyIterator(key);
while (cursor.hasNext()) {
key = cursor.next();
if (get(key, Long.MAX_VALUE) != null) {
return key;
}
}
return null;
}
/**
* Get the smallest key that is larger or equal to this key.
*
* @param key the key (may not be null)
* @return the result
*/
public K ceilingKey(K key) {
int test;
// TODO this method is slow
Cursor<K> cursor = map.keyIterator(key);
while (cursor.hasNext()) {
key = cursor.next();
if (get(key) != null) {
return key;
}
}
return null;
// TODO transactional ceilingKey
// return map.ceilingKey(key);
}
/**
* Get the smallest key that is larger than the given key, or null if no
* such key exists.
*
* @param key the key (may not be null)
* @return the result
*/
public K higherKey(K key) {
// TODO transactional higherKey
return map.higherKey(key);
}
/**
* Get the largest key that is smaller than the given key, or null if no
* such key exists.
*
* @param key the key (may not be null)
* @return the result
*/
public K lowerKey(K key) {
// TODO Auto-generated method stub
return map.lowerKey(key);
}
/**
* Iterate over all keys.
*
......@@ -985,41 +1066,6 @@ public class TransactionStore {
};
}
/**
* Get the smallest key that is larger or equal to this key.
*
* @param key the key (may not be null)
* @return the result
*/
public K ceilingKey(K key) {
// TODO transactional ceilingKey
return map.ceilingKey(key);
}
/**
* Get the smallest key that is larger than the given key, or null if no
* such key exists.
*
* @param key the key (may not be null)
* @return the result
*/
public K higherKey(K key) {
// TODO transactional higherKey
return map.higherKey(key);
}
/**
* Get the largest key that is smaller than the given key, or null if no
* such key exists.
*
* @param key the key (may not be null)
* @return the result
*/
public K lowerKey(K key) {
// TODO Auto-generated method stub
return map.lowerKey(key);
}
public Transaction getTransaction() {
return transaction;
}
......
......@@ -183,7 +183,7 @@ public class FilePathMem extends FilePath {
}
private boolean isRoot() {
return name.equals(getScheme());
return name.equals(getScheme() + ":");
}
private static String getCanonicalPath(String fileName) {
......
......@@ -6,6 +6,11 @@
*/
package org.h2.test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.sql.SQLException;
import java.util.Properties;
import org.h2.Driver;
......@@ -493,7 +498,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
private void runTests() throws SQLException {
int test;
mvStore = false;
// mvStore = true;
smallLog = big = networked = memory = ssl = false;
diskResult = traceSystemOut = diskUndo = false;
......@@ -583,7 +588,9 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestDeadlock().runTest(this);
new TestEncryptedDb().runTest(this);
new TestExclusive().runTest(this);
if (!mvStore) {
new TestFullText().runTest(this);
}
new TestFunctionOverload().runTest(this);
new TestFunctions().runTest(this);
new TestInit().runTest(this);
......@@ -599,13 +606,17 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestMultiThreadedKernel().runTest(this);
new TestOpenClose().runTest(this);
new TestOptimizations().runTest(this);
if (!mvStore) {
new TestOutOfMemory().runTest(this);
}
new TestPowerOff().runTest(this);
new TestQueryCache().runTest(this);
new TestReadOnly().runTest(this);
new TestRecursiveQueries().runTest(this);
new TestRights().runTest(this);
if (!mvStore) {
new TestRunscript().runTest(this);
}
new TestSQLInjection().runTest(this);
new TestSessionsLocks().runTest(this);
new TestSelectCountNonNullColumn().runTest(this);
......@@ -614,9 +625,11 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestSpeed().runTest(this);
new TestTableEngines().runTest(this);
new TestTempTables().runTest(this);
if (!mvStore) {
new TestTransaction().runTest(this);
new TestTriggersConstraints().runTest(this);
new TestTwoPhaseCommit().runTest(this);
}
new TestView().runTest(this);
new TestViewAlterTable().runTest(this);
new TestViewDropView().runTest(this);
......@@ -652,7 +665,9 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestConnectionPool().runTest(this);
new TestDataSource().runTest(this);
new TestXA().runTest(this);
if (!mvStore) {
new TestXASimple().runTest(this);
}
// server
new TestAutoServer().runTest(this);
......@@ -663,8 +678,10 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestMvcc1().runTest(this);
new TestMvcc2().runTest(this);
new TestMvcc3().runTest(this);
if (!mvStore) {
new TestMvccMultiThreaded().runTest(this);
new TestRowLocks().runTest(this);
}
// synth
new TestBtreeIndex().runTest(this);
......@@ -700,7 +717,9 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
// unit
new TestAutoReconnect().runTest(this);
if (!mvStore) {
new TestCache().runTest(this);
}
new TestClearReferences().runTest(this);
new TestCollation().runTest(this);
new TestCompress().runTest(this);
......@@ -717,7 +736,6 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestFileSystem().runTest(this);
new TestIntArray().runTest(this);
new TestIntIntHashMap().runTest(this);
// verify
new TestJmx().runTest(this);
new TestMathUtils().runTest(this);
new TestModifyOnWrite().runTest(this);
......@@ -726,12 +744,16 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestObjectDeserialization().runTest(this);
new TestMultiThreadedKernel().runTest(this);
new TestOverflow().runTest(this);
if (!mvStore) {
new TestPageStore().runTest(this);
}
new TestPageStoreCoverage().runTest(this);
new TestPattern().runTest(this);
new TestPgServer().runTest(this);
new TestReader().runTest(this);
if (!mvStore) {
new TestRecovery().runTest(this);
}
new TestSampleApps().runTest(this);
new TestScriptReader().runTest(this);
runTest("org.h2.test.unit.TestServlet");
......@@ -741,7 +763,9 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestStreams().runTest(this);
new TestStringCache().runTest(this);
new TestStringUtils().runTest(this);
if (!mvStore) {
new TestTools().runTest(this);
}
new TestTraceSystem().runTest(this);
new TestUpgrade().runTest(this);
new TestUtils().runTest(this);
......@@ -750,6 +774,19 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestValueMemory().runTest(this);
}
public static byte[] serialize(Object obj) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream os = new ObjectOutputStream(out);
os.writeObject(obj);
return out.toByteArray();
}
public static Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
ByteArrayInputStream in = new ByteArrayInputStream(data);
ObjectInputStream is = new ObjectInputStream(in);
return is.readObject();
}
private void runTest(String className) {
try {
Class<?> clazz = Class.forName(className);
......
......@@ -247,6 +247,10 @@ public abstract class TestBase {
protected String getURL(String name, boolean admin) {
String url;
if (name.startsWith("jdbc:")) {
if (config.mvStore) {
name = addOption(name, "DEFAULT_TABLE_ENGINE",
"org.h2.mvstore.db.MVTableEngine");
}
return name;
}
if (config.memory) {
......
......@@ -277,10 +277,14 @@ public class TestLob extends TestBase {
conn2.close();
}
/**
* A background task.
*/
private final class Deadlock2Task1 extends Task {
public final Connection conn;
private Deadlock2Task1() throws SQLException {
Deadlock2Task1() throws SQLException {
this.conn = getDeadlock2Connection();
}
......@@ -294,7 +298,9 @@ public class TestLob extends TestBase {
ResultSet rs = stat.executeQuery("select name from test where id = " + random.nextInt(999));
if (rs.next()) {
Reader r = rs.getClob("name").getCharacterStream();
while ( r.read(tmp) > 0) {}
while (r.read(tmp) > 0) {
// ignore
}
r.close();
}
rs.close();
......@@ -315,12 +321,17 @@ public class TestLob extends TestBase {
}
}
}
}
/**
* A background task.
*/
private final class Deadlock2Task2 extends Task {
public final Connection conn;
private Deadlock2Task2() throws SQLException {
Deadlock2Task2() throws SQLException {
this.conn = getDeadlock2Connection();
}
......@@ -332,6 +343,7 @@ public class TestLob extends TestBase {
stat.execute("update test set counter = " + random.nextInt(10) + " where id = " + random.nextInt(1000));
}
}
}
private void testDeadlock2() throws Exception {
......@@ -370,7 +382,7 @@ public class TestLob extends TestBase {
conn.close();
}
private Connection getDeadlock2Connection() throws SQLException {
Connection getDeadlock2Connection() throws SQLException {
return getConnection("lob;MULTI_THREADED=TRUE;LOCK_TIMEOUT=60000");
}
......@@ -473,15 +485,12 @@ public class TestLob extends TestBase {
Statement stat;
conn = getConnection("lob");
stat = conn.createStatement();
try {
stat.execute("create memory table test(x clob unique)");
stat.execute("insert into test values('hello')");
stat.execute("insert into test values('world')");
assertThrows(ErrorCode.DUPLICATE_KEY_1, stat).
execute("insert into test values('world')");
stat.execute("insert into test values(space(10000) || 'a')");
assertThrows(ErrorCode.DUPLICATE_KEY_1, stat).
execute("insert into test values(space(10000) || 'a')");
stat.execute("insert into test values(space(10000) || 'b')");
fail();
} catch (SQLException e) {
assertEquals(ErrorCode.FEATURE_NOT_SUPPORTED_1, e.getErrorCode());
}
conn.close();
}
......
......@@ -23,6 +23,7 @@ import org.h2.engine.Database;
import org.h2.jdbc.JdbcConnection;
import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase;
import org.h2.tools.Restore;
import org.h2.util.Task;
/**
......@@ -42,6 +43,7 @@ public class TestMVTableEngine extends TestBase {
@Override
public void test() throws Exception {
// testSpeed();
testRollbackAfterCrash();
testReferentialIntegrity();
testWriteDelay();
testAutoCommit();
......@@ -110,12 +112,90 @@ int test;
System.out.println((System.currentTimeMillis() - time) + " " + dbName + " after");
}
private void testRollbackAfterCrash() throws Exception {
FileUtils.deleteRecursive(getBaseDir(), true);
Connection conn;
Statement stat;
String url = "mvstore;default_table_engine=org.h2.mvstore.db.MVTableEngine";
String url2 = "mvstore2;default_table_engine=org.h2.mvstore.db.MVTableEngine";
conn = getConnection(url);
stat = conn.createStatement();
stat.execute("create table test(id int)");
stat.execute("insert into test values(0)");
stat.execute("set write_delay 0");
conn.setAutoCommit(false);
stat.execute("insert into test values(1)");
stat.execute("shutdown immediately");
conn = getConnection(url);
stat = conn.createStatement();
ResultSet rs = stat.executeQuery("select row_count_estimate " +
"from information_schema.tables where table_name='TEST'");
rs.next();
assertEquals(1, rs.getLong(1));
stat.execute("drop table test");
stat.execute("create table test(id int primary key, data clob)");
stat.execute("insert into test values(1, space(10000))");
conn.setAutoCommit(false);
stat.execute("delete from test");
stat.execute("checkpoint");
stat.execute("shutdown immediately");
conn = getConnection(url);
stat = conn.createStatement();
rs = stat.executeQuery("select * from test");
assertTrue(rs.next());
stat.execute("drop all objects delete files");
conn.close();
conn = getConnection(url);
stat = conn.createStatement();
stat.execute("create table test(id int primary key, name varchar)");
stat.execute("create index idx_name on test(name, id)");
stat.execute("insert into test select x, x || space(200 * x) from system_range(1, 10)");
conn.setAutoCommit(false);
stat.execute("delete from test where id > 5");
stat.execute("backup to '" + getBaseDir() + "/backup.zip'");
conn.rollback();
Restore.execute(getBaseDir() + "/backup.zip", getBaseDir(), "mvstore2");
Connection conn2;
conn2 = getConnection(url2);
conn.close();
conn2.close();
}
private void testReferentialIntegrity() throws Exception {
FileUtils.deleteRecursive(getBaseDir(), true);
Connection conn;
Statement stat;
conn = getConnection("mvstore;default_table_engine=org.h2.mvstore.db.MVTableEngine");
stat = conn.createStatement();
stat.execute("create table test(id int, parent int " +
"references test(id) on delete cascade)");
stat.execute("insert into test values(0, 0)");
stat.execute("delete from test");
stat.execute("drop table test");
stat.execute("create table parent(id int, name varchar)");
stat.execute("create table child(id int, parentid int, " +
"foreign key(parentid) references parent(id))");
stat.execute("insert into parent values(1, 'mary'), (2, 'john')");
stat.execute("insert into child values(10, 1), (11, 1), (20, 2), (21, 2)");
stat.execute("update parent set name = 'marc' where id = 1");
stat.execute("merge into parent key(id) values(1, 'marcy')");
stat.execute("drop table parent, child");
stat.execute("create table test(id identity, parent bigint, " +
"foreign key(parent) references(id))");
stat.execute("insert into test values(0, 0), (1, NULL), " +
"(2, 1), (3, 3), (4, 3)");
stat.execute("drop table test");
stat.execute("create table parent(id int)");
stat.execute("create table child(pid int)");
......@@ -126,7 +206,7 @@ int test;
"foreign key(pid) references parent(id)");
fail();
} catch (SQLException e) {
// expected
assertEquals(ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1, e.getErrorCode());
}
stat.execute("update child set pid=1");
stat.execute("drop table child, parent");
......@@ -140,16 +220,15 @@ int test;
"foreign key(pid) references parent(id)");
fail();
} catch (SQLException e) {
// expected
assertEquals(ErrorCode.REFERENTIAL_INTEGRITY_VIOLATED_PARENT_MISSING_1, e.getErrorCode());
}
stat.execute("drop table child, parent");
// currently not supported, as previous rows are not visible
// stat.execute("create table test(id identity, parent bigint,
// foreign key(parent) references(id))");
// stat.execute("insert into test values(0, 0), (1, NULL),
// (2, 1), (3, 3), (4, 3)");
// stat.execute("drop table test");
stat.execute("create table test(id identity, parent bigint, " +
"foreign key(parent) references(id))");
stat.execute("insert into test values(0, 0), (1, NULL), " +
"(2, 1), (3, 3), (4, 3)");
stat.execute("drop table test");
stat.execute("create table parent(id int, x int)");
stat.execute("insert into parent values(1, 2)");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论