I am trying to use an existing SQLite database that my Kotlin app downloads and then opens with Room. However, I think I am having an issue with a field that has a Date type in the original SQLite.
The SQLite DB:
CREATE TABLE `sets` (
id INTEGER PRIMARY KEY AUTOINCREMENT,
baseSetSize INTEGER,
block TEXT,
booster TEXT,
cardsphereSetId INTEGER,
code TEXT(8) UNIQUE NOT NULL,
isFoilOnly INTEGER NOT NULL DEFAULT 0,
isForeignOnly INTEGER NOT NULL DEFAULT 0,
isNonFoilOnly INTEGER NOT NULL DEFAULT 0,
isOnlineOnly INTEGER NOT NULL DEFAULT 0,
isPartialPreview INTEGER NOT NULL DEFAULT 0,
keyruneCode TEXT,
mcmId INTEGER,
mcmIdExtras INTEGER,
mcmName TEXT,
mtgoCode TEXT,
name TEXT,
parentCode TEXT,
releaseDate DATE,
sealedProduct TEXT,
tcgplayerGroupId INTEGER,
totalSetSize INTEGER,
type TEXT
)
My Room entity class:
@Entity(tableName = "sets", indices = [Index(value = ["code"], unique = true)])
data class Sets(
@PrimaryKey @ColumnInfo(name = "id") val id: Int?,
@ColumnInfo(name = "baseSetSize") val baseSetSize: Int?,
@ColumnInfo(name = "block") val block: String?,
@ColumnInfo(name = "booster") val booster: String?,
@ColumnInfo(name = "cardsphereSetId") val cardsphereSetId: Int?,
@ColumnInfo(name = "code") val code: String,
@ColumnInfo(name = "isFoilOnly", defaultValue = "0") val isFoilOnly: Int,
@ColumnInfo(name = "isForeignOnly", defaultValue = "0") val isForeignOnly: Int,
@ColumnInfo(name = "isNonFoilOnly", defaultValue = "0") val isNonFoilOnly: Int,
@ColumnInfo(name = "isOnlineOnly", defaultValue = "0") val isOnlineOnly: Int,
@ColumnInfo(name = "isPartialPreview", defaultValue = "0") val isPartialPreview: Int,
@ColumnInfo(name = "keyruneCode") val keyruneCode: String?,
@ColumnInfo(name = "mcmId") val mcmId: Int?,
@ColumnInfo(name = "mcmIdExtras") val mcmIdExtras: Int?,
@ColumnInfo(name = "mcmName") val mcmName: String?,
@ColumnInfo(name = "mtgoCode") val mtgoCode: String?,
@ColumnInfo(name = "name") val name: String?,
@ColumnInfo(name = "parentCode") val parentCode: String?,
@ColumnInfo(name = "releaseDate") val releaseDate: Date?,
@ColumnInfo(name = "sealedProduct") val sealedProduct: String?,
@ColumnInfo(name = "tcgplayerGroupId") val tcgplayerGroupId: Int?,
@ColumnInfo(name = "totalSetSize") val totalSetSize: Int?,
@ColumnInfo(name = "type") val type: String?
)
And my database class:
@Database(
version = 1,
exportSchema = false,
entities = [
Cards::class,
Sets::class]
)
@TypeConverters(Converters::class)
abstract class PrintingDatabase : RoomDatabase() {
abstract fun cardsDAO(): CardsDAO
abstract fun setsDAO(): SetsDAO
companion object {
// Singleton prevents multiple instances of database opening at the same time.
@Volatile
private var INSTANCE: PrintingDatabase? = null
fun getInstance(context: Context): PrintingDatabase {
val dbfile = File(context.dataDir.absolutePath + "/" + MTGJsonWorker.MTGJSON_PRINTING_FILE)
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
context.applicationContext,
PrintingDatabase::class.java,
"printing_database"
)
.createFromFile(dbfile)
.fallbackToDestructiveMigration()
.build()
INSTANCE = instance
instance
}
}
}
}
And lastly the Converters class:
object Converters {
@TypeConverter
fun fromTimestamp(value: Long): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
}
In a CoroutineWorker, I have a method that creates an instance of the DB, and it is here that I get a failure:
java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Pre-packaged database has an invalid schema: sets(net.redlightning.mtgdeck.thirdparty.mtgjson.database.printing.schema.Sets).
Expected:
TableInfo{name='sets', columns={sealedProduct=Column{name='sealedProduct', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, cardsphereSetId=Column{name='cardsphereSetId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, code=Column{name='code', type='TEXT', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, isFoilOnly=Column{name='isFoilOnly', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='0'}, releaseDate=Column{name='releaseDate', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, isOnlineOnly=Column{name='isOnlineOnly', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='0'}, keyruneCode=Column{name='keyruneCode', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, mcmIdExtras=Column{name='mcmIdExtras', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, tcgplayerGroupId=Column{name='tcgplayerGroupId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, type=Column{name='type', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, booster=Column{name='booster', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, mtgoCode=Column{name='mtgoCode', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, baseSetSize=Column{name='baseSetSize', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, isPartialPreview=Column{name='isPartialPreview', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='0'}, parentCode=Column{name='parentCode', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, isForeignOnly=Column{name='isForeignOnly', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='0'}, isNonFoilOnly=Column{name='isNonFoilOnly', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='0'}, name=Column{name='name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, mcmName=Column{name='mcmName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, block=Column{name='block', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, totalSetSize=Column{name='totalSetSize', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}, mcmId=Column{name='mcmId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[Index{name='index_sets_code', unique=true, columns=[code], orders=[ASC]}]}
Found:
TableInfo{name='sets', columns={sealedProduct=Column{name='sealedProduct', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, cardsphereSetId=Column{name='cardsphereSetId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, code=Column{name='code', type='TEXT(8)', affinity='2', notNull=true, primaryKeyPosition=0, defaultValue='null'}, isFoilOnly=Column{name='isFoilOnly', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='0'}, releaseDate=Column{name='releaseDate', type='DATE', affinity='1', notNull=false, primaryKeyPosition=0, defaultValue='null'}, isOnlineOnly=Column{name='isOnlineOnly', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='0'}, keyruneCode=Column{name='keyruneCode', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, mcmIdExtras=Column{name='mcmIdExtras', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, tcgplayerGroupId=Column{name='tcgplayerGroupId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, type=Column{name='type', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, booster=Column{name='booster', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, mtgoCode=Column{name='mtgoCode', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, baseSetSize=Column{name='baseSetSize', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, isPartialPreview=Column{name='isPartialPreview', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='0'}, parentCode=Column{name='parentCode', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, isForeignOnly=Column{name='isForeignOnly', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='0'}, isNonFoilOnly=Column{name='isNonFoilOnly', type='INTEGER', affinity='3', notNull=true, primaryKeyPosition=0, defaultValue='0'}, name=Column{name='name', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, mcmName=Column{name='mcmName', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, block=Column{name='block', type='TEXT', affinity='2', notNull=false, primaryKeyPosition=0, defaultValue='null'}, totalSetSize=Column{name='totalSetSize', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}, id=Column{name='id', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=1, defaultValue='null'}, mcmId=Column{name='mcmId', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}}, foreignKeys=[], indices=[Index{name='index_sets_code', unique=true, columns=[code], orders=[ASC]}]}
When I do a text compare to see what the differences are, I find only:
In Expected, the column code
has a type of 'TEXT'
while Found has a type of 'TEXT(8)'
, which I assume is fine.
Otherwise Expected has column releaseDate
has type INTEGER
and affinity 3
, while in Found it has DATE
with affinity 1
, and I am thinking this is where the problem is:
releaseDate=Column{name='releaseDate', type='INTEGER', affinity='3', notNull=false, primaryKeyPosition=0, defaultValue='null'}
vs
releaseDate=Column{name='releaseDate', type='DATE', affinity='1', notNull=false, primaryKeyPosition=0, defaultValue='null'}
I have tried messing around with the Converter class, changing the typeAffinity
property of the field in the Room Entity, switching it to a String?
or Int?
, etc and so far no luck.
Am I missing something in my Converters class to properly handle the DATE
from the imported SQLite DB? Is there a better way of declaring it in my Entity? How can I get Room to open it correctly?