简体   繁体   中英

Pre-packaged database has an invalid schema error

I'm building an Android application based on an old Android project. In my new application I'm using Room. I have to use the same database that is used in the first project. Furthermore, I've extracted the database from the first project using com.amitshekhar.android:debug-db library. After obtaining the database file I would like to open it with the Room.

I am building database like this:

Room.databaseBuilder(
            androidContext(),
            Database::class.java, "database.db"
        ).createFromAsset("database.db")
            .build()

Currently I'm using this createFromAsset() method, although later I would use the createFromFile() method, as my database should be downloaded from the server.

But I'm getting the java.lang.IllegalStateException: Pre-packaged database has an invalid schema This happens because there are several datatypes in the database that are not supported in Room such as NVARCHAR(200), DATE or bit.

I'm aware that Room is using only five Sql types, but I do not know how to change this so that Room can open this kind of database using above mentioned methods.

The problem is how to convert NVARCHAR(200), DATE or bit into datatypes that are supported by Room?

You have to convert the database to use specific column type affinities that are supported by Room and that match the entities.

For NVARCHAR(200) you need to have TEXT replace NVARCHAR(200) with the Entity defining the column as a String.

For DATE it depends upon the Entity definition if you are using String based dates eg YYYY-MM-DD hh:mm:ss then the Entity should be String and the column affinity TEXT . If storing the date as a timestamp then the Entity should be long and the column affinity INTEGER .

The answer here Can't migrate a table to Room do to an error with the way booleans are saved in Sqlite does a conversion to change BOOL's to INTEGER.

You could adapt this (although I would be cautious with DATE) to suit.

Additional

You may find the following to be of use. You run it against the pre-existing database in your favourite SQLite Manager tool.

WITH potentialRoomChanges AS (
    SELECT sm.name AS tablename, pti.name AS columnname, pti.type, dflt_value, pk,
        CASE 
            WHEN instr(upper(pti.type),'INT') THEN 'INTEGER'
            WHEN instr(upper(pti.type),'CHAR') OR instr(upper(pti.type),'CLOB') OR instr(upper(pti.type),'TEXT') THEN 'TEXT'
            WHEN instr(upper(pti.type),'BLOB') THEN 'BLOB'
            WHEN instr(upper(pti.type),'REAL') OR instr(upper(pti.type),'FLOA') OR instr(upper(pti.type),'DOUB') THEN 'REAL'
            ELSE 'NUMERIC'
        END AS roomtype ,
        CASE WHEN pti.[notnull] THEN 'Investigate NOT NULL USE' END AS nnindicator,
        sql
    FROM sqlite_master AS sm JOIN pragma_table_info(sm.name) AS pti
    WHERE 
        sm.type = 'table' 
        AND sm.name NOT LIKE 'sqlite_%' 
        AND sm.name <> 'android_metadata' 
        AND (
            upper(pti.type) <> roomtype 
            OR instr(roomtype,'NUMERIC') 
            OR nnindicator IS NOT NULL
            OR dflt_value IS NOT NULL
            OR pk > 0
        )
    ORDER BY sm.name,pti.cid
)
SELECT tablename, columnname, type, roomtype, 
CASE WHEN upper(type) <> upper(roomtype) THEN 'Investigate TYPE should be ' ||roomtype END AS typechange_notes,
CASE WHEN roomtype = 'NUMERIC' THEN 'Investigate NUMERIC' END AS numeric_notes, 
CASE WHEN dflt_value IS NOT NULL THEN 'Investigate DEFAULT VALUE of '||dflt_value END AS default_notes,
CASE WHEN pk > 0 THEN 'Investigate PRIMARY KEY inclusion' END AS primarykey_notes,
nnindicator AS notnull_notes 
FROM potentialRoomChanges
;

Example output :-

在此处输入图片说明

Hopefully the columns/text are self-explanatory. This is based upon the column types defined (which may differ from the type used). eg FLOATING POINT (5th row shown) you would think would be REAL. However according to the derived type affinity the first rule (if the type includes INT it is INTEGER) has been applied.

Rules as per Datatypes In SQLite Version 3 - 3.1. Determination Of Column Affinity .

NUMERIC from my limited experience with room isn't a type that it uses, so it should always be changed to one of the other types.

Use @NonNull before every field of you Pojo (entity) class.
there is no need to add @NonNull to primary key field.
an example is below

@Entity(tableName = "station")
public class Station {
    @PrimaryKey
    private int id;
    @NonNull
    private String name;
    @NonNull
    private int line;
    @NonNull
    private double lat;
    @NonNull
    private double lon;

    ... constructor, getters and setters
}

For me the problem was Not-Null , for every column in the database with NOT NULL it should be reflected in you model with @NonNull .

if you LastName in the database is

"LastName"  TEXT NOT NULL,

in your code on your Model it should be

@NonNull
private String LastName;

In my case error was solved by changing the name parameter different from my database name. in this code, I set the name parameter to "my_db"

fun getInstance(context: Context): AppDatabase {
    if (INSTANCE == null) {
        INSTANCE = Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "my_db" //this parameter
        )
        .allowMainThreadQueries()
        .createFromAsset("database/harry_potter.db")
        .build()
    }
}

This should be resolvable with ColumnInfo.typeAffinity. Unfortunately, there's an open issue relating to typeAffinity and schema validation:

ColumnInfo type affinity is being suppressed by column adapters.

Maybe go there and hit the "+" so this issue gets some attention.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM