Use temporary, in memory database for update process.

This increases the speed of the complex queries required at the end
of the update process to:
 * calculate suggested version codes
 * figure out icon urls
 * etc,
by two orders of magnitude.
This commit is contained in:
Peter Serwylo 2016-04-28 09:00:41 +10:00
parent ab248525e0
commit 721dcb00c1
3 changed files with 50 additions and 21 deletions

View File

@ -91,9 +91,8 @@ public class RepoPersister {
// end of the process. This is due to the fact that we can't verify the cert // end of the process. This is due to the fact that we can't verify the cert
// the index was signed with until we've finished reading it - and we don't // the index was signed with until we've finished reading it - and we don't
// want to put stuff in the real database until we are sure it is from a // want to put stuff in the real database until we are sure it is from a
// trusted source. // trusted source. It also helps performance as it is done via an in-memory database.
TempAppProvider.Helper.init(context); TempAppProvider.Helper.init(context);
TempApkProvider.Helper.init(context);
hasBeenInitialized = true; hasBeenInitialized = true;
} }

View File

@ -68,12 +68,16 @@ public class TempApkProvider extends ApkProvider {
/** /**
* Deletes the old temporary table (if it exists). Then creates a new temporary apk provider * Deletes the old temporary table (if it exists). Then creates a new temporary apk provider
* table and populates it with all the data from the real apk provider table. * table and populates it with all the data from the real apk provider table.
*
* This is package local because it must be invoked after
* {@link org.fdroid.fdroid.data.TempAppProvider.Helper#init(Context)}. Due to this
* dependence, that method invokes this one itself, rather than leaving it to the
* {@link RepoPersister}.
*/ */
public static void init(Context context) { static void init(Context context) {
Uri uri = Uri.withAppendedPath(getContentUri(), PATH_INIT); Uri uri = Uri.withAppendedPath(getContentUri(), PATH_INIT);
context.getContentResolver().insert(uri, new ContentValues()); context.getContentResolver().insert(uri, new ContentValues());
} }
} }
@Override @Override
@ -123,11 +127,11 @@ public class TempApkProvider extends ApkProvider {
private void initTable() { private void initTable() {
final SQLiteDatabase db = db(); final SQLiteDatabase db = db();
db.execSQL("DROP TABLE IF EXISTS " + getTableName()); final String memoryDbName = TempAppProvider.DB;
db.execSQL("CREATE TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APK); db.execSQL("CREATE TABLE " + memoryDbName + "." + getTableName() + " AS SELECT * FROM main." + DBHelper.TABLE_APK);
db.execSQL("CREATE INDEX IF NOT EXISTS apk_vercode on " + getTableName() + " (vercode);"); db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_vercode on " + getTableName() + " (vercode);");
db.execSQL("CREATE INDEX IF NOT EXISTS apk_id on " + getTableName() + " (id);"); db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_id on " + getTableName() + " (id);");
db.execSQL("CREATE INDEX IF NOT EXISTS apk_compatible ON " + getTableName() + " (compatible);"); db.execSQL("CREATE INDEX IF NOT EXISTS " + memoryDbName + ".apk_compatible ON " + getTableName() + " (compatible);");
} }
} }

View File

