Migrating from Existing Room database to Sqlcipher - android

My app is currently using room database. I'm tying to migrate to use Sqlcipher data base. I have fallbackToDestructiveMigration() enabled but still throwing the following error
java.lang.RuntimeException: Exception while computing database live data.
at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:92)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
Caused by: net.sqlcipher.database.SQLiteException: file is not a database: , while compiling: select count(*) from sqlite_master;
at net.sqlcipher.database.SQLiteCompiledSql.native_compile(Native Method)
at net.sqlcipher.database.SQLiteCompiledSql.compile(SQLiteCompiledSql.java:91)
at net.sqlcipher.database.SQLiteCompiledSql.<init>(SQLiteCompiledSql.java:64)
at net.sqlcipher.database.SQLiteProgram.<init>(SQLiteProgram.java:91)
at net.sqlcipher.database.SQLiteQuery.<init>(SQLiteQuery.java:48)
at net.sqlcipher.database.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:60)
at net.sqlcipher.database.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:2016)
at net.sqlcipher.database.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1902)
at net.sqlcipher.database.SQLiteDatabase.keyDatabase(SQLiteDatabase.java:2673)
at net.sqlcipher.database.SQLiteDatabase.openDatabaseInternal(SQLiteDatabase.java:2603)
at net.sqlcipher.database.SQLiteDatabase.openDatabase(SQLiteDatabase.java:1247)
at net.sqlcipher.database.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:1322)
at net.sqlcipher.database.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:166)
at net.sqlcipher.database.SupportHelper.getWritableDatabase(SupportHelper.java:83)
at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476)
at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281)
at androidx.room.RoomDatabase.query(RoomDatabase.java:324)
at androidx.room.util.DBUtil.query(DBUtil.java:83)
at com.screenlocker.secure.room.MyDao_Impl$29.call(MyDao_Impl.java:1249)
at com.screenlocker.secure.room.MyDao_Impl$29.call(MyDao_Impl.java:1246)
at androidx.room.RoomTrackingLiveData$1.run(RoomTrackingLiveData.java:90)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) 
at java.lang.Thread.run(Thread.java:764) 
Is there any way to destroy all my database and move to Sqlcipher? i have also tried database.delete("table_name",null,null) command to manually deleting tables n migration but it is still not working. My code fro database creation is following.
DatabaseSecretProvider provider = new DatabaseSecretProvider(context);
byte[] passphrase = provider.getOrCreateDatabaseSecret().asBytes();
SupportFactory factory = new SupportFactory(passphrase);
instance = Room.databaseBuilder(context, MyAppDatabase.class, AppConstants.DATABASE_NAME)
.openHelperFactory(factory)
.fallbackToDestructiveMigration()
.build();
I'm using following version of Sqlcipher
implementation 'net.zetetic:android-database-sqlcipher:4.3.0'
implementation "androidx.sqlite:sqlite:2.1.0"

