提交 945c201f authored 作者: Thomas Mueller's avatar Thomas Mueller

New experimental server-less multi-connection mode

上级 c142344e
......@@ -4085,6 +4085,10 @@ public class Parser {
readIfEqualOrTo();
read();
return new NoOperation(session);
} else if (readIf("OPEN_NEW")) {
readIfEqualOrTo();
read();
return new NoOperation(session);
} else if (readIf("RECOVER")) {
readIfEqualOrTo();
read();
......
......@@ -368,6 +368,12 @@ public abstract class Prepared {
return sqlStatement;
}
/**
* Get the SQL snippet of the value list.
*
* @param values the value list
* @return the SQL snippet
*/
protected String getSQL(Value[] values) {
StringBuffer buff = new StringBuffer();
for (int i = 0; i < values.length; i++) {
......@@ -382,6 +388,12 @@ public abstract class Prepared {
return buff.toString();
}
/**
* Get the SQL snippet of the expression list.
*
* @param list the expression list
* @return the SQL snippet
*/
protected String getSQL(Expression[] list) {
StringBuffer buff = new StringBuffer();
for (int i = 0; i < list.length; i++) {
......@@ -396,7 +408,14 @@ public abstract class Prepared {
return buff.toString();
}
/**
* Set the SQL statement of the exception to the given row.
*
* @param ex the exception
* @param rowId the row number
* @param values the values of the row
* @return the exception
*/
protected SQLException setRow(SQLException ex, int rowId, String values) {
if (ex instanceof JdbcSQLException) {
JdbcSQLException e = (JdbcSQLException) ex;
......
......@@ -9,13 +9,11 @@ package org.h2.command.dml;
import java.sql.SQLException;
import org.h2.command.Prepared;
import org.h2.constant.SysProperties;
import org.h2.engine.Database;
import org.h2.engine.Session;
import org.h2.log.LogSystem;
import org.h2.message.Message;
import org.h2.result.LocalResult;
import org.h2.store.PageStore;
/**
* Represents a transactional statement.
......@@ -124,12 +122,7 @@ public class TransactionCommand extends Prepared {
break;
case CHECKPOINT:
session.getUser().checkAdmin();
if (SysProperties.PAGE_STORE) {
PageStore store = session.getDatabase().getPageStore();
store.checkpoint();
}
session.getDatabase().getLog().checkpoint();
session.getDatabase().getTempFileDeleter().deleteUnused();
session.getDatabase().checkpoint();
break;
case SAVEPOINT:
session.addSavepoint(savepointName);
......
......@@ -446,6 +446,15 @@ public class SysProperties {
*/
public static final boolean RECOMPILE_ALWAYS = getBooleanSetting("h2.recompileAlways", false);
/**
* System property <code>h2.reconnectCheckDelay</code> (default: 250).<br />
* Check the .lock.db file every this many milliseconds to detect that the
* database was changed. The process writing to the database must first
* notify a change in the .lock.db file, then wait twice this many
* milliseconds before updating the database.
*/
public static final int RECONNECT_CHECK_DELAY = getIntSetting("h2.reconnectCheckDelay", 250);
/**
* System property <code>h2.redoBufferSize</code> (default: 262144).<br />
* Size of the redo buffer (used at startup when recovering).
......@@ -558,6 +567,11 @@ public class SysProperties {
*/
public static final int COLLATOR_CACHE_SIZE = getCollatorCacheSize();
/**
* The current time this class was loaded (in milliseconds).
*/
public static final long TIME_START = System.currentTimeMillis();
private static final String H2_BASE_DIR = "h2.baseDir";
private SysProperties() {
......
......@@ -277,6 +277,18 @@ public class ConnectionInfo implements Cloneable {
userPasswordHash = sha.getKeyPasswordHash(user, password);
}
/**
* Get a boolean property if it is set and return the value.
*
* @param key the property name
* @param defaultValue the default value
* @return the value
*/
public boolean getProperty(String key, boolean defaultValue) {
String x = getProperty(key, null);
return x == null ? defaultValue : Boolean.valueOf(x).booleanValue();
}
/**
* Remove a boolean property if it is set and return the value.
*
......
......@@ -13,6 +13,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
......@@ -165,6 +166,10 @@ public class Database implements DataHandler {
private TempFileDeleter tempFileDeleter = TempFileDeleter.getInstance();
private PageStore pageStore;
private Properties reconnectLastLock;
private long reconnectCheckNext;
private boolean reconnectChangePending;
public Database(String name, ConnectionInfo ci, String cipher) throws SQLException {
this.compareMode = new CompareMode(null, null, 0);
this.persistent = ci.isPersistent();
......@@ -172,15 +177,18 @@ public class Database implements DataHandler {
this.databaseName = name;
this.databaseShortName = parseDatabaseShortName();
this.cipher = cipher;
String lockMethodName = ci.removeProperty("FILE_LOCK", null);
this.accessModeLog = ci.removeProperty("ACCESS_MODE_LOG", "rw").toLowerCase();
this.accessModeData = ci.removeProperty("ACCESS_MODE_DATA", "rw").toLowerCase();
this.autoServerMode = ci.removeProperty("AUTO_SERVER", false);
String lockMethodName = ci.getProperty("FILE_LOCK", null);
this.accessModeLog = ci.getProperty("ACCESS_MODE_LOG", "rw").toLowerCase();
this.accessModeData = ci.getProperty("ACCESS_MODE_DATA", "rw").toLowerCase();
this.autoServerMode = ci.getProperty("AUTO_SERVER", false);
if ("r".equals(accessModeData)) {
readOnly = true;
accessModeLog = "r";
}
this.fileLockMethod = FileLock.getFileLockMethod(lockMethodName);
if (fileLockMethod == FileLock.LOCK_SERIALIZED) {
writeDelay = SysProperties.MIN_WRITE_DELAY;
}
this.databaseURL = ci.getURL();
this.eventListener = ci.getDatabaseEventListenerObject();
ci.removeDatabaseEventListenerObject();
......@@ -199,12 +207,16 @@ public class Database implements DataHandler {
if (ignoreSummary != null) {
this.recovery = true;
}
this.multiVersion = ci.removeProperty("MVCC", false);
boolean closeAtVmShutdown = ci.removeProperty("DB_CLOSE_ON_EXIT", true);
this.multiVersion = ci.getProperty("MVCC", false);
boolean closeAtVmShutdown = ci.getProperty("DB_CLOSE_ON_EXIT", true);
int traceLevelFile = ci.getIntProperty(SetTypes.TRACE_LEVEL_FILE, TraceSystem.DEFAULT_TRACE_LEVEL_FILE);
int traceLevelSystemOut = ci.getIntProperty(SetTypes.TRACE_LEVEL_SYSTEM_OUT,
TraceSystem.DEFAULT_TRACE_LEVEL_SYSTEM_OUT);
this.cacheType = StringUtils.toUpperEnglish(ci.removeProperty("CACHE_TYPE", CacheLRU.TYPE_NAME));
openDatabase(traceLevelFile, traceLevelSystemOut, closeAtVmShutdown);
}
private void openDatabase(int traceLevelFile, int traceLevelSystemOut, boolean closeAtVmShutdown) throws SQLException {
try {
open(traceLevelFile, traceLevelSystemOut);
if (closeAtVmShutdown) {
......@@ -295,6 +307,25 @@ public class Database implements DataHandler {
return modificationDataId;
}
private void reconnectModified(boolean pending) {
if (readOnly || pending == reconnectChangePending || lock == null) {
return;
}
try {
if (pending) {
getTrace().debug("wait before writing");
Thread.sleep((int) (SysProperties.RECONNECT_CHECK_DELAY * 1.1));
}
lock.setProperty("modificationDataId", Long.toString(modificationDataId));
lock.setProperty("modificationMetaId", Long.toString(modificationMetaId));
lock.setProperty("changePending", pending ? "true" : null);
lock.save();
reconnectChangePending = pending;
} catch (Exception e) {
getTrace().error("pending:"+ pending, e);
}
}
public long getNextModificationDataId() {
return ++modificationDataId;
}
......@@ -478,12 +509,14 @@ public class Database implements DataHandler {
}
}
if (!readOnly && fileLockMethod != FileLock.LOCK_NO) {
lock = new FileLock(traceSystem, Constants.LOCK_SLEEP);
lock.lock(databaseName + Constants.SUFFIX_LOCK_FILE, fileLockMethod == FileLock.LOCK_SOCKET);
lock = new FileLock(traceSystem, databaseName + Constants.SUFFIX_LOCK_FILE, Constants.LOCK_SLEEP);
lock.lock(fileLockMethod);
if (autoServerMode) {
startServer(lock.getUniqueId());
}
}
// wait until pending changes are written
isReconnectNeeded();
if (SysProperties.PAGE_STORE) {
PageStore store = getPageStore();
if (!store.isNew()) {
......@@ -602,7 +635,8 @@ public class Database implements DataHandler {
"-key", key, databaseName});
server.start();
String address = NetUtils.getLocalAddress() + ":" + server.getPort();
lock.addProperty("server", address);
lock.setProperty("server", address);
lock.save();
}
private void stopServer() {
......@@ -2138,4 +2172,83 @@ public class Database implements DataHandler {
return null;
}
public boolean isReconnectNeeded() {
if (fileLockMethod != FileLock.LOCK_SERIALIZED) {
return false;
}
long now = System.currentTimeMillis();
if (now < reconnectCheckNext) {
return false;
}
reconnectCheckNext = now + SysProperties.RECONNECT_CHECK_DELAY;
if (lock == null) {
lock = new FileLock(traceSystem, databaseName + Constants.SUFFIX_LOCK_FILE, Constants.LOCK_SLEEP);
}
Properties prop;
try {
while (true) {
prop = lock.load();
if (prop.equals(reconnectLastLock)) {
return false;
}
if (prop.getProperty("changePending", null) == null) {
break;
}
getTrace().debug("delay (change pending)");
Thread.sleep(SysProperties.RECONNECT_CHECK_DELAY);
}
reconnectLastLock = prop;
} catch (Exception e) {
getTrace().error("readOnly:" + readOnly, e);
// ignore
}
return true;
}
/**
* This method is called after writing to the database.
*/
public void afterWriting() throws SQLException {
if (fileLockMethod != FileLock.LOCK_SERIALIZED || readOnly) {
return;
}
reconnectCheckNext = System.currentTimeMillis() + 1;
}
/**
* Flush all changes when using the serialized mode, and if there are
* pending changes.
*/
public void checkpointIfRequired() throws SQLException {
if (fileLockMethod != FileLock.LOCK_SERIALIZED || readOnly || !reconnectChangePending) {
return;
}
long now = System.currentTimeMillis();
if (now > reconnectCheckNext) {
getTrace().debug("checkpoint");
checkpoint();
reconnectModified(false);
}
}
/**
* Flush all changes and open a new log file.
*/
public void checkpoint() throws SQLException {
if (SysProperties.PAGE_STORE) {
pageStore.checkpoint();
}
getLog().checkpoint();
getTempFileDeleter().deleteUnused();
}
/**
* This method is called before writing to the log file.
*/
public void beforeWriting() {
if (fileLockMethod == FileLock.LOCK_SERIALIZED) {
reconnectModified(true);
}
}
}
......@@ -16,6 +16,7 @@ import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.message.Message;
import org.h2.message.Trace;
import org.h2.store.FileLock;
import org.h2.util.RandomUtils;
import org.h2.util.StringUtils;
......@@ -41,7 +42,7 @@ public class Engine {
private Session openSession(ConnectionInfo ci, boolean ifExists, String cipher) throws SQLException {
String name = ci.getName();
Database database;
boolean openNew = ci.removeProperty("OPEN_NEW", false);
boolean openNew = ci.getProperty("OPEN_NEW", false);
if (openNew || ci.isUnnamedInMemory()) {
database = null;
} else {
......@@ -105,8 +106,20 @@ public class Engine {
*/
public Session getSession(ConnectionInfo ci) throws SQLException {
try {
ConnectionInfo backup = null;
String lockMethodName = ci.getProperty("FILE_LOCK", null);
int fileLockMethod = FileLock.getFileLockMethod(lockMethodName);
if (fileLockMethod == FileLock.LOCK_SERIALIZED) {
try {
backup = (ConnectionInfo) ci.clone();
} catch (CloneNotSupportedException e) {
}
}
Session session = openSession(ci);
validateUserAndPassword(true);
if (backup != null) {
session.setConnectionInfo(backup);
}
return session;
} catch (SQLException e) {
if (e.getErrorCode() == ErrorCode.WRONG_USER_OR_PASSWORD) {
......
......@@ -46,7 +46,7 @@ import org.h2.value.ValueNull;
* mode, this object resides on the server side and communicates with a
* SessionRemote object on the client side.
*/
public class Session implements SessionInterface {
public class Session extends SessionWithState {
/**
* The prefix of generated identifiers. It may not have letters, because
......@@ -56,9 +56,10 @@ public class Session implements SessionInterface {
private static int nextSerialId;
private final int serialId = nextSerialId++;
private Database database;
private ConnectionInfo connectionInfo;
private User user;
private int id;
private Database database;
private ObjectArray locks = new ObjectArray();
private UndoLog undoLog;
private boolean autoCommit = true;
......@@ -97,6 +98,7 @@ public class Session implements SessionInterface {
private boolean commitOrRollbackDisabled;
private Table waitForLock;
private int modificationId;
private int modificationIdState;
Session(Database database, User user, int id) {
this.database = database;
......@@ -625,12 +627,13 @@ public class Session implements SessionInterface {
}
}
private void unlockAll() {
private void unlockAll() throws SQLException {
if (SysProperties.CHECK) {
if (undoLog.size() > 0) {
Message.throwInternalError();
}
}
database.afterWriting();
if (locks.size() > 0) {
synchronized (database) {
for (int i = 0; i < locks.size(); i++) {
......@@ -641,6 +644,10 @@ public class Session implements SessionInterface {
}
}
savepoints = null;
if (modificationIdState != modificationId) {
sessionStateChanged = true;
}
}
private void cleanTempTables(boolean closeSession) throws SQLException {
......@@ -1104,4 +1111,20 @@ public class Session implements SessionInterface {
return modificationId;
}
public boolean isReconnectNeeded() {
return database.isReconnectNeeded();
}
public SessionInterface reconnect() throws SQLException {
readSessionState();
Session newSession = Engine.getInstance().getSession(connectionInfo);
newSession.sessionState = sessionState;
newSession.recreateSessionState();
return newSession;
}
public void setConnectionInfo(ConnectionInfo ci) {
connectionInfo = ci;
}
}
......@@ -72,4 +72,18 @@ public interface SessionInterface {
* Cancel the current or next command (called when closing a connection).
*/
void cancel();
/**
* Check if the database changed and therefore reconnecting is required.
*
* @return true if reconnecting is required
*/
boolean isReconnectNeeded();
/**
* Close the connection and open a new connection.
*
* @return the new connection
*/
SessionInterface reconnect() throws SQLException;
}
......@@ -41,7 +41,7 @@ import org.h2.value.ValueString;
* The client side part of a session when using the server mode. This object
* communicates with a Session on the server side.
*/
public class SessionRemote implements SessionInterface, SessionFactory, DataHandler {
public class SessionRemote extends SessionWithState implements SessionFactory, DataHandler {
public static final int SESSION_PREPARE = 0;
public static final int SESSION_CLOSE = 1;
......@@ -81,9 +81,6 @@ public class SessionRemote implements SessionInterface, SessionFactory, DataHand
private boolean autoReconnect;
private int lastReconnect;
private SessionInterface embedded;
private boolean sessionStateChanged;
private ObjectArray sessionState;
private boolean sessionStateUpdating;
private DatabaseEventListener eventListener;
public SessionRemote() {
......@@ -259,7 +256,7 @@ public class SessionRemote implements SessionInterface, SessionFactory, DataHand
// OPEN_NEW must be removed now, otherwise
// opening a session with AUTO_SERVER fails
// if another connection is already open
backup.removeProperty("OPEN_NEW", false);
backup.removeProperty("OPEN_NEW", null);
connectServer(backup);
return this;
}
......@@ -451,19 +448,7 @@ public class SessionRemote implements SessionInterface, SessionFactory, DataHand
// unfortunately
connectEmbeddedOrServer(true);
}
if (sessionState != null && sessionState.size() > 0) {
sessionStateUpdating = true;
try {
for (int i = 0; i < sessionState.size(); i++) {
String sql = (String) sessionState.get(i);
CommandInterface ci = prepareCommand(sql, Integer.MAX_VALUE);
ci.executeUpdate();
}
} finally {
sessionStateUpdating = false;
sessionStateChanged = false;
}
}
recreateSessionState();
if (eventListener != null) {
eventListener.setProgress(DatabaseEventListener.STATE_RECONNECTED, databaseName, count,
SysProperties.MAX_RECONNECT);
......@@ -663,25 +648,16 @@ public class SessionRemote implements SessionInterface, SessionFactory, DataHand
return lastReconnect;
}
/**
* Read the session state if necessary.
*/
public void readSessionState() throws SQLException {
if (!sessionStateChanged || sessionStateUpdating) {
return;
}
sessionStateChanged = false;
sessionState = new ObjectArray();
CommandInterface ci = prepareCommand("SELECT * FROM INFORMATION_SCHEMA.SESSION_STATE", Integer.MAX_VALUE);
ResultInterface result = ci.executeQuery(0, false);
while (result.next()) {
Value[] row = result.currentRow();
sessionState.add(row[1].getString());
public TempFileDeleter getTempFileDeleter() {
return TempFileDeleter.getInstance();
}
public boolean isReconnectNeeded() {
return false;
}
public TempFileDeleter getTempFileDeleter() {
return TempFileDeleter.getInstance();
public SessionInterface reconnect() {
return this;
}
}
/*
* Copyright 2004-2009 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.engine;
import java.sql.SQLException;
import org.h2.command.CommandInterface;
import org.h2.result.ResultInterface;
import org.h2.util.ObjectArray;
import org.h2.value.Value;
/**
* The base class for both remote and embedded sessions.
*/
public abstract class SessionWithState implements SessionInterface {
protected ObjectArray sessionState;
protected boolean sessionStateChanged;
private boolean sessionStateUpdating;
/**
* Re-create the session state using the stored sessionState list.
*/
protected void recreateSessionState() throws SQLException {
if (sessionState != null && sessionState.size() > 0) {
sessionStateUpdating = true;
try {
for (int i = 0; i < sessionState.size(); i++) {
String sql = (String) sessionState.get(i);
CommandInterface ci = prepareCommand(sql, Integer.MAX_VALUE);
ci.executeUpdate();
}
} finally {
sessionStateUpdating = false;
sessionStateChanged = false;
}
}
}
/**
* Read the session state if necessary.
*/
public void readSessionState() throws SQLException {
if (!sessionStateChanged || sessionStateUpdating) {
return;
}
sessionStateChanged = false;
sessionState = new ObjectArray();
CommandInterface ci = prepareCommand("SELECT * FROM INFORMATION_SCHEMA.SESSION_STATE", Integer.MAX_VALUE);
ResultInterface result = ci.executeQuery(0, false);
while (result.next()) {
Value[] row = result.currentRow();
sessionState.add(row[1].getString());
}
}
}
......@@ -62,9 +62,6 @@ import java.sql.SQLClientInfoException;
* </p>
*/
public class JdbcConnection extends TraceObject implements Connection {
// TODO test: check if enough synchronization on jdbc objects
// TODO feature: auto-reconnect on lost connection
private String url;
private String user;
......@@ -1266,6 +1263,12 @@ public class JdbcConnection extends TraceObject implements Connection {
if (session.isClosed()) {
throw Message.getSQLException(ErrorCode.DATABASE_CALLED_AT_SHUTDOWN);
}
if (session.isReconnectNeeded()) {
trace.debug("reconnect");
session = session.reconnect();
trace = session.getTrace();
setTrace(trace, TraceObject.CONNECTION, getTraceId());
}
}
String getURL() throws SQLException {
......
......@@ -509,6 +509,7 @@ public class LogSystem {
return;
}
database.checkWritingAllowed();
database.beforeWriting();
if (!file.isDataFile()) {
storageId = -storageId;
}
......@@ -535,6 +536,7 @@ public class LogSystem {
return;
}
database.checkWritingAllowed();
database.beforeWriting();
int storageId = record.getStorageId();
if (!file.isDataFile()) {
storageId = -storageId;
......
......@@ -178,6 +178,10 @@ public class TraceSystem implements TraceWriter {
updateLevel();
}
public int getLevelSystemOut() {
return levelSystemOut;
}
/**
* Set the file trace level.
*
......@@ -209,6 +213,10 @@ public class TraceSystem implements TraceWriter {
updateLevel();
}
public int getLevelFile() {
return levelFile;
}
private String format(String module, String s) {
synchronized (dateFormat) {
return dateFormat.format(new Date()) + module + ": " + s;
......
......@@ -54,8 +54,14 @@ public class FileLock {
*/
public static final int LOCK_SOCKET = 2;
/**
* This locking method means multiple writers are allowed, and they
* synchronize themselves.
*/
public static final int LOCK_SERIALIZED = 3;
private static final String MAGIC = "FileLock";
private static final String FILE = "file", SOCKET = "socket";
private static final String FILE = "file", SOCKET = "socket", SERIALIZED = "serialized";
private static final int RANDOM_BYTES = 16;
private static final int SLEEP_GAP = 25;
private static final int TIME_GRANULARITY = 2000;
......@@ -101,30 +107,34 @@ public class FileLock {
* @param traceSystem the trace system to use
* @param sleep the number of milliseconds to sleep
*/
public FileLock(TraceSystem traceSystem, int sleep) {
public FileLock(TraceSystem traceSystem, String fileName, int sleep) {
this.trace = traceSystem.getTrace(Trace.FILE_LOCK);
this.fileName = fileName;
this.sleep = sleep;
}
/**
* Lock the file if possible. A file may only be locked once.
*
* @param fileName the name of the properties file to use
* @param allowSocket if the socket locking protocol should be used if
* possible
* @param fileLockMethod the file locking method to use
* @throws SQLException if locking was not successful
*/
public synchronized void lock(String fileName, boolean allowSocket) throws SQLException {
public synchronized void lock(int fileLockMethod) throws SQLException {
this.fs = FileSystem.getInstance(fileName);
this.fileName = fileName;
checkServer();
if (locked) {
Message.throwInternalError("already locked");
}
if (allowSocket) {
lockSocket();
} else {
switch (fileLockMethod) {
case LOCK_FILE:
lockFile();
break;
case LOCK_SOCKET:
lockSocket();
break;
case LOCK_SERIALIZED:
lockSerialized();
break;
}
locked = true;
}
......@@ -155,14 +165,18 @@ public class FileLock {
}
/**
* Add a setting to the properties file.
* Add or change a setting to the properties. This call does not save the
* file.
*
* @param key the key
* @param value the value
*/
public void addProperty(String key, String value) throws SQLException {
public void setProperty(String key, String value) throws SQLException {
if (value == null) {
properties.remove(key);
} else {
properties.put(key, value);
save();
}
}
/**
......@@ -187,11 +201,10 @@ public class FileLock {
/**
* Save the lock file.
*/
void save() throws SQLException {
public void save() throws SQLException {
try {
OutputStream out = fs.openFileOutputStream(fileName, false);
try {
properties.setProperty("method", String.valueOf(method));
properties.store(out, MAGIC);
} finally {
out.close();
......@@ -242,7 +255,12 @@ public class FileLock {
}
}
private Properties load() throws SQLException {
/**
* Load the properties file.
*
* @return the properties
*/
public Properties load() throws SQLException {
try {
Properties p2 = SortedProperties.loadProperties(fileName);
if (trace.isDebugEnabled()) {
......@@ -281,9 +299,17 @@ public class FileLock {
properties.setProperty("id", uniqueId);
}
private void lockSerialized() throws SQLException {
method = SERIALIZED;
properties = new SortedProperties();
properties.setProperty("method", String.valueOf(method));
setUniqueId();
}
private void lockFile() throws SQLException {
method = FILE;
properties = new SortedProperties();
properties.setProperty("method", String.valueOf(method));
setUniqueId();
if (!fs.createNewFile(fileName)) {
waitUntilOld();
......@@ -337,6 +363,7 @@ public class FileLock {
private void lockSocket() throws SQLException {
method = SOCKET;
properties = new SortedProperties();
properties.setProperty("method", String.valueOf(method));
setUniqueId();
// if this returns 127.0.0.1,
// the computer is probably not networked
......@@ -458,6 +485,8 @@ public class FileLock {
return FileLock.LOCK_NO;
} else if (method.equalsIgnoreCase("SOCKET")) {
return FileLock.LOCK_SOCKET;
} else if (method.equalsIgnoreCase("SERIALIZED")) {
return FileLock.LOCK_SERIALIZED;
} else {
throw Message.getSQLException(ErrorCode.UNSUPPORTED_LOCK_METHOD_1, method);
}
......
......@@ -63,6 +63,7 @@ public class PageStore implements CacheWriter {
// synchronized correctly (on the index?)
// TODO two phase commit: append (not patch) commit & rollback
// TODO remove trace or use isDebugEnabled
// TODO recover tool: don't re-do uncommitted operations
/**
* The smallest possible page size.
......
......@@ -142,6 +142,17 @@ public class WriterThread extends Thread {
if (Constants.FLUSH_INDEX_DELAY != 0) {
flushIndexes(database);
}
// checkpoint if required
try {
database.checkpointIfRequired();
} catch (SQLException e) {
TraceSystem traceSystem = database.getTraceSystem();
if (traceSystem != null) {
traceSystem.getTrace(Trace.LOG).error("reconnectCheckpoint", e);
}
}
LogSystem log = database.getLog();
if (log == null) {
break;
......@@ -154,6 +165,7 @@ public class WriterThread extends Thread {
traceSystem.getTrace(Trace.LOG).error("flush", e);
}
}
// TODO log writer: could also flush the dirty cache when there is
// low activity
int wait = writeDelay;
......
......@@ -283,23 +283,15 @@ java org.h2.test.TestAll timer
/*
maybe close the database when a final static field is set to null?
select 1 from dual a where 1 in(select 1 from dual b
where 1 in(select 1 from dual c where a.x=1));
use 127.0.0.1 if other addresses don't work
isShutdown
error message on insert / merge: include SQL statement (at least table name)
PageStore.switchLogIfPossible()
drop table test;
create table test(id int);
select 1 from test where 'a'=1;
Fails: Oracle, PostgreSQL, H2
Works: MySQL, HSQLDB
use 127.0.0.1 if other addresses don't work
select for update in mvcc mode: only lock the selected records?
test case for daylight saving time enabled/move to a timezone (locking,...)
JCR: for each node type, create a table; one 'dynamic' table with parameter;
option to cache the results
<link rel="icon" type="image/png" href="/path/image.png">
......
......@@ -375,8 +375,8 @@ public abstract class TestBase {
e.printStackTrace();
try {
TraceSystem ts = new TraceSystem(null, false);
FileLock lock = new FileLock(ts, 1000);
lock.lock("error.lock", false);
FileLock lock = new FileLock(ts, "error.lock", 1000);
lock.lock(FileLock.LOCK_FILE);
FileWriter fw = new FileWriter("ERROR.txt", true);
PrintWriter pw = new PrintWriter(fw);
e.printStackTrace(pw);
......
......@@ -62,9 +62,9 @@ public class TestFileLock extends TestBase implements Runnable {
public void run() {
while (!stop) {
FileLock lock = new FileLock(new TraceSystem(null, false), 100);
FileLock lock = new FileLock(new TraceSystem(null, false), FILE, 100);
try {
lock.lock(FILE, allowSockets);
lock.lock(allowSockets ? FileLock.LOCK_SOCKET : FileLock.LOCK_FILE);
base.trace(lock + " locked");
locks++;
if (locks > 1) {
......
/*
* Copyright 2004-2009 H2 Group. Multiple-Licensed under the H2 License,
* Version 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.test.unit;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.h2.test.TestBase;
/**
* Test the serialized (server-less) mode.
*/
public class TestFileLockSerialized extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String[] a) throws Exception {
TestBase.createCaller().init().test();
}
public void test() throws Exception {
// TODO support long running queries
deleteDb("fileLockSerialized");
String url = "jdbc:h2:" + baseDir + "/fileLockSerialized";
String writeUrl = url + ";FILE_LOCK=SERIALIZED;OPEN_NEW=TRUE";
// ;TRACE_LEVEL_SYSTEM_OUT=3
// String readUrl = writeUrl + ";ACCESS_MODE_LOG=R;ACCESS_MODE_DATA=R";
trace("create database");
Class.forName("org.h2.Driver");
Connection conn = DriverManager.getConnection(writeUrl, "sa", "sa");
Statement stat = conn.createStatement();
stat.execute("create table test(id int primary key)");
Connection conn3 = DriverManager.getConnection(writeUrl, "sa", "sa");
Statement stat3 = conn3.createStatement();
Connection conn2 = DriverManager.getConnection(writeUrl, "sa", "sa");
Statement stat2 = conn2.createStatement();
printResult(stat2, "select * from test");
stat2.execute("create local temporary table temp(name varchar)");
printResult(stat2, "select * from temp");
trace("insert row 1");
stat.execute("insert into test values(1)");
trace("insert row 2");
stat3.execute("insert into test values(2)");
printResult(stat2, "select * from test");
printResult(stat2, "select * from temp");
conn.close();
conn2.close();
conn3.close();
}
private void printResult(Statement stat, String sql) throws SQLException {
trace("query: " + sql);
ResultSet rs = stat.executeQuery(sql);
int rowCount = 0;
while (rs.next()) {
trace(" " + rs.getString(1));
rowCount++;
}
trace(" " + rowCount + " row(s)");
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论