提交 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.
</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>Support compatibility for jdbc:hsqldb:res:
</li><li>In the MySQL and PostgreSQL, use lower case identifiers by default (DatabaseMetaData.storesLowerCaseIdentifiers = true)
</li></ul>
<h3>Not Planned</h3>
......
......@@ -8,6 +8,7 @@ import java.sql.SQLException;
import org.h2.command.Parser;
import org.h2.command.Prepared;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.constraint.ConstraintReferential;
import org.h2.engine.Database;
import org.h2.engine.DbObject;
......@@ -199,7 +200,7 @@ public class AlterTableAlterColumn extends SchemaCommand {
int id = -1;
TableData newTable = getSchema().createTable(tempName, id, newColumns, persistent, false);
newTable.setComment(table.getComment());
execute(newTable.getCreateSQL());
execute(newTable.getCreateSQL(), true);
newTable = (TableData) newTable.getSchema().getTableOrView(session, newTable.getName());
ObjectArray children = table.getChildren();
for (int i=0; i<children.size(); i++) {
......@@ -226,7 +227,7 @@ public class AlterTableAlterColumn extends SchemaCommand {
sql = child.getCreateSQLForCopy(newTable, quotedName);
}
if(sql != null) {
execute(sql);
execute(sql, true);
}
}
StringBuffer columnList = new StringBuffer();
......@@ -275,10 +276,10 @@ public class AlterTableAlterColumn extends SchemaCommand {
String sql = buff.toString();
newTable.setCheckForeignKeyConstraints(session, false, false);
try {
execute(sql);
execute(sql, false);
} catch(SQLException e) {
unlinkSequences(newTable);
execute("DROP TABLE " + newTable.getSQL());
execute("DROP TABLE " + newTable.getSQL(), true);
throw e;
}
newTable.setCheckForeignKeyConstraints(session, true, false);
......@@ -292,7 +293,7 @@ public class AlterTableAlterColumn extends SchemaCommand {
columns[i].setSequence(null);
}
}
execute("DROP TABLE " + table.getSQL());
execute("DROP TABLE " + table.getSQL(), true);
db.renameSchemaObject(session, newTable, tableName);
children = newTable.getChildren();
for (int i=0; i<children.size(); i++) {
......@@ -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);
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 {
......
......@@ -35,6 +35,7 @@ import org.h2.store.FileStore;
import org.h2.store.LogSystem;
import org.h2.store.RecordReader;
import org.h2.store.Storage;
import org.h2.store.UndoLogRecord;
import org.h2.store.WriterThread;
import org.h2.table.Column;
import org.h2.table.MetaTable;
......@@ -517,16 +518,16 @@ public class Database implements DataHandler {
rec.execute(this, systemSession, eventListener);
}
// try to recompile the views that are invalid
recompileInvalidViews();
recompileInvalidViews(systemSession);
starting = false;
addDefaultSetting(SetTypes.DEFAULT_LOCK_TIMEOUT, null, Constants.INITIAL_LOCK_TIMEOUT);
addDefaultSetting(SetTypes.DEFAULT_TABLE_TYPE, null, Constants.DEFAULT_TABLE_TYPE);
addDefaultSetting(SetTypes.TRACE_LEVEL_FILE, null, traceSystem.getLevelFile());
addDefaultSetting(SetTypes.TRACE_LEVEL_SYSTEM_OUT, null, traceSystem.getLevelSystemOut());
addDefaultSetting(SetTypes.CACHE_SIZE, null, SysProperties.CACHE_SIZE_DEFAULT);
addDefaultSetting(SetTypes.CLUSTER, Constants.CLUSTERING_DISABLED, 0);
addDefaultSetting(SetTypes.WRITE_DELAY, null, Constants.DEFAULT_WRITE_DELAY);
removeUnusedStorages();
addDefaultSetting(systemSession, SetTypes.DEFAULT_LOCK_TIMEOUT, null, Constants.INITIAL_LOCK_TIMEOUT);
addDefaultSetting(systemSession, SetTypes.DEFAULT_TABLE_TYPE, null, Constants.DEFAULT_TABLE_TYPE);
addDefaultSetting(systemSession, SetTypes.TRACE_LEVEL_FILE, null, traceSystem.getLevelFile());
addDefaultSetting(systemSession, SetTypes.TRACE_LEVEL_SYSTEM_OUT, null, traceSystem.getLevelSystemOut());
addDefaultSetting(systemSession, SetTypes.CACHE_SIZE, null, SysProperties.CACHE_SIZE_DEFAULT);
addDefaultSetting(systemSession, SetTypes.CLUSTER, Constants.CLUSTERING_DISABLED, 0);
addDefaultSetting(systemSession, SetTypes.WRITE_DELAY, null, Constants.DEFAULT_WRITE_DELAY);
removeUnusedStorages(systemSession);
systemSession.commit(true);
if(!readOnly) {
emergencyReserve = openFile(createTempFile(), "rw", false);
......@@ -536,7 +537,7 @@ public class Database implements DataHandler {
traceSystem.getTrace(Trace.DATABASE).info("opened " + databaseName);
}
private void recompileInvalidViews() {
private void recompileInvalidViews(Session session) {
boolean recompileSuccessful;
do {
recompileSuccessful = false;
......@@ -547,7 +548,7 @@ public class Database implements DataHandler {
TableView view = (TableView) obj;
if(view.getInvalid()) {
try {
view.recompile(systemSession);
view.recompile(session);
} catch(Throwable e) {
// ignore
}
......@@ -560,18 +561,18 @@ public class Database implements DataHandler {
} while(recompileSuccessful);
}
private void removeUnusedStorages() throws SQLException {
private void removeUnusedStorages(Session session) throws SQLException {
if(persistent) {
for(int i=0; i<storages.size(); i++) {
Storage storage = (Storage) storages.get(i);
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) {
return;
}
......@@ -583,7 +584,7 @@ public class Database implements DataHandler {
} else {
setting.setStringValue(stringValue);
}
addDatabaseObject(systemSession, setting);
addDatabaseObject(session, setting);
}
}
......@@ -632,20 +633,27 @@ public class Database implements DataHandler {
objectIds.set(obj.getId());
meta.lock(session, true);
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 {
SearchRow r = meta.getTemplateSimpleRow(false);
r.setValue(0, ValueInt.get(id));
Cursor cursor = metaIdIndex.find(session, r, r);
cursor.next();
Row found = cursor.get();
if(found!=null) {
if(cursor.next()) {
Row found = cursor.get();
meta.lock(session, true);
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);
if(SysProperties.CHECK) {
checkMetaFree(id);
checkMetaFree(session, id);
}
}
}
......@@ -878,12 +886,11 @@ public class Database implements DataHandler {
storages = new ObjectArray();
}
private void checkMetaFree(int id) throws SQLException {
private void checkMetaFree(Session session, int id) throws SQLException {
SearchRow r = meta.getTemplateSimpleRow(false);
r.setValue(0, ValueInt.get(id));
Cursor cursor = metaIdIndex.find(systemSession, r, r);
cursor.next();
if(cursor.getPos() != Cursor.POS_NO_ROW) {
Cursor cursor = metaIdIndex.find(session, r, r);
if(cursor.next()) {
throw Message.getInternalError();
}
}
......@@ -1108,14 +1115,14 @@ public class Database implements DataHandler {
removeMeta(session, id);
}
private String getFirstInvalidTable() {
private String getFirstInvalidTable(Session session) {
String conflict = null;
try {
ObjectArray list = getAllSchemaObjects(DbObject.TABLE_OR_VIEW);
for(int i=0; i<list.size(); i++) {
Table t = (Table)list.get(i);
conflict = t.getSQL();
systemSession.prepare(t.getCreateSQL());
session.prepare(t.getCreateSQL());
}
} catch(SQLException e) {
return conflict;
......@@ -1136,7 +1143,7 @@ public class Database implements DataHandler {
removeDatabaseObject(session, comment);
}
obj.getSchema().remove(session, obj);
String invalid = getFirstInvalidTable();
String invalid = getFirstInvalidTable(session);
if(invalid != null) {
obj.getSchema().add(obj);
throw Message.getSQLException(ErrorCode.CANNOT_DROP_2, new String[]{obj.getSQL(), invalid});
......
......@@ -11,8 +11,6 @@ import org.h2.result.SearchRow;
public interface Cursor {
int POS_NO_ROW = -1;
Row get() throws SQLException;
SearchRow getSearchRow() throws SQLException;
......
......@@ -29,7 +29,7 @@ public class HashCursor implements Cursor {
}
public int getPos() {
return row == null ? -1 : row.getPos();
return row.getPos();
}
public boolean next() {
......
......@@ -29,7 +29,7 @@ public class LinearHashCursor implements Cursor {
}
public int getPos() {
return row == null ? -1 : row.getPos();
return row.getPos();
}
public boolean next() {
......
package org.h2.index;
import java.sql.SQLException;
import org.h2.constant.SysProperties;
import org.h2.engine.Session;
import org.h2.message.Message;
import org.h2.result.Row;
import org.h2.result.SearchRow;
......@@ -9,32 +11,136 @@ public class MultiVersionCursor implements Cursor {
private final MultiVersionIndex index;
private final Session session;
private final Cursor base, delta;
private final Cursor baseCursor, deltaCursor;
private SearchRow baseRow;
private Row deltaRow;
private boolean onBase;
private SearchRow current;
private boolean end;
private boolean needNewDelta, needNewBase;
MultiVersionCursor(Session session, MultiVersionIndex index, Cursor base, Cursor delta) throws SQLException {
this.session = session;
this.index = index;
this.base = base;
this.delta = delta;
boolean b = base.next();
boolean d = delta.next();
this.baseCursor = base;
this.deltaCursor = delta;
needNewDelta = needNewBase = true;
}
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 {
return onBase ? base.get() : delta.get();
if(SysProperties.CHECK && end) {
throw Message.getInternalError();
}
return onBase ? baseCursor.get() : deltaCursor.get();
}
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 {
return onBase ? base.getSearchRow() : delta.getSearchRow();
if(SysProperties.CHECK && end) {
throw Message.getInternalError();
}
return onBase ? baseCursor.getSearchRow() : deltaCursor.getSearchRow();
}
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;
import java.sql.SQLException;
import org.h2.constant.SysProperties;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.message.Message;
......@@ -14,17 +15,21 @@ import org.h2.util.ObjectArray;
public class MultiVersionIndex implements Index {
private Index base;
private TreeIndex delta;
private final Index base;
private final TreeIndex delta;
private final TableData table;
public MultiVersionIndex(Index base, TableData table) throws SQLException {
this.base = base;
this.table = table;
IndexType deltaIndexType = IndexType.createNonUnique(false);
this.delta = new TreeIndex(table, -1, "DELTA" ,base.getColumns(), deltaIndexType);
}
public void add(Session session, Row row) throws SQLException {
base.add(session, row);
// for example rolling back an delete operation
removeIfExists(session, row);
delta.add(session, row);
}
......@@ -55,10 +60,27 @@ public class MultiVersionIndex implements Index {
public boolean 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 {
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 {
......@@ -71,7 +93,7 @@ public class MultiVersionIndex implements Index {
}
public void commit(Row row) throws SQLException {
delta.remove(null, row);
removeIfExists(null, row);
}
public int compareKeys(SearchRow rowData, SearchRow compare) {
......@@ -127,6 +149,7 @@ public class MultiVersionIndex implements Index {
}
public long getRowCount(Session session) {
// TODO
return base.getRowCount(session);
}
......@@ -143,7 +166,8 @@ public class MultiVersionIndex implements Index {
}
public void removeChildrenAndResources(Session session) throws SQLException {
base.removeChildrenAndResources(session);
table.removeIndex(this);
remove(session);
}
public String getSQL() {
......
......@@ -39,7 +39,7 @@ public class ScanCursor implements Cursor {
}
public int getPos() {
return row == null ? -1 : row.getPos();
return row.getPos();
}
public boolean next() throws SQLException {
......
......@@ -33,7 +33,7 @@ public class TreeCursor implements Cursor {
}
public int getPos() {
return node == null ? -1 : node.row.getPos();
return node.row.getPos();
}
public boolean next() throws SQLException {
......
......@@ -65,5 +65,23 @@ public class Row extends Record implements SearchRow {
public int getMemorySize() {
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 {
}
record.setDeleted(session, true);
int blockCount = record.getBlockCount();
free(pos, blockCount);
if(SysProperties.MVCC) {
int todoMustFreeSpaceOnCommit;
} else {
free(pos, blockCount);
}
recordCount--;
file.removeRecord(session, pos, record, blockCount);
}
......
......@@ -237,6 +237,11 @@ public class TableData extends Table implements RecordReader {
}
for(int i=0; i<list.size(); 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);
}
list.clear();
......
......@@ -92,6 +92,10 @@ java -Xmx512m -Xrunhprof:cpu=samples,depth=8 org.h2.tools.RunScript -url jdbc:h2
TestAll test = new TestAll();
test.printSystem();
//int testMVCC;
// System.setProperty("h2.mvcc", "true");
/*
CREATE TABLE Parent(ID INT PRIMARY KEY, Name VARCHAR);
......
package org.h2.test.mvcc;
import java.sql.*;
import java.util.Random;
import org.h2.tools.DeleteDbFiles;
public class TestMVCC {
......@@ -14,9 +15,13 @@ public class TestMVCC {
}
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: getRowCount: different row count for different sessions: TableData
// TODO Prio 1: free up disk space (for deleted rows and old versions of updated rows) on commit
// 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: getFirst / getLast in MultiVersionIndex
// TODO Prio 2: Support snapshot isolation (currently read-committed, not repeatable read)
System.setProperty("h2.mvcc", "true");
DeleteDbFiles.execute(null, "test", true);
......@@ -27,16 +32,92 @@ public class TestMVCC {
s2 = c2.createStatement();
c1.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 PRIMARY KEY, NAME VARCHAR)");
s1.execute("INSERT INTO TEST VALUES(1, 'Hello')");
test(s2, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'", "0");
test(s1, "SELECT COUNT(*) FROM TEST WHERE NAME!='X'", "1");
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");
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();
c2.close();
}
private void test(Statement stat, String sql, String expected) throws Exception {
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论