/* * Copyright 2004-2011 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 java.util.UUID; import org.h2.engine.ConnectionInfo; import org.h2.jdbc.JdbcConnection; import org.h2.message.DbException; import org.h2.store.fs.FileUtils; import org.h2.util.StringUtils; 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 upgradeClassesPresent; private static boolean scriptInTempDir; private static boolean deleteOldDb; static { upgradeClassesPresent = Utils.isClassPresent("org.h2.upgrade.v1_1.Driver"); } /** * If the upgrade classes are present, upgrade the database, or connect * using the old version (if the parameter NO_UPGRADE is set to true). If * the database is upgraded, or if no upgrade is possible or needed, this * methods returns null. * * @param url the database URL * @param info the properties * @return the connection if connected with the old version (NO_UPGRADE) */ public static Connection connectOrUpgrade(String url, Properties info) throws SQLException { if (!upgradeClassesPresent) { return null; } Properties i2 = new Properties(); i2.putAll(info); // clone so that the password (if set as a char array) is not cleared Object o = info.get("password"); if (o != null && o instanceof char[]) { i2.put("password", StringUtils.cloneCharArray((char[]) o)); } info = i2; ConnectionInfo ci = new ConnectionInfo(url, info); if (ci.isRemote() || !ci.isPersistent()) { return null; } String name = ci.getName(); if (FileUtils.exists(name + ".h2.db")) { return null; } if (!FileUtils.exists(name + ".data.db")) { return null; } if (ci.removeProperty("NO_UPGRADE", false)) { return connectWithOldVersion(url, info); } synchronized (DbUpgrade.class) { upgrade(ci, info); return null; } } /** * 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 scriptInTempDir true if the conversion script should be * located in the temp directory. */ public static void setScriptInTempDir(boolean scriptInTempDir) { DbUpgrade.scriptInTempDir = scriptInTempDir; } /** * 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) { DbUpgrade.deleteOldDb = deleteOldDb; } private static Connection connectWithOldVersion(String url, Properties info) throws SQLException { url = "jdbc:h2v1_1:" + url.substring("jdbc:h2:".length()) + ";IGNORE_UNKNOWN_SETTINGS=TRUE"; return DriverManager.getConnection(url, info); } private static void upgrade(ConnectionInfo ci, Properties info) throws SQLException { String name = ci.getName(); String data = name + ".data.db"; String index = name + ".index.db"; String lobs = name + ".lobs.db"; String backupData = data + ".backup"; String backupIndex = index + ".backup"; String backupLobs = lobs + ".backup"; String script = null; try { if (scriptInTempDir) { new File(Utils.getProperty("java.io.tmpdir", ".")).mkdirs(); script = File.createTempFile("h2dbmigration", "backup.sql").getAbsolutePath(); } else { script = name + ".script.sql"; } String oldUrl = "jdbc:h2v1_1:" + name + ";UNDO_LOG=0;LOG=0;LOCK_MODE=0"; String cipher = ci.getProperty("CIPHER", null); if (cipher != null) { oldUrl += ";CIPHER=" + cipher; } Connection conn = DriverManager.getConnection(oldUrl, info); Statement stat = conn.createStatement(); String uuid = UUID.randomUUID().toString(); if (cipher != null) { stat.execute("script to '" + script + "' cipher aes password '" + uuid + "' --hide--"); } else { stat.execute("script to '" + script + "'"); } conn.close(); FileUtils.moveTo(data, backupData); FileUtils.moveTo(index, backupIndex); if (FileUtils.exists(lobs)) { FileUtils.moveTo(lobs, backupLobs); } ci.removeProperty("IFEXISTS", false); conn = new JdbcConnection(ci, true); stat = conn.createStatement(); if (cipher != null) { stat.execute("runscript from '" + script + "' cipher aes password '" + uuid + "' --hide--"); } else { stat.execute("runscript from '" + script + "'"); } stat.execute("analyze"); stat.execute("shutdown compact"); stat.close(); conn.close(); if (deleteOldDb) { FileUtils.delete(backupData); FileUtils.delete(backupIndex); FileUtils.deleteRecursive(backupLobs, false); } } catch (Exception e) { if (FileUtils.exists(backupData)) { FileUtils.moveTo(backupData, data); } if (FileUtils.exists(backupIndex)) { FileUtils.moveTo(backupIndex, index); } if (FileUtils.exists(backupLobs)) { FileUtils.moveTo(backupLobs, lobs); } FileUtils.delete(name + ".h2.db"); throw DbException.toSQLException(e); } finally { if (script != null) { FileUtils.delete(script); } } } }