Whenever I update my database I get this error. But when I rerun the app as it is, the database gets updated.
android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032)[
Code:
public DBAdapter(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
ctx = context;
db = getWritableDatabase();
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
ctx.deleteDatabase(DATABASE_NAME);
new DBAdapter(ctx);
} else {
super.onUpgrade(db, oldVersion, newVersion);
}
}
As one of the SO answers suggested, I have added this too:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
BTW: I am using SQLiteAssetHelper to create prebuilt database
This is not a solution to prevent this issue but a work around.
public DBAdapter(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
ctx = context;
try {
db = getWritableDatabase();
} catch (SQLiteReadOnlyDatabaseException e){
ctx.startActivity(new Intent(ctx, MainActivity.class));
}
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
ctx.deleteDatabase(DATABASE_NAME);
new DBAdapter(ctx);
} else {
super.onUpgrade(db, oldVersion, newVersion);
}
}
First time when the adapter is initialized, a writable db is created. Then onUpgrade gets called. Here when the database is deleted, the adapter get reinitialized. But the connection of the db is not deleted and persists hence, the second time when db = getWritableDatabase(); is executed SQLiteReadOnlyDatabaseException occurs. The original activity that initialized DBAdapter is restarted. Now the Adapter is reinitialized and the onUpgrade method is not called and hence SQLiteReadOnlyDatabaseException does not occur.
All this process happens very fast and the user experience does not become bad in my case.
Note: new DBAdapter(ctx); does not seem to be necessary and deleteDatabase seems to recreate the adapter. But for caution, I have written this line of code.
I would love to get some information on the cause and solution for this error.
I had some similar issues with Android SQLite databases. I submitted a bug report on it long ago at https://code.google.com/p/android/issues/detail?id=174566. This report discusses my findings on the reasons in more detail. I am not sure if it is related to your issue or not, but it seems to share some characteristics.
To summarize here, my debugging indicated that Android opens the database file, calls onUpgrade(), and if you replace the database file during the onUpgrade() call, the Android side file handle points to the old file and thus causes the app to crash when you return from onUpgrade() and Android tries to access the old file.
Here is some code I used to get around the issue:
When the app starts, I did this in onCreate():
Thread t = new Thread(new Runnable() {
#Override
public void run() {
Context context = getApplicationContext();
DBReader.copyDB(MainActivity.this);
DBReader.initialize(context);
}
});
t.start();
This causes the update of the database file to happen in the background while the app is starting and user is occupied with awe of the awesome application. Because my file was rather big and it took a while to copy. Notice that I completely avoid doing anything in onUpgrade() here.
DBReader is my own class, for which the main code of interest is this:
SharedPreferences prefs = context.getSharedPreferences(Const.KEY_PREFERENCES, Context.MODE_PRIVATE);
//here we have stored the latest version of DB copied
String dbVersion = prefs.getString(Const.KEY_DB_VERSION, "0");
int dbv = Integer.parseInt(dbVersion);
if (checkIfInitialized(context) && dbv == DBHelper.DB_VERSION) {
return;
}
File target = context.getDatabasePath(DBHelper.DB_NAME);
String path = target.getAbsolutePath();
//Log.d("Awesome APP", "Copying database to " + path);
path = path.substring(0, path.lastIndexOf("/"));
File targetDir = new File(path);
targetDir.mkdirs();
//Copy the database from assets
InputStream mInput = context.getAssets().open(DBHelper.DB_NAME);
OutputStream mOutput = new FileOutputStream(target.getAbsolutePath());
byte[] mBuffer = new byte[1024];
int mLength;
while ((mLength = mInput.read(mBuffer)) > 0) {
mOutput.write(mBuffer, 0, mLength);
}
mOutput.flush();
mOutput.close();
mInput.close();
SharedPreferences.Editor edit = prefs.edit();
edit.putString(Const.KEY_DB_VERSION, "" + DBHelper.DB_VERSION);
edit.apply();
and the code for checkIfInitialized():
public static synchronized boolean checkIfInitialized(Context context) {
File dbFile = context.getDatabasePath(DBHelper.DB_NAME);
return dbFile.exists();
}
So, to make the story short, I just avoided onUpgrade() alltogether and implemented my own custom upgrade functionality. This avoids the problem of the Android OS crashing on old and invalid filehandles caused by change of the database in onUpgrade().
Kind of odd, I though, for the onUpgrade() to cause the OS to crash your app if you actually end up upgrading your database file in a function intended to let you upgrade your database. And the Google comments on the bug report were made few years after so I no longer had the original crashing code around for easy proof of concept.
Your problem might be slightly different in that you are not copying the database file, but you still seem to be modifying it, so the root cause might be similar.
Related
I am using SQLCipher v3.5.7 and observed an unexpected behavior from SQLiteDatabase with incorrect password.
I encrypted the database with "key1".
Closed the database connection.
Then I tried to open my database with "key2", the SQLiteDatabase is not throwing an exception. Instead, it is updating the old password (key1) to new password (key2). I verified this by opening the .db file in SQLiteBrowser.
Can somebody help me why it is behaving this way?
private static SQLiteCipherDatabaseHelper createDBConnection(Context context, String databasePath, final String key) throws SQLiteDatabaseException {
if (dbInstance == null) {
dbInstance = new SQLiteCipherDatabaseHelper(context, databasePath);
String path = context.getDatabasePath(databasePath).getPath();
File dbPathFile = new File(path);
if (!dbPathFile.exists()) {
dbPathFile.getParentFile().mkdirs();
}
setDatabaseWithDBEncryption(key);
}
return dbInstance;
}
private static void setDatabaseWithDBEncryption(String encryptionKey) throws SQLiteDatabaseException {
loadSQLCipherLibs();
try {
sqliteDatabase = SQLiteDatabase.openOrCreateDatabase(new File(context.getDatabasePath(databasePath).getPath()), encryptionKey, null);
} catch (Exception e) {
SyncLogger.getSharedInstance().logFatal("SQLiteCipherDatabaseHelper", "Failed to open or create database. Please provide a valid encryption key");
throw new SQLiteDatabaseException(SyncErrorCodes.EC_DB_SQLCIPHER_FAILED_TO_OPEN_OR_CREATE_DATABASE, SyncErrorDomains.ED_OFFLINE_OBJECTS, SyncErrorMessages.EM_DB_SQLCIPHER_FAILED_TO_OPEN_OR_CREATE_DATABASE, e);
}
}
Have you upgrade your db version ??
private static final int DATABASE_VERSION = 2;//from 1 to 2
private static class OpenHelper extends SQLiteOpenHelper {
OpenHelper(Context context) // constructor
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
//Changes in db mentioned here
}
}
Are you actually populating the database with tables and data after keying it? It seems most likely that for some reason you are recreating the database each time you run the test. Have you verified that the actual database is encrypted by pulling it off the device and examining the file? Perhaps you are recreating a new database each time you run the test, in which case the new key would just be used.
It's worth noting that this behavior is covered in the SQLCipher for Android Test project.
https://github.com/sqlcipher/sqlcipher-android-tests/blob/master/src/main/java/net/zetetic/tests/InvalidPasswordTest.java
If you suspect an issue you can try running the test suite on your device, or create a new test case to verify the behavior with your own code.
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.
I have 2 classes: TableA and TableB, corresponding to the SQLite tables in the first version of my App.
Now I am ready to release the next version, where I have to add another table, TableC.
In each class, I have an onCreate(SQLiteDatabase sqlDatabase) and onUpgrade(SQLiteDatabase sqlDatabase, int oldVersion, int newVersion) method.
Here is what I want to do: keep TableA as it is,
add a new column to TableB
and, of course, create a new TableC.
I have updated my Database version number in the SQLiteOpenHelper
I have my methods implemented as below:
TableA.onUpgrade(/*parameters*/){
//Nothing to do here
}
TableB.onUpgrade(/*parameters*/){
sqlDatabase.execSQL("ALTER TABLE TableB ADD COLUMN columnFoo INTEGER DEFAULT 1");
}
TableC.onUpgrade(/*parameters*/){
sqlDatabase.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(sqlDatabase); //calls sqlDatabase.execSQL(CREATE_TableC);
}
My SQLiteOpenHelper subclass implementation looks as below:
#Override
public void onCreate(SQLiteDatabase sqlDatabase) {
TableA.onCreate(sqlDatabase);
TableB.onCreate(sqlDatabase);
TableC.onCreate(sqlDatabase);
}
#Override
public void onUpgrade(SQLiteDatabase sqlDatabase, int oldVersion, int newVersion)
{
TableA.onUpgrade(sqlDatabase);
TableB.onUpgrade(sqlDatabase);
TableC.onUpgrade(sqlDatabase);
}
My problem is that I am having trouble testing this.
When I installed the new version of my app on a fresh Genymotion Android VM, I got an error on a query which I do in my Launch activity on TableB, saying that the new column does not exist.
I tested by upgrading on an existing Genymotion Android VM which had version 1 of my app installed.
There it seemed to work fine.
I certainly can go on creating new VM's for every run, and installing my app to test.
But I wanted to ask what I could be doing wrong, and what would be a good way to test.
How do I correctly handle updating the database for my existing users, and how do I create new database/tables for my new users?
Any help is appreciated!!
My App supports Android API 14 (ICS) and above.
Problem happens when device #1 have app db version lets say 2, device #2 have app db version 5, and new release have db version 6.
Then ideally:
device 1 should start upgrade from 2->3->4->5->6.
device 2: 5->6.
So old upgrade code stay intact, and you keep adding new upgrade code for new version.
Here is an example (one of the way to handle onUpgrade()):
#Override
public void onUpgrade(SQLiteDatabase sqlDatabase, int oldVersion, int newVersion) {
try {
if (oldVersion < 51) {
upgradeToVersion51(db); // From 50 or 51
oldVersion = 51;
}
if (oldVersion == 51) {
upgradeToVersion52(db);
oldVersion += 1;
}
if (oldVersion == 52) {
upgradeToVersion53(db);
oldVersion += 1;
}
} catch (SQLiteException e) {
Log.e(TAG, "onUpgrade: SQLiteException, recreating db. ", e);
Log.e(TAG, "(oldVersion was " + oldVersion + ")");
return; // this was lossy
}
}
Note: you need to bump static final int DATABASE_VERSION; whenever you upgrade, check this link for example.
Check how Calendar handles db upgrade here: http://grepcode.com/file_/repository.grepcode.com/java/ext/com.google.android/android-apps/4.0.1_r1/com/android/providers/calendar/CalendarDatabaseHelper.java/?v=source
Hope this helps!
I'm developing a quiz-type application for android, and aiming to be able to expand the question bank over time. To do this, I'm pre-packaging the SQLite database, and checking for updates and overwriting it on upgrade:
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if(newVersion>oldVersion) {
InputStream inputStream = null;
OutputStream outStream = null;
String dbFilePath = DATABASE_PATH + DATABASE_NAME;
try {
inputStream = myContext.getAssets().open("questions.db");
outStream = new FileOutputStream(dbFilePath);
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer))>0) {
outStream.write(buffer, 0, length);
}
outStream.flush();
outStream.close();
inputStream.close();
} catch(IOException e) {
throw new Error("problem copying database from resource file");
}
}
}
What I'm unsure of is how to save whether a question has been attempted (and prevent that from being overwritten on upgrade). It seems my options are:
1) Save a boolean shared preference for every question id (potentially a LOT of sharedprefs)
2) Create a separate database to hold the data on which questions have been answered (seems like overkill)
3) Keep the results data in a separate table of the same database, and then ensure that only the questions table gets overwritten on upgrade
4) Keep the user data in its own column within the questions table, but prevent that column from being overwritten.
Of the above, 4 seems the most elegant (though not sure if possible). What is the best way of going about it (and if it's option 3 or 4, how do I go about modifying the code?!)
Much appreciated!
If you want to preserve data in the old database, use SQL to parse/read your records for both database. Only insert new questions in your old database thus getting a new database will all the data via a "INSERT OR UPDATE" statement.
no way! because of structure of SQLite database you cant change its file yourself
but maybe you have to alternative:
read all of database, change your desire table then write it.
store your more rewritable table separate. (this solution is dummy, I know)
I initially created an SQLite database on Windows and then had problems accessing it within Android.
Subsequently I created a database on Android and then copied it out. At this point it only had the android_metadata table in it.
I then imported some data via CSV and added it back into my project. The DbHelper class in my project copies the database into /data/data/my.project/databases/.
Now, when I run a raw query from this database, if I try to access the table imported by CSV, I get an error saying that the table doesn't exist. If I try to access the android_metadata table which I created on Android then there is no error.
The database in my assets definitely has the table in that I wish to copy over to the /data/data/example.project/databases folder and the copy routine is definitely called - I've checked with the log output.
Now, if I comment out the copy code, a database is automatically created which contains the android_metadata table in there and it is ~3 KB.
When the copy code is live the database is created as ~8 KB. This is the size of the database in the assets, so it appears that it has been successfully copied. However, when I pull that database back to my desktop from DDMS it is ~8 KB, but it doesn't contain the table which is in the one in the assets folder. If I manually copy directly from desktop into /data/data... then the database works (but this will not be possible with a market app).
Here is my copy code for copying the database:
public void createDatabase() throws IOException {
Log.i(TAG, "createDatabase called");
InputStream assetsDB = mContext.getAssets().open(DATABASE_NAME);
OutputStream dbOut = new FileOutputStream(DATABASE_PATH);
Log.i(TAG, DATABASE_PATH);
Log.i(TAG, assetsDB.toString());
byte[] buffer = new byte[1024];
int length;
while ((length = assetsDB.read(buffer))>0) {
Log.i(TAG, "WritingDB block" + length);
dbOut.write(buffer, 0, length);
}
dbOut.flush();
dbOut.close();
assetsDB.close();
}
How can I fix this problem?
I've rectified this using another example which doesn't override onCreate with the database copy code and handles the copying of the database on its own. I don't really understand why it doesn't work when calling the onCreate method.
Have you seen Using your own SQLite database in Android applications?
This page is a good source for the topic. But, there is a little problem. Actually, it is not a problem and explained how to fix in the page. Look at comments.
If your database is sort of largish or smallish (> 1 MB, < 100 KB (I am not sure about these values)). It seems that it is compressed and that causes confusion in the Android read on the InputStream. The trick is to rename your asset to a file that the packager will not try to compress. Renaming the database file from xxx to xxx.mp3 or xxx.txt or something like that does the trick.
If I clearly understand you, I had the same problem with loading an SQLite database from other sources (I used to the Firefox SQLite manger too).
I want to read a temporary database from the assets folder at startup and fill my application database with test data, and I usually get this error.
I need to put this code before loading my test database:
final SQLiteDatabase db = getReadableDatabase();
db.close();
My database helper class:
public DataBaseHelper(Context context) {
super(context,
DataBaseHelper.DATABASE_NAME,
null,
DataBaseHelper.DATABASE_VERSION);
this.context = context;
// Temporary copy test database
loadMockDataBase();
dataBase = getWritableDatabase();
}
#Override
public final synchronized void close() {
if (dataBase != null) {
dataBase.close();
}
super.close();
}
private void loadMockDataBase() {
final SQLiteDatabase db = getReadableDatabase();
db.close();
try {
copyDataBase();
}
catch (final IOException e) {
Log.d(SystemConfiguration.LOG_TAG, e.getMessage());
}
}