Well, i am trying to test my database migration. Unfortunatly something looks like wrong.
#RunWith(AndroidJUnit4ClassRunner.class)
public class MigrationTest {
private static final String TEST_DB = "migration-test";
#Rule
public MigrationTestHelper helper;
public MigrationTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
AppDatabase.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());
}
#Test
public void migrateAll() throws IOException {
// Create earliest version of the database.
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
db.close();
// Open latest version of the database. Room will validate the schema
// once all migrations execute.
AppDatabase appDb = Room.databaseBuilder(
InstrumentationRegistry.getInstrumentation().getTargetContext(),
AppDatabase.class,
TEST_DB)
.addMigrations(ALL_MIGRATIONS).build();
appDb.getOpenHelper().getWritableDatabase();
appDb.close();
}
// Array of all migrations
private static final Migration[] ALL_MIGRATIONS = new Migration[]{MIGRATION_1_2};
}
Migration code.
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
database.execSQL("ALTER TABLE mytable ADD COLUMN reference_code TEXT");
}
};
All is working fine with real migration but in junit test case i have the following error.
E/SQLiteLog: (1) duplicate column name: reference_code
E/TestRunner: failed: migrateAll(com.apps.MigrationTest)
E/TestRunner: ----- begin exception -----
E/TestRunner: android.database.sqlite.SQLiteException: duplicate column name: reference_code (code 1 SQLITE_ERROR): , while compiling: ALTER TABLE mytable ADD COLUMN reference_code TEXT
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:986)
at a
As i understand, it looks like SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1); is creating the schema V2 of my database (and not the version 1).
As a result the new column is taggued as duplicate.
To fix it i have to rollback my version = 1 to #Database class and then start my junit test again.
Can anyone help me on it ?
I follow the google guide here : https://developer.android.com/training/data-storage/room/migrating-db-versions.html
Well, i finally found it. It looks like a wrong schema has been generated in my assets folder.
To fix the issue, here is what i did.
Delete 1.json and 2.json files from the assets folder (each file contains the structure of the database version)
Rollback to the version 1 database (in my code), build > make projet
You will see the 1.json in your assets folder
Made my changes, i mean adding my new column in my Table.java file
Build > make projet
You will see the 2.json in your assets folder
Run the junit test, working now
Here is the difference bewteen my java object and database version 1 et 2
#Entity(tableName = "Table")
public class Table implements Parcelable {
#ColumnInfo(name = COLUMN_REFERENCE_CODE)
private String referenceCode;
}
Hope this will help.
Related
I'm trying to migrate a Room database from versions 2 and 3 to 4 like so:
private static final Migration MIGRATION_2_4 = new Migration(2, 4) {
#Override
public void migrate(#NonNull #NotNull SupportSQLiteDatabase database) {
database.execSQL(
"ALTER TABLE 'market_data' ADD COLUMN 'watch_list_boolean' TEXT NOT NULL DEFAULT '0'");
database.execSQL("DROP TABLE 'developer_data'");
}
};
but it's not working, what is wrong here?
The problem, most likely (post your stack trace to help future readers), is that your DB won't be able to perform migrations 2->3 and 3->4
So, your code will only work if your db is upgraded from 2 directly to 4 and will throw an exception (that indicates what migration is missing) if db is upgraded from 2 to 3 or from 3 to 4.
Best practice is to create separate migrations - 2 to 3, and 3 to 4.
Room will know to execute the correct migrations and in the right order (2->3 or 3->4 or 2->3->4):
private static final Migration MIGRATION_2_3 = new Migration(2, 3) {
#Override
public void migrate(#NonNull #NotNull SupportSQLiteDatabase database) {
database.execSQL(
"ALTER TABLE 'market_data' ADD COLUMN 'watch_list_boolean' TEXT NOT NULL DEFAULT '0'");
}
};
private static final Migration MIGRATION_3_4 = new Migration(3, 4) {
#Override
public void migrate(#NonNull #NotNull SupportSQLiteDatabase database) {
database.execSQL("DROP TABLE 'developer_data'");
}
};
Don't forget to update DB version :)
With all due respect, you are taking a conservative approach.
Since the Room database uses Gradle to set its version number, it's very easy to change it.
So, instead of relying on the tools of Gradle and SQLiteDatabase to do this job for you, use the in-memory version of your database and just create the column using plain SQL.
I'm trying to test Room DB after including a new Column date. But I'm not getting how to insert data as I cannot use context in Testing.java file.
my code looks like this,
#RunWith(AndroidJUnit4.class)
public class MigrationTest {
private static final String TEST_DB = "note_database";
#Rule
public MigrationTestHelper helper;
public MigrationTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
NoteDatabase.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());
}
#Test
public void migrateAll() throws IOException {
// Create earliest version of the database.
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2);
db.execSQL("INSERT INTO note_table (title,description,priority,date)" +
" VALUES ('title_test','description_test','priority_test','10/12/2019'); ");
db.close();
// Open latest version of the database. Room will validate the schema
// once all migrations execute.
NoteDatabase appDb = Room.databaseBuilder(
InstrumentationRegistry.getInstrumentation().getTargetContext(),
NoteDatabase.class,
TEST_DB)
.addMigrations(ALL_MIGRATIONS).build();
appDb.getOpenHelper().getWritableDatabase();
appDb.close();
}
// Array of all migrations
private static final Migration[] ALL_MIGRATIONS = new Migration[]{
MIGRATION_1_2};
}
This code was working fine but , look at this reference code. i need to do something like this.
and I'm not getting how to read getMigratedRoomDatabase data. can anyone help me on this.
// MigrationTestHelper automatically verifies the schema
//changes, but not the data validity
// Validate that the data was migrated properly.
User dbUser = getMigratedRoomDatabase().userDao().getUser();
assertEquals(dbUser.getId(), USER.getId());
assertEquals(dbUser.getUserName(), USER.getUserName());
// The date was missing in version 2, so it should be null in
//version 3
assertEquals(dbUser.getDate(), null);
That is nothing but appDb in your code. it will look like
appDb.userDao().getUser();
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);
I updated my AbstractFooEntity class by adding an integer field like below, and I bumped the DB version (the DB is initialized with new DatabaseSource(context, Models.DEFAULT, DB_VERSION).
#Entity
abstract class AbstractFooEntity {
// this was in DB schema v1
String someField;
// added in DB schema v2
int newField = 0;
}
When I deploy this code and the (automatic) DB migration is performed when the user runs the new version of the Android app, I get the following error at runtime: "Cannot add a NOT NULL column with default value NULL".
What's the proper way to annotate the entity so that the framework correctly handles the automatic DB migration in this scenario?
I found a solution of this.
database.execSQL("ALTER TABLE table_name ADD COLUMN colum_name INTEGER DEFAULT 1 not null");
add the command : "DEFAUT value" after data type will solve your problem.
There are two options, first one is probably to be preferred - in the second one, you need to handle possible nullpointers in the code:
option 1
#Entity
abstract class AbstractFooEntity {
...
#Column(value = "0")
int newField;
}
option 2
#Entity
abstract class AbstractFooEntity {
...
Integer newField;
}
I use in my project "ActiveAndroid". I have a model which I retain. here is a model:
#Table(name="Params")
public class Params extends Model {
public Params() {
super();
}
#Expose
#Column(name="height")
#SerializedName("heigth")
public int heigth;
#Expose
#Column(name="weight")
#SerializedName("weight")
public int weight;
}
here all save:
public void success(User user, Response response) {
user.params.save();
user.save();
ActiveAndroid.clearCache();
}
Everything works fine! But if I want to add another field in the model:
#Expose
#Column(name="strong")
#SerializedName("strong")
public int strong;
I get an error:
android.database.sqlite.SQLiteException: table Params has no column named strong (code 1): , while compiling: INSERT INTO Params(Id,weight,height,strong) VALUES (?,?,?,?)
of course I know why this error. Because the table is no such column. That's the question, how to add this column ???
Now that I've tried:
remove programs completely, again to compile, run, and everything works fine! because it creates a new table from this column.
Tried to change the version in the manifest database, but this has not led to success.
I know that with normal use of the database in the method of upgrade you can change the version of the database and restructure table. but how to do it in ORM "ActiveAndroid" ?
There is a thing known as Migration, in Active Android you can do that like this
Add the field in your model (which you already did)
Change the database version the the AndroidManifest.xml's metadata
Write your migration script. Name your script [newDatabaseVersion].sql, and place it in the directory [YourApp’sName]/app/src/main/assets/migrations. In my specific example, I’ll create the file [MyAppName]/app/src/main/assets/migrations/2.sql.
E.G. ALTER TABLE Items ADD COLUMN Priority TEXT;