提交 4bab319a authored 作者: Thomas Mueller's avatar Thomas Mueller

Multiple processes can now access the same database.

上级 59c152d1
......@@ -4035,6 +4035,10 @@ public class Parser {
readIfEqualOrTo();
read();
return new NoOperation(session);
} else if (readIf("AUTO_SERVER")) {
readIfEqualOrTo();
read();
return new NoOperation(session);
} else if (readIf("ASSERT")) {
readIfEqualOrTo();
read();
......
......@@ -17,6 +17,7 @@ import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.message.Message;
import org.h2.security.SHA256;
import org.h2.util.ByteUtils;
import org.h2.util.FileUtils;
import org.h2.util.MathUtils;
import org.h2.util.ObjectArray;
......@@ -25,9 +26,9 @@ import org.h2.util.StringUtils;
/**
* Encapsulates the connection settings, including user name and password.
*/
public class ConnectionInfo {
public class ConnectionInfo implements Cloneable {
private static final HashSet KNOWN_SETTINGS = new HashSet();
private final Properties prop = new Properties();
private Properties prop = new Properties();
private String originalURL;
private String url;
private String user;
......@@ -42,6 +43,14 @@ public class ConnectionInfo {
private boolean ssl;
private boolean persistent;
private boolean unnamed;
public Object clone() throws CloneNotSupportedException {
ConnectionInfo clone = (ConnectionInfo) super.clone();
clone.prop = (Properties) prop.clone();
clone.filePasswordHash = ByteUtils.cloneByteArray(filePasswordHash);
clone.userPasswordHash = ByteUtils.cloneByteArray(userPasswordHash);
return clone;
}
static {
ObjectArray list = SetTypes.getSettings();
......@@ -51,7 +60,7 @@ public class ConnectionInfo {
// TODO document these settings
String[] connectionTime = new String[] { "ACCESS_MODE_LOG", "ACCESS_MODE_DATA", "AUTOCOMMIT", "CIPHER",
"CREATE", "CACHE_TYPE", "DB_CLOSE_ON_EXIT", "FILE_LOCK", "IGNORE_UNKNOWN_SETTINGS", "IFEXISTS",
"PASSWORD", "RECOVER", "STORAGE", "USER", "DATABASE_EVENT_LISTENER_OBJECT" };
"PASSWORD", "RECOVER", "STORAGE", "USER", "DATABASE_EVENT_LISTENER_OBJECT", "AUTO_SERVER" };
for (int i = 0; i < connectionTime.length; i++) {
String key = connectionTime[i];
if (SysProperties.CHECK && KNOWN_SETTINGS.contains(key)) {
......@@ -360,7 +369,7 @@ public class ConnectionInfo {
* @param defaultValue the default value
* @return the value as a String
*/
String getProperty(String key, String defaultValue) {
public String getProperty(String key, String defaultValue) {
if (SysProperties.CHECK && !KNOWN_SETTINGS.contains(key)) {
throw Message.getInternalError(key);
}
......@@ -500,4 +509,16 @@ public class ConnectionInfo {
String format = Constants.URL_FORMAT;
return Message.getSQLException(ErrorCode.URL_FORMAT_ERROR_2, new String[] { format, url });
}
/**
* Switch to server mode.
*
* @param server the server
*/
public void setServer(String server) {
remote = true;
persistent = false;
name = server + "/" + name;
}
}
......@@ -51,6 +51,7 @@ import org.h2.table.Table;
import org.h2.table.TableData;
import org.h2.table.TableView;
import org.h2.tools.DeleteDbFiles;
import org.h2.tools.Server;
import org.h2.util.BitField;
import org.h2.util.ByteUtils;
import org.h2.util.CacheLRU;
......@@ -58,6 +59,7 @@ import org.h2.util.ClassUtils;
import org.h2.util.FileUtils;
import org.h2.util.IOUtils;
import org.h2.util.IntHashMap;
import org.h2.util.NetUtils;
import org.h2.util.ObjectArray;
import org.h2.util.SmallLRUCache;
import org.h2.util.StringUtils;
......@@ -155,8 +157,9 @@ public class Database implements DataHandler {
private int maxOperationMemory = SysProperties.DEFAULT_MAX_OPERATION_MEMORY;
private boolean lobFilesInDirectories = SysProperties.LOB_FILES_IN_DIRECTORIES;
private SmallLRUCache lobFileListCache = new SmallLRUCache(128);
private boolean autoServerMode;
private Object reserveMemory;
private Server server;
public Database(String name, ConnectionInfo ci, String cipher) throws SQLException {
this.compareMode = new CompareMode(null, null, 0);
......@@ -168,6 +171,7 @@ public class Database implements DataHandler {
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);
if ("r".equals(accessModeData)) {
readOnly = true;
accessModeLog = "r";
......@@ -392,6 +396,7 @@ public class Database implements DataHandler {
fileIndex = null;
}
if (lock != null) {
stopServer();
lock.unlock();
lock = null;
}
......@@ -446,10 +451,13 @@ public class Database implements DataHandler {
*
* @param cipher the cipher algorithm
* @param hash the hash code
* @return true if the password matches
* @return true if the cipher algorithm and the password match
*/
public boolean validateFilePasswordHash(String cipher, byte[] hash) {
return ByteUtils.compareSecure(hash, filePasswordHash) && StringUtils.equals(cipher, this.cipher);
public boolean validateFilePasswordHash(String cipher, byte[] hash) throws SQLException {
if (!StringUtils.equals(cipher, this.cipher)) {
return false;
}
return ByteUtils.compareSecure(hash, filePasswordHash);
}
private void openFileData() throws SQLException {
......@@ -490,6 +498,7 @@ public class Database implements DataHandler {
// if it is already read-only because ACCESS_MODE_DATA=r
readOnly = readOnly | FileUtils.isReadOnly(dataFileName);
textStorage = isTextStorage(dataFileName, textStorage);
lobFilesInDirectories &= !ValueLob.existsLobFile(getDatabasePath());
lobFilesInDirectories |= FileUtils.exists(databaseName + Constants.SUFFIX_LOBS_DIRECTORY);
}
}
......@@ -507,9 +516,17 @@ public class Database implements DataHandler {
traceSystem.setLevelSystemOut(traceLevelSystemOut);
traceSystem.getTrace(Trace.DATABASE)
.info("opening " + databaseName + " (build " + Constants.BUILD_ID + ")");
if (autoServerMode) {
if (readOnly || fileLockMethod == FileLock.LOCK_NO) {
throw Message.getSQLException(ErrorCode.FEATURE_NOT_SUPPORTED);
}
}
if (!readOnly && fileLockMethod != FileLock.LOCK_NO) {
lock = new FileLock(traceSystem, Constants.LOCK_SLEEP);
lock.lock(databaseName + Constants.SUFFIX_LOCK_FILE, fileLockMethod == FileLock.LOCK_SOCKET);
if (autoServerMode) {
startServer();
}
}
deleteOldTempFiles();
log = new LogSystem(this, databaseName, readOnly, accessModeLog);
......@@ -603,6 +620,20 @@ public class Database implements DataHandler {
traceSystem.getTrace(Trace.DATABASE).info("opened " + databaseName);
}
private void startServer() throws SQLException {
server = Server.createTcpServer(new String[]{"-tcpPort", "0"});
server.start();
String address = NetUtils.getLocalAddress() + ":" + server.getPort();
lock.addProperty("server", address);
}
private void stopServer() {
if (server != null) {
server.stop();
server = null;
}
}
private void recompileInvalidViews(Session session) {
boolean recompileSuccessful;
do {
......@@ -1021,6 +1052,7 @@ public class Database implements DataHandler {
}
closing = true;
}
stopServer();
try {
if (systemSession != null) {
ObjectArray tablesAndViews = getAllSchemaObjects(DbObject.TABLE_OR_VIEW);
......
......@@ -22,6 +22,7 @@ import java.sql.Statement;
import java.util.Map;
import java.util.Properties;
import org.h2.command.CommandInterface;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
......@@ -105,7 +106,28 @@ public class JdbcConnection extends TraceObject implements Connection {
ci.setBaseDir(baseDir);
}
}
session = si.createSession(ci);
boolean autoServerMode = Boolean.parseBoolean(ci.getProperty("AUTO_SERVER", "false"));
ConnectionInfo backup = null;
if (autoServerMode) {
backup = (ConnectionInfo) ci.clone();
}
try {
session = si.createSession(ci);
} catch (SQLException e) {
int errorCode = e.getErrorCode();
if (errorCode == ErrorCode.DATABASE_ALREADY_OPEN_1) {
if (autoServerMode) {
String server = (String) ((JdbcSQLException) e).getPayload();
if (server != null) {
backup.setServer(server);
session = new SessionRemote().createSession(backup);
}
}
}
if (session == null) {
throw e;
}
}
}
trace = session.getTrace();
int id = getNextId(TraceObject.CONNECTION);
......
......@@ -23,6 +23,7 @@ public class JdbcSQLException extends SQLException {
private final String stackTrace;
private String message;
private String sql;
private volatile Object payload;
/**
* Creates a SQLException a message, sqlstate and cause.
......@@ -173,4 +174,22 @@ public class JdbcSQLException extends SQLException {
return stackTrace;
}
/**
* Get the error related payload object.
*
* @return the payload
*/
public Object getPayload() {
return payload;
}
/**
* Set the error related payload object.
*
* @param payload the new payload
*/
public void setPayload(Object payload) {
this.payload = payload;
}
}
......@@ -19,6 +19,7 @@ import java.util.Properties;
import org.h2.constant.ErrorCode;
import org.h2.constant.SysProperties;
import org.h2.jdbc.JdbcSQLException;
import org.h2.message.Message;
import org.h2.message.Trace;
import org.h2.message.TraceSystem;
......@@ -102,7 +103,7 @@ 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, int sleep) {
this.trace = traceSystem.getTrace(Trace.FILE_LOCK);
this.sleep = sleep;
}
......@@ -154,6 +155,17 @@ public class FileLock {
locked = false;
}
/**
* Add a setting to the properties file.
*
* @param key the key
* @param value the value
*/
public void addProperty(String key, String value) throws SQLException {
properties.put(key, value);
save();
}
/**
* This finalizer unlocks the file if necessary.
*/
......@@ -377,7 +389,15 @@ public class FileLock {
}
private SQLException error(String reason) {
return Message.getSQLException(ErrorCode.DATABASE_ALREADY_OPEN_1, reason);
JdbcSQLException ex = Message.getSQLException(ErrorCode.DATABASE_ALREADY_OPEN_1, reason);
String server = null;
try {
server = load().getProperty("server");
} catch (SQLException e) {
// ignore
}
ex.setPayload(server);
return ex;
}
/**
......
......@@ -188,7 +188,7 @@ public class ByteUtils {
* @param buff the byte array
*/
public static void clear(byte[] buff) {
for (int i = 0; i < buff.length; i++) {
for (int i = 0; buff != null && i < buff.length; i++) {
buff[i] = 0;
}
}
......@@ -241,6 +241,9 @@ public class ByteUtils {
* @return a new byte array
*/
public static byte[] cloneByteArray(byte[] b) {
if (b == null) {
return null;
}
int len = b.length;
if (len == 0) {
return b;
......
......@@ -77,6 +77,7 @@ import org.h2.test.mvcc.TestMvcc2;
import org.h2.test.mvcc.TestMvcc3;
import org.h2.test.mvcc.TestMvccMultiThreaded;
import org.h2.test.rowlock.TestRowLocks;
import org.h2.test.server.TestAutoServer;
import org.h2.test.server.TestNestedLoop;
import org.h2.test.server.TestPgServer;
import org.h2.test.server.TestWeb;
......@@ -273,6 +274,8 @@ java org.h2.test.TestAll timer
/*
test on linux
main methods for the tests and make that work
TestMVCC:
......@@ -519,6 +522,7 @@ http://www.w3schools.com/sql/
new TestXASimple().runTest(this);
// server
new TestAutoServer().runTest(this);
new TestNestedLoop().runTest(this);
new TestWeb().runTest(this);
new TestPgServer().runTest(this);
......
......@@ -19,7 +19,6 @@ import java.sql.Statement;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Properties;
import org.h2.jdbc.JdbcConnection;
import org.h2.message.TraceSystem;
......@@ -136,9 +135,13 @@ public abstract class TestBase {
protected Connection getConnection(String name, String user, String password) throws SQLException {
return getConnectionInternal(getURL(name, false), user, password);
}
protected String getPassword(String userPassword) {
return config.cipher == null ? userPassword : "filePassword " + userPassword;
}
protected String getPassword() {
return "123";
return getPassword("123");
}
private void deleteIndexFiles(String name) {
......@@ -222,6 +225,9 @@ public abstract class TestBase {
if (config.diskResult && admin) {
url += ";MAX_MEMORY_ROWS=100;CACHE_SIZE=0";
}
if (config.cipher != null) {
url += ";CIPHER=" + config.cipher;
}
return "jdbc:h2:" + url;
}
......@@ -230,20 +236,7 @@ public abstract class TestBase {
// url += ";DEFAULT_TABLE_TYPE=1";
// Class.forName("org.hsqldb.jdbcDriver");
// return DriverManager.getConnection("jdbc:hsqldb:" + name, "sa", "");
Connection conn;
if (config.cipher != null) {
url += ";cipher=" + config.cipher;
password = "filePassword " + password;
Properties info = new Properties();
info.setProperty("user", user);
info.setProperty("password", password);
// a bug in the PostgreSQL driver: throws a NullPointerException if we do this
// info.put("password", password.toCharArray());
conn = DriverManager.getConnection(url, info);
} else {
conn = DriverManager.getConnection(url, user, password);
}
return conn;
return DriverManager.getConnection(url, user, password);
}
/**
......
/*
* Copyright 2004-2008 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.server;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import org.h2.test.TestBase;
import org.h2.util.SortedProperties;
/**
* Tests automatic embedded/server mode.
*/
public class TestAutoServer extends TestBase {
static final boolean SLOW = false;
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String[] a) throws Exception {
new TestAutoServer().init().test();
}
public void test() throws Exception {
if (config.memory) {
return;
}
deleteDb("autoServer");
String url = getURL("autoServer;AUTO_SERVER=TRUE", true);
String user = getUser(), password = getPassword();
Connection conn = getConnection(url, user, password);
conn.close();
String[] procDef = new String[] { "java", "-cp", "bin", TestAutoServer2.class.getName(), url, user, password };
// TestAutoServer2.main(new String[]{url, user, password});
Process proc = Runtime.getRuntime().exec(procDef);
int i = SLOW ? Integer.MAX_VALUE : 30;
for (; i > 0; i--) {
Thread.sleep(100);
SortedProperties prop = SortedProperties.loadProperties(baseDir + "/autoServer.lock.db");
String server = prop.getProperty("server");
if (server != null) {
String u2 = url.substring(url.indexOf("autoServer"));
u2 = "jdbc:h2:tcp://" + server + "/" + baseDir + "/" + u2;
conn = DriverManager.getConnection(u2, user, password);
conn.close();
break;
}
}
if (i <= 0) {
fail();
}
conn = getConnection(url);
Statement stat = conn.createStatement();
stat.execute("CREATE ALIAS HALT FOR \"" + getClass().getName() + ".halt\"");
try {
stat.execute("CALL HALT(11)");
} catch (SQLException e) {
assertKnownException(e);
}
conn.close();
assertEquals(11, proc.exitValue());
// proc.destroy();
}
public static void halt(int exitValue) {
Runtime.getRuntime().halt(exitValue);
}
}
/*
* Copyright 2004-2008 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.server;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.sql.Connection;
import java.sql.DriverManager;
import org.h2.test.TestBase;
/**
* Tests automatic embedded/server mode.
*/
public class TestAutoServer2 extends TestBase {
public static void main(String[] a) throws Exception {
if (a.length == 3) {
// PrintStream ps = new PrintStream(new File("test.txt"));
PrintStream ps = new PrintStream(new ByteArrayOutputStream());
System.setErr(ps);
System.setOut(ps);
ps.println(new java.sql.Timestamp(System.currentTimeMillis()).toString());
ps.flush();
try {
ps.println("loading");
ps.flush();
org.h2.Driver.load();
ps.println("connecting url:" + a[0] + " user:" + a[1] + " pwd:" + a[2]);
ps.flush();
Connection conn = DriverManager.getConnection(a[0], a[1], a[2]);
ps.println("waiting");
ps.flush();
Thread.sleep(TestAutoServer.SLOW ? 60000 : 5000);
ps.println("closing");
ps.flush();
conn.close();
ps.println("closed");
ps.flush();
} catch (Throwable t) {
t.printStackTrace(ps);
t.printStackTrace();
}
ps.close();
System.exit(0);
} else {
new TestAutoServer2().init().test();
}
}
public void test() throws Exception {
deleteDb("autoServer");
String url = getURL("autoServer;AUTO_SERVER=TRUE", true);
String user = getUser(), password = getPassword();
main(new String[]{url, user, password});
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论