hal/src/zutil/db/DBUpgradeHandler.java

209 lines
8.4 KiB
Java
Raw Normal View History

2015-12-08 17:58:18 +01:00
package zutil.db;
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.
* NOTE: only works with sqllite
*
* Created by Ziver
*/
public class DBUpgradeHandler {
private static final Logger logger = LogUtil.getLogger();
private DBConnection reference;
private DBConnection target;
2015-12-09 14:51:58 +01:00
private boolean forceUpgrade = false;
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 forceDBUpgrade(boolean enable){
this.forceUpgrade = enable;
}
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...");
2015-12-08 17:58:18 +01:00
target.exec("BEGIN IMMEDIATE TRANSACTION");
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...");
2015-12-08 17:58:18 +01:00
target.exec("COMMIT TRANSACTION");
} catch(SQLException e){
try {
target.exec("ROLLBACK TRANSACTION");
} catch (SQLException secondary_e) {
logger.log(Level.SEVERE, null, secondary_e);
}
throw e;
}
}
2015-12-09 14:51:58 +01:00
private void upgradeRenameTables() throws SQLException {
if(tableRenameMap.size() > 0) {
List<String> targetTables = target.exec("SELECT name FROM sqlite_master WHERE type='table';", new ListSQLResult<String>());
for (String oldTableName : tableRenameMap.keySet()) {
if (targetTables.contains(oldTableName)) {
String newTableName = tableRenameMap.get(oldTableName);
logger.fine("Renaming table from: " + oldTableName + ", to: " + newTableName);
target.exec("ALTER TABLE "+oldTableName+" RENAME TO "+newTableName);
}
}
}
}
2015-12-08 17:58:18 +01:00
2015-12-09 14:51:58 +01:00
private void upgradeCreateTables() throws SQLException {
2015-12-08 17:58:18 +01:00
List<String> refTables = reference.exec("SELECT name FROM sqlite_master WHERE type='table';", new ListSQLResult<String>());
2015-12-08 22:38:56 +01:00
List<String> targetTables = target.exec("SELECT name FROM sqlite_master WHERE type='table';", new ListSQLResult<String>());
2015-12-08 17:58:18 +01:00
for(String table : refTables){
if(!targetTables.contains(table)){
logger.fine("Creating new table: "+ table);
2015-12-08 21:51:18 +01:00
// Get reference create sql
PreparedStatement stmt = reference.getPreparedStatement("SELECT sql FROM sqlite_master WHERE name == ?");
stmt.setString(1, table);
String sql = DBConnection.exec(stmt, new SimpleSQLResult<String>());
// 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 {
List<String> refTables = reference.exec("SELECT name FROM sqlite_master WHERE type='table';", new ListSQLResult<String>());
2015-12-08 22:38:56 +01:00
List<String> targetTables = target.exec("SELECT name FROM sqlite_master WHERE type='table';", new ListSQLResult<String>());
2015-12-08 21:51:18 +01:00
for(String table : targetTables){
if(!refTables.contains(table)){
2015-12-08 22:38:56 +01:00
logger.fine("Dropping table: " + table);
target.exec("DROP TABLE " + table);
2015-12-08 17:58:18 +01:00
}
}
}
private void upgradeAlterTables() throws SQLException {
2015-12-08 21:51:18 +01:00
List<String> refTables = reference.exec("SELECT name FROM sqlite_master WHERE type='table';", new ListSQLResult<String>());
2015-12-08 22:38:56 +01:00
List<String> targetTables = target.exec("SELECT name FROM sqlite_master WHERE type='table';", new ListSQLResult<String>());
2015-12-08 21:51:18 +01:00
for(String table : targetTables){
if(refTables.contains(table)){
// Get reference structure
List<DBColumn> refStruct = reference.exec("PRAGMA table_info("+table+")",
new TableStructureResultHandler());
2015-12-08 21:51:18 +01:00
// Get target structure
List<DBColumn> targetStruct = target.exec("PRAGMA table_info("+table+")",
new TableStructureResultHandler());
2015-12-08 21:51:18 +01:00
// Check existing columns
for(DBColumn column : refStruct) {
if(!targetStruct.contains(column)) {
logger.fine("Adding column '" + column.name + "' to table: " + table);
2015-12-08 22:38:56 +01:00
target.exec("ALTER TABLE "+table+" ADD COLUMN"
+ " " + column.name // Column name
+ " " + column.type // Column type
+ (column.defaultValue != null ? " DEFAULT '"+column.defaultValue+"'" : "")
+ (column.notNull ? " NOT NULL" : "")
+ (column.publicKey ? " PRIMARY KEY" : ""));
2015-12-08 21:51:18 +01:00
}
}
// Check unnecessary columns
for(DBColumn column : targetStruct) {
if(!refStruct.contains(column)) {
2015-12-09 14:51:58 +01:00
if(forceUpgrade){
/*
- put in a list the existing columns List<String> columns = DBUtils.GetColumns(db, TableName);
- backup table (ALTER table " + TableName + " RENAME TO 'temp_" + TableName)
- create new table (the newest table creation schema)
- get the intersection with the new columns, this time columns taken from the upgraded table (columns.retainAll(DBUtils.GetColumns(db, TableName));)
- restore data (String cols = StringUtils.join(columns, ",");
db.execSQL(String.format(
"INSERT INTO %s (%s) SELECT %s from temp_%s",
TableName, cols, cols, TableName));
)
- remove backup table (DROP table 'temp_" + TableName)
*/
}
else
logger.warning("Unable to drop column: '" + column.name + "' from table: "+ table +" (SQLite does not support dropping columns)");
2015-12-08 21:51:18 +01:00
}
}
}
}
2015-12-08 17:58:18 +01:00
}
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);
}
}
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
}
}