提交 03d85cbe authored 作者: Thomas Mueller's avatar Thomas Mueller

--no commit message

--no commit message
上级 29edd875
...@@ -1126,6 +1126,7 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch. ...@@ -1126,6 +1126,7 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch.
</li><li>Use JDK 1.4 file locking to create the lock file (but not yet by default); writing a system property to detect concurrent access from the same VM (different classloaders). </li><li>Use JDK 1.4 file locking to create the lock file (but not yet by default); writing a system property to detect concurrent access from the same VM (different classloaders).
</li><li>Read-only sessions (Connection.setReadOnly) </li><li>Read-only sessions (Connection.setReadOnly)
</li><li>Support compatibility for jdbc:hsqldb:res: </li><li>Support compatibility for jdbc:hsqldb:res:
</li><li>In the MySQL and PostgreSQL, use lower case identifiers by default (DatabaseMetaData.storesLowerCaseIdentifiers = true)
</li></ul> </li></ul>
<h3>Not Planned</h3> <h3>Not Planned</h3>
......
...@@ -8,6 +8,7 @@ import java.sql.SQLException; ...@@ -8,6 +8,7 @@ import java.sql.SQLException;
import org.h2.command.Parser; import org.h2.command.Parser;
import org.h2.command.Prepared; import org.h2.command.Prepared;
import org.h2.constant.ErrorCode; import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.constraint.ConstraintReferential; import org.h2.constraint.ConstraintReferential;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.engine.DbObject; import org.h2.engine.DbObject;
...@@ -199,7 +200,7 @@ public class AlterTableAlterColumn extends SchemaCommand { ...@@ -199,7 +200,7 @@ public class AlterTableAlterColumn extends SchemaCommand {
int id = -1; int id = -1;
TableData newTable = getSchema().createTable(tempName, id, newColumns, persistent, false); TableData newTable = getSchema().createTable(tempName, id, newColumns, persistent, false);
newTable.setComment(table.getComment()); newTable.setComment(table.getComment());
execute(newTable.getCreateSQL()); execute(newTable.getCreateSQL(), true);
newTable = (TableData) newTable.getSchema().getTableOrView(session, newTable.getName()); newTable = (TableData) newTable.getSchema().getTableOrView(session, newTable.getName());
ObjectArray children = table.getChildren(); ObjectArray children = table.getChildren();
for (int i=0; i<children.size(); i++) { for (int i=0; i<children.size(); i++) {
...@@ -226,7 +227,7 @@ public class AlterTableAlterColumn extends SchemaCommand { ...@@ -226,7 +227,7 @@ public class AlterTableAlterColumn extends SchemaCommand {
sql = child.getCreateSQLForCopy(newTable, quotedName); sql = child.getCreateSQLForCopy(newTable, quotedName);
} }
if(sql != null) { if(sql != null) {
execute(sql); execute(sql, true);
} }
} }
StringBuffer columnList = new StringBuffer(); StringBuffer columnList = new StringBuffer();
...@@ -275,10 +276,10 @@ public class AlterTableAlterColumn extends SchemaCommand { ...@@ -275,10 +276,10 @@ public class AlterTableAlterColumn extends SchemaCommand {
String sql = buff.toString(); String sql = buff.toString();
newTable.setCheckForeignKeyConstraints(session, false, false); newTable.setCheckForeignKeyConstraints(session, false, false);
try { try {
execute(sql); execute(sql, false);
} catch(SQLException e) { } catch(SQLException e) {
unlinkSequences(newTable); unlinkSequences(newTable);
execute("DROP TABLE " + newTable.getSQL()); execute("DROP TABLE " + newTable.getSQL(), true);
throw e; throw e;
} }
newTable.setCheckForeignKeyConstraints(session, true, false); newTable.setCheckForeignKeyConstraints(session, true, false);
...@@ -292,7 +293,7 @@ public class AlterTableAlterColumn extends SchemaCommand { ...@@ -292,7 +293,7 @@ public class AlterTableAlterColumn extends SchemaCommand {
columns[i].setSequence(null); columns[i].setSequence(null);
} }
} }
execute("DROP TABLE " + table.getSQL()); execute("DROP TABLE " + table.getSQL(), true);
db.renameSchemaObject(session, newTable, tableName); db.renameSchemaObject(session, newTable, tableName);
children = newTable.getChildren(); children = newTable.getChildren();
for (int i=0; i<children.size(); i++) { for (int i=0; i<children.size(); i++) {
...@@ -323,9 +324,13 @@ public class AlterTableAlterColumn extends SchemaCommand { ...@@ -323,9 +324,13 @@ public class AlterTableAlterColumn extends SchemaCommand {
} }
} }
private void execute(String sql) throws SQLException { private void execute(String sql, boolean ddl) throws SQLException {
Prepared command = session.prepare(sql); Prepared command = session.prepare(sql);
command.update(); command.update();
if(ddl && SysProperties.MVCC) {
// TODO this should work without MVCC, but avoid risks at the moment
session.commit(true);
}
} }
private void dropSingleColumnIndexes() throws SQLException { private void dropSingleColumnIndexes() throws SQLException {
......
...@@ -35,6 +35,7 @@ import org.h2.store.FileStore; ...@@ -35,6 +35,7 @@ import org.h2.store.FileStore;
import org.h2.store.LogSystem; import org.h2.store.LogSystem;
import org.h2.store.RecordReader; import org.h2.store.RecordReader;
import org.h2.store.Storage; import org.h2.store.Storage;
import org.h2.store.UndoLogRecord;
import org.h2.store.WriterThread; import org.h2.store.WriterThread;
import org.h2.table.Column; import org.h2.table.Column;
import org.h2.table.MetaTable; import org.h2.table.MetaTable;
...@@ -517,16 +518,16 @@ public class Database implements DataHandler { ...@@ -517,16 +518,16 @@ public class Database implements DataHandler {
rec.execute(this, systemSession, eventListener); rec.execute(this, systemSession, eventListener);
} }
// try to recompile the views that are invalid // try to recompile the views that are invalid
recompileInvalidViews(); recompileInvalidViews(systemSession);
starting = false; starting = false;
addDefaultSetting(SetTypes.DEFAULT_LOCK_TIMEOUT, null, Constants.INITIAL_LOCK_TIMEOUT); addDefaultSetting(systemSession, SetTypes.DEFAULT_LOCK_TIMEOUT, null, Constants.INITIAL_LOCK_TIMEOUT);
addDefaultSetting(SetTypes.DEFAULT_TABLE_TYPE, null, Constants.DEFAULT_TABLE_TYPE); addDefaultSetting(systemSession, SetTypes.DEFAULT_TABLE_TYPE, null, Constants.DEFAULT_TABLE_TYPE);
addDefaultSetting(SetTypes.TRACE_LEVEL_FILE, null, traceSystem.getLevelFile()); addDefaultSetting(systemSession, SetTypes.TRACE_LEVEL_FILE, null, traceSystem.getLevelFile());
addDefaultSetting(SetTypes.TRACE_LEVEL_SYSTEM_OUT, null, traceSystem.getLevelSystemOut()); addDefaultSetting(systemSession, SetTypes.TRACE_LEVEL_SYSTEM_OUT, null, traceSystem.getLevelSystemOut());
addDefaultSetting(SetTypes.CACHE_SIZE, null, SysProperties.CACHE_SIZE_DEFAULT); addDefaultSetting(systemSession, SetTypes.CACHE_SIZE, null, SysProperties.CACHE_SIZE_DEFAULT);
addDefaultSetting(SetTypes.CLUSTER, Constants.CLUSTERING_DISABLED, 0); addDefaultSetting(systemSession, SetTypes.CLUSTER, Constants.CLUSTERING_DISABLED, 0);
addDefaultSetting(SetTypes.WRITE_DELAY, null, Constants.DEFAULT_WRITE_DELAY); addDefaultSetting(systemSession, SetTypes.WRITE_DELAY, null, Constants.DEFAULT_WRITE_DELAY);
removeUnusedStorages(); removeUnusedStorages(systemSession);
systemSession.commit(true); systemSession.commit(true);
if(!readOnly) { if(!readOnly) {
emergencyReserve = openFile(createTempFile(), "rw", false); emergencyReserve = openFile(createTempFile(), "rw", false);
...@@ -536,7 +537,7 @@ public class Database implements DataHandler { ...@@ -536,7 +537,7 @@ public class Database implements DataHandler {
traceSystem.getTrace(Trace.DATABASE).info("opened " + databaseName); traceSystem.getTrace(Trace.DATABASE).info("opened " + databaseName);
} }
private void recompileInvalidViews() { private void recompileInvalidViews(Session session) {
boolean recompileSuccessful; boolean recompileSuccessful;
do { do {
recompileSuccessful = false; recompileSuccessful = false;
...@@ -547,7 +548,7 @@ public class Database implements DataHandler { ...@@ -547,7 +548,7 @@ public class Database implements DataHandler {
TableView view = (TableView) obj; TableView view = (TableView) obj;
if(view.getInvalid()) { if(view.getInvalid()) {
try { try {
view.recompile(systemSession); view.recompile(session);
} catch(Throwable e) { } catch(Throwable e) {
// ignore // ignore
} }
...@@ -560,18 +561,18 @@ public class Database implements DataHandler { ...@@ -560,18 +561,18 @@ public class Database implements DataHandler {
} while(recompileSuccessful); } while(recompileSuccessful);
} }
private void removeUnusedStorages() throws SQLException { private void removeUnusedStorages(Session session) throws SQLException {
if(persistent) { if(persistent) {
for(int i=0; i<storages.size(); i++) { for(int i=0; i<storages.size(); i++) {
Storage storage = (Storage) storages.get(i); Storage storage = (Storage) storages.get(i);
if(storage != null && storage.getRecordReader()==null) { if(storage != null && storage.getRecordReader()==null) {
storage.delete(systemSession); storage.delete(session);
} }
} }
} }
} }
private void addDefaultSetting(int type, String stringValue, int intValue) throws SQLException { private void addDefaultSetting(Session session, int type, String stringValue, int intValue) throws SQLException {
if(readOnly) { if(readOnly) {
return; return;
} }
...@@ -583,7 +584,7 @@ public class Database implements DataHandler { ...@@ -583,7 +584,7 @@ public class Database implements DataHandler {
} else { } else {
setting.setStringValue(stringValue); setting.setStringValue(stringValue);
} }
addDatabaseObject(systemSession, setting); addDatabaseObject(session, setting);
} }
} }
...@@ -632,20 +633,27 @@ public class Database implements DataHandler { ...@@ -632,20 +633,27 @@ public class Database implements DataHandler {
objectIds.set(obj.getId()); objectIds.set(obj.getId());
meta.lock(session, true); meta.lock(session, true);
meta.addRow(session, r); meta.addRow(session, r);
if(SysProperties.MVCC) {
// TODO this should work without MVCC, but avoid risks at the moment
session.log(meta, UndoLogRecord.INSERT, r);
}
} }
private void removeMeta(Session session, int id) throws SQLException { private void removeMeta(Session session, int id) throws SQLException {
SearchRow r = meta.getTemplateSimpleRow(false); SearchRow r = meta.getTemplateSimpleRow(false);
r.setValue(0, ValueInt.get(id)); r.setValue(0, ValueInt.get(id));
Cursor cursor = metaIdIndex.find(session, r, r); Cursor cursor = metaIdIndex.find(session, r, r);
cursor.next(); if(cursor.next()) {
Row found = cursor.get(); Row found = cursor.get();
if(found!=null) {
meta.lock(session, true); meta.lock(session, true);
meta.removeRow(session, found); meta.removeRow(session, found);
if(SysProperties.MVCC) {
// TODO this should work without MVCC, but avoid risks at the moment
session.log(meta, UndoLogRecord.DELETE, found);
}
objectIds.clear(id); objectIds.clear(id);
if(SysProperties.CHECK) { if(SysProperties.CHECK) {
checkMetaFree(id); checkMetaFree(session, id);
} }
} }
} }
...@@ -878,12 +886,11 @@ public class Database implements DataHandler { ...@@ -878,12 +886,11 @@ public class Database implements DataHandler {
storages = new ObjectArray(); storages = new ObjectArray();
} }
private void checkMetaFree(int id) throws SQLException { private void checkMetaFree(Session session, int id) throws SQLException {
SearchRow r = meta.getTemplateSimpleRow(false); SearchRow r = meta.getTemplateSimpleRow(false);
r.setValue(0, ValueInt.get(id)); r.setValue(0, ValueInt.get(id));
Cursor cursor = metaIdIndex.find(systemSession, r, r); Cursor cursor = metaIdIndex.find(session, r, r);
cursor.next(); if(cursor.next()) {
if(cursor.getPos() != Cursor.POS_NO_ROW) {
throw Message.getInternalError(); throw Message.getInternalError();
} }
} }
...@@ -1108,14 +1115,14 @@ public class Database implements DataHandler { ...@@ -1108,14 +1115,14 @@ public class Database implements DataHandler {
removeMeta(session, id); removeMeta(session, id);
} }
private String getFirstInvalidTable() { private String getFirstInvalidTable(Session session) {
String conflict = null; String conflict = null;
try { try {
ObjectArray list = getAllSchemaObjects(DbObject.TABLE_OR_VIEW); ObjectArray list = getAllSchemaObjects(DbObject.TABLE_OR_VIEW);
for(int i=0; i<list.size(); i++) { for(int i=0; i<list.size(); i++) {
Table t = (Table)list.get(i); Table t = (Table)list.get(i);
conflict = t.getSQL(); conflict = t.getSQL();
systemSession.prepare(t.getCreateSQL()); session.prepare(t.getCreateSQL());
} }
} catch(SQLException e) { } catch(SQLException e) {
return conflict; return conflict;
...@@ -1136,7 +1143,7 @@ public class Database implements DataHandler { ...@@ -1136,7 +1143,7 @@ public class Database implements DataHandler {
removeDatabaseObject(session, comment); removeDatabaseObject(session, comment);
} }
obj.getSchema().remove(session, obj); obj.getSchema().remove(session, obj);
String invalid = getFirstInvalidTable(); String invalid = getFirstInvalidTable(session);
if(invalid != null) { if(invalid != null) {
obj.getSchema().add(obj); obj.getSchema().add(obj);
throw Message.getSQLException(ErrorCode.CANNOT_DROP_2, new String[]{obj.getSQL(), invalid}); throw Message.getSQLException(ErrorCode.CANNOT_DROP_2, new String[]{obj.getSQL(), invalid});
......
...@@ -11,8 +11,6 @@ import org.h2.result.SearchRow; ...@@ -11,8 +11,6 @@ import org.h2.result.SearchRow;
public interface Cursor { public interface Cursor {
int POS_NO_ROW = -1;
Row get() throws SQLException; Row get() throws SQLException;
SearchRow getSearchRow() throws SQLException; SearchRow getSearchRow() throws SQLException;
......
...@@ -29,7 +29,7 @@ public class HashCursor implements Cursor { ...@@ -29,7 +29,7 @@ public class HashCursor implements Cursor {
} }
public int getPos() { public int getPos() {
return row == null ? -1 : row.getPos(); return row.getPos();
} }
public boolean next() { public boolean next() {
......
...@@ -29,7 +29,7 @@ public class LinearHashCursor implements Cursor { ...@@ -29,7 +29,7 @@ public class LinearHashCursor implements Cursor {
} }
public int getPos() { public int getPos() {
return row == null ? -1 : row.getPos(); return row.getPos();
} }
public boolean next() { public boolean next() {
......
package org.h2.index; package org.h2.index;
import java.sql.SQLException; import java.sql.SQLException;
import org.h2.constant.SysProperties;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.message.Message;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.SearchRow; import org.h2.result.SearchRow;
...@@ -9,32 +11,136 @@ public class MultiVersionCursor implements Cursor { ...@@ -9,32 +11,136 @@ public class MultiVersionCursor implements Cursor {
private final MultiVersionIndex index; private final MultiVersionIndex index;
private final Session session; private final Session session;
private final Cursor base, delta; private final Cursor baseCursor, deltaCursor;
private SearchRow baseRow;
private Row deltaRow;
private boolean onBase; private boolean onBase;
private SearchRow current; private boolean end;
private boolean needNewDelta, needNewBase;
MultiVersionCursor(Session session, MultiVersionIndex index, Cursor base, Cursor delta) throws SQLException { MultiVersionCursor(Session session, MultiVersionIndex index, Cursor base, Cursor delta) throws SQLException {
this.session = session; this.session = session;
this.index = index; this.index = index;
this.base = base; this.baseCursor = base;
this.delta = delta; this.deltaCursor = delta;
boolean b = base.next(); needNewDelta = needNewBase = true;
boolean d = delta.next(); }
private void loadNext(boolean base) throws SQLException {
if(base) {
if(baseCursor.next()) {
baseRow = baseCursor.getSearchRow();
} else {
baseRow = null;
}
} else {
if(deltaCursor.next()) {
deltaRow = deltaCursor.get();
} else {
deltaRow = null;
}
}
} }
public Row get() throws SQLException { public Row get() throws SQLException {
return onBase ? base.get() : delta.get(); if(SysProperties.CHECK && end) {
throw Message.getInternalError();
}
return onBase ? baseCursor.get() : deltaCursor.get();
} }
public int getPos() { public int getPos() {
return onBase ? base.getPos() : delta.getPos(); if(SysProperties.CHECK && end) {
throw Message.getInternalError();
}
return onBase ? baseCursor.getPos() : deltaCursor.getPos();
} }
public SearchRow getSearchRow() throws SQLException { public SearchRow getSearchRow() throws SQLException {
return onBase ? base.getSearchRow() : delta.getSearchRow(); if(SysProperties.CHECK && end) {
throw Message.getInternalError();
}
return onBase ? baseCursor.getSearchRow() : deltaCursor.getSearchRow();
} }
public boolean next() throws SQLException { public boolean next() throws SQLException {
return false; if(SysProperties.CHECK && end) {
throw Message.getInternalError();
}
while(true) {
if(needNewDelta) {
loadNext(false);
needNewDelta = false;
}
if(needNewBase) {
loadNext(true);
needNewBase = false;
}
if(deltaRow == null) {
if(baseRow == null) {
end = true;
return false;
} else {
onBase = true;
needNewBase = true;
return true;
}
}
boolean isThisSession = deltaRow.getSessionId() == session.getId();
boolean isDeleted = deltaRow.getDeleted();
if(isThisSession && isDeleted) {
needNewDelta = true;
continue;
}
if(baseRow == null) {
if(isDeleted) {
if(isThisSession) {
end = true;
return false;
} else {
// the row was deleted by another session: return it
onBase = false;
needNewDelta = true;
return true;
}
}
throw Message.getInternalError();
}
int compare = index.compareRows(deltaRow, baseRow);
if(compare == 0) {
compare = index.compareKeys(deltaRow, baseRow);
}
if(compare == 0) {
if(isDeleted) {
if(isThisSession) {
throw Message.getInternalError();
} else {
// another session updated the row: must be deleted in base as well
throw Message.getInternalError();
}
} else {
if(isThisSession) {
onBase = false;
needNewBase = true;
needNewDelta = true;
return true;
} else {
// another session inserted the row: ignore
needNewBase = true;
needNewDelta = true;
continue;
}
}
}
if(compare > 0) {
needNewBase = true;
return true;
}
if(!isDeleted) {
throw Message.getInternalError();
}
needNewDelta = true;
return true;
}
} }
} }
package org.h2.index; package org.h2.index;
import java.sql.SQLException; import java.sql.SQLException;
import org.h2.constant.SysProperties;
import org.h2.engine.Database; import org.h2.engine.Database;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.message.Message; import org.h2.message.Message;
...@@ -14,17 +15,21 @@ import org.h2.util.ObjectArray; ...@@ -14,17 +15,21 @@ import org.h2.util.ObjectArray;
public class MultiVersionIndex implements Index { public class MultiVersionIndex implements Index {
private Index base; private final Index base;
private TreeIndex delta; private final TreeIndex delta;
private final TableData table;
public MultiVersionIndex(Index base, TableData table) throws SQLException { public MultiVersionIndex(Index base, TableData table) throws SQLException {
this.base = base; this.base = base;
this.table = table;
IndexType deltaIndexType = IndexType.createNonUnique(false); IndexType deltaIndexType = IndexType.createNonUnique(false);
this.delta = new TreeIndex(table, -1, "DELTA" ,base.getColumns(), deltaIndexType); this.delta = new TreeIndex(table, -1, "DELTA" ,base.getColumns(), deltaIndexType);
} }
public void add(Session session, Row row) throws SQLException { public void add(Session session, Row row) throws SQLException {
base.add(session, row); base.add(session, row);
// for example rolling back an delete operation
removeIfExists(session, row);
delta.add(session, row); delta.add(session, row);
} }
...@@ -55,10 +60,27 @@ public class MultiVersionIndex implements Index { ...@@ -55,10 +60,27 @@ public class MultiVersionIndex implements Index {
public boolean needRebuild() { public boolean needRebuild() {
return base.needRebuild(); return base.needRebuild();
} }
private boolean removeIfExists(Session session, Row row) throws SQLException {
// maybe it was inserted by the same session just before
Cursor c = delta.find(session, row, row);
while(c.next()) {
Row r = c.get();
if(r.getPos() == row.getPos()) {
delta.remove(session, row);
return true;
}
}
return false;
}
public void remove(Session session, Row row) throws SQLException { public void remove(Session session, Row row) throws SQLException {
base.remove(session, row); base.remove(session, row);
delta.add(session, row); if(removeIfExists(session, row)) {
// added and deleted in the same transaction: no change
} else {
delta.add(session, row);
}
} }
public void remove(Session session) throws SQLException { public void remove(Session session) throws SQLException {
...@@ -71,7 +93,7 @@ public class MultiVersionIndex implements Index { ...@@ -71,7 +93,7 @@ public class MultiVersionIndex implements Index {
} }
public void commit(Row row) throws SQLException { public void commit(Row row) throws SQLException {
delta.remove(null, row); removeIfExists(null, row);
} }
public int compareKeys(SearchRow rowData, SearchRow compare) { public int compareKeys(SearchRow rowData, SearchRow compare) {
...@@ -127,6 +149,7 @@ public class MultiVersionIndex implements Index { ...@@ -127,6 +149,7 @@ public class MultiVersionIndex implements Index {
} }
public long getRowCount(Session session) { public long getRowCount(Session session) {
// TODO
return base.getRowCount(session); return base.getRowCount(session);
} }
...@@ -143,7 +166,8 @@ public class MultiVersionIndex implements Index { ...@@ -143,7 +166,8 @@ public class MultiVersionIndex implements Index {
} }
public void removeChildrenAndResources(Session session) throws SQLException { public void removeChildrenAndResources(Session session) throws SQLException {
base.removeChildrenAndResources(session); table.removeIndex(this);
remove(session);
} }
public String getSQL() { public String getSQL() {
......
...@@ -39,7 +39,7 @@ public class ScanCursor implements Cursor { ...@@ -39,7 +39,7 @@ public class ScanCursor implements Cursor {
} }
public int getPos() { public int getPos() {
return row == null ? -1 : row.getPos(); return row.getPos();
} }
public boolean next() throws SQLException { public boolean next() throws SQLException {
......
...@@ -33,7 +33,7 @@ public class TreeCursor implements Cursor { ...@@ -33,7 +33,7 @@ public class TreeCursor implements Cursor {
} }
public int getPos() { public int getPos() {
return node == null ? -1 : node.row.getPos(); return node.row.getPos();
} }
public boolean next() throws SQLException { public boolean next() throws SQLException {
......
...@@ -65,5 +65,23 @@ public class Row extends Record implements SearchRow { ...@@ -65,5 +65,23 @@ public class Row extends Record implements SearchRow {
public int getMemorySize() { public int getMemorySize() {
return blockCount * (DiskFile.BLOCK_SIZE / 16) + memory * 4; return blockCount * (DiskFile.BLOCK_SIZE / 16) + memory * 4;
} }
public String toString() {
int testing;
StringBuffer buff = new StringBuffer(data.length*5);
buff.append('(');
for(int i=0; i<data.length; i++) {
if(i>0) {
buff.append(", ");
}
buff.append(data[i].getSQL());
}
buff.append(')');
buff.append(" /* pos: " + getPos() + "*/ ");
if(getDeleted()) {
buff.append(" /* deleted /*");
}
return buff.toString();
}
} }
...@@ -139,7 +139,11 @@ public class Storage { ...@@ -139,7 +139,11 @@ public class Storage {
} }
record.setDeleted(session, true); record.setDeleted(session, true);
int blockCount = record.getBlockCount(); int blockCount = record.getBlockCount();
free(pos, blockCount); if(SysProperties.MVCC) {
int todoMustFreeSpaceOnCommit;
} else {
free(pos, blockCount);
}
recordCount--; recordCount--;
file.removeRecord(session, pos, record, blockCount); file.removeRecord(session, pos, record, blockCount);
} }
......
...@@ -237,6 +237,11 @@ public class TableData extends Table implements RecordReader { ...@@ -237,6 +237,11 @@ public class TableData extends Table implements RecordReader {
} }
for(int i=0; i<list.size(); i++) { for(int i=0; i<list.size(); i++) {
Row r = (Row) list.get(i); Row r = (Row) list.get(i);
if(SysProperties.MVCC) {
// when adding referential integrity to a table, the index is created first, and the rows are inserted to this index
// if the session is not set, it would look like an insert from another session
r.setDeleted(session, false);
}
index.add(session, r); index.add(session, r);
} }
list.clear(); list.clear();
......
...@@ -92,6 +92,10 @@ java -Xmx512m -Xrunhprof:cpu=samples,depth=8 org.h2.tools.RunScript -url jdbc:h2 ...@@ -92,6 +92,10 @@ java -Xmx512m -Xrunhprof:cpu=samples,depth=8 org.h2.tools.RunScript -url jdbc:h2
TestAll test = new TestAll(); TestAll test = new TestAll();
test.printSystem(); test.printSystem();
//int testMVCC;
// System.setProperty("h2.mvcc", "true");
/* /*
CREATE TABLE Parent(ID INT PRIMARY KEY, Name VARCHAR); CREATE TABLE Parent(ID INT PRIMARY KEY, Name VARCHAR);
......
package org.h2.test.mvcc; package org.h2.test.mvcc;
import java.sql.*; import java.sql.*;
import java.util.Random;
import org.h2.tools.DeleteDbFiles; import org.h2.tools.DeleteDbFiles;
public class TestMVCC { public class TestMVCC {
...@@ -14,9 +15,13 @@ public class TestMVCC { ...@@ -14,9 +15,13 @@ public class TestMVCC {
} }
void test() throws Exception { void test() throws Exception {
// TODO Prio 1: don't store records before they are committed (otherwise re-reading from a different session may return the wrong value) // TODO Prio 1: free up disk space (for deleted rows and old versions of updated rows) on commit
// TODO Prio 1: getRowCount: different row count for different sessions: TableData // TODO Prio 1: ScanIndex: never remove uncommitted data from cache (lost sessionId)
// TODO Prio 1: getRowCount: different row count for different sessions: TableData (remove field?)
// TODO Prio 1: getRowCount: different row count for different sessions: MultiVersionIndex (hash set for deltas?)
// TODO Prio 2: getRowCount: different row count for different sessions: TableLink (use different connections?) // TODO Prio 2: getRowCount: different row count for different sessions: TableLink (use different connections?)
// TODO Prio 2: getFirst / getLast in MultiVersionIndex
// TODO Prio 2: Support snapshot isolation (currently read-committed, not repeatable read)
System.setProperty("h2.mvcc", "true"); System.setProperty("h2.mvcc", "true");
DeleteDbFiles.execute(null, "test", true); DeleteDbFiles.execute(null, "test", true);
...@@ -27,16 +32,92 @@ public class TestMVCC { ...@@ -27,16 +32,92 @@ public class TestMVCC {
s2 = c2.createStatement(); s2 = c2.createStatement();
c1.setAutoCommit(false); c1.setAutoCommit(false);
c2.setAutoCommit(false); c2.setAutoCommit(false);
s1.execute("CREATE TABLE A(ID INT PRIMARY KEY, SK INT)");
s1.execute("INSERT INTO A VALUES(1, 2)");
test(s1, "SELECT 1 FROM (SELECT SK FROM PUBLIC.A ORDER BY SK) C WHERE NOT EXISTS(SELECT 1 FROM PUBLIC.A P WHERE C.SK=P.ID)", "1");
c1.commit();
test(s1, "SELECT 1 FROM (SELECT SK FROM PUBLIC.A ORDER BY SK) C WHERE NOT EXISTS(SELECT 1 FROM PUBLIC.A P WHERE C.SK=P.ID)", "1");
try {
s1.execute("ALTER TABLE A ADD CONSTRAINT AC FOREIGN KEY(SK) REFERENCES A(ID)");
throw new Exception("unexpected success");
} catch(SQLException e) {
// expected
}
c1.commit();
s1.execute("CREATE TABLE TEST(ID INT IDENTITY, NAME VARCHAR)");
s1.execute("INSERT INTO TEST(NAME) VALUES('Ruebezahl')");
s1.execute("INSERT INTO TEST(NAME) VALUES('Ruebezahl')");
s1.execute("DROP TABLE TEST");
c1.commit();
s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)");
s1.execute("INSERT INTO TEST VALUES(1, 'Hello')");
c1.commit();
s1.execute("DELETE FROM TEST WHERE ID=1");
c1.rollback();
s1.execute("DROP TABLE TEST");
c1.commit();
Random random = new Random(1);
s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)");
for(int i=0; i<100; i++) {
switch(random.nextInt(3)) {
case 0:
s1.execute("INSERT INTO TEST VALUES("+ i + ", 'Hello')");
break;
case 1:
s1.execute("UPDATE TEST SET NAME=" + i + " WHERE ID=" + random.nextInt(i));
break;
case 2:
s1.execute("DELETE FROM TEST WHERE ID=" + random.nextInt(i));
break;
}
s2.execute("SELECT * FROM TEST ORDER BY ID");
}
s1.execute("DROP TABLE TEST");
c1.commit();
c2.commit();
s1.execute("CREATE TABLE TEST(ID INT, NAME VARCHAR)"); s1.execute("CREATE TABLE TEST(ID INT, NAME VARCHAR)");
// s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)");
s1.execute("INSERT INTO TEST VALUES(1, 'Hello')"); s1.execute("INSERT INTO TEST VALUES(1, 'Hello')");
test(s2, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'", "0"); test(s2, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'", "0");
test(s1, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'", "1");
c1.commit(); c1.commit();
// TODO support snapshot isolation
// test(s2, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'", "0");
test(s2, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'", "1"); test(s2, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'", "1");
test(s2, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'", "1");
s1.execute("DROP TABLE TEST");
c1.commit();
c2.commit();
s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)");
s1.execute("INSERT INTO TEST VALUES(1, 'Hello')");
test(s2, "SELECT COUNT(*) FROM TEST WHERE ID<100", "0");
test(s1, "SELECT COUNT(*) FROM TEST WHERE ID<100", "1");
c1.commit();
test(s2, "SELECT COUNT(*) FROM TEST WHERE ID<100", "1");
test(s2, "SELECT COUNT(*) FROM TEST WHERE ID<100", "1");
s1.execute("DROP TABLE TEST");
c1.commit();
c2.commit();
s1.execute("CREATE TABLE TEST(ID INT, NAME VARCHAR, PRIMARY KEY(ID, NAME))");
s1.execute("INSERT INTO TEST VALUES(1, 'Hello')");
c1.commit();
test(s2, "SELECT NAME FROM TEST WHERE ID=1", "Hello");
s1.execute("UPDATE TEST SET NAME = 'Hallo' WHERE ID=1");
test(s2, "SELECT NAME FROM TEST WHERE ID=1", "Hello");
test(s1, "SELECT NAME FROM TEST WHERE ID=1", "Hallo");
s1.execute("DROP TABLE TEST");
c1.commit();
c2.commit();
c1.close(); c1.close();
c2.close(); c2.close();
} }
private void test(Statement stat, String sql, String expected) throws Exception { private void test(Statement stat, String sql, String expected) throws Exception {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论