提交 194fbc5c authored 作者: christian.peter.io's avatar christian.peter.io

Automatic database upgrade from non page store is now possible

上级 001f9d04
......@@ -68,6 +68,8 @@ Advanced
Setting the Server Bind Address</a><br />
<a href="#file_system">
Pluggable File System</a><br />
<a href="#database_upgrade">
Database Upgrade</a><br />
<a href="#limits_limitations">
Limits and Limitations</a><br />
<a href="#glossary_links">
......@@ -526,7 +528,7 @@ run <code>odbcad32.exe</code> to open the Data Source Administrator. Then click
and select the PostgreSQL Unicode driver. Then click 'Finish'.
You will be able to change the connection properties.
The property column represents the property key in the <code>odbc.ini</code> file
(which may be different from the GUI).
(which may be different from the GUI).
</p>
<table>
<tr><th>Property</th><th>Example</th><th>Remarks</th></tr>
......@@ -1221,6 +1223,29 @@ To register a new file system, extend the classes <code>org.h2.store.fs.FileSyst
and call the method <code>FileSystem.register</code> before using it.
</p>
<h2 id="database_upgrade">Database Upgrade</h2>
<p>
In version 1.2, H2 introduced a new file store implementation which is incompatible to the one used in versions &lt; 1.2.
To automatically convert databases to the new file store, it is necessary to include an additional jar file.
The file can be found at <a href="http://h2database.com/h2mig_pagestore_addon.jar">http://h2database.com/h2mig_pagestore_addon.jar</a> .
If this file is in the classpath, every connect to an older database will result in a conversion process.
</p>
<p>
The conversion itself is done via 'script to' and 'runscript from'. After the conversion process, the files will be
renamed from
<ul>
<li>dbName.data.db to dbName.data.db.backup
</li><li>dbName.index.db to dbName.index.db.backup
</li></ul>
by default. Also, the temporary script will be written to the database directory instead of a temporary directory.
Both defaults can be customized via
<ul>
<li>org.h2.upgrade.DbUpgradeNonPageStoreToCurrent.setDeleteOldDb(boolean)
</li><li>org.h2.upgrade.DbUpgradeNonPageStoreToCurrent.setScriptInTmpDir(boolean)
</li></ul>
prior opening a database connection.
</p>
<h2 id="limits_limitations">Limits and Limitations</h2>
<p>
This database has the following known limitations:
......
......@@ -18,12 +18,16 @@ Change Log
<h1>Change Log</h1>
<h2>Next Version (unreleased)</h2>
<ul><li>The MultiDimension tools was extended with a few helper methods.
<ul><li>Automatic database upgrade from non page store is now possible. If the file
h2mig_pagestore_addon.jar is found, the database will be converted to the current
page store format automatically. The file can be found in this zip:
<a href="http://h2database.com/h2mig_pagestore_addon.jar">http://h2database.com/h2mig_pagestore_addon.jar</a>
</li><li>The MultiDimension tools was extended with a few helper methods.
The API was unified a bit.
</li><li>ODBC: additional connection settings can now be added to the database name.
Example: ~/test;cipher=xtea. Therefore, encrypted databases are supported.
</li><li>Local temporary tables can now be created without having to commit a transaction
using CREATE LOCAL TEMPORARY TABLE TEMP(ID INT PRIMARY KEY) TRANSACTIONAL.
using CREATE LOCAL TEMPORARY TABLE TEMP(ID INT PRIMARY KEY) TRANSACTIONAL.
</li><li>New system property h2.dropRestrict (default false) to change the
default action for DROP TABLE and DROP VIEW (false for CASCADE, true for RESTRICT).
The plan is to enable this property by default in version 1.3.x.
......
......@@ -43,6 +43,11 @@ Downloads
<a href="http://www.h2database.com/automated/h2-latest.jar">Latest Automated Build (not released)</a>
</p>
<h3>Database Upgrade Helper Files</h3>
<p>
<a href="http://h2database.com/h2mig_pagestore_addon.jar">Upgrade database from 1.1 to the current version</a>
</p>
<h3>Subversion Source Repository</h3>
<p>
<a href="http://code.google.com/p/h2database/source">Google Code</a>
......
......@@ -575,10 +575,10 @@ When using H2 as the backend database for Apache ActiveMQ, please use the <code>
instead of the default locking mechanism. Otherwise the database file will grow without bounds. The problem is that the
default locking mechanism uses an uncommitted <code>UPDATE</code> transaction, which keeps the transaction log
from shrinking (causes the database file to grow). Instead of using an <code>UPDATE</code> statement, the <code>TransactDatabaseLocker</code> uses
<code>SELECT ... FOR UPDATE</code> which is not problematic.
To use it, change the ApacheMQ configuration element <code>&lt;jdbcPersistenceAdapter&gt;</code> element, property
<code>SELECT ... FOR UPDATE</code> which is not problematic.
To use it, change the ApacheMQ configuration element <code>&lt;jdbcPersistenceAdapter&gt;</code> element, property
<code>databaseLocker="org.apache.activemq.store.jdbc.adapter.TransactDatabaseLocker"</code>.
However, using the MVCC mode will again result in the same problem. Therefore, please do not use the MVCC mode in this case.
However, using the MVCC mode will again result in the same problem. Therefore, please do not use the MVCC mode in this case.
Another (more dangerous) solution is to set <code>useDatabaseLock</code> to false.
</p>
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -16,6 +16,7 @@ import org.h2.engine.Constants;
import org.h2.jdbc.JdbcConnection;
import org.h2.message.DbException;
import org.h2.message.TraceSystem;
import org.h2.upgrade.DbUpgrade;
/**
* The database driver. An application should not use this class directly. The
......@@ -55,6 +56,9 @@ public class Driver implements java.sql.Driver {
if (!acceptsURL(url)) {
return null;
}
// Upgrade the database if needed and conversion classes are found
DbUpgrade.upgrade(url, info);
return new JdbcConnection(url, info);
} catch (Exception e) {
throw DbException.toSQLException(e);
......
......@@ -184,7 +184,7 @@ CREATE [ CACHED | MEMORY ] [ TEMP | [ GLOBAL | LOCAL ] TEMPORARY ]
TABLE [ IF NOT EXISTS ] name
{ { ( { columnDefinition | constraint } [,...] ) [ AS select ] }
| { AS select } }
[ ENGINE tableEngineName ] [ NOT PERSISTENT ]
[ ENGINE tableEngineName ] [ TRANSACTIONAL ] [ NOT PERSISTENT ]
","
Creates a new table."
"Commands (DDL)","CREATE TRIGGER","
......@@ -241,9 +241,9 @@ DROP SEQUENCE [ IF EXISTS ] sequenceName
","
Drops a sequence."
"Commands (DDL)","DROP TABLE","
DROP TABLE [ IF EXISTS ] tableName [,...]
DROP TABLE [ IF EXISTS ] tableName [,...] [ CASCADE | RESTRICT ]
","
Drops an existing table, or a list of existing tables."
Drops an existing table, or a list of tables."
"Commands (DDL)","DROP TRIGGER","
DROP TRIGGER [ IF EXISTS ] triggerName
","
......@@ -255,7 +255,7 @@ Drops a user."
"Commands (DDL)","DROP VIEW","
DROP VIEW [ IF EXISTS ] viewName [ RESTRICT | CASCADE ]
","
Drops a view, and all dependent views if the CASCADE clause is used."
Drops an existing view."
"Commands (DDL)","TRUNCATE TABLE","
TRUNCATE TABLE tableName
","
......
/*
* Copyright 2004-2010 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.upgrade;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;
import java.util.Properties;
import org.h2.util.Utils;
/**
* This class starts the conversion from older database versions to the current
* version if the respective classes are found.
*/
public class DbUpgrade {
private static boolean nonPageStoreToCurrentEnabled;
private static Map<String, DbUpgradeNonPageStoreToCurrent> runningConversions;
static {
// static initialize block
nonPageStoreToCurrentEnabled = Utils.isClassPresent("org.h2.upgrade.v1_1_to_v1_2.Driver");
runningConversions = Collections.synchronizedMap(new Hashtable<String, DbUpgradeNonPageStoreToCurrent>(1));
}
/**
* Starts the conversion if the respective classes are found. Is automatically
* called on connect.
*
* @param url The connection string
* @param info The connection Properties
* @throws SQLException
*/
public synchronized static void upgrade(String url, Properties info) throws SQLException {
if (nonPageStoreToCurrentEnabled) {
upgradeFromNonPageStore(url, info);
}
}
private static void upgradeFromNonPageStore(String url, Properties info) throws SQLException {
if (runningConversions.containsKey(url)) {
// do not migrate, because we are currently migrating, and this is
// the connection where "runscript from" will be executed
return;
}
try {
DbUpgradeNonPageStoreToCurrent instance = new DbUpgradeNonPageStoreToCurrent(url, info);
runningConversions.put(url, instance);
instance.upgrade(url, info);
} finally {
runningConversions.remove(url);
}
}
}
/*
* Copyright 2004-2010 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.upgrade;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import org.h2.message.DbException;
import org.h2.util.Utils;
/**
* Class to convert a 1.1 DB (non page store) to a 1.2 DB (page store) format.
* Conversion is done via "script to" and "runscript from".
*/
public class DbUpgradeNonPageStoreToCurrent {
private static boolean scriptInTmpDir = false;
private static boolean deleteOldDb = false;
private String url;
private Properties info;
private boolean mustBeConverted;
private String newName;
private String oldUrl;
private File oldDataFile;
private File oldIndexFile;
private File newFile;
private File backupDataFile;
private File backupIndexFile;
private boolean successful;
/**
* Converts a database from a 1.1 DB (non page store) to a 1.2 DB
* (page store) format.
*
* @param url The connection string
* @param info The connection properties
* @throws SQLException if an exception occurred
*/
public DbUpgradeNonPageStoreToCurrent(String url, Properties info) throws SQLException {
this.url = url;
this.info = info;
init();
}
private void init() throws SQLException {
try {
String oldStartUrlPrefix = (String) Utils.getStaticField("org.h2.upgrade.v1_1_to_v1_2.engine.Constants.START_URL");
oldUrl = url;
oldUrl = oldUrl.replaceAll(org.h2.engine.Constants.START_URL, oldStartUrlPrefix);
oldUrl = oldUrl.replaceAll(";IGNORE_UNKNOWN_SETTINGS=TRUE", "");
oldUrl = oldUrl.replaceAll(";IGNORE_UNKNOWN_SETTINGS=FALSE", "");
oldUrl = oldUrl.replaceAll(";IFEXISTS=TRUE", "");
oldUrl = oldUrl.replaceAll(";IFEXISTS=FALSE", "");
oldUrl += ";IGNORE_UNKNOWN_SETTINGS=TRUE";
Object ci = Utils.newInstance("org.h2.upgrade.v1_1_to_v1_2.engine.ConnectionInfo", oldUrl, info);
boolean isRemote = (Boolean) Utils.callMethod("isRemote", ci);
boolean isPersistent = (Boolean) Utils.callMethod("isPersistent", ci);
String dbName = (String) Utils.callMethod("getName", ci);
if (!isRemote && isPersistent) {
String oldDataName = dbName + ".data.db";
String oldIndexName = dbName + ".index.db";
newName = dbName + ".h2.db";
oldDataFile = new File(oldDataName).getAbsoluteFile();
oldIndexFile = new File(oldIndexName).getAbsoluteFile();
newFile = new File(newName).getAbsoluteFile();
backupDataFile = new File(oldDataFile.getAbsolutePath() + ".backup");
backupIndexFile = new File(oldIndexFile.getAbsolutePath() + ".backup");
mustBeConverted = oldDataFile.exists() && !newFile.exists();
}
} catch (Exception e) {
DbException.toSQLException(e);
}
}
/**
* Returns if a database must be converted by this class.
*
* @param url The connection string
* @param info The connection properties
* @return if the conversion classes were found and the database must be
* converted
* @throws SQLException
*/
public boolean mustBeConverted(String url, Properties info) throws SQLException {
return mustBeConverted;
}
/**
* Converts the database from 1.1 (non page store) to current (page store).
*
* @param url The connection string
* @param info The connection properties
* @throws SQLException
*/
public void upgrade(String url, Properties info) throws SQLException {
successful = true;
if (!mustBeConverted) {
return;
}
File scriptFile = null;
try {
if (scriptInTmpDir) {
scriptFile = File.createTempFile("h2dbmigration", "backup.sql");
} else {
scriptFile = new File(oldDataFile.getAbsolutePath() + "_script.sql");
}
// outputStream.println("H2 Migrating '" + oldFile.getPath() +
// "' to '" + newFile.getPath() + "' via '" + scriptFile.getPath()
// + "'");
Utils.callStaticMethod("org.h2.upgrade.v1_1_to_v1_2.Driver.load");
Connection connection = DriverManager.getConnection(oldUrl, info);
Statement stmt = connection.createStatement();
stmt.execute("script to '" + scriptFile + "'");
stmt.close();
connection.close();
oldDataFile.renameTo(backupDataFile);
oldIndexFile.renameTo(backupIndexFile);
connection = DriverManager.getConnection(url, info);
stmt = connection.createStatement();
stmt.execute("runscript from '" + scriptFile + "'");
stmt.close();
connection.close();
if (deleteOldDb) {
backupDataFile.delete();
backupIndexFile.delete();
}
// outputStream.println("H2 Migration of '" + oldFile.getPath() +
// "' finished successfully");
} catch (Exception e) {
successful = false;
if (backupDataFile.exists()) {
backupDataFile.renameTo(oldDataFile);
}
if (backupIndexFile.exists()) {
backupIndexFile.renameTo(oldIndexFile);
}
newFile.delete();
// errorStream.println("H2 Migration of '" + oldFile.getPath() +
// "' finished with error: " + e.getMessage());
throw DbException.toSQLException(e);
} finally {
if (scriptFile != null) {
scriptFile.delete();
}
}
}
/**
* Returns if the database upgrade was successful.
*
* @return if the database upgrade was successful
*/
public boolean wasSuccessful() {
return successful;
}
/**
* The conversion script file will per default be created in the db
* directory. Use this method to change the directory to the temp
* directory.
*
* @param scriptInTmpDir true if the conversion script should be
* located in the temp directory.
*/
public static void setScriptInTmpDir(boolean scriptInTmpDir) {
DbUpgradeNonPageStoreToCurrent.scriptInTmpDir = scriptInTmpDir;
}
/**
* Old files will be renamed to .backup after a successful conversion. To
* delete them after the conversion, use this method with the parameter
* 'true'.
*
* @param deleteOldDb if true, the old db files will be deleted.
*/
public static void setDeleteOldDb(boolean deleteOldDb) {
DbUpgradeNonPageStoreToCurrent.deleteOldDb = deleteOldDb;
}
}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!--
Copyright 2004-2010 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
-->
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /><title>
Javadoc package documentation
</title></head><body style="font: 9pt/130% Tahoma, Arial, Helvetica, sans-serif; font-weight: normal;">
Implementation of the database upgrade mechanism.
</body></html>
\ No newline at end of file
......@@ -12,6 +12,10 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
......@@ -543,4 +547,205 @@ public class Utils {
}
}
/**
* Calls a static method via reflection. The order in which the method is
* searched:
* - With object arguments, eg. doSomething(Integer)
* - With primitive arguments, eg. doSomething(int)
*
* @param classAndMethod a string with the entire class and method name,
* eg. "java.lang.System.gc"
* @param params the method parameters
* @throws ClassNotFoundException if the class was not found
* @throws NoSuchMethodException if the class doesn't contain the method
* @throws InvocationTargetException if an exception occurred in the called
* method
* @throws IllegalAccessException if the reflection call is not allowed
* @throws IllegalArgumentException if the reflection call arguments are
* wrong
* @return Return value from this call
*/
public static Object callStaticMethod(String classAndMethod, Object ... params) throws ClassNotFoundException,
NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
int lastDot = classAndMethod.lastIndexOf('.');
String className = classAndMethod.substring(0, lastDot);
String methodName = classAndMethod.substring(lastDot + 1);
Class< ? >[] paramTypes = getParameterTypesObjects(params);
Class< ? > c = Class.forName(className);
Method m;
try {
m = c.getMethod(methodName, paramTypes);
} catch (NoSuchMethodException e) {
paramTypes = getParameterTypesPrimitives(params);
m = c.getMethod(methodName, paramTypes);
}
return m.invoke(null, params);
}
/**
* Calls an instance method via reflection. The order in which the method is
* searched:
* - With object arguments, eg. doSomething(Integer)
* - With primitive arguments, eg. doSomething(int)
*
* @param methodName a string with the method name
* @param instance the instance on which the call is done
* @param params the method parameters
* @throws ClassNotFoundException if the class was not found
* @throws NoSuchMethodException if the class doesn't contain the method
* @throws IllegalArgumentException if the reflection call arguments are
* wrong
* @throws IllegalAccessException if the reflection call is not allowed
* @throws InvocationTargetException if an exception occurred in the called
* method
* @return Return value from this call
*/
public static Object callMethod(String methodName, Object instance, Object ... params) throws ClassNotFoundException,
NoSuchMethodException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
Class< ? >[] paramTypes = getParameterTypesObjects(params);
Class< ? > c = instance.getClass();
Method m;
try {
m = c.getMethod(methodName, paramTypes);
} catch (NoSuchMethodException e) {
paramTypes = getParameterTypesPrimitives(params);
m = c.getMethod(methodName, paramTypes);
}
return m.invoke(instance, params);
}
/**
* Creates a new instance. The order in which the constructor is searched:
* - With object arguments, eg. SomeClass(Integer)
* - With primitive arguments, eg. SomeClass(int)
*
* @param className a string with the entire class, eg. "java.lang.Integer"
* @param params the constructor parameters
* @throws ClassNotFoundException if the class was not found
* @throws NoSuchMethodException if the class doesn't contain the
* constructor
* @throws IllegalArgumentException if the reflection call arguments are
* wrong
* @throws InstantiationException if it is not possible to instantiate the
* object
* @throws IllegalAccessException if the reflection call is not allowed
* @throws InvocationTargetException if an exception occurred in the called
* method
* @return the newly created object
*/
public static Object newInstance(String className, Object ... params) throws ClassNotFoundException, NoSuchMethodException,
IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
Class< ? > c = Class.forName(className);
Class< ? >[] paramTypes = getParameterTypesObjects(params);
Constructor< ? > constructor;
try {
constructor = c.getConstructor(paramTypes);
} catch (NoSuchMethodException e) {
paramTypes = getParameterTypesPrimitives(params);
constructor = c.getConstructor(paramTypes);
}
return constructor.newInstance(params);
}
/**
* Returns a static field.
*
* @param classAndField a string with the entire class and field name
* @return the field value
* @throws ClassNotFoundException if the class was not found
* @throws IllegalAccessException if it is not allowed to access the class
* @throws NoSuchFieldException if the field was not found
*/
public static Object getStaticField(String classAndField) throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
int lastDot = classAndField.lastIndexOf('.');
String className = classAndField.substring(0, lastDot);
String fieldName = classAndField.substring(lastDot + 1);
Class< ? > c = Class.forName(className);
Field f = c.getField(fieldName);
return f.get(null);
}
/**
* Returns a static field.
*
* @param instance the instance on which the call is done
* @param fieldName the field name
* @return the field value
* @throws ClassNotFoundException if the class was not found
* @throws IllegalAccessException if it is not allowed to access the class
* @throws NoSuchFieldException if the field was not found
*/
public static Object getField(Object instance, String fieldName) throws ClassNotFoundException, IllegalAccessException, NoSuchFieldException {
Class< ? > c = instance.getClass();
Field f = c.getField(fieldName);
return f.get(instance);
}
/**
* Returns true if the class is present in the current vm
*
* @param fullyQualifiedClassName a string with the entire class name, eg.
* "java.lang.System"
* @return true if the class is present in the current vm
*/
public static boolean isClassPresent(String fullyQualifiedClassName) {
try {
Class.forName(fullyQualifiedClassName);
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
private static Class< ? >[] getParameterTypesObjects(Object... params) {
Class< ? >[] paramTypes = new Class[params.length];
for (int i = 0; i < params.length; i++) {
paramTypes[i] = params[i].getClass();
}
return paramTypes;
}
private static Class< ? >[] getParameterTypesPrimitives(Object... params) {
Class< ? >[] paramTypes = new Class[params.length];
for (int i = 0; i < params.length; i++) {
paramTypes[i] = getPrimitiveIfPossible(params[i].getClass());
}
return paramTypes;
}
private static Class< ? > getPrimitiveIfPossible(Class< ? > clazz) {
if (clazz == Boolean.class) {
return boolean.class;
}
if (clazz == Byte.class) {
return byte.class;
}
if (clazz == Character.class) {
return char.class;
}
if (clazz == Double.class) {
return double.class;
}
if (clazz == Float.class) {
return float.class;
}
if (clazz == Integer.class) {
return int.class;
}
if (clazz == Long.class) {
return long.class;
}
if (clazz == Short.class) {
return short.class;
}
if (clazz == Void.class) {
return void.class;
}
return clazz;
}
}
......@@ -647,4 +647,5 @@ recorder grajciar recording slovensky uninitialized arriving lubomir unchanged
erik dick calculations lutin cite bom evaluating telegard excel bbs deprecation
importing cumulative fired convenient sums judged anybody vacuum encountered
corresponds cnf informatique ilm boundaries shao crossed retroweaver usr pico
pengxiang china timestampadd picked histogram transact locker activemq
\ No newline at end of file
pengxiang china timestampadd picked releasing autoboxing conversions
pagestore addon defaults introduced customized histogram transact locker activemq
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论