Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Room Database Migration doesnt properly handle ALTER TABLE migration

Java.lang.IllegalStateException

Migration didn't properly handle user(therealandroid.github.com.roomcore.java.User).

Expected:

TableInfo{name='user', columns={name=Column{name='name', type='TEXT', notNull=false, primaryKeyPosition=0}, age=Column{name='age', type='INTEGER', notNull=true, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', notNull=true, primaryKeyPosition=1}}, foreignKeys=[]} Found:

Found

TableInfo{ name='user', columns={name=Column{name='name', type='TEXT', notNull=false, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', notNull=true, primaryKeyPosition=1}, age=Column{name='age', type='INTEGER', notNull=false, primaryKeyPosition=0}}, foreignKeys=[]}

I'm trying to perform a simple migration, I have a class called Userand it have two columns ID (primary key) and NAME TEXT and then I populate database with two users data, then I add the column AGE in the object User and in the Migration constant I add an alter table to add this new column and lastly I replace version of the database 1 to 2.

Here is the code

User.class

@Entity(tableName = "user")
  public class User {

  @PrimaryKey
  private int id;

  @ColumnInfo(name = "name")
  private String name;

  @ColumnInfo(name = "age")
  private int age;


  public int getId() {
      return id;
  }

  public void setId(int id) {
      this.id = id;
  }

  public String getName() {
      return name;
  }

  public void setName(String name) {
      this.name = name;
  }

  public int getAge() {
      return age;
  }

  public void setAge(int age) {
      this.age = age;
  }
}

Database class

@Database(entities = {User.class}, version = 2)
public abstract class RoomDatabaseImpl extends RoomDatabase {
    abstract UserDao userDao();
}

Migration code

public static Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE 'user' ADD COLUMN 'age' INTEGER");
    }
 };

and it call

Room.databaseBuilder(context, RoomDatabaseImpl.class, "Sample.db")
            .addMigrations(MIGRATION_1_2)
            .allowMainThreadQueries()
            .build();

Before change the object adding AGE and performing the migration I add two register and it works.

After performing the migration, I just tried to add a new User as bellow:

  User user = new User();
  user.setName("JoooJ");
  user.setId(3);
  user.setAge(18);

  List<User> userList = new ArrayList<>();
  userList.add(user);
  App.database(this).userDao().insertAll(userList);  // The crash happens here

Other informations:

Android Studio 3 and I didn't tested in the actual.

Dependencies:

compile "android.arch.persistence.room:runtime:1.0.0-alpha9-1"
annotationProcessor "android.arch.persistence.room:compiler:1.0.0-alpha9-1"

compile "android.arch.persistence.room:rxjava2:1.0.0-alpha9-1"
gradle 2.3.3

Can someone help me please, I realy don't know what im doing wrong or if it is a bug.

like image 976
diogojme Avatar asked Sep 22 '17 19:09

diogojme


3 Answers

The error message is hard to parse, but there's a difference:

TableInfo{name='user', columns={name=Column{name='name', type='TEXT', notNull=false, primaryKeyPosition=0}, age=Column{name='age', type='INTEGER', notNull=true, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', notNull=true, primaryKeyPosition=1}}, foreignKeys=[]} Found:

Found

TableInfo{ name='user', columns={name=Column{name='name', type='TEXT', notNull=false, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', notNull=true, primaryKeyPosition=1}, age=Column{name='age', type='INTEGER', notNull=false, primaryKeyPosition=0}}, foreignKeys=[]}

Age is nullable but Room expected it to be not null.

Change your migration to:

database.execSQL("ALTER TABLE 'user' ADD COLUMN 'age' INTEGER NOT NULL");

Since this exception explanation is VERY difficult to parse, I have created a small script that does the diff for you.

Example:

mig "java.lang.IllegalStateException: Migration failed. expected:TableInfo{name='user', columns={name=Column{name='name', type='TEXT', notNull=false, primaryKeyPosition=0}, age=Column{name='age', type='INTEGER', notNull=true, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', notNull=true, primaryKeyPosition=1}}, foreignKeys=[]} , found:TableInfo{name='user', columns={name=Column{name='name', type='TEXT', notNull=false, primaryKeyPosition=0}, id=Column{name='id', type='INTEGER', notNull=true, primaryKeyPosition=1}, age=Column{name='age', type='INTEGER', notNull=false, primaryKeyPosition=0}}, foreignKeys=[]}"

Result:

expected/found diff

like image 137
Benoit Duffez Avatar answered Oct 18 '22 23:10

Benoit Duffez


I too wrote a small JS script which you can find https://hrankit.github.io/RoomSQLiteDifferenceFinder/

The process is pretty Simple.

  1. Input the Expected error log in Expected column which is the Left One.

  2. Input the Found error log in Found column which is the Right One.

  3. Press Go. button. The error logs get converted to JSON.

  4. Press Compare button and Voila, you have the difference you need.

This plugin finds out the difference in the two Expected and Found dump from the Android Studio Logcat.

Checkout the image of comparison here

like image 41
HRankit Avatar answered Oct 19 '22 00:10

HRankit


None of the answers are correct in any of the links. After much experiments, found a way for it. The ALTER query needs to be written in the following way to make it work:

database.execSQL("ALTER TABLE 'user' ADD COLUMN 'age' INTEGER NOT NULL DEFAULT 0")

However, the Integer DEFAULT value can be anything.

If you want to add String type column, add in the following manner:

database.execSQL("ALTER TABLE 'user' ADD COLUMN 'address' TEXT")

This works like a charm.

like image 25
Prabhtej Singh Avatar answered Oct 18 '22 22:10

Prabhtej Singh