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

--no commit message

--no commit message
上级 6b606ae5
......@@ -245,16 +245,23 @@
</jar>
</target>
<target name="mavenUploadCentral" depends="jar">
<target name="mavenDeployCentral" depends="jar">
<copy tofile="bin/h2-${version.name.maven}.jar" file="bin/h2.jar" />
<copy tofile="bin/pom.xml" filtering="true" file="src/installer/pomUpload.xml">
<filterset>
<filter token="version" value="${version.name.maven}"/>
</filterset>
</copy>
<exec executable="mvn.bat">
<arg value="deploy:deploy-file"/>
<arg value="-Dfile=bin/h2.jar"/>
<arg value="-Durl=file://C:/data/h2database/m2-repo"/>
<arg value="-Dpackaging=jar"/>
<arg value="-DpomFile=bin/pom.xml"/>
</exec>
</target>
<target name="mavenUploadLocal" depends="jar">
<target name="mavenInstallLocal" depends="jar">
<copy tofile="bin/pom.xml" filtering="true" file="src/installer/pom.xml">
<filterset>
<filter token="version" value="1.0-SNAPSHOT"/>
......@@ -264,8 +271,6 @@
<arg value="install:install-file"/>
<arg value="-Dversion=1.0-SNAPSHOT"/>
<arg value="-Dfile=bin/h2.jar"/>
<arg value="-DgroupId=org.h2database"/>
<arg value="-DartifactId=h2"/>
<arg value="-Dpackaging=jar"/>
<arg value="-DpomFile=bin/pom.xml"/>
</exec>
......
......@@ -81,7 +81,7 @@ The version is currently 1.0.&lt;year&gt;&lt;month&gt;&lt;day&gt;. Example:
<p>
To build a 'snapshot' H2 .jar file and upload it the to the local Maven 2 repository, execute the following command:
<pre>
ant mavenUploadLocal
ant mavenInstallLocal
</pre>
Afterwards, you can include the database in your Maven 2 project as a dependency:
<pre>
......
......@@ -991,7 +991,7 @@ will always enable the trace mode when connecting.
If the database files are read-only, then the database is read-only as well.
It is not possible to create new tables, add or modify data in this database.
Only SELECT statements are allowed.
To create a read-only database, close the database so that the log file is deleted.
To create a read-only database, close the database so that the log file gets smaller. Do not delete the log file.
Then, make the database files read-only using the operating system.
When you open the database now, it is read-only.
There are two ways an application can find out a database is read-only:
......
......@@ -43,6 +43,12 @@ Hypersonic SQL or HSQLDB. H2 is built from scratch.
<li>New experimental feature MVCC (multi version concurrency control).
Can be set as a option when opening the database (jdbc:h2:test;MVCC=TRUE)
or as a system property (-Dh2.mvcc=true). This is work-in-progress, use it at your own risk. Feedback is welcome.
</li><li>The backup tool (org.h2.tools.Backup) did not work. The restore tool did not work when the -db parameter was used. Fixed.
The documentation of the backup tool has been changed: only one database may be backed up at any time.
</li><li>Opening large read-only databases was very slow. Fixed.
</li><li>New Japanese translation of the error messages thanks to Ikemoto Masahiro. Thanks a lot!
</li><li>Disabling / enabling referential integrity for a table can now be used inside a transaction.
</li><li>Rights checking for dynamic tables (SELECT * FROM (SELECT ...)) did not work. Fixed.
</li><li>Creating more than 10 views that depend on each other was very slow. Reconnecting was slow as well. Fixed.
</li><li>When used as as Servlet, the H2 Console did not work with SSL (using Tomcat). Fixed.
</li><li>When altering a table with foreign key constraint, if there was no manual index created
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -150,6 +150,8 @@
90127=The result set is not updatable. The query must select all columns from a unique key. Only one table may be selected.
90128=The result set is not scrollable and can not be reset. You may need to use conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY).
90129=Transaction {0} not found
90130=This method is not allowed for a prepared statement; use a regular statement instead.
90131=Concurrent update in table {0}\: another transaction has updated or deleted the same row
HY000=General error\: {0}
HY004=Unknown data type\: {0}
HYC00=Feature not supported
......
......@@ -19,10 +19,4 @@
</scm>
<dependencies>
</dependencies>
<distributionManagement>
<repository>
<id>ftp-repository</id>
<url>ftp://h2database.com/httpdocs/m2-repo</url>
</repository>
</distributionManagement>
</project>
\ No newline at end of file
......@@ -48,6 +48,7 @@ import org.h2.command.ddl.GrantRevoke;
import org.h2.command.ddl.PrepareProcedure;
import org.h2.command.ddl.SetComment;
import org.h2.command.ddl.TruncateTable;
import org.h2.command.dml.AlterTableSet;
import org.h2.command.dml.BackupCommand;
import org.h2.command.dml.Call;
import org.h2.command.dml.Delete;
......@@ -801,7 +802,9 @@ public class Parser {
s = session;
}
String tempViewName = s.getNextTempViewName();
table = new TableView(mainSchema, 0, tempViewName, querySQL, query.getParameters(), null, s, false);
TableView v = new TableView(mainSchema, 0, tempViewName, querySQL, query.getParameters(), null, s, false);
v.setOwner(session.getUser());
table = v;
if(s != database.getSystemSession()) {
table.setOnCommitDrop(true);
}
......@@ -3837,15 +3840,13 @@ public class Parser {
read("REFERENTIAL_INTEGRITY");
int type;
if(readIf("TRUE")) {
type = AlterTableAddConstraint.REFERENTIAL_INTEGRITY_TRUE;
type = AlterTableSet.REFERENTIAL_INTEGRITY_TRUE;
} else {
read("FALSE");
type = AlterTableAddConstraint.REFERENTIAL_INTEGRITY_FALSE;
type = AlterTableSet.REFERENTIAL_INTEGRITY_FALSE;
}
AlterTableAddConstraint command = new AlterTableAddConstraint(session, table.getSchema());
command.setTableName(table.getName());
command.setType(type);
if(readIf("CHECK")) {
AlterTableSet command = new AlterTableSet(session, table.getSchema(), type);
command.setTableName(table.getName()); if(readIf("CHECK")) {
command.setCheckExisting(true);
} else if(readIf("NOCHECK")) {
command.setCheckExisting(false);
......
......@@ -33,8 +33,6 @@ import org.h2.util.ObjectArray;
public class AlterTableAddConstraint extends SchemaCommand {
public static final int CHECK = 0, UNIQUE = 1, REFERENTIAL = 2, PRIMARY_KEY = 3;
public static final int REFERENTIAL_INTEGRITY_TRUE = 4;
public static final int REFERENTIAL_INTEGRITY_FALSE = 5;
private int type;
private String constraintName;
private String tableName;
......@@ -164,12 +162,6 @@ public class AlterTableAddConstraint extends SchemaCommand {
ref.setUpdateAction(session, updateAction);
break;
}
case REFERENTIAL_INTEGRITY_TRUE:
table.setCheckForeignKeyConstraints(session, true, checkExisting);
return 0;
case REFERENTIAL_INTEGRITY_FALSE:
table.setCheckForeignKeyConstraints(session, false, false);
return 0;
default:
throw Message.getInternalError("type="+type);
}
......
/*
* Copyright 2004-2007 H2 Group. Licensed under the H2 License, Version 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.command.dml;
import java.sql.SQLException;
import org.h2.command.ddl.SchemaCommand;
import org.h2.engine.Right;
import org.h2.engine.Session;
import org.h2.message.Message;
import org.h2.result.LocalResult;
import org.h2.schema.Schema;
import org.h2.table.Table;
public class AlterTableSet extends SchemaCommand {
private String tableName;
private final int type;
private boolean checkExisting;
public static final int REFERENTIAL_INTEGRITY_TRUE = 4;
public static final int REFERENTIAL_INTEGRITY_FALSE = 5;
public AlterTableSet(Session session, Schema schema, int type) {
super(session, schema);
this.type = type;
}
public void setCheckExisting(boolean b) {
this.checkExisting = b;
}
public boolean isTransactional() {
return true;
}
public void setTableName(String tableName) {
this.tableName = tableName;
}
public int update() throws SQLException {
Table table = getSchema().getTableOrView(session, tableName);
session.getUser().checkRight(table, Right.ALL);
table.lock(session, true, true);
switch(type) {
case REFERENTIAL_INTEGRITY_TRUE:
table.setCheckForeignKeyConstraints(session, true, checkExisting);
break;
case REFERENTIAL_INTEGRITY_FALSE:
table.setCheckForeignKeyConstraints(session, false, false);
break;
default:
throw Message.getInternalError("type="+type);
}
return 0;
}
public LocalResult queryMeta() {
return null;
}
}
......@@ -322,7 +322,6 @@ public class Select extends Query {
}
topTableFilter.startQuery(session);
topTableFilter.reset();
// TODO lock tables of sub queries
topTableFilter.lock(session, isForUpdate, isForUpdate);
if(isQuickQuery) {
queryQuick(columnCount, result);
......
......@@ -312,6 +312,7 @@ public class ErrorCode {
public static final int RESULT_SET_NOT_SCROLLABLE = 90128;
public static final int TRANSACTION_NOT_FOUND_1 = 90129;
public static final int METHOD_NOT_ALLOWED_FOR_PREPARED_STATEMENT = 90130;
public static final int CONCURRENT_UPDATE_1 = 90131;
/**
* INTERNAL
......
......@@ -436,9 +436,7 @@ public class Database implements DataHandler {
log = new LogSystem(this, databaseName, readOnly, accessModeLog);
openFileData();
openFileIndex();
if(!readOnly) {
log.recover();
}
log.recover();
fileData.init();
try {
fileIndex.init();
......
......@@ -194,14 +194,16 @@ public class Session implements SessionInterface {
if(undoLog.size() > 0) {
if(database.isMultiVersion()) {
ArrayList rows = new ArrayList();
while (undoLog.size() > 0) {
UndoLogRecord entry = undoLog.getAndRemoveLast();
entry.commit();
rows.add(entry.getRow());
}
for(int i=0; i<rows.size(); i++) {
Row r = (Row) rows.get(i);
r.commit();
synchronized(database) {
while (undoLog.size() > 0) {
UndoLogRecord entry = undoLog.getAndRemoveLast();
entry.commit();
rows.add(entry.getRow());
}
for(int i=0; i<rows.size(); i++) {
Row r = (Row) rows.get(i);
r.commit();
}
}
}
undoLog.clear();
......
......@@ -13,6 +13,7 @@ import org.h2.security.SHA256;
import org.h2.table.MetaTable;
import org.h2.table.RangeTable;
import org.h2.table.Table;
import org.h2.table.TableView;
import org.h2.util.ByteUtils;
import org.h2.util.ObjectArray;
import org.h2.util.RandomUtils;
......@@ -78,6 +79,14 @@ public class User extends RightOwner {
// everybody has access to the metadata information
return;
}
if(Table.VIEW.equals(table.getTableType())) {
TableView v = (TableView) table;
if(v.getOwner() == this) {
// the owner of a view has access:
// SELECT * FROM (SELECT * FROM ...)
return;
}
}
if(!isRightGrantedRecursive(table, rightMask)) {
throw Message.getSQLException(ErrorCode.NOT_ENOUGH_RIGHTS_FOR_1, table.getSQL());
}
......
......@@ -6,7 +6,6 @@ package org.h2.expression;
import java.sql.SQLException;
import java.util.HashMap;
import org.h2.command.Parser;
import org.h2.command.dml.Select;
import org.h2.constant.ErrorCode;
......
......@@ -5,7 +5,6 @@
package org.h2.expression;
import java.sql.SQLException;
import org.h2.constant.ErrorCode;
import org.h2.engine.Session;
import org.h2.message.Message;
......
......@@ -16,22 +16,24 @@ public class MultiVersionCursor implements Cursor {
private final MultiVersionIndex index;
private final Session session;
private final Cursor baseCursor, deltaCursor;
private final Object sync;
private SearchRow baseRow;
private Row deltaRow;
private boolean onBase;
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, Object sync) throws SQLException {
this.session = session;
this.index = index;
this.baseCursor = base;
this.deltaCursor = delta;
this.sync = sync;
needNewDelta = needNewBase = true;
}
private void loadNext(boolean base) throws SQLException {
synchronized(index) {
synchronized(sync) {
if(base) {
if(baseCursor.next()) {
baseRow = baseCursor.getSearchRow();
......@@ -49,7 +51,7 @@ public class MultiVersionCursor implements Cursor {
}
public Row get() throws SQLException {
synchronized(index) {
synchronized(sync) {
if(SysProperties.CHECK && end) {
throw Message.getInternalError();
}
......@@ -58,7 +60,7 @@ public class MultiVersionCursor implements Cursor {
}
public int getPos() {
synchronized(index) {
synchronized(sync) {
if(SysProperties.CHECK && end) {
throw Message.getInternalError();
}
......@@ -67,7 +69,7 @@ public class MultiVersionCursor implements Cursor {
}
public SearchRow getSearchRow() throws SQLException {
synchronized(index) {
synchronized(sync) {
if(SysProperties.CHECK && end) {
throw Message.getInternalError();
}
......@@ -76,7 +78,7 @@ public class MultiVersionCursor implements Cursor {
}
public boolean next() throws SQLException {
synchronized(index) {
synchronized(sync) {
if(SysProperties.CHECK && end) {
throw Message.getInternalError();
}
......@@ -99,7 +101,12 @@ public class MultiVersionCursor implements Cursor {
return true;
}
}
boolean isThisSession = deltaRow.getSessionId() == session.getId();
int sessionId = deltaRow.getSessionId();
if(sessionId == 0) {
int testing;
System.out.println("sessionId==0");
}
boolean isThisSession = sessionId == session.getId();
boolean isDeleted = deltaRow.getDeleted();
if(isThisSession && isDeleted) {
needNewDelta = true;
......
......@@ -21,39 +21,47 @@ public class MultiVersionIndex implements Index {
private final Index base;
private final TreeIndex delta;
private final TableData table;
private final Object sync;
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);
this.sync = base.getDatabase();
}
public synchronized void add(Session session, Row row) throws SQLException {
base.add(session, row);
// for example rolling back an delete operation
removeIfExists(session, row);
if(row.getSessionId() != 0) {
// don't insert rows that are added when creating an index
delta.add(session, row);
public void add(Session session, Row row) throws SQLException {
synchronized(sync) {
base.add(session, row);
// for example rolling back an delete operation
removeIfExists(session, row);
if(row.getSessionId() != 0) {
// don't insert rows that are added when creating an index
delta.add(session, row);
}
}
}
public synchronized void close(Session session) throws SQLException {
base.close(session);
public void close(Session session) throws SQLException {
synchronized(sync) {
base.close(session);
}
}
public synchronized Cursor find(Session session, SearchRow first, SearchRow last) throws SQLException {
Cursor baseCursor = base.find(session, first, last);
Cursor deltaCursor = delta.find(session, first, last);
return new MultiVersionCursor(session, this, baseCursor, deltaCursor);
public Cursor find(Session session, SearchRow first, SearchRow last) throws SQLException {
synchronized(sync) {
Cursor baseCursor = base.find(session, first, last);
Cursor deltaCursor = delta.find(session, first, last);
return new MultiVersionCursor(session, this, baseCursor, deltaCursor, sync);
}
}
public boolean canGetFirstOrLast(boolean first) {
return false;
}
public synchronized SearchRow findFirstOrLast(Session session, boolean first) throws SQLException {
public SearchRow findFirstOrLast(Session session, boolean first) throws SQLException {
throw Message.getUnsupportedException();
}
......@@ -65,7 +73,7 @@ public class MultiVersionIndex implements Index {
return base.needRebuild();
}
private synchronized boolean removeIfExists(Session session, Row row) throws SQLException {
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()) {
......@@ -78,26 +86,42 @@ public class MultiVersionIndex implements Index {
return false;
}
public synchronized void remove(Session session, Row row) throws SQLException {
base.remove(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, Row row) throws SQLException {
synchronized(sync) {
base.remove(session, row);
if(removeIfExists(session, row)) {
// added and deleted in the same transaction: no change
} else {
int testing;
if(row.getSessionId() == 0) {
System.out.println("stop! " + row);
System.out.flush();
new Error().printStackTrace();
Runtime.getRuntime().halt(1);
}
delta.add(session, row);
}
}
}
public synchronized void remove(Session session) throws SQLException {
base.remove(session);
public void remove(Session session) throws SQLException {
synchronized(sync) {
base.remove(session);
}
}
public synchronized void truncate(Session session) throws SQLException {
delta.truncate(session);
base.truncate(session);
public void truncate(Session session) throws SQLException {
synchronized(sync) {
delta.truncate(session);
base.truncate(session);
}
}
public synchronized void commit(int operation, Row row) throws SQLException {
removeIfExists(null, row);
public void commit(int operation, Row row) throws SQLException {
synchronized(sync) {
removeIfExists(null, row);
}
}
public int compareKeys(SearchRow rowData, SearchRow compare) {
......@@ -168,9 +192,11 @@ public class MultiVersionIndex implements Index {
return base.isNull(newRow);
}
public synchronized void removeChildrenAndResources(Session session) throws SQLException {
table.removeIndex(this);
remove(session);
public void removeChildrenAndResources(Session session) throws SQLException {
synchronized(sync) {
table.removeIndex(this);
remove(session);
}
}
public String getSQL() {
......
......@@ -52,6 +52,10 @@ public class ScanCursor implements Cursor {
while(true) {
if(deleted.hasNext()) {
row = (Row) deleted.next();
if(row.getDeleted() && row.getSessionId() == session.getId()) {
row = null;
continue;
}
} else {
row = scan.getNextRow(session, row);
}
......
......@@ -128,7 +128,7 @@ public class ScanIndex extends BaseIndex {
}
if(database.isMultiVersion()) {
if(deleted != null) {
deleted.add(row);
deleted.remove(row);
}
incrementRowCount(session.getId(), 1);
}
......
......@@ -58,7 +58,7 @@ implements ParameterMetaData
/**
* Returns the parameter type.
* Always returns Types.VARCHAR everything can be passed as a VARCHAR.
* Always returns Types.VARCHAR as everything can be passed as a VARCHAR.
*
* @return Types.VARCHAR
*/
......
......@@ -151,6 +151,7 @@
90128=Kann nicht an den Anfang der Resultat-Zeilen springen. M\u00F6gliche L\u00F6sung\: conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY).
90129=Transaktion {0} nicht gefunden
90130=\#This method is not allowed for a prepared statement; use a regular statement instead.
90131=\#Concurrent update in table {0}\: another transaction has updated or deleted the same row
HY000=Allgemeiner Fehler\: {0}
HY004=Unbekannter Datentyp\: {0}
HYC00=Dieses Feature wird unterst\u00FCtzt
......
......@@ -151,6 +151,7 @@
90128=The result set is not scrollable and can not be reset. You may need to use conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY).
90129=Transaction {0} not found
90130=This method is not allowed for a prepared statement; use a regular statement instead.
90131=Concurrent update in table {0}\: another transaction has updated or deleted the same row
HY000=General error\: {0}
HY004=Unknown data type\: {0}
HYC00=Feature not supported
......
差异被折叠。
......@@ -151,6 +151,7 @@
90128=\#The result set is not scrollable and can not be reset. You may need to use conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY).
90129=\#Transaction {0} not found
90130=\#This method is not allowed for a prepared statement; use a regular statement instead.
90131=\#Concurrent update in table {0}\: another transaction has updated or deleted the same row
HY000=Blad ogolny\: {0}
HY004=Nieznany typ danyche\: {0}
HYC00=Cecha nie jest wspierana
......
......@@ -248,6 +248,7 @@ ALTER TABLE TEST DROP CONSTRAINT UNIQUE_NAME
ALTER TABLE tableName SET REFERENTIAL_INTEGRITY {FALSE | TRUE [CHECK|NOCHECK]}
","
Disables or enables referential integrity checking for a table.
This command can be used inside a transaction.
Enabling it does not check existing data, except if CHECK is specified.
Use SET REFERENTIAL_INTEGRITY to disable it for all tables (the global flag and the flag for each table are independent).
","
......
......@@ -66,22 +66,22 @@ public class Row extends Record implements SearchRow {
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();
}
// 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();
// }
}
......@@ -66,7 +66,9 @@ public class LogFile {
unwritten = new ObjectArray();
try {
readHeader();
writeHeader();
if(!log.getDatabase().getReadOnly()) {
writeHeader();
}
pos = getBlock();
firstUncommittedPos = pos;
} catch(SQLException e) {
......@@ -173,7 +175,7 @@ public class LogFile {
return s;
}
private boolean redoOrUndo(boolean undo) throws SQLException {
private boolean redoOrUndo(boolean undo, boolean readOnly) throws SQLException {
int pos = getBlock();
DataPage in = readPage();
int blocks = in.readInt();
......@@ -209,6 +211,9 @@ public class LogFile {
throw Message.getInternalError("can't undo summary");
}
}
if(readOnly && type != 'S') {
return true;
}
if(undo) {
if(logSystem.isSessionCommitted(sessionId, id, pos)) {
logSystem.removeSession(sessionId);
......@@ -216,7 +221,9 @@ public class LogFile {
}
} else {
if(type != 'S') {
logSystem.addUndoLogRecord(this, pos, sessionId);
if(!readOnly) {
logSystem.addUndoLogRecord(this, pos, sessionId);
}
}
}
int storageId = in.readInt();
......@@ -286,6 +293,7 @@ public class LogFile {
}
public void redoAllGoEnd() throws SQLException {
boolean readOnly = logSystem.getDatabase().getReadOnly();
long length = file.length();
if(length<=FileStore.HEADER_LENGTH) {
return;
......@@ -298,7 +306,7 @@ public class LogFile {
if((long)pos * BLOCK_SIZE >= length) {
break;
}
boolean more = redoOrUndo(false);
boolean more = redoOrUndo(false, readOnly);
if(!more) {
break;
}
......@@ -326,7 +334,7 @@ public class LogFile {
void undo(int pos) throws SQLException {
go(pos);
redoOrUndo(true);
redoOrUndo(true, false);
}
void flush() throws SQLException {
......
......@@ -47,7 +47,7 @@ public class LogSystem {
this.database = database;
this.readOnly = readOnly;
this.accessMode = accessMode;
if (database == null || readOnly) {
if (database == null) {
return;
}
this.fileNamePrefix = fileNamePrefix;
......@@ -107,21 +107,31 @@ public class LogSystem {
for (int i = 0; i < activeLogs.size(); i++) {
LogFile l = (LogFile) activeLogs.get(i);
if (l.getFirstUncommittedPos() == LOG_WRITTEN) {
closeOldFile(l);
// must remove the log file first
// if we don't do that, the file is closed but still in the list
activeLogs.remove(i);
i--;
closeOldFile(l);
}
}
}
public void close() throws SQLException {
if(database == null || readOnly) {
if(database == null) {
return;
}
synchronized (database) {
if(closed) {
return;
}
if(readOnly) {
for (int i = 0; i < activeLogs.size(); i++) {
LogFile l = (LogFile) activeLogs.get(i);
l.close(false);
}
closed = true;
return;
}
// TODO refactor flushing and closing files when we know what to do exactly
SQLException closeException = null;
try {
......@@ -202,7 +212,7 @@ public class LogSystem {
boolean fileChanged = undo.size() > 0;
undo = null;
storages.clear();
if (fileChanged && !containsInDoubtTransactions()) {
if (!readOnly && fileChanged && !containsInDoubtTransactions()) {
checkpoint();
}
return fileChanged;
......
......@@ -5,9 +5,6 @@
package org.h2.store;
import java.sql.SQLException;
import org.h2.constant.SysProperties;
import org.h2.engine.Session;
import org.h2.util.CacheObject;
/**
......@@ -37,13 +34,15 @@ public abstract class Record extends CacheObject {
return false;
}
public void setDeleted(Session session, boolean deleted) {
this.sessionId = session.getId();
public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
public void setSessionId(int sessionId) {
this.sessionId = sessionId;
}
public int getSessionId() {
int testing;
return sessionId;
}
......
......@@ -110,7 +110,7 @@ public class Storage {
}
public void updateRecord(Session session, Record record) throws SQLException {
record.setDeleted(session, false);
record.setDeleted(false);
file.updateRecord(session, record);
}
......@@ -118,7 +118,7 @@ public class Storage {
record.setStorageId(id);
int size = file.getRecordOverhead() + record.getByteCount(dummy);
size = MathUtils.roundUp(size, DiskFile.BLOCK_SIZE);
record.setDeleted(session, false);
record.setDeleted(false);
int blockCount = size / DiskFile.BLOCK_SIZE;
if(pos == ALLOCATE_POS) {
pos = allocate(blockCount);
......@@ -137,7 +137,7 @@ public class Storage {
if(SysProperties.CHECK && record.getDeleted()) {
throw Message.getInternalError("duplicate delete " + pos);
}
record.setDeleted(session, true);
record.setDeleted(true);
int blockCount = record.getBlockCount();
if(database.isMultiVersion()) {
int todoMustFreeSpaceOnCommit;
......
......@@ -78,6 +78,9 @@ public class TableData extends Table implements RecordReader {
public void addRow(Session session, Row row) throws SQLException {
int i = 0;
lastModificationId = database.getNextModificationDataId();
if(database.isMultiVersion()) {
row.setSessionId(session.getId());
}
try {
for (; i < indexes.size(); i++) {
Index index = (Index) indexes.get(i);
......@@ -261,8 +264,14 @@ public class TableData extends Table implements RecordReader {
lastModificationId = database.getNextModificationDataId();
if(database.isMultiVersion()) {
if(row.getDeleted()) {
int testingWrongExceptionConcurrentUpdateOrSo;
throw Message.getSQLException(ErrorCode.LOCK_TIMEOUT_1);
throw Message.getSQLException(ErrorCode.CONCURRENT_UPDATE_1, getName());
}
int old = row.getSessionId();
int newId = session.getId();
if(old == 0) {
row.setSessionId(newId);
} else if(old != newId) {
throw Message.getSQLException(ErrorCode.CONCURRENT_UPDATE_1, getName());
}
}
for (int i = indexes.size() - 1; i >= 0; i--) {
......
......@@ -55,14 +55,15 @@ public class TableFilter implements ColumnResolver {
private boolean outerJoin;
private boolean foundOne;
private Expression fullCondition;
private final boolean rightsChecked;
public TableFilter(Session session, Table table, String alias, boolean rightsChecked, Select select) {
public TableFilter(Session session, Table table, String alias, boolean rightsChecked, Select select) throws SQLException {
this.session = session;
this.table = table;
this.alias = alias;
this.rightsChecked = rightsChecked;
this.select = select;
if(!rightsChecked) {
session.getUser().checkRight(table, Right.SELECT);
}
}
public Select getSelect() {
......@@ -74,9 +75,6 @@ public class TableFilter implements ColumnResolver {
}
public void lock(Session session, boolean exclusive, boolean force) throws SQLException {
if(!rightsChecked) {
session.getUser().checkRight(table, Right.SELECT);
}
table.lock(session, exclusive, force);
for(int i=0; joins != null && i<joins.size(); i++) {
getTableFilter(i).lock(session, exclusive, force);
......
......@@ -10,6 +10,7 @@ import org.h2.command.dml.Query;
import org.h2.constant.ErrorCode;
import org.h2.engine.Constants;
import org.h2.engine.Session;
import org.h2.engine.User;
import org.h2.expression.Expression;
import org.h2.index.Index;
import org.h2.index.IndexType;
......@@ -35,6 +36,7 @@ public class TableView extends Table {
private SmallLRUCache indexCache = new SmallLRUCache(Constants.VIEW_INDEX_CACHE_SIZE);
private long lastModificationCheck;
private long maxDataModificationId;
private User owner;
public TableView(Schema schema, int id, String name, String querySQL, ObjectArray params, String[] columnNames, Session session, boolean recursive) throws SQLException {
super(schema, id, name, false);
......@@ -44,7 +46,7 @@ public class TableView extends Table {
index = new ViewIndex(this, querySQL, params, recursive);
initColumnsAndTables(session);
}
private void initColumnsAndTables(Session session) throws SQLException {
Column[] cols;
removeViewFromTables();
......@@ -274,5 +276,13 @@ public class TableView extends Table {
t.addView(this);
}
}
public void setOwner(User owner) {
this.owner = owner;
}
public User getOwner() {
return owner;
}
}
......@@ -38,7 +38,7 @@ public class Backup {
* <li>-help or -? (print the list of options)
* </li><li>-file filename (the default is backup.zip)
* </li><li>-dir database directory (the default is the current directory)
* </li><li>-db database name (all databases if no name is specified)
* </li><li>-db database name (not required if there is only one database)
* </li><li>-quiet does not print progress information
* </li></ul>
*
......@@ -76,7 +76,7 @@ public class Backup {
*
* @param zipFileName the name of the backup file
* @param directory the directory name
* @param db the database name (null for all databases)
* @param db the database name (null if there is only one database)
* @param quiet don't print progress information
* @throws SQLException
*/
......@@ -101,7 +101,6 @@ public class Backup {
String fileName = (String) list.get(i);
if(fileName.endsWith(Constants.SUFFIX_DATA_FILE)) {
base = FileUtils.getParent(fileName);
base = FileUtils.getAbsolutePath(fileName);
}
}
for(int i=0; i<list.size(); i++) {
......
......@@ -130,6 +130,9 @@ public class Restore {
if(originalDbName == null) {
throw new IOException("No database named " + db + " found");
}
if(originalDbName.startsWith(File.separator)) {
originalDbName = originalDbName.substring(1);
}
}
in = FileUtils.openFileInputStream(zipFileName);
ZipInputStream zipIn = new ZipInputStream(in);
......
......@@ -244,9 +244,6 @@ public class FileUtils {
if(file.exists()) {
for(int i=0; i<SysProperties.MAX_FILE_RETRY; i++) {
trace("delete", fileName, null);
if(fileName.indexOf("1459.146") >= 0) {
new Error(fileName).printStackTrace();
}
boolean ok = file.delete();
if(ok) {
return;
......
......@@ -100,11 +100,27 @@ java -Xmx512m -Xrunhprof:cpu=samples,depth=8 org.h2.tools.RunScript -url jdbc:h2
/*
only admins can use nested tables:
CREATE USER TEST PASSWORD 'TEST';
SELECT * FROM (SELECT * FROM DUAL);
CREATE TABLE TEST(BIRTH TIMESTAMP);
INSERT INTO TEST VALUES('2006-04-03 10:20:30'), ('2006-04-03 10:20:31');
SELECT CAST(BIRTH AS DATE) B
FROM TEST GROUP BY CAST(BIRTH AS DATE)
HAVING CAST(BIRTH AS DATE) = '2004-05-05';
SELECT 1 FROM (SELECT CAST(BIRTH AS DATE) B
FROM TEST GROUP BY CAST(BIRTH AS DATE)) A
WHERE A.B = '2004-05-05';
DROP TABLE TEST;
CREATE TABLE TEST (ID integer NOT NULL PRIMARY KEY);
@LOOP 1000 INSERT INTO TEST VALUES(?);
CREATE VIEW TESTVIEW AS SELECT src.ID as VID FROM TEST AS h
INNER JOIN TEST AS src ON h.ID = src.ID GROUP BY src.ID;
-- slow
SELECT COUNT(*) FROM TESTVIEW AS S LEFT JOIN TESTVIEW AS T ON S.VID = T.VID;
DROP VIEW TESTVIEW;
DROP TABLE TEST;
m2-repo
add to maven
http://maven.apache.org/guides/mini/guide-central-repository-upload.html
add MVCC
......@@ -114,8 +130,6 @@ test and document fulltext search
clustered tables: test, document
add to maven
Switching off and switching on constraints could be made transactional.
Add version number. Install directory: h2-1.0, jar file: h2-1.0.jar
......
......@@ -26,20 +26,32 @@ public class TestRights extends TestBase {
// rights on tables and views
executeSuccess("CREATE USER PASS_READER PASSWORD 'abc'");
executeSuccess("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)");
executeSuccess("CREATE TABLE PASS(ID INT PRIMARY KEY, NAME VARCHAR, PASSWORD VARCHAR)");
executeSuccess("CREATE VIEW PASS_NAME AS SELECT ID, NAME FROM PASS");
executeSuccess("GRANT SELECT ON PASS_NAME TO PASS_READER");
executeSuccess("GRANT SELECT, INSERT, UPDATE ON TEST TO PASS_READER");
conn.close();
conn = getConnection("rights", "PASS_READER", "abc");
stat = conn.createStatement();
executeSuccess("SELECT * FROM PASS_NAME");
executeSuccess("SELECT * FROM (SELECT * FROM PASS_NAME)");
executeSuccess("SELECT (SELECT NAME FROM PASS_NAME) P FROM PASS_NAME");
executeError("SELECT (SELECT PASSWORD FROM PASS) P FROM PASS_NAME");
executeError("SELECT * FROM PASS");
executeError("INSERT INTO TEST SELECT 1, PASSWORD FROM PASS");
executeError("INSERT INTO TEST VALUES(SELECT PASSWORD FROM PASS)");
executeError("UPDATE TEST SET NAME=(SELECT PASSWORD FROM PASS)");
executeError("DELETE FROM TEST WHERE NAME=(SELECT PASSWORD FROM PASS)");
executeError("SELECT * FROM (SELECT * FROM PASS)");
executeError("CREATE VIEW X AS SELECT * FROM PASS_READER");
conn.close();
conn = getConnection("rights");
stat = conn.createStatement();
executeSuccess("DROP TABLE TEST");
executeSuccess("CREATE USER TEST PASSWORD 'abc'");
executeSuccess("ALTER USER TEST ADMIN TRUE");
executeSuccess("CREATE TABLE TEST(ID INT)");
......@@ -103,6 +115,7 @@ public class TestRights extends TestBase {
executeError("DELETE FROM ROLE_TABLE");
executeError("SELECT * FROM HIDDEN");
executeError("UPDATE TEST SET ID=0");
executeError("CALL SELECT MIN(PASSWORD) FROM PASS");
executeSuccess("SELECT * FROM SUB_TABLE");
executeSuccess("INSERT INTO SUB_TABLE VALUES(1)");
executeError("DELETE FROM SUB_TABLE");
......
......@@ -19,9 +19,8 @@ public class TestMVCC {
}
void test() throws Exception {
// TODO Prio 1: exception when deleting / updating the same in two transactions
// TODO Prio 1: document: exclusive table lock still used when altering tables, adding indexes, select ... for update; table level locks are checked
// TODO Prio 1: make unit test work
// TODO Prio 1: document: exclusive table lock still used when altering tables, adding indexes, select ... for update; table level locks are checked
// 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: Test with Hibernate
......@@ -43,20 +42,35 @@ public class TestMVCC {
s2 = c2.createStatement();
c1.setAutoCommit(false);
c2.setAutoCommit(false);
s1.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))");
s1.execute("INSERT INTO TEST VALUES(1, 'Test')");
c1.commit();
test(s1, "select max(id) from test", "1");
s1.execute("INSERT INTO TEST VALUES(2, 'World')");
c1.rollback();
test(s1, "select max(id) from test", "1");
c1.commit();
c2.commit();
s1.execute("DROP TABLE TEST");
s1.execute("create table test as select * from table(id int=(1, 2))");
s1.execute("update test set id=1 where id=1");
s1.execute("select max(id) from test");
test(s1, "select max(id) from test", "2");
c1.commit();
c2.commit();
s1.execute("DROP TABLE TEST");
s1.execute("CREATE TABLE TEST(ID INT)");
s1.execute("INSERT INTO TEST VALUES(1)");
c1.commit();
// test(s2, "SELECT COUNT(*) FROM TEST", "1");
System.out.println(s1.executeUpdate("DELETE FROM TEST"));
ResultSet rs = s2.executeQuery("SELECT * FROM TEST");
while(rs.next()) {
System.out.println(" " + rs.getString(1));
}
//
test(s2, "SELECT COUNT(*) FROM TEST", "1");
System.out.println(s2.executeUpdate("DELETE FROM TEST"));
s1.executeUpdate("DELETE FROM TEST");
test(s2, "SELECT COUNT(*) FROM TEST", "1");
test(s1, "SELECT COUNT(*) FROM TEST", "0");
c1.commit();
test(s2, "SELECT COUNT(*) FROM TEST", "0");
c1.commit();
c2.commit();
......
......@@ -7,6 +7,8 @@ package org.h2.test.unit;
import java.sql.*;
import org.h2.test.TestBase;
import org.h2.tools.Backup;
import org.h2.tools.Restore;
import org.h2.tools.Script;
import org.h2.tools.ChangePassword;
import org.h2.tools.DeleteDbFiles;
......@@ -22,7 +24,8 @@ public class TestTools extends TestBase {
testResourceGenerator();
testChangePassword();
testServer();
testBackupRunscript();
testScriptRunscript();
testBackupRestore();
}
private void testManagementDb() throws Exception {
......@@ -35,7 +38,7 @@ public class TestTools extends TestBase {
}
}
private void testBackupRunscript() throws Exception {
private void testScriptRunscript() throws Exception {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:" + BASE_DIR+ "/utils";
String user = "sa", password = "abc";
......@@ -53,6 +56,27 @@ public class TestTools extends TestBase {
conn.close();
}
private void testBackupRestore() throws Exception {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:" + BASE_DIR+ "/utils";
String user = "sa", password = "abc";
String fileName = BASE_DIR + "/b2.zip";
DeleteDbFiles.main(new String[]{"-dir", BASE_DIR, "-db", "utils", "-quiet"});
Connection conn = DriverManager.getConnection(url, user, password);
conn.createStatement().execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)");
conn.createStatement().execute("INSERT INTO TEST VALUES(1, 'Hello')");
conn.close();
Backup.main(new String[]{"-file", fileName, "-dir", BASE_DIR, "-db", "utils", "-quiet"});
DeleteDbFiles.main(new String[]{"-dir", BASE_DIR, "-db", "utils", "-quiet"});
Restore.main(new String[]{"-file", fileName, "-dir", BASE_DIR, "-db", "utils", "-quiet"});
conn = DriverManager.getConnection("jdbc:h2:" + BASE_DIR+ "/utils", "sa", "abc");
ResultSet rs = conn.createStatement().executeQuery("SELECT * FROM TEST");
check(rs.next());
checkFalse(rs.next());
conn.close();
DeleteDbFiles.main(new String[]{"-dir", BASE_DIR, "-db", "utils", "-quiet"});
}
private void testResourceGenerator() throws Exception {
Resources.main(new String[]{"."});
}
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论