提交 d49cefed authored 作者: Thomas Mueller's avatar Thomas Mueller

MVStore: table engine

上级 5ff23847
...@@ -13,7 +13,6 @@ import org.h2.engine.Right; ...@@ -13,7 +13,6 @@ import org.h2.engine.Right;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.result.ResultInterface; import org.h2.result.ResultInterface;
import org.h2.table.Column; import org.h2.table.Column;
import org.h2.table.RegularTable;
import org.h2.table.Table; import org.h2.table.Table;
import org.h2.util.StatementBuilder; import org.h2.util.StatementBuilder;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -53,7 +52,7 @@ public class Analyze extends DefineCommand { ...@@ -53,7 +52,7 @@ public class Analyze extends DefineCommand {
* @param manual whether the command was called by the user * @param manual whether the command was called by the user
*/ */
public static void analyzeTable(Session session, Table table, int sample, boolean manual) { public static void analyzeTable(Session session, Table table, int sample, boolean manual) {
if (!(table instanceof RegularTable) || table.isHidden() || session == null) { if (!(table.getTableType().equals(Table.TABLE)) || table.isHidden() || session == null) {
return; return;
} }
if (!manual) { if (!manual) {
......
...@@ -21,6 +21,7 @@ import org.h2.engine.Database; ...@@ -21,6 +21,7 @@ import org.h2.engine.Database;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.expression.Expression; import org.h2.expression.Expression;
import org.h2.message.DbException; import org.h2.message.DbException;
import org.h2.mvstore.db.MVTableEngine;
import org.h2.result.ResultInterface; import org.h2.result.ResultInterface;
import org.h2.store.FileLister; import org.h2.store.FileLister;
import org.h2.store.PageStore; import org.h2.store.PageStore;
...@@ -56,6 +57,7 @@ public class BackupCommand extends Prepared { ...@@ -56,6 +57,7 @@ public class BackupCommand extends Prepared {
throw DbException.get(ErrorCode.DATABASE_IS_NOT_PERSISTENT); throw DbException.get(ErrorCode.DATABASE_IS_NOT_PERSISTENT);
} }
try { try {
MVTableEngine.flush(db);
String name = db.getName(); String name = db.getName();
name = FileUtils.getName(name); name = FileUtils.getName(name);
OutputStream zip = FileUtils.newOutputStream(fileName, false); OutputStream zip = FileUtils.newOutputStream(fileName, false);
...@@ -75,6 +77,9 @@ public class BackupCommand extends Prepared { ...@@ -75,6 +77,9 @@ public class BackupCommand extends Prepared {
if (n.endsWith(Constants.SUFFIX_LOB_FILE)) { if (n.endsWith(Constants.SUFFIX_LOB_FILE)) {
backupFile(out, base, n); backupFile(out, base, n);
} }
if (n.endsWith(Constants.SUFFIX_MV_FILE)) {
backupFile(out, base, n);
}
} }
} }
out.close(); out.close();
......
...@@ -603,8 +603,8 @@ public class MVStore { ...@@ -603,8 +603,8 @@ public class MVStore {
/** /**
* Commit all changes and persist them to disk. This method does nothing if * Commit all changes and persist them to disk. This method does nothing if
* there are no unsaved changes, otherwise it stores the data and increments * there are no unsaved changes, otherwise it increments the current version
* the current version. * and stores the data (for file based stores).
* *
* @return the new version (incremented if there were changes) * @return the new version (incremented if there were changes)
*/ */
...@@ -619,6 +619,10 @@ public class MVStore { ...@@ -619,6 +619,10 @@ public class MVStore {
long version = incrementVersion(); long version = incrementVersion();
long time = getTime(); long time = getTime();
if (file == null) {
return version;
}
// the last chunk was not completely correct in the last store() // the last chunk was not completely correct in the last store()
// this needs to be updated now (it's better not to update right after // this needs to be updated now (it's better not to update right after
// storing, because that would modify the meta map again) // storing, because that would modify the meta map again)
......
...@@ -29,7 +29,7 @@ public class MVDelegateIndex extends BaseIndex { ...@@ -29,7 +29,7 @@ public class MVDelegateIndex extends BaseIndex {
IndexColumn[] cols = IndexColumn.wrap(new Column[] { table.getColumn(mainIndex.getMainIndexColumn())}); IndexColumn[] cols = IndexColumn.wrap(new Column[] { table.getColumn(mainIndex.getMainIndexColumn())});
this.initBaseIndex(table, id, name, cols, indexType); this.initBaseIndex(table, id, name, cols, indexType);
this.mainIndex = mainIndex; this.mainIndex = mainIndex;
if (!database.isPersistent() || id < 0) { if (id < 0) {
throw DbException.throwInternalError("" + name); throw DbException.throwInternalError("" + name);
} }
} }
......
...@@ -103,6 +103,7 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -103,6 +103,7 @@ public class MVPrimaryIndex extends BaseIndex {
throw e; throw e;
} }
map.put(row.getKey(), array); map.put(row.getKey(), array);
lastKey = Math.max(lastKey, row.getKey());
} }
@Override @Override
...@@ -172,6 +173,9 @@ public class MVPrimaryIndex extends BaseIndex { ...@@ -172,6 +173,9 @@ public class MVPrimaryIndex extends BaseIndex {
@Override @Override
public void truncate(Session session) { public void truncate(Session session) {
if (mvTable.getContainsLargeObject()) {
database.getLobStorage().removeAllForTable(table.getId());
}
map.clear(); map.clear();
} }
......
...@@ -26,6 +26,7 @@ import org.h2.table.IndexColumn; ...@@ -26,6 +26,7 @@ import org.h2.table.IndexColumn;
import org.h2.util.New; import org.h2.util.New;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueLong; import org.h2.value.ValueLong;
import org.h2.value.ValueNull;
/** /**
* A table stored in a MVStore. * A table stored in a MVStore.
...@@ -40,6 +41,9 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -40,6 +41,9 @@ public class MVSecondaryIndex extends BaseIndex {
IndexColumn[] columns, IndexType indexType) { IndexColumn[] columns, IndexType indexType) {
this.mvTable = table; this.mvTable = table;
initBaseIndex(table, id, indexName, columns, indexType); initBaseIndex(table, id, indexName, columns, indexType);
if (!database.isStarting()) {
checkIndexColumnTypes(columns);
}
// always store the row key in the map key, // always store the row key in the map key,
// even for unique indexes, as some of the index columns could be null // even for unique indexes, as some of the index columns could be null
keyColumns = columns.length + 1; keyColumns = columns.length + 1;
...@@ -54,6 +58,15 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -54,6 +58,15 @@ public class MVSecondaryIndex extends BaseIndex {
map = table.getStore().openMap(getName() + "_" + getId(), map); map = table.getStore().openMap(getName() + "_" + getId(), map);
} }
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 @Override
public void close(Session session) { public void close(Session session) {
// ok // ok
...@@ -158,10 +171,16 @@ public class MVSecondaryIndex extends BaseIndex { ...@@ -158,10 +171,16 @@ public class MVSecondaryIndex extends BaseIndex {
@Override @Override
public Cursor findFirstOrLast(Session session, boolean first) { public Cursor findFirstOrLast(Session session, boolean first) {
if (map.getSize() == 0) {
return new MVStoreCursor(session, Collections.<Value[]>emptyList().iterator(), null);
}
Value[] key = first ? map.firstKey() : map.lastKey(); Value[] key = first ? map.firstKey() : map.lastKey();
while (true) {
if (key == null) {
return new MVStoreCursor(session, Collections.<Value[]>emptyList().iterator(), null);
}
if (key[0] != ValueNull.INSTANCE) {
break;
}
key = first ? map.higherKey(key) : map.lowerKey(key);
}
ArrayList<Value[]> list = New.arrayList(); ArrayList<Value[]> list = New.arrayList();
list.add(key); list.add(key);
MVStoreCursor cursor = new MVStoreCursor(session, list.iterator(), null); MVStoreCursor cursor = new MVStoreCursor(session, list.iterator(), null);
......
...@@ -12,6 +12,7 @@ import java.util.Comparator; ...@@ -12,6 +12,7 @@ import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.h2.api.DatabaseEventListener; import org.h2.api.DatabaseEventListener;
import org.h2.command.ddl.Analyze;
import org.h2.command.ddl.CreateTableData; import org.h2.command.ddl.CreateTableData;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties; import org.h2.constant.SysProperties;
...@@ -37,6 +38,7 @@ import org.h2.table.Table; ...@@ -37,6 +38,7 @@ import org.h2.table.Table;
import org.h2.table.TableBase; import org.h2.table.TableBase;
import org.h2.util.MathUtils; import org.h2.util.MathUtils;
import org.h2.util.New; import org.h2.util.New;
import org.h2.value.DataType;
import org.h2.value.Value; import org.h2.value.Value;
/** /**
...@@ -53,6 +55,10 @@ public class MVTable extends TableBase { ...@@ -53,6 +55,10 @@ public class MVTable extends TableBase {
private volatile Session lockExclusive; private volatile Session lockExclusive;
private HashSet<Session> lockShared = New.hashSet(); private HashSet<Session> lockShared = New.hashSet();
private final Trace traceLock; private final Trace traceLock;
private int changesSinceAnalyze;
private int nextAnalyze;
private boolean containsLargeObject;
private Column rowIdColumn;
/** /**
* True if one thread ever was waiting to lock this table. This is to avoid * True if one thread ever was waiting to lock this table. This is to avoid
...@@ -63,9 +69,15 @@ public class MVTable extends TableBase { ...@@ -63,9 +69,15 @@ public class MVTable extends TableBase {
public MVTable(CreateTableData data, String storeName, MVStore store) { public MVTable(CreateTableData data, String storeName, MVStore store) {
super(data); super(data);
nextAnalyze = database.getSettings().analyzeAuto;
this.storeName = storeName; this.storeName = storeName;
this.store = store; this.store = store;
this.isHidden = data.isHidden; this.isHidden = data.isHidden;
for (Column col : getColumns()) {
if (DataType.isLargeObject(col.getType())) {
containsLargeObject = true;
}
}
traceLock = database.getTrace(Trace.LOCK); traceLock = database.getTrace(Trace.LOCK);
} }
...@@ -475,7 +487,6 @@ public class MVTable extends TableBase { ...@@ -475,7 +487,6 @@ public class MVTable extends TableBase {
for (; i >= 0; i--) { for (; i >= 0; i--) {
Index index = indexes.get(i); Index index = indexes.get(i);
index.remove(session, row); index.remove(session, row);
checkRowCount(session, index, -1);
} }
rowCount--; rowCount--;
} catch (Throwable e) { } catch (Throwable e) {
...@@ -483,7 +494,6 @@ public class MVTable extends TableBase { ...@@ -483,7 +494,6 @@ public class MVTable extends TableBase {
while (++i < indexes.size()) { while (++i < indexes.size()) {
Index index = indexes.get(i); Index index = indexes.get(i);
index.add(session, row); index.add(session, row);
checkRowCount(session, index, 0);
} }
} catch (DbException e2) { } catch (DbException e2) {
// this could happen, for example on failure in the storage // this could happen, for example on failure in the storage
...@@ -506,6 +516,7 @@ public class MVTable extends TableBase { ...@@ -506,6 +516,7 @@ public class MVTable extends TableBase {
index.truncate(session); index.truncate(session);
} }
rowCount = 0; rowCount = 0;
changesSinceAnalyze = 0;
storeIfRequired(); storeIfRequired();
} }
...@@ -517,7 +528,6 @@ public class MVTable extends TableBase { ...@@ -517,7 +528,6 @@ public class MVTable extends TableBase {
for (int size = indexes.size(); i < size; i++) { for (int size = indexes.size(); i < size; i++) {
Index index = indexes.get(i); Index index = indexes.get(i);
index.add(session, row); index.add(session, row);
checkRowCount(session, index, 1);
} }
rowCount++; rowCount++;
} catch (Throwable e) { } catch (Throwable e) {
...@@ -525,7 +535,6 @@ public class MVTable extends TableBase { ...@@ -525,7 +535,6 @@ public class MVTable extends TableBase {
while (--i >= 0) { while (--i >= 0) {
Index index = indexes.get(i); Index index = indexes.get(i);
index.remove(session, row); index.remove(session, row);
checkRowCount(session, index, 0);
} }
} catch (DbException e2) { } catch (DbException e2) {
// this could happen, for example on failure in the storage // this could happen, for example on failure in the storage
...@@ -552,12 +561,17 @@ public class MVTable extends TableBase { ...@@ -552,12 +561,17 @@ public class MVTable extends TableBase {
storeIfRequired(); storeIfRequired();
} }
private void checkRowCount(Session session, Index index, int offset) {
// TODO verify
}
private void analyzeIfRequired(Session session) { private void analyzeIfRequired(Session session) {
// TODO analyze if (nextAnalyze == 0 || nextAnalyze > changesSinceAnalyze++) {
return;
}
changesSinceAnalyze = 0;
int n = 2 * nextAnalyze;
if (n > 0) {
nextAnalyze = n;
}
int rows = session.getDatabase().getSettings().analyzeSample;
Analyze.analyzeTable(session, this, rows, false);
} }
@Override @Override
...@@ -590,6 +604,10 @@ public class MVTable extends TableBase { ...@@ -590,6 +604,10 @@ public class MVTable extends TableBase {
return lastModificationId; return lastModificationId;
} }
public boolean getContainsLargeObject() {
return containsLargeObject;
}
@Override @Override
public boolean isDeterministic() { public boolean isDeterministic() {
return true; return true;
...@@ -606,6 +624,12 @@ public class MVTable extends TableBase { ...@@ -606,6 +624,12 @@ public class MVTable extends TableBase {
} }
public void removeChildrenAndResources(Session session) { public void removeChildrenAndResources(Session session) {
if (containsLargeObject) {
// unfortunately, the data is gone on rollback
truncate(session);
database.getLobStorage().removeAllForTable(getId());
database.lockMeta(session);
}
super.removeChildrenAndResources(session); super.removeChildrenAndResources(session);
// go backwards because database.removeIndex will call table.removeIndex // go backwards because database.removeIndex will call table.removeIndex
while (indexes.size() > 1) { while (indexes.size() > 1) {
...@@ -658,4 +682,12 @@ public class MVTable extends TableBase { ...@@ -658,4 +682,12 @@ public class MVTable extends TableBase {
return store; return store;
} }
public Column getRowIdColumn() {
if (rowIdColumn == null) {
rowIdColumn = new Column(Column.ROWID, Value.LONG);
rowIdColumn.setTable(this, -1);
}
return rowIdColumn;
}
} }
...@@ -28,6 +28,21 @@ public class MVTableEngine implements TableEngine { ...@@ -28,6 +28,21 @@ public class MVTableEngine implements TableEngine {
static final Map<String, Store> STORES = new WeakHashMap<String, Store>(); static final Map<String, Store> STORES = new WeakHashMap<String, Store>();
public static void flush(Database db) {
String storeName = db.getDatabasePath();
if (storeName == null) {
return;
}
synchronized (STORES) {
Store store = STORES.get(storeName);
if (store == null) {
return;
}
// TODO this stores uncommitted transactions as well
store.store.store();
}
}
@Override @Override
public TableBase createTable(CreateTableData data) { public TableBase createTable(CreateTableData data) {
Database db = data.session.getDatabase(); Database db = data.session.getDatabase();
...@@ -61,11 +76,13 @@ public class MVTableEngine implements TableEngine { ...@@ -61,11 +76,13 @@ public class MVTableEngine implements TableEngine {
static void closeTable(String storeName, MVTable table) { static void closeTable(String storeName, MVTable table) {
synchronized (STORES) { synchronized (STORES) {
Store store = STORES.get(storeName); Store store = STORES.get(storeName);
store.openTables.remove(table); if (store != null) {
if (store.openTables.size() == 0) { store.openTables.remove(table);
store.store.store(); if (store.openTables.size() == 0) {
store.store.close(); store.store.store();
STORES.remove(storeName); store.store.close();
STORES.remove(storeName);
}
} }
} }
} }
......
...@@ -582,6 +582,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -582,6 +582,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestLargeBlob().runTest(this); new TestLargeBlob().runTest(this);
new TestLinkedTable().runTest(this); new TestLinkedTable().runTest(this);
new TestListener().runTest(this); new TestListener().runTest(this);
// verify
new TestLob().runTest(this); new TestLob().runTest(this);
new TestMemoryUsage().runTest(this); new TestMemoryUsage().runTest(this);
new TestMultiConn().runTest(this); new TestMultiConn().runTest(this);
...@@ -596,8 +597,10 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -596,8 +597,10 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestReadOnly().runTest(this); new TestReadOnly().runTest(this);
new TestRecursiveQueries().runTest(this); new TestRecursiveQueries().runTest(this);
new TestRights().runTest(this); new TestRights().runTest(this);
// verify
new TestRunscript().runTest(this); new TestRunscript().runTest(this);
new TestSQLInjection().runTest(this); new TestSQLInjection().runTest(this);
// verify
new TestSessionsLocks().runTest(this); new TestSessionsLocks().runTest(this);
new TestSelectCountNonNullColumn().runTest(this); new TestSelectCountNonNullColumn().runTest(this);
new TestSequence().runTest(this); new TestSequence().runTest(this);
...@@ -706,6 +709,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -706,6 +709,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new TestFileSystem().runTest(this); new TestFileSystem().runTest(this);
new TestIntArray().runTest(this); new TestIntArray().runTest(this);
new TestIntIntHashMap().runTest(this); new TestIntIntHashMap().runTest(this);
// verify
new TestJmx().runTest(this); new TestJmx().runTest(this);
new TestMathUtils().runTest(this); new TestMathUtils().runTest(this);
new TestModifyOnWrite().runTest(this); new TestModifyOnWrite().runTest(this);
......
...@@ -8,6 +8,7 @@ package org.h2.test.store; ...@@ -8,6 +8,7 @@ package org.h2.test.store;
import java.sql.Connection; import java.sql.Connection;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.Statement; import java.sql.Statement;
import java.util.Random;
import org.h2.mvstore.db.MVTableEngine; import org.h2.mvstore.db.MVTableEngine;
import org.h2.store.fs.FileUtils; import org.h2.store.fs.FileUtils;
import org.h2.test.TestBase; import org.h2.test.TestBase;
...@@ -37,18 +38,53 @@ public class TestMVTableEngine extends TestBase { ...@@ -37,18 +38,53 @@ public class TestMVTableEngine extends TestBase {
deleteDb("cases"); deleteDb("cases");
Connection conn = getConnection("cases"); Connection conn = getConnection("cases");
Statement stat = conn.createStatement(); Statement stat = conn.createStatement();
ResultSet rs;
conn = getConnection("cases");
stat = conn.createStatement(); Random random = new Random(1);
stat.execute("set max_operation_memory 1"); int len = getSize(50, 500);
stat.execute("create table test(id int)"); for (int i = 0; i < len; i++) {
stat.execute("insert into test values(1), (2)"); stat.execute("drop table if exists test");
stat.execute("create index idx on test(id)"); stat.execute("create table test(x int)");
conn.setAutoCommit(false); if (random.nextBoolean()) {
stat.execute("update test set id = id where id=2"); int count = random.nextBoolean() ? 1 : 1 + random.nextInt(len);
stat.execute("update test set id = id"); if (count > 0) {
conn.rollback(); stat.execute("insert into test select null from system_range(1, " + count + ")");
}
}
int maxExpected = -1;
int minExpected = -1;
if (random.nextInt(10) != 1) {
minExpected = 1;
maxExpected = 1 + random.nextInt(len);
stat.execute("insert into test select x from system_range(1, " + maxExpected + ")");
}
String sql = "create index idx on test(x";
if (random.nextBoolean()) {
sql += " desc";
}
if (random.nextBoolean()) {
if (random.nextBoolean()) {
sql += " nulls first";
} else {
sql += " nulls last";
}
}
sql += ")";
stat.execute(sql);
rs = stat.executeQuery("select min(x), max(x) from test");
rs.next();
int min = rs.getInt(1);
if (rs.wasNull()) {
min = -1;
}
int max = rs.getInt(2);
if (rs.wasNull()) {
max = -1;
}
assertEquals(minExpected, min);
assertEquals(maxExpected, max);
}
conn.close(); conn.close();
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论