You can encrypt an unencrypted database with the sqlcipher_export convenience method from sqlcipher. So you don't have to do use fallbackToDestructiveMigration or spend time writing your custom migration tool.
From the developer's website:
To use sqlcipher_export() to encrypt an existing database, first open up the standard SQLite database, but don’t provide a key. Next, ATTACH a new encrypted database, and then call the sqlcipher_export() function in a SELECT statement, passing in the name of the attached database you want to write the main database schema and data to.
$ ./sqlcipher plaintext.db
sqlite> ATTACH DATABASE 'encrypted.db' AS encrypted KEY 'newkey';
sqlite> SELECT sqlcipher_export('encrypted');
sqlite> DETACH DATABASE encrypted;
Finally, securely delete the existing plaintext database, and then open up the new encrypted database as usual using sqlite3_key or PRAGMA key.
Source: https://discuss.zetetic.net/t/how-to-encrypt-a-plaintext-sqlite-database-to-use-sqlcipher-and-avoid-file-is-encrypted-or-is-not-a-database-errors/868
#commonsguy also has an example of how to do this in Android:
/**
* Replaces this database with a version encrypted with the supplied
* passphrase, deleting the original. Do not call this while the database
* is open, which includes during any Room migrations.
*
* #param ctxt a Context
* #param originalFile a File pointing to the database
* #param passphrase the passphrase from the user
* #throws IOException
*/
public static void encrypt(Context ctxt, File originalFile, byte[] passphrase)
throws IOException {
SQLiteDatabase.loadLibs(ctxt);
if (originalFile.exists()) {
File newFile=File.createTempFile("sqlcipherutils", "tmp",
ctxt.getCacheDir());
SQLiteDatabase db=
SQLiteDatabase.openDatabase(originalFile.getAbsolutePath(),
"", null, SQLiteDatabase.OPEN_READWRITE);
int version=db.getVersion();
db.close();
db=SQLiteDatabase.openDatabase(newFile.getAbsolutePath(), passphrase,
null, SQLiteDatabase.OPEN_READWRITE, null, null);
final SQLiteStatement st=db.compileStatement("ATTACH DATABASE ? AS plaintext KEY ''");
st.bindString(1, originalFile.getAbsolutePath());
st.execute();
db.rawExecSQL("SELECT sqlcipher_export('main', 'plaintext')");
db.rawExecSQL("DETACH DATABASE plaintext");
db.setVersion(version);
st.close();
db.close();
originalFile.delete();
newFile.renameTo(originalFile);
}
else {
throw new FileNotFoundException(originalFile.getAbsolutePath()+ " not found");
}
}
Source: https://github.com/commonsguy/cwac-saferoom/blob/v1.2.1/saferoom/src/main/java/com/commonsware/cwac/saferoom/SQLCipherUtils.java#L175-L224
You can pass context.getDatabasePath(DATABASE_NAME) as originalFile parameter.
Also, see this comment from commonsguy which explains how you can use this in combination with the getDatabaseState function to migrate your data from an existing plain-text database to a sqlcipher encrypted database.

This worked for me but I feel like its not the best answer:
val factory: SupportFactory = SupportFactory(masterKeyAlias.toByteArray())
private fun buildDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"MyDatabaseNew.db" // <<--- change the name of this database file
).openHelperFactory(factory)
.build()
This is a brand new database and the data needs to be populated brand new. Maybe there is a way to migrate it in a migration.

It can be fixed as using SQLCipherUtils.encrypt method from thelib based on SQLCipherUtils Database state as mentioned below:
#Synchronized
fun getInstance(context: Context,key :String): AppDB? {
val state= SQLCipherUtils.getDatabaseState(context,
DB_NAME)
if (state == SQLCipherUtils.State.UNENCRYPTED) {
SQLCipherUtils.encrypt(
context,
DB_NAME,
Base64.decode(key, Base64.DEFAULT)
)
}
if (INSTANCE == null) {
val factory =
SafeHelperFactory(Base64.decode(key, Base64.DEFAULT))
INSTANCE = Room.databaseBuilder(
context.applicationContext,
AppDB::class.java, DB_NAME
)
.openHelperFactory(factory)
.build()
appContext = context.applicationContext
}
return INSTANCE
}
make sure to call SQLCipherUtils encrypt before DB opened

There is one ugly catch with Mad's answer. It crashes on some devices (Xiaomi for me) with "file is not a database". Apparently files created using File.createTempFile are not considered database on such devices. Don't ask me why, I simply don't know. So I've had to use standard new File call
So my implementation looks like this:
private fun encrypt(context: Context, originalFile: File, passphrase: ByteArray) {
SQLiteDatabase.loadLibs(context)
if (originalFile.exists()) {
val newFile = File(context.cacheDir, "sqlcipherutils.db")
if (!newFile.createNewFile()) {
throw FileNotFoundException(newFile.absolutePath.toString() + " not created")
}
// get database version from existing database
val databaseVersion = SQLiteDatabase.openDatabase(originalFile.absolutePath, "", null, SQLiteDatabase.OPEN_READWRITE).use { database ->
database.version
}
SQLiteDatabase.openDatabase(
newFile.absolutePath, passphrase, null,
SQLiteDatabase.OPEN_READWRITE, null, null
).use { temporaryDatabase ->
temporaryDatabase.rawExecSQL("ATTACH DATABASE '${originalFile.absolutePath}' AS sqlcipher4 KEY ''")
temporaryDatabase.rawExecSQL("SELECT sqlcipher_export('main', 'sqlcipher4')")
temporaryDatabase.rawExecSQL("DETACH DATABASE sqlcipher4")
temporaryDatabase.version = databaseVersion
}
originalFile.delete()
newFile.renameTo(originalFile)
} else {
throw FileNotFoundException(originalFile.absolutePath.toString() + " not found")
}
}

Related

How to pre-populate an existing DB with data?

The app I'm building currently has a Workout database.
Workout DB has a Workout table and a WoroutSets table.
The data in these two tables is inserted(saved) through user input.
And that's where some data is stored.
By the way, I want to put pre-populated data called WorkoutList into this Workout DB.
I consulted the docs for this.
I exported Workout.db and pre-populated it with data in DB Browser for SQLite.
And we are going to use createFromAsset("Workout.db") as per the docs. (Haven't tried yet)
However, what I am concerned about is whether there is a conflict between the Work DB of the existing app and the Workout DB to which the WorkoutList table has been added.
Assuming that you want to preserve each app users workouts and workoutsetss that they have input then you would not want to overwrite them by using createFromAsset.
Rather I suspect that what you want to do is introduce a new workoutlist table that is populated with predefined/pre-existing rows in the workoutlist as per a database supplied as an asset. In this case you do not want to use the createFromAsset method (although you could potentially have a second database created from the asset, attach it to the original and then merge the data - this would be more complex than it need be).
You also have to consider how to handle new installs, in which case there will be no existing user input workouts and workoutsetss, in which case you could use createFromAsset method. However, you would not want any other user's workouts and workoutsetss rows.
Based upon this assumption perhaps consider this demo that introduces a new table (workoutlist) whose data is retrieved from an asset maintaining the original user data in the other tables (workout and workoutset) but for a new install of the App creates database from the asset.
the schema is made up so will very likely differ from yours but the principle applies.
Java has been used but it would take little to change it to Kotlin
Workout
#Entity
class Workout {
#PrimaryKey
Long workoutId=null;
String workoutName;
Workout(){};
#Ignore
Workout(String workoutName) {
this.workoutId=null;
this.workoutName=workoutName;
}
}
WorkoutSet (plural not used but easily changed)
#Entity
class WorkoutSet {
#PrimaryKey
Long workoutSetId=null;
String workoutSetName;
long workoutIdMap;
WorkoutSet(){}
#Ignore
WorkoutSet(String workoutSetName, long parentWorkout) {
this.workoutSetId=null;
this.workoutSetName = workoutSetName;
this.workoutIdMap = parentWorkout;
}
}
WorkkoutList (the new table)
#Entity
class WorkoutList {
#PrimaryKey
Long workoutListId=null;
String workoutListName;
}
AllDAO (just for completeness)
#Dao
abstract class AllDAO {
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(Workout workout);
#Insert(onConflict = OnConflictStrategy.IGNORE)
abstract long insert(WorkoutSet workoutSet);
#Query("SELECT count(*) FROM workout")
abstract long getNumberOfWorkouts();
}
WorkoutDatabase the #Database annotated class
#Database(entities = {Workout.class,WorkoutSet.class, WorkoutList.class /*<<<<<<<<<< ADDED for V2 */}, exportSchema = false, version = MainActivity.DATABASE_VERSION)
abstract class WorkoutDatabase extends RoomDatabase {
abstract AllDAO getAllDAO();
private static Context passed_context;
private static volatile WorkoutDatabase INSTANCE;
static WorkoutDatabase getInstance(Context context) {
passed_context = context;
if (INSTANCE==null && MainActivity.DATABASE_VERSION == 1) {
INSTANCE = Room.databaseBuilder(context,WorkoutDatabase.class,MainActivity.DATABASE_NAME)
.allowMainThreadQueries()
.build();
}
if (INSTANCE ==null && MainActivity.DATABASE_VERSION > 1) {
INSTANCE = Room.databaseBuilder(context,WorkoutDatabase.class,MainActivity.DATABASE_NAME)
.allowMainThreadQueries()
.createFromAsset(MainActivity.DATABASE_ASSET_NAME) /* so new App installs use asset */
.addMigrations(MIGRATION_1_TO_2) /* to handle migration */
.build();
}
return INSTANCE;
}
static Migration MIGRATION_1_TO_2 = new Migration(1,2) {
#SuppressLint("Range")
#Override
public void migrate(#NonNull SupportSQLiteDatabase database) {
/* Create the new table */
database.execSQL("CREATE TABLE IF NOT EXISTS `WorkoutList` (`workoutListId` INTEGER, `workoutListName` TEXT, PRIMARY KEY(`workoutListId`))");
/* Cater for copying the data from the asset */
String tempDBName = "temp_" + MainActivity.DATABASE_NAME; /* name of the temporary/working database NOT an SQLITE TEMP database */
String newTableName = "workoutlist"; /* The table name */
String qualifiedNewTableName = tempDBName + "." + newTableName; /* The fully qualified new table name for the attached temp/wrk db */
String tempDBPath = passed_context.getDatabasePath(MainActivity.DATABASE_NAME).getParent() + File.separator + tempDBName; /* path to temp/wrk db */
try {
/* Copy the asset to a second DB */
InputStream asset = passed_context.getAssets().open(MainActivity.DATABASE_ASSET_NAME); /* open the asset */
File tempDB_File = new File(tempDBPath); /* File for temp/wrk database */
OutputStream tempdb = new FileOutputStream(tempDB_File); /* now an output stream ready for the copy */
int bufferLength = 1024 * 8; /* length of buffer set to 8k */
byte[] buffer = new byte[bufferLength]; /* the buffer for the copy */
/* copy the temp/wrk database from the asset to it's location */
while(asset.read(buffer) > 0) {
tempdb.write(buffer);
}
/* clean up after copy */
tempdb.flush();
tempdb.close();
asset.close();
/*Use the temporary/working database to populate the actual database */
/* Issues with WAL file change because migration is called within a transaction as per */
/* java.lang.IllegalStateException: Write Ahead Logging (WAL) mode cannot be enabled or disabled while there are transactions in progress. .... */
/* SO COMMENTED OUT */
//database.execSQL("ATTACH DATABASE '" + tempDBPath + "' AS " + tempDBName);
//database.execSQL("INSERT INTO " + newTableName + " SELECT * FROM " + qualifiedNewTableName);
//database.execSQL("DETACH " + tempDBName);
/* Alternative to ATTACH */
SQLiteDatabase assetdb = SQLiteDatabase.openDatabase(tempDB_File.getPath(),null,SQLiteDatabase.OPEN_READONLY);
Cursor csr = assetdb.query(newTableName,null,null,null,null,null,null);
ContentValues cv = new ContentValues();
while (csr.moveToNext()) {
cv.clear();
for (String s: csr.getColumnNames()) {
cv.put(s,csr.getString(csr.getColumnIndex(s)));
}
database.insert(newTableName,SQLiteDatabase.CONFLICT_IGNORE,cv);
}
assetdb.close();
tempDB_File.delete(); /* delete the temporary/working copy of the asset */
} catch (Exception e) {
/* handle issues here e.g. no asset, unable to read/write an so on */
e.printStackTrace();
}
}
};
}
This has been written to allow easy switching/running of the App with either version, simply two changes to run as old or new version of the App.
to run as version 1 DATABASE_VERSION (in MainActivity) is set to 1
AND the WorkoutSet class is commented out in the #Database annotation.
the Mirgration handles the copy of the data from the asset if the database already exists, otherwise for a new file then createFromAssets is used to copy the database from the asset.
MainActivity the activity code that does something with the database to ensure that it is opened/accessed
public class MainActivity extends AppCompatActivity {
public static final String DATABASE_NAME = "workout.db";
public static final int DATABASE_VERSION = 2;
public static final String DATABASE_ASSET_NAME = "testit.db"/*DATABASE_NAME*/ /* could be different */;
WorkoutDatabase wdb;
AllDAO dao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
wdb = WorkoutDatabase.getInstance(this);
dao = wdb.getAllDAO();
if (dao.getNumberOfWorkouts() < 1) addInitialData();
//wdb.close(); // FORCE close so WAL is full checkpointed (i.e. no -wal or -shm)
}
private void addInitialData() {
String prefix = String.valueOf(System.currentTimeMillis()); // to differentiate devices
dao.insert(new Workout(prefix + "-W001"));
dao.insert(new Workout(prefix + "-W002"));
dao.insert(new WorkoutSet(prefix + "-WS001",1));
dao.insert(new WorkoutSet(prefix + "-WS002",2));
}
}
note the wdb.close() is uncommented when creating the database to be loaded into the SQlite tool.
testit.db the SQLite database file as modified to introduce the new workoutList table. This first copied from a run of the App at Version 1 and then adding the new table (according SQL copied from the createAllTables method of WorkoutDataabase_Impl class in the generated java (aka the exact table as per Room's expectations)).
Note really the rows should be deleted as they are user specific. Instead -WRONG has been added to the end of the data (helps to prove the some points)
as above
The new table
Navicat was the SQLite tool used rather than DB Browser.
Run 1 running at version 1 to populate the database
DATABASE_VERSION = 1
#Database(entities = {Workout.class,WorkoutSet.class/*, WorkoutList.class*/ /*<<<<<<<<<< ADDED for V2 */}, exportSchema = false, version = MainActivity.DATABASE_VERSION)
i.e. the WorkoutList table has been excluded
App Inspection shows:-
and
Impotantly no WorkoutList table
Run 2 version 2 and introduction of new workoutlist table
DATABASE_VERSION = 2
#Database(entities = {Workout.class,WorkoutSet.class, WorkoutList.class /*<<<<<<<<<< ADDED for V2 */}, exportSchema = false, version = MainActivity.DATABASE_VERSION)
i.e. WorkoutList now introduced
App Inspection shows:-
old rows retained (they do not have -WRONG as per the asset)
old rows retained
the new table, populated as per the asset
Run 3 new install (after App uninstalled) at VERSION 2
as an be seen the incorrectly left in the assets rows exist (obviously you would delete the rows in real life but leaving them in shows that they are from the asset rather than generated by the code in the activity)
likewise
Conclusion
So the new install of the App correctly copies the asset database via createFromAsset whilst if migrating only the new data in the workoutlist table is copied from the asset.

Not able to update sqlite_sequence table using RoomDatabase.query

We try to update sqlite_sequence with the following code.
WeNoteRoomDatabase weNoteRoomDatabase = WeNoteRoomDatabase.instance();
weNoteRoomDatabase.query(new SimpleSQLiteQuery("UPDATE sqlite_sequence SET seq = 0 WHERE name = 'attachment'"));
However, it has no effect at all. I exam the sqlite_sequence table content using SQLite browser. The counter is not reset to 0.
If we try to run the same query manually using SQLite browser on same SQLite file, it works just fine.
Our Room database is pretty straightforward.
#Database(
entities = {Attachment.class},
version = 6
)
public abstract class WeNoteRoomDatabase extends RoomDatabase {
private volatile static WeNoteRoomDatabase INSTANCE;
private static final String NAME = "wenote";
public abstract AttachmentDao attachmentDao();
public static WeNoteRoomDatabase instance() {
if (INSTANCE == null) {
synchronized (WeNoteRoomDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
WeNoteApplication.instance(),
WeNoteRoomDatabase.class,
NAME
)
.build();
}
}
}
return INSTANCE;
}
}
Any idea what we had missed out?
Additional information : clearing sqlite_sequence is not working in android room
Room doesn't use SQLiteDatabase - but it uses SupportSQLiteDatabase, while it's source code states, that it delegates all calls to an implementation of {#link SQLiteDatabase}... I could even dig further - but I'm convinced, that this is a consistency feature and not a bug.
SQLiteDatabase.execSQL() still works fine, but with SupportSQLiteDatabase.execSQL() the same UPDATE or DELETE queries against internal tables have no effect and do not throw errors.
my MaintenanceHelper is available on GitHub. it is important that one initially lets Room create the database - then one can manipulate the internal tables with SQLiteDatabase.execSQL(). while researching I've came across annotation #SkipQueryVerification, which could possibly permit UPDATE or DELETE on table sqlite_sequence; I've only managed to perform a SELECT with Dao... which in general all hints for the internal tables are being treated as read-only, from the perspective of the publicly available API; all manipulation attempts are being silently ignored.
i think query is wrong, you should try below query
weNoteRoomDatabase.query(new SimpleSQLiteQuery("UPDATE sqlite_sequence SET seq = 0 WHERE name = attachment"));
I'm using room database version 2.2.5
Here I'm unable to execute this query using Room Dao structure, so make one simple class and access method as like this and I got successful outcomes so this one is tested result. I'm using RxJava and RxAndroid for same.
public class SqlHelper {
private static SqlHelper helper = null;
public static SqlHelper getInstance() {
if (helper == null) {
helper = new SqlHelper();
}
return helper;
}
public Completable resetSequence(Context context) {
return Completable.create(emitter -> {
try {
AppDatabase.getDatabase(context)
.getOpenHelper()
.getWritableDatabase()
.execSQL("DELETE FROM sqlite_sequence WHERE name='<YOUR_TABLE_NAME>'");
emitter.onComplete();
} catch (Exception e) {
emitter.onError(e);
}
});
}
}
Execute:
SqlHelper.getInstance()
.resetQuizSequence(context)
.subscribeOn(Schedulers.io()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> {}, error -> {});
Table sql_sequence is not managed by Room, so you need to edit it using a SupportSQLiteDatabase.
Try this:
String sqlQuery = "DELETE FROM sqlite_sequence WHERE name='attachment'";
weNoteRoomDatabase().getOpenHelper().getWritableDatabase().execSQL(sqlQuery);
This works for me - Room 2.2.6
String sqlQuery = "DELETE FROM sqlite_sequence WHERE name='attachment'";
<YourDatabase>.getInstance(mContext).getOpenHelper().getWritableDatabase().execSQL(sqlQuery);

How to update data using room database?

I am using Room database and I want to update a row. I am able to update the row but getting this warning
12-05 20:59:17.635 29363-29372/com.example.parmarravi21.recyclerviewiv W/SQLiteConnectionPool: A SQLiteConnection object for database '/data/user/0/com.example.parmarravi21.recyclerviewiv/databases/RecP10' was leaked! Please fix your application to end transactions in progress properly and to close the database when it is no longer needed.
This is my code
//defining database
db = Room.databaseBuilder(getActivity(), AppDatabase.class, "RecP10")
.fallbackToDestructiveMigration()
.allowMainThreadQueries().build();
TimeDateModel timeDateModel = new TimeDateModel(positionTime, TimeMode);
ContentValues contentValues = new ContentValues();
contentValues.put("posItem", timeDateModel.getPosItem());
contentValues.put("TimeDateMode", timeDateModel.getTimeDateMode());
if (db.timeDao().getItemAtTimePos(positionTime) != null) {
Log.e(TAG, "updated database");
db.beginTransaction();
try {
db.getOpenHelper().getWritableDatabase().update("Datetime", 0,
contentValues, "posItem =" + positionTime, null);
db.setTransactionSuccessful();
} finally {
db.endTransaction(); // commit or rollback
db.close();
}
} else {
Log.e(TAG, "New item added");
db.timeDao().insertAll(timeDateModel);
}
I have also tried the dao update function but the data is not updating .
db.timeDao().update(timeDateModel);
I am new to database in android please help me out .
Github link for complete code
#Query("UPDATE Test SET test_name=:arg1,test_startDate=:arg2,test_endDate=:arg3,test_location=:arg4,test_ringerMode=:arg5,test_latitude=:arg6,test_longitude=:arg7 WHERE testId = :arg0")
fun updateTest( testId: Int,testName:String,testStartTime:String,testEndTime:String,testLocation:String,testRingerMode:Int,testLatitude:Double,testLongitude:Double)
Try coding this way.
Using an #Update annotation should do the trick , and according to the Android developer Documentation, you shouldn't perform database Queries on the main Thread ,thus might cause your database issues
Something like these
#Update
void updateTimeEntry(TimeEntry timeEntry)
with TimeEntry being the name of your model class

SQLiteOpenHelper multiple in-memory databases

android.database.sqlite.SQLiteOpenHelper provides the ability to use an in-memory database if the name argument to its constructor is null:
String: of the database file, or null for an in-memory database
If SQLiteOpenHelper is instantiated multiple times with a null name argument, do they access the same in-memory database or is a separate in-memory database created each time?
From SQLite official documentation In-Memory Databases
Opening two database connections each with the filename ":memory:" will create two independent in-memory databases.
In Android, pass null instead of ":memory:"
So, If you instantiate SQLiteOpenHelper multiple times with a null name argument then it create separate in-memory database created each time
If we look at the source code, we see that in the constructor mName would get set to null.
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
DatabaseErrorHandler errorHandler) {
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
mContext = context;
mName = name;
mFactory = factory;
mNewVersion = version;
mErrorHandler = errorHandler;
}
Which means getDatabaseName() returns null.
public String getDatabaseName() {
return mName;
}
Later, through the use of getReadableDatabase() or getWritableDatabase(), if mName is null, then it calls the create method for an in-memory database instead of trying to opening one from disk.
if (mName == null) {
db = SQLiteDatabase.create(null); // in-memory
} else {
// db file opened or created
}
...
return db;
That db variable is maintained in the SQLiteOpenHelper until it is closed, which in the case of an in-memory database, means the data is deleted.
To clarify,
Each instance of a SQLiteOpenHelper that uses an in-memory database will its own database while the same instance will use one database and persist that data until it is closed.

moving from android sqlcipher to android sqlite database

From the last 3 days i am trying to upgrade my database to a higher version of SQLCipher library (v3.1.0). I did every step and followed a few tutorials too. But keep on getting the error "File is encrypted or not a Database". Now am trying to move to unencrypted database ie. simple sqlite database.
Do we have a way to move to encrypted database to un-encrypted database? Thanks in advance.
This is the code i am working on:
public MyDBAdapter(Context context) {
this.context = context;
File dbFile = context.getDatabasePath(DATABASE_NAME);
String dbPath = context.getDatabasePath(DATABASE_NAME).toString();
if (dbFile.exists()) {
try {
SQLiteDatabase.loadLibs(context.getApplicationContext());//load SqlCipher libraries
SQLiteDatabase db = getExistDataBaseFile(dbPath, KEY_PASSPHRASE_ENCRYPTION, dbFile);
if (version == 1) {
MigrateDatabaseFrom1xFormatToCurrentFormat(
dbFile, KEY_PASSPHRASE_ENCRYPTION);
}
System.out.println("Old Database found and updated.");
} catch (Exception e) {
System.out.println("No Old Database found");
}
}
this.dbhelper = new MyDBHelper(this.context, DATABASE_NAME, null,
DATABASE_Version);
db = dbhelper.getWritableDatabase(KEY_PASSPHRASE_ENCRYPTION);
}
private SQLiteDatabase getExistDataBaseFile(String FULL_DB_Path, String password, File dbFile) {// this function to open an Exist database
SQLiteDatabase.loadLibs(context.getApplicationContext());
SQLiteDatabaseHook hook = new SQLiteDatabaseHook() {
public void preKey(SQLiteDatabase database) {
System.out.println("-----Inside preKey");
}
public void postKey(SQLiteDatabase database) {
System.out.println("-----Inside postKey");
database.rawExecSQL("PRAGMA cipher_migrate;");
}
};
SQLiteDatabase database = SQLiteDatabase.openOrCreateDatabase(
dbFile, "Test123", null, hook); // Exception
return database;
}
If you are upgrading your SQLCipher library to the latest version, currently at 3.1.0, and your previous version was 2.x (as you mentioned in the comments above), you will need to upgrade the database file format as well. One of the big changes in the 3.x release was an increase in key derivation length, from 4,000 to 64,000. If you are using all of the standard SQLCipher configurations, upgrading the database format is straight forward. We have included a new PRAGMA call cipher_migrate that will perform this operation for you. You can execute this within the postKey event of the SQLiteDatabaseHook which is to be provided in your call to SQLiteDatabase.openOrCreateDatabase. An example of this can be found in the SQLCipher for Android test suite here.

Categories

Resources