@ -3,8 +3,10 @@ package org.fdroid.fdroid.data;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.UriMatcher; import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.Utils;
@ -16,6 +18,11 @@ public class TempAppProvider extends AppProvider {
private static final String TAG = "TempAppProvider"; private static final String TAG = "TempAppProvider";
/**
* The name of the in memory database used for updating.
*/
static final String DB = "temp_update_db";
private static final String PROVIDER_NAME = "TempAppProvider"; private static final String PROVIDER_NAME = "TempAppProvider";
private static final String TABLE_TEMP_APP = "temp_" + DBHelper.TABLE_APP; private static final String TABLE_TEMP_APP = "temp_" + DBHelper.TABLE_APP;
@ -60,6 +67,7 @@ public class TempAppProvider extends AppProvider {
public static void init(Context context) { public static void init(Context context) {
Uri uri = Uri.withAppendedPath(getContentUri(), PATH_INIT); Uri uri = Uri.withAppendedPath(getContentUri(), PATH_INIT);
context.getContentResolver().insert(uri, new ContentValues()); context.getContentResolver().insert(uri, new ContentValues());
TempApkProvider.Helper.init(context);
} }
/** /**
@ -111,13 +119,30 @@ public class TempAppProvider extends AppProvider {
return count; return count;
} }
private void ensureTempTableDetached(SQLiteDatabase db) {
Cursor cursor = db.rawQuery("PRAGMA database_list", null);
try {
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
String name = cursor.getString(cursor.getColumnIndex("name"));
if (TextUtils.equals(name, DB)) {
db.execSQL("DETACH DATABASE " + DB);
}
cursor.moveToNext();
}
} finally {
cursor.close();
}
}
private void initTable() { private void initTable() {
final SQLiteDatabase db = db(); final SQLiteDatabase db = db();
db.execSQL("DROP TABLE IF EXISTS " + getTableName()); ensureTempTableDetached(db);
db.execSQL("CREATE TABLE " + getTableName() + " AS SELECT * FROM " + DBHelper.TABLE_APP); db.execSQL("ATTACH DATABASE ':memory:' AS " + DB);
db.execSQL("CREATE INDEX IF NOT EXISTS app_id ON " + getTableName() + " (id);"); db.execSQL("CREATE TABLE " + DB + "." + getTableName() + " AS SELECT * FROM main." + DBHelper.TABLE_APP);
db.execSQL("CREATE INDEX IF NOT EXISTS app_upstreamVercode ON " + getTableName() + " (upstreamVercode);"); db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_id ON " + getTableName() + " (id);");
db.execSQL("CREATE INDEX IF NOT EXISTS app_compatible ON " + getTableName() + " (compatible);"); db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_upstreamVercode ON " + getTableName() + " (upstreamVercode);");
db.execSQL("CREATE INDEX IF NOT EXISTS " + DB + ".app_compatible ON " + getTableName() + " (compatible);");
} }
private void commitTable() { private void commitTable() {
@ -125,21 +150,22 @@ public class TempAppProvider extends AppProvider {
try { try {
db.beginTransaction(); db.beginTransaction();
Log.i(TAG, "Renaming " + TABLE_TEMP_APP + " to " + DBHelper.TABLE_APP); final String tempApp = DB + "." + TempAppProvider.TABLE_TEMP_APP;
db.execSQL("DROP TABLE " + DBHelper.TABLE_APP); final String tempApk = DB + "." + TempApkProvider.TABLE_TEMP_APK;
db.execSQL("ALTER TABLE " + TABLE_TEMP_APP + " RENAME TO " + DBHelper.TABLE_APP);
Log.i(TAG, "Renaming " + TempApkProvider.TABLE_TEMP_APK + " to " + DBHelper.TABLE_APK); db.execSQL("DELETE FROM " + DBHelper.TABLE_APP + " WHERE 1");
db.execSQL("DROP TABLE " + DBHelper.TABLE_APK); db.execSQL("INSERT INTO " + DBHelper.TABLE_APP + " SELECT * FROM " + tempApp);
db.execSQL("ALTER TABLE " + TempApkProvider.TABLE_TEMP_APK + " RENAME TO " + DBHelper.TABLE_APK);
db.execSQL("DELETE FROM " + DBHelper.TABLE_APK + " WHERE 1");
db.execSQL("INSERT INTO " + DBHelper.TABLE_APK + " SELECT * FROM " + tempApk);
Utils.debugLog(TAG, "Successfully renamed both tables, will commit transaction");
db.setTransactionSuccessful(); db.setTransactionSuccessful();
getContext().getContentResolver().notifyChange(AppProvider.getContentUri(), null); getContext().getContentResolver().notifyChange(AppProvider.getContentUri(), null);
getContext().getContentResolver().notifyChange(ApkProvider.getContentUri(), null); getContext().getContentResolver().notifyChange(ApkProvider.getContentUri(), null);
} finally { } finally {
db.endTransaction(); db.endTransaction();
db.execSQL("DETACH DATABASE " + DB); // Can't be done in a transaction.
} }
} }
} }