Commit fa3ac506 authored by Romain Bertozzi's avatar Romain Bertozzi Committed by Alexandre Lision
Browse files

database: set columns name explicitely

ORMLite behaviour differs in release mode. Columns and table names are
by default optimized out, which can break our queries.
This patch sets explicitely names for fields in HistoryCall and
HistoryText classes.
The names of the fields are not guaranteed to be kept if not set
explicitely.
Since there are naming changes, a data migration must be performed.
An incremental database update engine is now available in
DatabaseHelper.java for this purpose.

Change-Id: I30ad25347289dc9862306d2c843511f8235167eb
Tuleap: #629
parent 74d03665
......@@ -18,28 +18,36 @@
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package cx.ring.history;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.util.Log;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;
import java.sql.SQLException;
import java.util.ArrayList;
/**
* Database History Version
* 7 : changing columns names. See https://gerrit-ring.savoirfairelinux.com/#/c/4297
*/
/**
* Database helper class used to manage the creation and upgrading of your database. This class also usually provides
* the DAOs used by the other classes.
*/
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
private static final String TAG = DatabaseHelper.class.getSimpleName();
private static final String DATABASE_NAME = "history.db";
// any time you make changes to your database objects, you may have to increase the database version
private static final int DATABASE_VERSION = 6;
private static final int DATABASE_VERSION = 7;
private Dao<HistoryCall, Integer> historyDao = null;
private Dao<HistoryText, Integer> historyTextDao = null;
......@@ -55,12 +63,11 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
@Override
public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
try {
//TableUtils.dropTable(connectionSource, HistoryCall.class, true);
Log.i(DatabaseHelper.class.getName(), "onCreate");
Log.d(TAG, "onCreate");
TableUtils.createTable(connectionSource, HistoryCall.class);
TableUtils.createTable(connectionSource, HistoryText.class);
} catch (SQLException e) {
Log.e(DatabaseHelper.class.getName(), "Can't create database", e);
Log.e(TAG, "Can't create database", e);
throw new RuntimeException(e);
}
}
......@@ -71,20 +78,13 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
*/
@Override
public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
Log.i(TAG, "onUpgrade " + oldVersion + " -> " + newVersion);
try {
Log.i(DatabaseHelper.class.getName(), "onUpgrade " + oldVersion + " -> " + newVersion);
if (oldVersion == 4 && newVersion == 5) {
getTextHistoryDao().executeRaw("ALTER TABLE `historytext` ADD COLUMN read INTEGER;");
} else {
//TableUtils.
TableUtils.dropTable(connectionSource, HistoryCall.class, true);
TableUtils.dropTable(connectionSource, HistoryText.class, true);
// after we drop the old databases, we create the new ones
onCreate(db, connectionSource);
}
} catch (SQLException e) {
Log.e(DatabaseHelper.class.getName(), "Can't update databases", e);
throw new RuntimeException(e);
updateDatabase(oldVersion, db);
} catch (SQLiteException exc) {
exc.printStackTrace();
clearDatabase(db);
onCreate(db, connectionSource);
}
}
......@@ -98,6 +98,7 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
}
return historyDao;
}
public Dao<HistoryText, Integer> getTextHistoryDao() throws SQLException {
if (historyTextDao == null) {
historyTextDao = getDao(HistoryText.class);
......@@ -114,4 +115,122 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
historyDao = null;
historyTextDao = null;
}
/**
* Main method to update the database from an old version to the last
*
* @param fromDatabaseVersion the old version of the database
* @param db the SQLiteDatabase to work with
* @throws SQLiteException
*/
private void updateDatabase(int fromDatabaseVersion, SQLiteDatabase db) throws SQLiteException {
try {
while (fromDatabaseVersion < DATABASE_VERSION) {
switch (fromDatabaseVersion) {
case 6:
updateDatabaseFrom6(db);
break;
}
fromDatabaseVersion++;
}
Log.d(TAG, "Database has been updated to the last version.");
} catch (SQLiteException exc) {
Log.e(TAG, "Database has failed to update to the last version.");
throw exc;
}
}
/**
* Executes the migration from the database version 6 to the next
*
* @param db the SQLiteDatabase to work with
* @throws SQLiteException
*/
private void updateDatabaseFrom6(SQLiteDatabase db) throws SQLiteException {
if (db != null && db.isOpen()) {
try {
Log.d(TAG, "Will begin migration from database version 6 to next.");
db.beginTransaction();
//~ Create the new historyCall table and int index
db.execSQL("CREATE TABLE IF NOT EXISTS `historycall` (`accountID` VARCHAR , `callID` VARCHAR , " +
"`call_end` BIGINT , `TIMESTAMP_START` BIGINT , `contactID` BIGINT , " +
"`contactKey` VARCHAR , `direction` INTEGER , `missed` SMALLINT , " +
"`number` VARCHAR , `recordPath` VARCHAR ) ;");
db.execSQL("CREATE INDEX IF NOT EXISTS `historycall_TIMESTAMP_START_idx` ON `historycall` " +
"( `TIMESTAMP_START` );");
//~ Create the new historyText table and int indexes
db.execSQL("CREATE TABLE IF NOT EXISTS `historytext` (`accountID` VARCHAR , `callID` VARCHAR , " +
"`contactID` BIGINT , `contactKey` VARCHAR , `direction` INTEGER , " +
"`id` BIGINT , `message` VARCHAR , `number` VARCHAR , `read` SMALLINT , " +
"`TIMESTAMP` BIGINT , PRIMARY KEY (`id`) );");
db.execSQL("CREATE INDEX IF NOT EXISTS `historytext_TIMESTAMP_idx` ON `historytext` ( `TIMESTAMP` );");
db.execSQL("CREATE INDEX IF NOT EXISTS `historytext_id_idx` ON `historytext` ( `id` );");
Cursor hasATable = db.rawQuery("SELECT name FROM sqlite_master WHERE type=? AND name=?;",
new String[]{"table", "a"});
if (hasATable.getCount() > 0) {
//~ Copying data from the old table "a"
db.execSQL("INSERT INTO `historycall` (TIMESTAMP_START, call_end, number, missed," +
"direction, recordPath, accountID, contactID, contactKey, callID) " +
"SELECT TIMESTAMP_START,b,c,d,e,f,g,h,i,j FROM a;");
db.execSQL("DROP TABLE IF EXISTS a_TIMESTAMP_START_idx;");
db.execSQL("DROP TABLE a;");
}
hasATable.close();
Cursor hasETable = db.rawQuery("SELECT name FROM sqlite_master WHERE type=? AND name=?;",
new String[]{"table", "e"});
if (hasETable.getCount() > 0) {
//~ Copying data from the old table "e"
db.execSQL("INSERT INTO historytext (id, TIMESTAMP, number, direction, accountID," +
"contactID, contactKey, callID, message, read) " +
"SELECT id,TIMESTAMP,c,d,e,f,g,h,i,j FROM e;");
//~ Remove old tables "a" and "e"
db.execSQL("DROP TABLE IF EXISTS e_TIMESTAMP_idx;");
db.execSQL("DROP TABLE IF EXISTS e_id_idx;");
db.execSQL("DROP TABLE e;");
}
hasETable.close();
db.setTransactionSuccessful();
db.endTransaction();
Log.d(TAG, "Migration from database version 6 to next, done.");
} catch (SQLiteException exception) {
Log.e(TAG, "Migration from database version 6 to next, failed.");
throw exception;
}
}
}
/**
* Removes all the data from the database, ie all the tables.
*
* @param db the SQLiteDatabase to work with
*/
private void clearDatabase(SQLiteDatabase db) {
if (db != null && db.isOpen()) {
Log.d(TAG, "Will clear database.");
ArrayList<String> tableNames = new ArrayList<>();
Cursor c = db.rawQuery("SELECT name FROM sqlite_master WHERE type='table'", null);
if (c.moveToFirst()) {
while (!c.isAfterLast()) {
tableNames.add(c.getString(0));
c.moveToNext();
}
}
c.close();
try {
db.beginTransaction();
for (String tableName : tableNames) {
db.execSQL("DROP TABLE " + tableName + ";");
}
db.setTransactionSuccessful();
db.endTransaction();
Log.d(TAG, "Database is cleared");
} catch (SQLiteException exc) {
exc.printStackTrace();
}
}
}
}
......@@ -27,6 +27,7 @@ import android.os.Parcel;
import android.os.Parcelable;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import cx.ring.R;
import cx.ring.model.SipCall;
......@@ -37,27 +38,40 @@ import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
@DatabaseTable(tableName = HistoryCall.TABLE_NAME)
public class HistoryCall implements Parcelable {
@DatabaseField(index = true, columnName = "TIMESTAMP_START")
public static final String TABLE_NAME = "historycall";
public static final String COLUMN_TIMESTAMP_START_NAME = "TIMESTAMP_START";
public static final String COLUMN_TIMESTAMP_END_NAME = "call_end";
public static final String COLUMN_NUMBER_NAME = "number";
public static final String COLUMN_MISSED_NAME = "missed";
public static final String COLUMN_DIRECTION_NAME = "direction";
public static final String COLUMN_RECORD_PATH_NAME = "recordPath";
public static final String COLUMN_ACCOUNT_ID_NAME = "accountID";
public static final String COLUMN_CONTACT_ID_NAME = "contactID";
public static final String COLUMN_CONTACT_KEY_NAME = "contactKey";
public static final String COLUMN_CALL_ID_NAME = "callID";
@DatabaseField(index = true, columnName = COLUMN_TIMESTAMP_START_NAME)
public long call_start;
@DatabaseField
@DatabaseField(columnName = COLUMN_TIMESTAMP_END_NAME)
public long call_end;
@DatabaseField
@DatabaseField(columnName = COLUMN_NUMBER_NAME)
public String number;
@DatabaseField
@DatabaseField(columnName = COLUMN_MISSED_NAME)
boolean missed;
@DatabaseField
@DatabaseField(columnName = COLUMN_DIRECTION_NAME)
int direction;
@DatabaseField
@DatabaseField(columnName = COLUMN_RECORD_PATH_NAME)
String recordPath;
@DatabaseField
@DatabaseField(columnName = COLUMN_ACCOUNT_ID_NAME)
String accountID;
@DatabaseField
@DatabaseField(columnName = COLUMN_CONTACT_ID_NAME)
long contactID;
@DatabaseField
@DatabaseField(columnName = COLUMN_CONTACT_KEY_NAME)
String contactKey;
@DatabaseField
@DatabaseField(columnName = COLUMN_CALL_ID_NAME)
String callID;
public String getAccountID() {
......
......@@ -140,13 +140,13 @@ public class HistoryManager {
public List<HistoryCall> getAll() throws SQLException {
QueryBuilder<HistoryCall, Integer> qb = getHelper().getHistoryDao().queryBuilder();
qb.orderBy("TIMESTAMP_START", true);
qb.orderBy(HistoryCall.COLUMN_TIMESTAMP_START_NAME, true);
return getHelper().getHistoryDao().query(qb.prepare());
}
public List<HistoryText> getAllTextMessages() throws SQLException {
QueryBuilder<HistoryText, Integer> qb = getHelper().getTextHistoryDao().queryBuilder();
qb.orderBy("TIMESTAMP", true);
qb.orderBy(HistoryText.COLUMN_TIMESTAMP_NAME, true);
return getHelper().getTextHistoryDao().query(qb.prepare());
}
......@@ -171,7 +171,7 @@ public class HistoryManager {
DeleteBuilder<HistoryText, Integer> deleteTextHistoryBuilder = getHelper()
.getTextHistoryDao()
.deleteBuilder();
deleteTextHistoryBuilder.where().in("id", textMessagesIds);
deleteTextHistoryBuilder.where().in(HistoryText.COLUMN_ID_NAME, textMessagesIds);
deleteTextHistoryBuilder.delete();
//~ Deleting calls
ArrayList<String> callIds = new ArrayList<>(entry.getValue().getCalls().size());
......@@ -181,7 +181,7 @@ public class HistoryManager {
DeleteBuilder<HistoryCall, Integer> deleteCallsHistoryBuilder = getHelper()
.getHistoryDao()
.deleteBuilder();
deleteCallsHistoryBuilder.where().in("callId", callIds);
deleteCallsHistoryBuilder.where().in(HistoryCall.COLUMN_CALL_ID_NAME, callIds);
deleteCallsHistoryBuilder.delete();
}
} catch (SQLException e) {
......
......@@ -22,33 +22,47 @@
package cx.ring.history;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import java.util.Date;
import java.util.Random;
import cx.ring.model.TextMessage;
@DatabaseTable(tableName = HistoryText.TABLE_NAME)
public class HistoryText
{
@DatabaseField(index = true, columnName="id", id = true)
public static final String TABLE_NAME = "historytext";
public static final String COLUMN_ID_NAME = "id";
public static final String COLUMN_TIMESTAMP_NAME = "TIMESTAMP";
public static final String COLUMN_NUMBER_NAME = "number";
public static final String COLUMN_DIRECTION_NAME = "direction";
public static final String COLUMN_ACCOUNT_ID_NAME = "accountID";
public static final String COLUMN_CONTACT_ID_NAME = "contactID";
public static final String COLUMN_CONTACT_KEY_NAME = "contactKey";
public static final String COLUMN_CALL_ID_NAME = "callID";
public static final String COLUMN_MESSAGE_NAME = "message";
public static final String COLUMN_READ_NAME = "read";
@DatabaseField(index = true, columnName=COLUMN_ID_NAME, id = true)
public long id;
@DatabaseField(index = true, columnName="TIMESTAMP")
@DatabaseField(index = true, columnName=COLUMN_TIMESTAMP_NAME)
public long time;
@DatabaseField
@DatabaseField(columnName=COLUMN_NUMBER_NAME)
public String number;
@DatabaseField
@DatabaseField(columnName=COLUMN_DIRECTION_NAME)
public int direction;
@DatabaseField
@DatabaseField(columnName=COLUMN_ACCOUNT_ID_NAME)
String accountID;
@DatabaseField
@DatabaseField(columnName=COLUMN_CONTACT_ID_NAME)
long contactID;
@DatabaseField
@DatabaseField(columnName=COLUMN_CONTACT_KEY_NAME)
String contactKey;
@DatabaseField
@DatabaseField(columnName=COLUMN_CALL_ID_NAME)
String callID;
@DatabaseField
@DatabaseField(columnName=COLUMN_MESSAGE_NAME)
String message;
@DatabaseField
@DatabaseField(columnName=COLUMN_READ_NAME)
boolean read;
static private final Random R = new Random();
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment