My Problem : I'm converting an existing Java Desktop app and I'm learning Android Room development and have a prepopulated SQLite database created through SQLite Studio that contains a many-to-many join table (ie Author_By_Source ). This database and table are used in an existing Java desktop app. I'm trying to resolve the invalid schema mismatching between the ' notNull ' and ' primaryKeyPosition ' properties. I have not been able to get these two properties to update. This is the segment of the error I'm trying to resolve for the AuthorID field, but it is also the same differences for the SourceID in the same error message. So both fields need the same resolution:
Expected:
TableInfo{name='Author_By_Source', columns={AuthorID=Column{name='AuthorID', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}, ...
Found:
TableInfo{name='Author_By_Source', columns={AuthorID=Column{name='AuthorID', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, ...
What I have tried : I have tried using the SQLite Studio interface used to build and structure the database and edited the table structure for the ' Author_By_Source ' table. I set each field to Not Null but it is not reflected as 'true' when I rerun the app. I also haven't identified how to designate the primaryKeyPosition. I've tried the reverse by reading more thoroughly the options and methods of the Room Annotations but I haven't been successful. I have been reading through some Room documentation throughout my project and visited some forums and tutorials, but not found anything addressing quite this specific.
What I'm trying to do : I'm trying to use a prepopulated database with multiple tables of which some have join tables for many-to-many relationships. I am hoping for some direction.
Below is how I annotated my AuthorBySource class which may be of assistance:
@Entity(tableName = "Author_By_Source", primaryKeys = {"AuthorID", "SourceID"}, foreignKeys = {
@ForeignKey(entity = Authors.class, parentColumns = "AuthorID", childColumns = "AuthorID"),
@ForeignKey(entity = Sources.class, parentColumns = "SourceID", childColumns = "SourceID")},
indices = {@Index("AuthorID"), @Index("SourceID")})
public class AuthorBySource {
@ColumnInfo(name = "AuthorID")
private int authorID;
@ColumnInfo(name = "SourceID")
private int sourceID;
Are you re-copying the asset file and uninstalling the App each time you makes changes? If not and this doesn;t resolve the issue then :-
If you compile the room code then look at java(generated) in your_database_class _impl (where your_database_class is your @Database class name) and at the method createAllTables . This has SQL used to create the database (you can ignore the room_master SQL).
For example (this based upon your Entity and minimal referenced tables) :-
@Override
public void createAllTables(SupportSQLiteDatabase _db) {
_db.execSQL("CREATE TABLE IF NOT EXISTS `Authors` (`AuthorID` INTEGER, `AuthorName` TEXT, PRIMARY KEY(`AuthorID`))");
_db.execSQL("CREATE TABLE IF NOT EXISTS `Sources` (`SourceID` INTEGER, `SourceName` TEXT, PRIMARY KEY(`SourceID`))");
_db.execSQL("CREATE TABLE IF NOT EXISTS `Author_By_Source` (`AuthorID` INTEGER NOT NULL, `SourceID` INTEGER NOT NULL, PRIMARY KEY(`AuthorID`, `SourceID`), FOREIGN KEY(`AuthorID`) REFERENCES `Authors`(`AuthorID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`SourceID`) REFERENCES `Sources`(`SourceID`) ON UPDATE NO ACTION ON DELETE NO ACTION )");
_db.execSQL("CREATE INDEX IF NOT EXISTS `index_Author_By_Source_AuthorID` ON `Author_By_Source` (`AuthorID`)");
_db.execSQL("CREATE INDEX IF NOT EXISTS `index_Author_By_Source_SourceID` ON `Author_By_Source` (`SourceID`)");
_db.execSQL("CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)");
_db.execSQL("INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '953f8512db886fb7d206fa561a7117c4')");
}
You need to convert the database accordingly, that is to be as per the definition, so for example you could for the above, in SQLite Studio, use
/* Just once */
PRAGMA foreign_keys = 0;
/* Per table */
DROP TABLE IF EXISTS Author_By_Source_new;
DROP TABLE IF EXISTS Author_By_Source_original;
CREATE TABLE IF NOT EXISTS Author_By_Source_new copy_generated_sql_for_the_column_definitions_etc;
DELETE FROM Author_By_Source_new;
INSERT INTO Author_By_Source_new SELECT * FROM Author_By_Source;
ALTER TABLE Author_By_Source RENAME TO Author_By_Source_original;
ALTER TABLE Author_By_Source_new RENAME TO Author_By_Source;
/* IF HAPPY THEN DO */
-- DROP TABLE IF EXISTS Author_By_Source_original; /* NOTE TURNED OFF */
...... do the equivalent for all tables
/* Just once */
PRAGMA foreign_keys = 1;
PRAGMA foreign_key_check;
PRAGMA integrity_check;
INSERT INTO ....
statement assumes that the column order is the same for the new and original tables, otherwise use INSERT INTO (column_list)....
where the columns are as per the generated SQL eg INSERT INTO Author_By_Source_new (AuthorID,SourceID) SELECT * FROM Author_By_Source;
The CREATE TABLE SQL would be (based upon the generated SQL above)
CREATE TABLE IF NOT EXISTS `Author_By_Source_new` (`AuthorID` INTEGER NOT NULL, `SourceID` INTEGER NOT NULL, PRIMARY KEY(`AuthorID`, `SourceID`), FOREIGN KEY(`AuthorID`) REFERENCES `Authors`(`AuthorID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`SourceID`) REFERENCES `Sources`(`SourceID`) ON UPDATE NO ACTION ON DELETE NO ACTION );
Here's an Example based upon what's available from your code.
A database was generated using an SQLite Management Tool (Navicat) using :-
DROP TABLE IF EXISTS Author_By_Source;
DROP TABLE IF EXISTS Authors;
DROP TABLE IF EXISTS Sources;
CREATE TABLE IF NOT EXISTS Authors (AuthorID INTEGER PRIMARY KEY, AuthorName TEXT);
CREATE TABLE IF NOT EXISTS Sources (SourceID INTEGER PRIMARY KEY, SourceName TEXT);
/* Create the table so that it would cause issues in Room */
CREATE TABLE IF NOT EXISTS Author_By_Source (
AuthorId INTEGER, SourceId INTEGER,
FOREIGN KEY (AuthorID) REFERENCES Authors(AuthorID),
FOREIGN KEY (SourceID) REFERENCES Sources(SourceID)
);
INSERT INTO Authors (AuthorName) VALUES ('Fred'),('Mary'),('Joan'),('Bert'),('Alan');
INSERT INTO Sources (SourceName) VALUES ('S1'),('S2'),('S3'),('S4'),('S5');
INSERT INTO Author_By_Source VALUES(1,1),(2,2),(3,3),(4,4),(5,5),(1,4),(3,4),(5,2);
SELECT AuthorName, SourceName
FROM Author_By_Source
JOIN Authors ON Author_By_Source.AuthorID = Authors.AuthorID
JOIN Sources ON Author_By_Source.SourceID = Sources.SourceID
;
After running it being :-
AuthorName SourceName
Fred S1
Mary S2
Joan S3
Bert S4
Alan S5
Fred S4
Joan S4
Alan S2
As expected trying to use this results in :-
java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so59756782javaroomprepopulatedconversion/a.a.so59756782javaroomprepopulatedconversion.MainActivity}: java.lang.IllegalStateException: Pre-packaged database has an invalid schema: Authors(a.a.so59756782javaroomprepopulatedconversion.Authors).
Expected:
TableInfo{name='Authors', columns={AuthorName=Column{name='AuthorName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, AuthorID=Column{name='AuthorID', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=1, defaultValue='null'}}, foreignKeys=[], indices=[]}
Found:
TableInfo{name='Authors', columns={AuthorID=Column{name='AuthorID', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}, AuthorName=Column{name='AuthorName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[]}
The following, based upon the answer, was used to convert the database :-
BEGIN TRANSACTION;
DROP TABLE IF EXISTS Author_By_Source;
DROP TABLE IF EXISTS Authors;
DROP TABLE IF EXISTS Sources;
CREATE TABLE IF NOT EXISTS `Authors` (`AuthorID` INTEGER NOT NULL, `AuthorName` TEXT, PRIMARY KEY(`AuthorID`));
CREATE TABLE IF NOT EXISTS `Sources` (`SourceID` INTEGER NOT NULL, `SourceName` TEXT, PRIMARY KEY(`SourceID`));
/* Create the table so that it would cause issues in Room */
CREATE TABLE IF NOT EXISTS Author_By_Source (
AuthorId INTEGER, SourceId INTEGER,
FOREIGN KEY (AuthorID) REFERENCES Authors(AuthorID),
FOREIGN KEY (SourceID) REFERENCES Sources(SourceID)
);
INSERT INTO Authors (AuthorName) VALUES ('Fred'),('Mary'),('Joan'),('Bert'),('Alan');
INSERT INTO Sources (SourceName) VALUES ('S1'),('S2'),('S3'),('S4'),('S5');
INSERT INTO Author_By_Source VALUES(1,1),(2,2),(3,3),(4,4),(5,5),(1,4),(3,4),(5,2);
SELECT AuthorName, SourceName
FROM Author_By_Source
JOIN Authors ON Author_By_Source.AuthorID = Authors.AuthorID
JOIN Sources ON Author_By_Source.SourceID = Sources.SourceID
;
/* Just once */
PRAGMA foreign_keys = 0;
/* Per table */
DROP TABLE IF EXISTS Author_By_Source_new;
DROP TABLE IF EXISTS Author_By_Source_original;
CREATE TABLE IF NOT EXISTS `Author_By_Source_new`
(
`AuthorID` INTEGER NOT NULL,
`SourceID` INTEGER NOT NULL,
PRIMARY KEY(`AuthorID`, `SourceID`),
FOREIGN KEY(`AuthorID`) REFERENCES `Authors`(`AuthorID`) ON UPDATE NO ACTION ON DELETE NO ACTION ,
FOREIGN KEY(`SourceID`) REFERENCES `Sources`(`SourceID`) ON UPDATE NO ACTION ON DELETE NO ACTION
);
DELETE FROM Author_By_Source_new;
INSERT INTO Author_By_Source_new SELECT * FROM Author_By_Source;
ALTER TABLE Author_By_Source RENAME TO Author_By_Source_original;
ALTER TABLE Author_By_Source_new RENAME TO Author_By_Source;
/* IF HAPPY THEN DO */
DROP TABLE IF EXISTS Author_By_Source_original;
-- ...... do the equivalent for all tables
CREATE INDEX IF NOT EXISTS `index_Author_By_Source_SourceID` ON `Author_By_Source` (`SourceID`);
CREATE INDEX IF NOT EXISTS `index_Author_By_Source_AuthorID` ON `Author_By_Source` (`AuthorID`);
COMMIT;
/* Just once */
PRAGMA foreign_keys = 1;
PRAGMA foreign_key_check;
PRAGMA integrity_check;
SELECT sql FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite%';
SELECT AuthorName, SourceName
FROM Author_By_Source
JOIN Authors ON Author_By_Source.AuthorID = Authors.AuthorID
JOIN Sources ON Author_By_Source.SourceID = Sources.SourceID
;
The database was closed, timestamps and size checked, and then database file replaced the existing file in the assets folder. The App was uninstalled and then rerun with the following in the MainActivity :-
myDB = Room.databaseBuilder(this,MyAppDatabase.class,"mydb")
.createFromAsset("mydb")
.allowMainThreadQueries()
.build();
myDB.getOpenHelper().getWritableDatabase();
List<AuthorWithSources> authorWithSourcesList = myDB.allDao().getAllAuthorsWithSources();
for (AuthorWithSources aws: authorWithSourcesList) {
Log.d("AUTHORSOURCEINFO","Author = " + aws.authors.getAuthorName() + "Source = " + aws.sources.getSourceName());
}
}
myDB.getOpenHelper().getWritableDatabase();
used to force an immediate open and therefore copy of the database.
getAllAuthorsWithSources()
does as it says using @Query("SELECT * FROM Author_By_Source") List<AuthorWithSources> getAllAuthorsWithSources();
AuthorWithSources being :-
public class AuthorWithSources {
@Embedded
AuthorBySource authorBySource;
@Relation(entity = Authors.class,parentColumn = "AuthorID",entityColumn = "AuthorID")
Authors authors;
@Relation(entity = Sources.class,parentColumn = "SourceID",entityColumn = "SourceID")
Sources sources;
}
No Errors and in the log :-
2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = FredSource = S1 2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = MarySource = S2 2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = JoanSource = S3 2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = BertSource = S4 2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = AlanSource = S5 2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = FredSource = S4 2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = JoanSource = S4 2020-01-16 10:09:23.623 D/AUTHORSOURCEINFO: Author = AlanSource = S2
A tool, as an App, exists that converts databases for use by Room and generates the java code for the Entities/ Dao's.
The tool is detailed here RoomExistingSQLiteDBConverter
Using the tool and
and finally creating code to use the database eg
soanswersDatabase = Room.databaseBuilder(this,SoanswersDatabase.class,SoanswersDatabase.DBNAME) .allowMainThreadQueries() .createFromAsset(SoanswersDatabase.DBNAME) .build(); List<Author_By_Source> authorBySourceList = soanswersDatabase.getAuthor_By_SourceDao().getEveryAuthor_By_Source(); for (Author_By_Source abs: authorBySourceList) { Log.d("AUTHORSOURCEINFO", "AuthorReference = " + String.valueOf(abs.getAuthorId()) + " Sourcereference = " + String.valueOf(abs.getSourceId()) ); }
2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 1 Sourcereference = 1 2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 2 Sourcereference = 2 2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 3 Sourcereference = 3 2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 4 Sourcereference = 4 2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 5 Sourcereference = 5 2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 1 Sourcereference = 4 2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 3 Sourcereference = 4 2020-01-16 10:46:27.907 D/AUTHORSOURCEINFO: AuthorReference = 5 Sourcereference = 2
I was able to resolve this issue but what it amounted to was matching the external database field constraints to the Android Room Entities and learning a bit more about the Room Annotations. The issues were basically whether the field was notNull. For some reason, which could be a naive misunderstanding on my part, is when creating Entities with Android Room, fields would seem to automatically default to 'notNull' which I personally wouldn't expect unless I specified. However, through the experience I think I have a more stable database than I would have.
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.