64

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.

Community
  • 1
  • 1
diogojme
  • 2,309
  • 1
  • 19
  • 25
  • 2
    A bit of a shot in the dark, but perhaps try `"ALTER TABLE 'user' ADD COLUMN 'age' INTEGER NOT NULL DEFAULT 0"` (0 could be whatever you consider suitable). – MikeT Sep 22 '17 at 22:33
  • 1
    Room expects the column order to match the field order. It would appear that the results of the `ALTER TABLE` are resulting in a different order. – CommonsWare Sep 23 '17 at 11:36
  • Check bellow answer. It has complete description: https://stackoverflow.com/a/51245898/3073945 – Md. Sajedul Karim Jul 09 '18 at 12:49
  • @CommonsWare What determines the column ordering? – Skj Nov 10 '22 at 12:16
  • @Skj: It has been a few years since I worked with Room, but IIRC, the columns are in the order that the properties are in your entity class. You can always look at the generated SQL or the database that Room creates to see the column order. – CommonsWare Nov 10 '22 at 13:24

9 Answers9

130

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

Benoit Duffez
  • 11,839
  • 12
  • 77
  • 125
  • 5
    Works like a charm, thanks for helping. And now I understand, when you perform migrations, you must specify the DEFAULT value for the new column to be populated. – diogojme Sep 25 '17 at 17:43
  • Try your migrations on a dummy database and see if the query does what you expect it to do. – Benoit Duffez Sep 25 '17 at 18:40
  • 3
    I also found something interesting: if you created indices in your @Entity annotation in your model, you need to also do it in SQL in your migration script, for example `CREATE INDEX `index_History_nodeId` ON `History` (`nodeId`)` and if you don't you'll get a `Migration didn't properly handle` – Raphael C May 31 '18 at 14:02
  • Hi I am getting this error for Expected type='TEXT' and Found type='nvarchar'. How to represent type='nvarchar' in Java? – Sandeep Yohans May 29 '19 at 06:33
  • 1
    Please add default 'some_default_value' for the added column at the end in alter query otherwise you would get other exception related to migration like 'Cannot add a NOT NULL column with default value NULL' – Anand Kumar Jha Nov 16 '19 at 18:11
46

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

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
HRankit
  • 461
  • 6
  • 4
  • 2
    Whilst this may theoretically answer the question, [it would be preferable](//meta.stackoverflow.com/q/8259) to include the essential parts of the answer here, and provide the link for reference. – Adriaan Sep 01 '18 at 11:53
  • 1
    @HRankit Just Awesome, your script solved my problem. – Ritesh Adulkar Jan 09 '19 at 15:32
  • 2
    this tool is not working now, can u pls recheck it ? – Sandeep Yohans May 29 '19 at 09:00
  • @RiteshAdulkar What about if the existing sqlite table has VARCHAR columns and the new table has TEXT instead. Will it make difference while migrating ? – Ashish Kanswal Apr 15 '20 at 08:42
  • Hi @HRankit I appreciate the tool but I am unable to get it to see that my paste is legitimate. It is kicked out with the same error message that I am supposed to paste in a particular format. Can you give more debugging information when a paste fails? – Sasquatch Feb 19 '21 at 17:19
  • @HRankit This tool is not working now, although I have entered legit data. It throws an error that "Json Invalid Please paste from Expected TableInfo". – Umesh P Jan 03 '23 at 07:44
26

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.

quant
  • 2,184
  • 2
  • 19
  • 29
Prabhtej Singh
  • 335
  • 3
  • 7
7

I faced this issue today, I just changed int fields to Integer in Entities. As int cannot be null but Integer objects could be null.

Zaid Mirza
  • 3,540
  • 2
  • 24
  • 40
  • Caused a separate issue of `error: incomparable types: int and ` for me oddly enough – Prof Apr 02 '19 at 19:31
5

if you want to add Integer type column , add this code

database.execSQL("ALTER TABLE users"
                    + " ADD COLUMN year INTEGER NOT NULL DEFAULT 0 ")
Foroogh Varmazyar
  • 1,057
  • 1
  • 14
  • 18
1

If you are getting notNull differences, you can simply mark your class field with @NonNull annotation, or change your sql with ALTER TABLE. But if you are getting column type differences, such as expected: TYPE=TEXT, then found TYPE='' (COLLATE NOCASE), or expected INTEGER, found INT, then the only solution is to drop and recreate your table. Sqlite does not allow changing column types.

Use INTEGER in Sqlite instead of INT and mark your Java entity with @ColumnInfo(collate = NOCASE) (if you use NOCASE in Sqlite).

Take a look at the json file under app\schemas to get the sql for the expected queries.

static final Migration MIGRATION_2_3= new Migration(2, 3) {
        @Override
        public void migrate(SupportSQLiteDatabase database) {

            database.execSQL("DROP TABLE IF EXISTS table_tmp");

            database.execSQL("CREATE TABLE IF NOT EXISTS `table_tmp` ...");

            database.execSQL("insert into table_tmp (`id`, `name` , ...");

            database.execSQL("DROP INDEX IF EXISTS `index_table_name`");

            database.execSQL("CREATE INDEX IF NOT EXISTS `index_table_name` ON `table_tmp` (`name`)");

            database.execSQL("DROP TABLE IF EXISTS table");

            database.execSQL("alter table table_tmp rename to table");

        }
    };
live-love
  • 48,840
  • 22
  • 240
  • 204
0

I have faced notNull differences in kotlin and found exception like below

Expected:
TableInfo{name='enDic', columns={definition=Column{name='definition', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, _id=Column{name='_id', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}, favourite=Column{name='favourite', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}, word=Column{name='word', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, usage=Column{name='usage', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
Found:
TableInfo{name='enDic', columns={usage=Column{name='usage', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, definition=Column{name='definition', type='text', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, _id=Column{name='_id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}, favourite=Column{name='favourite', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, word=Column{name='word', type='text', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}

Then I used below mentioned code to solve that problem

@Entity(tableName = "enDic")
data class Word(

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "_id")
    var _id: Int?,

    @ColumnInfo(name = "word")
    var word: String?,

    @ColumnInfo(name = "definition")
    var definition: String,

    @ColumnInfo(name = "favourite")
    var favourite: Int?,

    @ColumnInfo(name = "usage")
    var usage: Int?

)

Instead of this code

@Entity(tableName = "enDic")
data class Word(

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "_id")
    var _id: Int,

    @ColumnInfo(name = "word")
    var word: String,

    @ColumnInfo(name = "definition")
    var definition: String,

    @ColumnInfo(name = "favourite")
    var favourite: Int,

    @ColumnInfo(name = "usage")
    var usage: Int

)
Dilanka Laksiri
  • 408
  • 3
  • 12
0

The tool mentioned by @HRankit in his answer: link did not seem to work when I tried it today. If that is the case for you too, please read on:

If anyone is struggling with error message mentioned in the question and have a huge table with lots of columns, you might want to try this online tool to check between expected and found schema.

Also its really important that you decide upon the column names and data types before the <db_version_number>.json (eg: 13.json) file is generated. If the json file was already generated and you made some changes to the Entity class afterwards, you might want to delete the json file and rebuild the project to generate it with correct set of values.

Lastly you should check your sql statements on the migration itself.

ravi
  • 899
  • 8
  • 31
0

I had problems with roomVersion '2.4.0-alpha01', this version not generate Index table for my case

@Entity(
    tableName = "SoundRules",
    indices = [
        Index(value = ["remoteId"], unique = true)
    ]
) 

I resolve problem just updated room version to '2.4.0-alpha03'

Coffe Milk
  • 21
  • 3