hal/src/zutil/db/DBUpgradeHandler.java

256 lines
9.9 KiB
Java
Raw Normal View History

2015-12-08 17:58:18 +01:00
package zutil.db;
2015-12-10 21:45:14 +01:00
import zutil.StringUtil;
2015-12-08 17:58:18 +01:00
import zutil.db.handler.ListSQLResult;
2015-12-08 21:51:18 +01:00
import zutil.db.handler.SimpleSQLResult;
2015-12-08 17:58:18 +01:00
import zutil.log.LogUtil;
2015-12-08 21:51:18 +01:00
import java.sql.PreparedStatement;
import java.sql.ResultSet;
2015-12-08 17:58:18 +01:00
import java.sql.SQLException;
2015-12-08 21:51:18 +01:00
import java.sql.Statement;
import java.util.ArrayList;
2015-12-09 14:51:58 +01:00
import java.util.HashMap;
2015-12-08 17:58:18 +01:00
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class will take a reference DB and alter
* the source DB to have the same structure.
2015-12-10 21:45:14 +01:00
* NOTE: only works with SQLite
2015-12-08 17:58:18 +01:00
*
* Created by Ziver
*/
public class DBUpgradeHandler {
private static final Logger logger = LogUtil.getLogger();
private DBConnection reference;
private DBConnection target;
2015-12-09 15:27:52 +01:00
private boolean forceUpgradeEnabled = false;
2015-12-09 14:51:58 +01:00
private HashMap<String,String> tableRenameMap;
2015-12-08 17:58:18 +01:00
public DBUpgradeHandler(DBConnection reference){
2015-12-09 14:51:58 +01:00
this.tableRenameMap = new HashMap<>();
2015-12-08 17:58:18 +01:00
this.reference = reference;
}
public void setTargetDB(DBConnection db){
this.target = db;
}
2015-12-09 14:51:58 +01:00
/**
* Will create a rename mapping where an existing table will be renamed.
*
* @param oldTableName current name of the table
* @param newTableName new name that ol table will be renamed to.
*/
public void setTableRenameMap(String oldTableName, String newTableName){
this.tableRenameMap.put(oldTableName, newTableName);
}
/**
* With the default behaviour unnecessary columns will not be removed.
* But if forced upgrade is set to true then the upgrade handler will
* create a new table and migrate the data from the old one to the new table.
*
* @param enable
*/
public void setForcedDBUpgrade(boolean enable){
2015-12-09 15:27:52 +01:00
this.forceUpgradeEnabled = enable;
2015-12-09 14:51:58 +01:00
}
2015-12-08 17:58:18 +01:00
public void upgrade() throws SQLException {
try {
2015-12-08 21:51:18 +01:00
logger.fine("Starting upgrade transaction...");
target.getConnection().setAutoCommit(false);
2015-12-08 17:58:18 +01:00
2015-12-09 14:51:58 +01:00
upgradeRenameTables();
upgradeCreateTables();
2015-12-08 21:51:18 +01:00
upgradeDropTables();
2015-12-08 17:58:18 +01:00
upgradeAlterTables();
2015-12-08 21:51:18 +01:00
logger.fine("Committing upgrade transaction...");
target.getConnection().commit();
2015-12-08 17:58:18 +01:00
} catch(SQLException e){
target.getConnection().rollback();
2015-12-08 17:58:18 +01:00
throw e;
} finally {
target.getConnection().setAutoCommit(true);
2015-12-08 17:58:18 +01:00
}
}
2015-12-09 14:51:58 +01:00
private void upgradeRenameTables() throws SQLException {
if(tableRenameMap.size() > 0) {
2015-12-09 15:27:52 +01:00
List<String> targetTables = getTableList(target);
2015-12-09 14:51:58 +01:00
for (String oldTableName : tableRenameMap.keySet()) {
if (targetTables.contains(oldTableName)) {
String newTableName = tableRenameMap.get(oldTableName);
logger.fine(String.format("Renaming table from: '%s' to: '%s'", oldTableName, newTableName));
2015-12-09 15:27:52 +01:00
target.exec(String.format(
"ALTER TABLE %s RENAME TO %s", oldTableName, newTableName));
2015-12-09 14:51:58 +01:00
}
}
}
}
2015-12-08 17:58:18 +01:00
2015-12-09 14:51:58 +01:00
private void upgradeCreateTables() throws SQLException {
2015-12-09 15:27:52 +01:00
List<String> refTables = getTableList(reference);
List<String> targetTables = getTableList(target);
2015-12-08 17:58:18 +01:00
for(String table : refTables){
if(!targetTables.contains(table)){
logger.fine(String.format("Creating new table: '%s'", table));
2015-12-08 21:51:18 +01:00
// Get reference create sql
2015-12-09 15:27:52 +01:00
String sql = getTableSql(reference, table);
2015-12-08 21:51:18 +01:00
// Execute sql on target
target.exec(sql);
}
}
}
2015-12-08 17:58:18 +01:00
2015-12-08 21:51:18 +01:00
private void upgradeDropTables() throws SQLException {
2015-12-09 15:27:52 +01:00
List<String> refTables = getTableList(reference);
List<String> targetTables = getTableList(target);
2015-12-08 21:51:18 +01:00
for(String table : targetTables){
if(!refTables.contains(table)){
logger.fine(String.format("Dropping table: '%s'", table));
2015-12-08 22:38:56 +01:00
target.exec("DROP TABLE " + table);
2015-12-08 17:58:18 +01:00
}
}
}
private void upgradeAlterTables() throws SQLException {
2015-12-09 15:27:52 +01:00
List<String> refTables = getTableList(reference);
List<String> targetTables = getTableList(target);
2015-12-08 21:51:18 +01:00
for(String table : targetTables){
if(refTables.contains(table)){
// Get reference structure
2015-12-09 15:27:52 +01:00
List<DBColumn> refStruct = getColumnList(reference, table);
2015-12-08 21:51:18 +01:00
// Get target structure
2015-12-09 15:27:52 +01:00
List<DBColumn> targetStruct = getColumnList(target, table);
2015-12-08 21:51:18 +01:00
// Check unnecessary columns
boolean execForcedUpgrade = false;
2015-12-08 21:51:18 +01:00
for(DBColumn column : targetStruct) {
if(refStruct.contains(column)) {
DBColumn refColumn = refStruct.get(refStruct.indexOf(column));
// Check if the columns have the same type
if(!column.type.equals(refColumn.type)){
if(forceUpgradeEnabled)
execForcedUpgrade = true;
else
logger.warning(String.format(
"Skipping alter(%s -> %s) column: '%s.%s' (no SQLite support, forced upgrade needed)",
column.type, refColumn.type, table, column.name));
}
}
else { // Column does not exist in reference DB, column should be removed
if (forceUpgradeEnabled)
execForcedUpgrade = true;
2015-12-09 14:51:58 +01:00
else
logger.warning(String.format(
"Skipping drop column: '%s.%s' (no SQLite support, forced upgrade needed)",
table, column.name));
2015-12-09 15:27:52 +01:00
}
}
// Do a forced upgrade where we create a new table and migrate the old data
if(execForcedUpgrade){
2015-12-09 15:27:52 +01:00
// Backup table
String backupTable = table+"_temp";
logger.fine(String.format("Forced Upgrade: Backing up table: '%s' to: '%s'", table, backupTable));
2015-12-09 15:27:52 +01:00
target.exec(String.format("ALTER TABLE %s RENAME TO %s", table, backupTable));
// Creating new table
logger.fine(String.format("Forced Upgrade: Creating new table: '%s'", table));
2015-12-09 15:27:52 +01:00
String sql = getTableSql(reference, table);
target.exec(sql);
// Restoring data
logger.fine(String.format("Forced Upgrade: Restoring data for table: '%s'", table));
2015-12-10 21:45:14 +01:00
String cols = StringUtil.join(refStruct, ",");
2015-12-09 15:27:52 +01:00
target.exec(String.format(
"INSERT INTO %s (%s) SELECT %s FROM %s",
table, cols, cols, backupTable));
// Remove backup table
logger.fine(String.format("Forced Upgrade: Dropping backup table: '%s'", backupTable));
2015-12-09 15:27:52 +01:00
target.exec("DROP TABLE " + backupTable);
}
// Do a
else{
// Add new columns
for(DBColumn column : refStruct) {
if(!targetStruct.contains(column)) {
logger.fine(String.format("Adding column '%s.%s'", table, column.name));
2015-12-09 15:27:52 +01:00
target.exec(
String.format("ALTER TABLE %s ADD COLUMN %s %s %s%s%s",
table,
column.name,
column.type,
(column.defaultValue != null ? " DEFAULT '"+column.defaultValue+"'" : ""),
(column.notNull ? " NOT NULL" : ""),
(column.publicKey ? " PRIMARY KEY" : "")));
}
2015-12-08 21:51:18 +01:00
}
}
}
}
2015-12-08 17:58:18 +01:00
}
2015-12-09 15:27:52 +01:00
private static List<String> getTableList(DBConnection db) throws SQLException {
return db.exec("SELECT name FROM sqlite_master WHERE type='table';", new ListSQLResult<String>());
}
private static String getTableSql(DBConnection db, String table) throws SQLException {
PreparedStatement stmt = db.getPreparedStatement("SELECT sql FROM sqlite_master WHERE name == ?");
stmt.setString(1, table);
return DBConnection.exec(stmt, new SimpleSQLResult<String>());
}
private static List<DBColumn> getColumnList(DBConnection db, String table) throws SQLException {
return db.exec(
String.format("PRAGMA table_info(%s)", table),
new TableStructureResultHandler());
}
2015-12-08 21:51:18 +01:00
private static class DBColumn{
String name;
String type;
boolean notNull;
String defaultValue;
boolean publicKey;
public boolean equals(Object obj){
return obj instanceof DBColumn &&
name.equals(((DBColumn)obj).name);
}
public String toString(){
return name;
}
2015-12-08 21:51:18 +01:00
}
private static class TableStructureResultHandler implements SQLResultHandler<List<DBColumn>>{
@Override
public List<DBColumn> handleQueryResult(Statement stmt, ResultSet result) throws SQLException {
ArrayList<DBColumn> list = new ArrayList<>();
while (result.next()){
DBColumn column = new DBColumn();
column.name = result.getString("name");
column.type = result.getString("type");
column.notNull = result.getBoolean("notnull");
column.defaultValue = result.getString("dflt_value");
column.publicKey = result.getBoolean("pk");
list.add(column);
}
return list;
}
2015-12-08 17:58:18 +01:00
}
}