package org.commcare.models.database; import android.content.Context; import android.database.Cursor; import android.database.DatabaseUtils; import android.util.Log; import net.sqlcipher.database.SQLiteDatabase; import net.sqlcipher.database.SQLiteDatabaseHook; import org.commcare.modern.database.DatabaseHelper; import java.io.File; public class DbUtil { private static final String TAG = DbUtil.class.getSimpleName(); public final static String orphanFileTableName = "OrphanedFiles"; /** * Provides a hook for Sqllite databases to be able to try to migrate themselves in place * from the writabledatabase method. Required due to SqlCipher making it incredibly difficult * and obnoxious to determine when your databases need an upgrade, so we'll just try to run * one any time the method would have crashed anyway. * * Will crash if this update doesn't work, so no return is needed */ public static void trySqlCipherDbUpdate(String key, Context context, String dbName) { //There's no clear way how to tell whether this call is the invalid db version //because SqlLite didn't actually provide that info (thanks!), but we can //test manually //Set up the hook to fire the right pragma ops SQLiteDatabaseHook updateHook = new SQLiteDatabaseHook() { @Override public void preKey(SQLiteDatabase database) { } @Override public void postKey(SQLiteDatabase database) { database.rawExecSQL("PRAGMA cipher_migrate;"); } }; //go find the db path because the helper hides this (thanks android) File dbPath = context.getDatabasePath(dbName); SQLiteDatabase oldDb = SQLiteDatabase.openOrCreateDatabase(dbPath, key, null, updateHook); //if we didn't get here, we didn't crash (what a great way to be testing our db version, right?) oldDb.close(); } public static void createNumbersTable(SQLiteDatabase db) { //Virtual Table String dropStatement = "DROP TABLE IF EXISTS integers;"; db.execSQL(dropStatement); String createStatement = "CREATE TABLE integers (i INTEGER);"; db.execSQL(createStatement); for (long i = 0; i < 10; ++i) { db.execSQL("INSERT INTO integers VALUES (" + i + ");"); } } public static void explainSql(SQLiteDatabase handle, String sql, String[] args) { Cursor explain = handle.rawQuery("EXPLAIN QUERY PLAN " + sql, args); Log.d(TAG, "SQL: " + sql); DatabaseUtils.dumpCursor(explain); explain.close(); } /** * Table of files scheduled for deletion. Entries added when file-based * database transactions fail or when file-backed entries are removed. */ public static void createOrphanedFileTable(SQLiteDatabase db) { String createStatement = "CREATE TABLE IF NOT EXISTS " + orphanFileTableName + " (" + DatabaseHelper.FILE_COL + ");"; db.execSQL(createStatement); } /** * Build and return SQL command to add a column to a table */ public static String addColumnToTable(String tableName, String columnName, String dataType) { return "ALTER TABLE " + tableName + " ADD " + AndroidTableBuilder.scrubName(columnName) + " " + dataType; } }