I learned a way which use SQLite3 on Android OS recently.
But it has a non acceptable part.
I can't understand why drop the table when called "onUpgrade" method of SQLiteOpenHelper.
Why need "onUpgarde" method?
If code executes "drop table", table data of old version DB will be removed, isn't it?
Why delete existing data of old DB?
How to restore existing DB data when drop the table?
[Here is learned code]
public class MySQLiteOpenHelper extends SQLiteOpenHelper {
final String CONFIRMED_SHEETS_TABLE = "confirmed_sheets";
public MySQLiteOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
#Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
String sql = "create table" +
CONFIRMED_SHEETS_TABLE +
"_id integer primary key autoincrement, " +
"group text, " +
"num001 text, " +
"num002 text, " +
"num003 text, " +
"num004 text, " +
"num005 text, " +
"num006 text, " +
"date text)";
sqLiteDatabase.execSQL(sql);
}
#Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
// non acceptable part //
String sql = "drop table if exists " + CONFIRMED_SHEETS_TABLE;
sqLiteDatabase.execSQL(sql);
onCreate(sqLiteDatabase);
}
}
If code executes "drop table", DB table data of old version DB will be
removed, isn't it?
Yup
Why need "onUpgrade" method?
If you are switching databases (for example because you added a new column), your app (usually) now depends on that change. Increasing the database version in your Helper class calls onUpgrade(), which allows you to take care of any migration to prepare the app to use your new schema.
Did you know why learned code executes "drop table"?
Convenience. It's not necessarily the right approach, but a database change can make it hard to take old data and merge it in the new table. Thus, it is easier logic-wise to simply start anew.
If you want to merge an existing and new table, have a look at this question.
You do not need to perform a DROP TABLE in onUpgrade(), as it is currently written in your code. The purpose of onUpgrade() is for your app to check if a new version of your app's database is being installed on a user's device, and if so, if there are any changes to your database, such as adding a new column, you can make those changes within onUpgrade(). If you never change your database schema, you never need to do anything in onUpgrade().
For more information and a introductory tutorial on Databases in Android, refer to the Notepad sample code here.
EDIT: also, here's an example of an onUpgrade() I wrote for one of my apps:
/**
* Handle upgrades to the database.
*/
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if(DBG) Utils.log(TAG, "DBHelper: onUpgrade()...");
// Logs that the database is being upgraded
Utils.log(TAG, "w", "Upgrading database from version " + oldVersion + " to "
+ newVersion);
/*
* Every time you add new columns to the database in the phone, you will want
* to increment the Database version above and then add a condition in here for
* upgrading to it. Otherwise it will cause upgrading users to be nontrivial and
* lead to unnecessary crashes or upgrade instructions.
*/
if (newVersion > oldVersion && newVersion <= 4) {
db.execSQL("ALTER TABLE " + TrackerDb.SomeTable.TABLE_NAME + " ADD COLUMN "
+ TrackerDb.SomeTable.COLUMN_NAME_DATE_MODIFIED + " TEXT");
}
}
OnUpgrade() is called when db versions "old and new" does not match; which means that the user wants to change Database Structure (Dropping a table, Adding new Table, Modifying Table definition).
So the OnUpgrade() should contain the logic as:
Take Backup of the existing data (e.g. Generate insert statements for existing
data).
Modify Database Structure accordingly.
Restore the data from the backup.
Related
We have an SQLite database and a corresponding SQLiteOpenHelper subclass. This helper has an onDowngrade implementation that I would like to write an Espresso test for.
The full onDowngrade implementation is available here. This is a simplified version of it:
#Override
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("CREATE TABLE IF NOT EXISTS foo_tmp (_id integer primary key, bar text not null, baz text not null);");
db.execSQL("INSERT INTO foo_tmp(_id,bar,baz) SELECT _id,bar,baz FROM foo;");
db.execSQL("DROP TABLE IF EXISTS foo;");
db.execSQL("RENAME TABLE foo_tmp TO foo;");
}
The test loads a database dump with a very high version number and added or removed columns. It then gets a readable database and ensures that the version has been downgraded to the current expected version and that the column names are the expected column names. The full source is available here. This is what it looks like:
#Test
public void testMigration() throws IOException {
writeDatabaseFile("database" + File.separator + dbFilename);
InstancesDatabaseHelper databaseHelper = new InstancesDatabaseHelper();
SQLiteDatabase db = databaseHelper.getReadableDatabase();
assertThat(db.getVersion(), is(InstancesDatabaseHelper.DATABASE_VERSION));
List<String> newColumnNames = InstancesDatabaseHelper.getInstancesColumnNames(db);
assertThat(newColumnNames, contains(InstancesDatabaseHelper.CURRENT_VERSION_COLUMN_NAMES));
}
Everything works as intended if I manually load the same database dumps into the app. However, when I run this test, it looks like the last RENAME in the migration is not executed. If I comment out the last two SQL statements in the migration (dropping the original table and renaming the temporary table to the original table name), I can assert that the temporary table has the expected contents (here is a commit that shows this).
With some experimentation, we have found that adding databaseHelper.getReadableDatabase().close(); in the test after instantiating the SQLiteOpenHelper makes the tests pass. Given that the onDowngrade call is wrapped in a transaction, I don't understand how this is possible.
Could this point to a bug in our onDowngrade implementation? Is triggering migrations in Espresso tests different in some way?
There probably is a race condition, because SQLite is a shared resource.
eg. when the test runs before the last one COMMIT statement was issued.
Wrap it into a transaction (also see Isolation In SQLite):
if(! BuildConfig.DEBUG) {
db.beginTransaction();
} else {
db.beginTransactionWithListener(new SQLiteTransactionListener() {
#Override public void onBegin() {Log.d(LOG_TAG, "onBegin()");}
#Override public void onCommit() {Log.d(LOG_TAG, "onCommit()");}
#Override public void onRollback() {Log.d(LOG_TAG, "onRollback()");}
});
}
try {
db.execSQL("CREATE TABLE IF NOT EXISTS foo_tmp (_id integer primary key, bar text not null, baz text not null);");
db.execSQL("INSERT INTO foo_tmp(_id,bar,baz) SELECT _id,bar,baz FROM foo;");
db.execSQL("DROP TABLE IF EXISTS foo;");
db.execSQL("RENAME TABLE foo_tmp TO foo;");
db.setTransactionSuccessful();
} catch(SQLException e){
Log.d(LOG_TAG, "" + e.getMessage());
} finally {
db.endTransaction();
}
db.close();
I would like to update my database and add a new table. So following this Stackoverflow question, I have incremented my database from version 1 to version 2. Then in onUpgrade, I wrote the code to add new table and insert data:
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion < 2) {
String CREATE_B_TABLE = "CREATE TABLE IF NOT EXISTS b ( " +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"qset TEXT, "+
"highscore TEXT )";
db.execSQL(CREATE_B_TABLE);
String ADD_B = "INSERT INTO b ( qset ) VALUES ('1B'), ('2B'), ('3B'), ('4B'), ('5B'), ('6B'), ('7B'), ('8B'), ('9B'), ('10B')";
db.execSQL(ADD_B);
}
this.onCreate(db);
}
I also put the same code in onCreate to cater for new users.
However, something weird happened when the app is upgraded. The Add_B is inserted twice to the table, so I am having duplicated data. If I removed the code in onUpgrade, new table is added without duplication. It seem like onCreate is called when the app is upgraded, contradicting with the answer from another stackoverflow question.
If I read correctly the code you are intentionally calling the onCreate method from your onUpgrade.
Just before the method ends you call: this.onCreate(db)
This question already has answers here:
Ship an application with a database
(15 answers)
Closed 8 years ago.
I am working on an Android application that will need several entries (a single table, with 1000-10000 rows) populated in that app's database before the user can use that app. I've looked around some tutorials and I am unsure of the best way to do this. Should I just check if the database exists each time the app is started and, if it isn't there, create it and insert the thousands of records I need? Or is there a better way to handle this problem? Ideally, it could be included as part of the app's install process, but I'm not sure if this is possible. Any feedback would be greatly appreciated.
Here is an example of how to create and populate a database, you can just do this on the app install, this only creates one entry though so may be inefficient for what you want to do.
private static class settingsDatabaseHelper extends SQLiteOpenHelper{
//SQL String for creating the table required
private static final String CREATE_SETTINGS_TABLE
= "CREATE TABLE tbl_settings(" +
"_ID INTEGER PRIMARY KEY AUTOINCREMENT," +
"VOIPUSERNAME TEXT," +
"VOIPAUTHID TEXT," +
"PASSWORD TEXT," +
"VOIPDISPLAYNAME TEXT," +
"SIPPROXYSERVER TEXT," +
"SIPREGISTRAR TEXT," +
"SIPREALM TEXT," +
"EXPIRESTIME INTEGER);";
//constructor
public settingsDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_SETTINGS_TABLE);
ContentValues initialValues = new ContentValues();
initialValues.put("VOIPUSERNAME", "xxxxx");
initialValues.put("VOIPAUTHID", "xxxxxxxxxx");
initialValues.put("PASSWORD", "xxxxxx");
initialValues.put("VOIPDISPLAYNAME", "xxxxxxxxx");
initialValues.put("SIPPROXYSERVER", "xxxxxxxxxxxxx");
initialValues.put("SIPREGISTRAR", "xxxxxxxxxxx");
initialValues.put("SIPREALM", "xxxxxxxxxx");
initialValues.put("EXPIRESTIME", xxxxxxxxxxx);
Log.d("1.6", "gets to here");
db.insert(SETTINGS_TABLE, null, initialValues);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to " +
newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + SETTINGS_TABLE);
onCreate(db);
}
}
//end helper class
}
the way I'm going here is to ship a prepopulated database in the assets folder. You can drop in files there and use them as-they-are. Beware, however, that there is a size limit of 1MB, so maybe you'll have to split files, or compress them.
Compression is quite handy and well supported by the os itself.
hope this was of any help :-)
JavaDoc from SQLiteOpenHelper:
A helper class to manage database
creation and version management. You
create a subclass implementing
onCreate(SQLiteDatabase),
onUpgrade(SQLiteDatabase, int, int)
and optionally onOpen(SQLiteDatabase),
and this class takes care of opening
the database if it exists, creating it
if it does not, and upgrading it as
necessary. Transactions are used to
make sure the database is always in a
sensible state.
For an example, see the
NotePadProvider class in the NotePad
sample application, in the samples/
directory of the SDK.
So if you extend this class, you have 3 methods which will be called in some cases and you can choose, what do to.
Thats the best practice :)
This question already has answers here:
Ship an application with a database
(15 answers)
Closed 8 years ago.
I am working on an Android application that will need several entries (a single table, with 1000-10000 rows) populated in that app's database before the user can use that app. I've looked around some tutorials and I am unsure of the best way to do this. Should I just check if the database exists each time the app is started and, if it isn't there, create it and insert the thousands of records I need? Or is there a better way to handle this problem? Ideally, it could be included as part of the app's install process, but I'm not sure if this is possible. Any feedback would be greatly appreciated.
Here is an example of how to create and populate a database, you can just do this on the app install, this only creates one entry though so may be inefficient for what you want to do.
private static class settingsDatabaseHelper extends SQLiteOpenHelper{
//SQL String for creating the table required
private static final String CREATE_SETTINGS_TABLE
= "CREATE TABLE tbl_settings(" +
"_ID INTEGER PRIMARY KEY AUTOINCREMENT," +
"VOIPUSERNAME TEXT," +
"VOIPAUTHID TEXT," +
"PASSWORD TEXT," +
"VOIPDISPLAYNAME TEXT," +
"SIPPROXYSERVER TEXT," +
"SIPREGISTRAR TEXT," +
"SIPREALM TEXT," +
"EXPIRESTIME INTEGER);";
//constructor
public settingsDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_SETTINGS_TABLE);
ContentValues initialValues = new ContentValues();
initialValues.put("VOIPUSERNAME", "xxxxx");
initialValues.put("VOIPAUTHID", "xxxxxxxxxx");
initialValues.put("PASSWORD", "xxxxxx");
initialValues.put("VOIPDISPLAYNAME", "xxxxxxxxx");
initialValues.put("SIPPROXYSERVER", "xxxxxxxxxxxxx");
initialValues.put("SIPREGISTRAR", "xxxxxxxxxxx");
initialValues.put("SIPREALM", "xxxxxxxxxx");
initialValues.put("EXPIRESTIME", xxxxxxxxxxx);
Log.d("1.6", "gets to here");
db.insert(SETTINGS_TABLE, null, initialValues);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
Log.w(TAG, "Upgrading database from version " + oldVersion + " to " +
newVersion + ", which will destroy all old data");
db.execSQL("DROP TABLE IF EXISTS " + SETTINGS_TABLE);
onCreate(db);
}
}
//end helper class
}
the way I'm going here is to ship a prepopulated database in the assets folder. You can drop in files there and use them as-they-are. Beware, however, that there is a size limit of 1MB, so maybe you'll have to split files, or compress them.
Compression is quite handy and well supported by the os itself.
hope this was of any help :-)
JavaDoc from SQLiteOpenHelper:
A helper class to manage database
creation and version management. You
create a subclass implementing
onCreate(SQLiteDatabase),
onUpgrade(SQLiteDatabase, int, int)
and optionally onOpen(SQLiteDatabase),
and this class takes care of opening
the database if it exists, creating it
if it does not, and upgrading it as
necessary. Transactions are used to
make sure the database is always in a
sensible state.
For an example, see the
NotePadProvider class in the NotePad
sample application, in the samples/
directory of the SDK.
So if you extend this class, you have 3 methods which will be called in some cases and you can choose, what do to.
Thats the best practice :)
So, I already have my app on playstore....
Now, I want to add a column to the database in my app. For this, I must upgrade my databse which can be done by changing the database version.
The users will already have some stuff in the database and when I will upload the updated version of my app (with changed version of the databse), it will create a new databse and user will loose all the stuff he/she has in his/her database.
What is the solution for this issue? And how to backup / restore contents of the old databse to new database? (I know how to backup the database by simply copy pasting the database to external storage programatically).
You can use onUpgrade() method for handling this.
Something like this:
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion == 1 && newVersion == 2) {
db.execSQL("create temporary table people_tmp ("
+ "id integer, name text, position text, posid integer);");
db.execSQL("insert into people_tmp select id, name, position, posid from people;");
db.execSQL("drop table people;");
db.execSQL("create table people ("
+ "id integer primary key autoincrement,"
+ "name text, posid integer);");
db.execSQL("insert into people select id, name, posid from people_tmp;");
db.execSQL("drop table people_tmp;");
}
}
So. You are creating temporary table and saving all needed info inside that table. Next you dropping your table, creating new one and inserting values to it from your temporary table. You can add additional fields and feel free to put there all what you want.
UPDATE:
After a little googling i found an easier solution:
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// If you need to add a column
if (newVersion == 2) {
db.execSQL("ALTER TABLE foo ADD COLUMN new_column INTEGER DEFAULT 0");
}
}
Alter table method will change your database structure without loosing data.
If you are only adding a new column, you can alter existing table instead of create new table. An example:
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if(oldVersion<2){
db.execSQL("ALTER TABLE "+this.getTableName()+" ADD COLUMN "+COLUMNS.NAME+ " integer default 0;", null);
db.execSQL("UPDATE "+this.getTableName()+ " SET "+COLUMNS.NAME+ "="+COLUMNS.NAMEVALUE+";", null);
}
};
Here is Android documentation on ALTER TABLE use case in onUpgrade(). So in this case, if you are not rename or remove existing table, you don't need to backup old table.
If you add new columns you can use ALTER TABLE to insert them into a
live table.
Also see: https://stackoverflow.com/a/8291718/2777098