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 forceDBUpgrade(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...");
|
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) {
|
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("Renaming table from: " + oldTableName + ", to: " + 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("Creating new table: "+ 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)){
|
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-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
|
2015-12-09 15:27:52 +01:00
|
|
|
boolean forcedUpgrade = false;
|
2015-12-08 21:51:18 +01:00
|
|
|
for(DBColumn column : targetStruct) {
|
|
|
|
|
if(!refStruct.contains(column)) {
|
2015-12-09 15:27:52 +01:00
|
|
|
if(forceUpgradeEnabled)
|
|
|
|
|
forcedUpgrade = true;
|
2015-12-09 14:51:58 +01:00
|
|
|
else
|
2015-12-09 15:27:52 +01:00
|
|
|
logger.warning("Unable to drop column: '" + column.name + "' from table: "+ table
|
|
|
|
|
+" (SQLite does not support dropping columns)");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Do a forced upgrade where we create a new table and migrate the old data
|
|
|
|
|
if(forcedUpgrade){
|
|
|
|
|
// Backup table
|
|
|
|
|
String backupTable = table+"_temp";
|
|
|
|
|
logger.fine("Forced Upgrade: Backing up table: "+table+", to: "+backupTable);
|
|
|
|
|
target.exec(String.format("ALTER TABLE %s RENAME TO %s", table, backupTable));
|
|
|
|
|
|
|
|
|
|
// Creating new table
|
|
|
|
|
logger.fine("Forced Upgrade: Creating new table: "+table);
|
|
|
|
|
String sql = getTableSql(reference, table);
|
|
|
|
|
target.exec(sql);
|
|
|
|
|
|
|
|
|
|
// Restoring data
|
|
|
|
|
logger.fine("Forced Upgrade: Restoring data for table: "+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("Forced Upgrade: Removing backup table: "+backupTable);
|
|
|
|
|
target.exec("DROP TABLE " + backupTable);
|
|
|
|
|
}
|
|
|
|
|
// Do a
|
|
|
|
|
else{
|
|
|
|
|
// Add new columns
|
|
|
|
|
for(DBColumn column : refStruct) {
|
|
|
|
|
if(!targetStruct.contains(column)) {
|
|
|
|
|
logger.fine("Adding column '" + column.name + "' to table: " + table);
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|