Twidere-App-Android-Twitter.../twidere/src/main/java/org/mariotaku/twidere/util/content/DatabaseUpgradeHelper.java

178 lines
8.1 KiB
Java

/*
* Twidere - Twitter client for Android
*
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.mariotaku.twidere.util.content;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.mariotaku.querybuilder.Columns;
import org.mariotaku.querybuilder.Columns.Column;
import org.mariotaku.querybuilder.Constraint;
import org.mariotaku.querybuilder.Expression;
import org.mariotaku.querybuilder.NewColumn;
import org.mariotaku.querybuilder.OnConflict;
import org.mariotaku.querybuilder.Tables;
import org.mariotaku.querybuilder.query.SQLInsertQuery;
import org.mariotaku.querybuilder.query.SQLSelectQuery;
import org.mariotaku.twidere.util.TwidereArrayUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import static org.mariotaku.querybuilder.SQLQueryBuilder.alterTable;
import static org.mariotaku.querybuilder.SQLQueryBuilder.createTable;
import static org.mariotaku.querybuilder.SQLQueryBuilder.dropTable;
import static org.mariotaku.querybuilder.SQLQueryBuilder.insertInto;
import static org.mariotaku.querybuilder.SQLQueryBuilder.select;
public final class DatabaseUpgradeHelper {
public static void safeUpgrade(final SQLiteDatabase db, final String table, final String[] newColNames,
final String[] newColTypes, final boolean dropDirectly,
final Map<String, String> colAliases, final OnConflict onConflict,
final Constraint... constraints) {
if (newColNames == null || newColTypes == null || newColNames.length != newColTypes.length)
throw new IllegalArgumentException("Invalid parameters for upgrading table " + table
+ ", length of columns and types not match.");
// First, create the table if not exists.
final NewColumn[] newCols = NewColumn.createNewColumns(newColNames, newColTypes);
final String createQuery = createTable(true, table).columns(newCols).constraint(constraints).buildSQL();
db.execSQL(createQuery);
// We need to get all data from old table.
final String[] oldCols = getColumnNames(db, table);
if (oldCols == null || TwidereArrayUtils.contentMatch(newColNames, oldCols)) return;
if (dropDirectly) {
db.beginTransaction();
db.execSQL(dropTable(true, table).getSQL());
db.execSQL(createQuery);
db.setTransactionSuccessful();
db.endTransaction();
return;
}
final String tempTable = String.format(Locale.US, "temp_%s_%d", table, System.currentTimeMillis());
db.beginTransaction();
db.execSQL(alterTable(table).renameTo(tempTable).buildSQL());
db.execSQL(createQuery);
final String[] notNullCols = getNotNullColumns(newCols);
final String insertQuery = createInsertDataQuery(table, tempTable, newColNames, oldCols, colAliases,
notNullCols, onConflict);
if (insertQuery != null) {
db.execSQL(insertQuery);
}
db.execSQL(dropTable(true, tempTable).getSQL());
db.setTransactionSuccessful();
db.endTransaction();
}
public static void safeUpgrade(final SQLiteDatabase db, final String table, final String[] newColNames,
final String[] newColTypes, final boolean dropDirectly, final Map<String, String> colAliases, final Constraint... constraints) {
safeUpgrade(db, table, newColNames, newColTypes, dropDirectly, colAliases, OnConflict.REPLACE, constraints);
}
private static String createInsertDataQuery(final String table, final String tempTable, final String[] newCols,
final String[] oldCols, final Map<String, String> colAliases, final String[] notNullCols,
final OnConflict onConflict) {
final SQLInsertQuery.Builder qb = insertInto(onConflict, table);
final List<String> newInsertColsList = new ArrayList<>();
for (final String newCol : newCols) {
final String oldAliasedCol = colAliases != null ? colAliases.get(newCol) : null;
if (ArrayUtils.contains(oldCols, newCol) || oldAliasedCol != null
&& ArrayUtils.contains(oldCols, oldAliasedCol)) {
newInsertColsList.add(newCol);
}
}
final String[] newInsertCols = newInsertColsList.toArray(new String[newInsertColsList.size()]);
if (!TwidereArrayUtils.contains(newInsertCols, notNullCols)) return null;
qb.columns(newInsertCols);
final Columns.Column[] oldDataCols = new Columns.Column[newInsertCols.length];
for (int i = 0, j = oldDataCols.length; i < j; i++) {
final String newCol = newInsertCols[i];
final String oldAliasedCol = colAliases != null ? colAliases.get(newCol) : null;
if (oldAliasedCol != null && ArrayUtils.contains(oldCols, oldAliasedCol)) {
oldDataCols[i] = new Columns.Column(oldAliasedCol, newCol);
} else {
oldDataCols[i] = new Columns.Column(newCol);
}
}
final SQLSelectQuery.Builder selectOldBuilder = select(new Columns(oldDataCols));
selectOldBuilder.from(new Tables(tempTable));
qb.select(selectOldBuilder.build());
return qb.buildSQL();
}
private static String[] getColumnNames(final SQLiteDatabase db, final String table) {
final Cursor cur = db.query(table, null, null, null, null, null, null, "1");
if (cur == null) return null;
try {
return cur.getColumnNames();
} finally {
cur.close();
}
}
private static String getCreateSQL(final SQLiteDatabase db, final String table) {
final SQLSelectQuery.Builder qb = select(new Column("sql"));
qb.from(new Tables("sqlite_master"));
qb.where(new Expression("type = ? AND name = ?"));
final Cursor c = db.rawQuery(qb.buildSQL(), new String[]{"table", table});
if (c == null) return null;
try {
if (c.moveToFirst()) return c.getString(0);
return null;
} finally {
c.close();
}
}
private static String[] getNotNullColumns(final NewColumn[] newCols) {
if (newCols == null) return null;
final String[] notNullCols = new String[newCols.length];
int count = 0;
for (final NewColumn column : newCols) {
if (column.getType().endsWith(" NOT NULL")) {
notNullCols[count++] = column.getName();
}
}
return TwidereArrayUtils.subArray(notNullCols, 0, count);
}
private static Map<String, String> getTypeMapByCreateQuery(final String query) {
if (TextUtils.isEmpty(query)) return Collections.emptyMap();
final int start = query.indexOf("("), end = query.lastIndexOf(")");
if (start < 0 || end < 0) return Collections.emptyMap();
final HashMap<String, String> map = new HashMap<>();
for (final String segment : query.substring(start + 1, end).split(",")) {
final String trimmed = segment.trim().replaceAll(" +", " ");
final int idx = trimmed.indexOf(" ");
map.put(trimmed.substring(0, idx), trimmed.substring(idx + 1, trimmed.length()));
}
return map;
}
}