private fun savetosqlite(CoinListesi: List<CoinInfo>){
launch{
val dao = CoinInfoDatabase(getApplication()).CoinDao()
dao.deleteAll()
val uuidList= dao.insertAll(*CoinListesi.toTypedArray())
}
dao is reset but primary key keeps increasing every time the function is called, also primary key doesn't start from 0 how do I solve it?
Dao
#Dao
interface CoinInfoDao {
#Insert
suspend fun insertAll(vararg CoinInfo: CoinInfo):List<Long>
#Query("DELETE FROM CoinInfo")
suspend fun deleteAll() }
model
#Entity
data class CoinInfo (...){
#PrimaryKey(autoGenerate = true)
var uuid:Int=0
}
Because autoGenerate/autoIncreament from the sqlite docs says it is to
prevent the reuse of ROWIDs over the lifetime of the database
As deleting all the rows with "DELETE FROM CoinInfo" does not affect the lifetime of this table in the database then the numbers continue to increase.
You need to end the "life" of the table with the SQL "DROP TABLE CoinInfo" and then re-create it for it to start a new lifetime and reset the auto generated Int.
Or you can directly reset the value of where SQLite stores then last number used with a query like "DELETE FROM sqlite_sequence WHERE name='CoinInfo'" (or set the value to be 0/1 of this row)
You would need to execute something like
CoinInfoDatabase.getDatabase(application)
.getOpenHelper()
.getWritableDatabase()
.execSQL("DELETE FROM sqlite_sequence WHERE name='CoinInfo'");
Or more efficient is do you really need autoincrement? as per the docs
Related
I am building an Android application with MVVM Architecture. Table A has an Auto-Generated Primary Key column which is a Foreign Key into Table B. When the user clicks a button on the main fragment a row is inserted into Table A. As part of this button's onClickListener, I'd like to retrieve the Auto-Generated Primary Key value (rowId) after creation and insert it into a column in Table B along with more data.
I am using MVVM Architecture, Coroutines, etc... but cannot figure out how to do this. Below is some code and I'm happy to post more if anyone can help.
**// DAO Code**
#Dao
interface WLDAO {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertTableA(tableAEntry: TableAEntry) : Long
**// Repo Code (function only)**
suspend fun insertTableA(tableAEntry: TableAEntry): Long {
return WLDAO.insertTableA(TableAEntry)
}
**// View Model Code (function only)**
fun addToTableA(tableAEntry: TableAEntry) = viewModelScope.launch {
repo.insertTableA(TableAEntry)
}
I'd like to retrieve the Auto-Generated Primary Key value (rowId) after creation and insert it into a column in Table B along with more data.
The Long returned by your Room DAO's #Insert function is the row ID associated with that INSERT operation.
For using that value in further data operations, handle that in your repository or viewmodel. For example, your repository could have something like:
suspend fun doStuffWithBothTables(tableAEntry: TableAEntry, stuffForTableB: Whatever): Long {
val rowID = WLDAO.insertTableA(TableAEntry)
// TODO use rowID and stuffForTableB for some other DAO operations
}
Since doStuffWithBothTables() is itself a suspend fun, you can call other suspend functions normally, as if there were no threads or anything involved. So doStuffWithBothTables() can get the row ID from the first insert and then do additional work with that result.
I have a database in Android with Room from which I have deleted a column. I was doing the migration, and I saw that it was not as simple as doing a DROP of the deleted column.
Then I have seen that I have to take a series of steps, creating a provisional table that will later be the new table with the deleted column, but the problem is that this table contains a field that is a String Array that I don't know how to declare in SQL.
#Entity(tableName = "recipe_table")
data class RecipesDb(
#PrimaryKey
#ColumnInfo(name = "id")
val id: Long,
#ColumnInfo(name = "name")
val name: String,
#ColumnInfo(name = "category")
val category: List<String>,
#ColumnInfo(name = "isRecommended")
val isRecommended: Boolean,
#ColumnInfo(name = "images")
val images: List<String>,
#ColumnInfo(name = "ingredients")
val ingredients: List<String>,
#ColumnInfo(name = "date")
val date: Long,
#ColumnInfo(name = "time")
val time: Int,
#ColumnInfo(name = "difficult")
val difficult: String,
#ColumnInfo(name = "originalUrl")
val originalURL: String? = null,
#ColumnInfo(name = "author")
val author: String,
#ColumnInfo(name = "siteName")
val siteName: String
)
And now I have removed the ingredients column. I wanted to do something like this:
private val MIGRATION_3_2 = object : Migration(3,2) {
override fun migrate(database: SupportSQLiteDatabase) {
//Drop column isn't supported by SQLite, so the data must manually be moved
with(database) {
execSQL("CREATE TABLE Users_Backup (id INTEGER, name TEXT, PRIMARY KEY (id))")
execSQL("INSERT INTO Users_Backup SELECT id, name FROM Users")
execSQL("DROP TABLE Users")
execSQL("ALTER TABLE Users_Backup RENAME to Users")
}
}
}
But when I declare the new temporary table User_Backup, I have no idea how to specify that one of the fields is an Array. In the end I was able to do it with Room's AutoMigrations and creating an interface, but I would like to know how to do it this way as well.
The simple way is to compile the code (Ctrl+F9) with the changed #Entity annotated classes in the list of entities of the #Database annotation.
Then look at the generated java (visible via the Android View in Android Studio). There will be a class that is the same name as the #Database annotated class but suffixed with _Impl.
In this class there will be a method that is named createAllTables, This includes the SQL that room uses for creating the tables.
Just copy and paste the appropriate SQL and then change the table name, this will not only use the correct type but also apply the correct column constraints that Room expects.
I would suggest
Adding an execSQL("DROP TABLE IF EXISTS the_backup_table_name;") before you create a table (just in case it already exists)
And instead of using execSQL("DROP TABLE Users") to use execSQL("DROP TABLE IF EXISTS the_original_table_name")
Personally I always RENAME the table name of the original, then RENAME the new table and then finally DROP the renamed original.
I would use:-
private val MIGRATION_3_2 = object : Migration(3,2) {
override fun migrate(database: SupportSQLiteDatabase) {
//Drop column isn't supported by SQLite, so the data must manually be moved
with(database) {
execSQL("DROP TABLE IF EXISTS Users_Backup")
execSQL("CREATE TABLE IF NOT EXISTS ....) //<<<<< SEE NOTES BELOW, the SQL MUST BE CHANGED.
execSQL("INSERT INTO Users_Backup SELECT id, name FROM Users")
execSQL("ALTER TABLE Users RENAME TO Old_Users")
execSQL("ALTER TABLE Users_Backup RENAME to Users")
execSQL("DROP TABLE IF EXISTS Old_users")
}
}
}
note .... indicates that the SQL is copied from the generated java and that the table name is changed from Users to Users_Backup
The first line will drop the Uers_backup just in case it happens to exist, it's just a little less likely to fail under unusual circumstances.
Rather than dropping the Users table before the RENAME of the Users_Backup to Users. The 4th execSQL changes the name of the Users table, so should there be an issue with changing the Users_Backup table to be the Users table, then the original Uers table is available as Old_users.
When all has been complted then the original Users table, now named Old_Users is then dropped.
These are all just a little safer/secure.
I have tried many ways to reset it.
allowBackupandfullBackupOnly had been set to false.
.fallbackToDestructiveMigration()
and delete database and cache files directly.
but it doesn't work.
Simplest way is to uninstall the app, this deletes the database file(s). So rerunning starts from a brand new database.
To use .fallbackToDestructiveMigration() you have to have to invoke a Migration by increasing the version number but NOT have a Migration for the particular path. You could argue that this doesn't reset the database as the newly created database will have the higher version number.
Using clearAllTables doesn't entirely reset the database as it will not delete the system tables. Most notably, sqlite_sequence, which is a table that hold the vale of the latest rowid on a per table basis. That is if you have autogenerate = true in the #PrimaryKey annotation for an field/column that resolves to a column type affinity of INTEGER then AUTOINCREMENT is coded then the sqlite_sequence table will be created (if not already in existence) and store the latest (and therefore highest) value of the said primary key. Thus if you have inserted 100 rows (for example) into a such a table, then after a clearAllTables the 100 will still be stored in the sqlite_sequnce table.
You could also, prior to building the database, delete the database. Here's an example that allows it to be deleted when building :-
#Database(entities = [Customer::class], version = 1)
abstract class CustomerDatabase: RoomDatabase() {
abstract fun customerDao(): CustomerDao
companion object {
private var instance: CustomerDatabase?=null
fun getDatabase(context: Context, resetDatabase: Boolean): CustomerDatabase {
if (resetDatabase && instance == null) {
(context.getDatabasePath("thedatabase.db")).delete()
}
if (instance == null) {
instance = Room.databaseBuilder(context,CustomerDatabase::class.java,"thedatabase.db")
.allowMainThreadQueries()
.build()
}
return instance as CustomerDatabase
}
fun getDatabase(context: Context): CustomerDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context,CustomerDatabase::class.java,"thedatabase.db")
.allowMainThreadQueries()
.build()
}
return instance as CustomerDatabase
}
}
}
note that in addition to requesting the reset, a check is also made to ensure that an instance of the database hasn't been retrieved.
This would also be more efficient as clearAllTables still incurs processing of the underlying data and the ensuing VACUUM which can be quite resource hungry.
You can use this clear all tables
THis deletes all rows from all the tables that are registered to this database as Database.entities().
The Room database has a clearAllTables function that does clearing entities you defined with #Entity annotation. But there is a catch. It does not clear the system generated tables such as sqlite_sequence, which stores the autoincrement values.
But there are more factors to consider. Since clearAllTables itself run in a transaction, we cannot run combination of clearAllTables and clearing sqlite_sequence in a single transaction. If you try to run clearAllTables in a transaction, it will fail with an IllgalStateException.
The android SQLite database library creates an additional table called android_metadata which stores database locale, and the room database library creates another table called room_master_table, which keeps track of database integrity and helps database migrations. We should not delete or clear these two tables. Additionally, SQLite will create a sqlite_sequence table if you have defined autoincrement columns. Deleting this table is not allowed, but clearing this will reset the autoincrement values.
The room database compiler generates the clearAllTables function in the database class. Basically, it disables foreign key constraints, then starts a transaction and clears all rows in tables you have given in the database class, and after the end of the transaction, it re-enables foreign key constraints. See how this is done in the room database compiler source code room / room-compiler / src / main / kotlin / androidx / room / writer / DatabaseWriter.kt / createClearAllTables. The generated function differs based on one factor, whether you have defined foreign key constraints or not.
Based on the compiler source code, I wrote an extension function to reset the database. It will clear all tables you defined and will reset the autoincrement values.
fun RoomDatabase.resetDatabase(tables: List<String>? = null): Boolean {
val db = openHelper.writableDatabase
val tableNames = db.getTableNames()
val hasForeignKeys = db.hasForeignKeys(tables ?: tableNames.minus("sqlite_sequence"))
val supportsDeferForeignKeys = db.supportsDeferForeignKeys()
return try {
if (hasForeignKeys && !supportsDeferForeignKeys) {
// clear enforcement of foreign key constraints.
db.execSQL("PRAGMA foreign_keys = FALSE")
}
db.beginTransaction()
if (hasForeignKeys && supportsDeferForeignKeys) {
// enforce foreign key constraints after outermost transaction is committed.
db.execSQL("PRAGMA defer_foreign_keys = TRUE")
}
// clear all tables including sqlite_sequence table.
// deleting sqlite_sequence table is required to reset autoincrement value.
val tablesToClear = tables?.let {
if (tableNames.contains("sqlite_sequence")) {
it.plus("sqlite_sequence")
} else {
it
}
} ?: tableNames
for (tableName in tablesToClear) {
db.execSQL("DELETE FROM $tableName")
}
db.setTransactionSuccessful()
true
} catch (e: Exception) {
false
} finally {
db.endTransaction()
if (hasForeignKeys && !supportsDeferForeignKeys) {
// restore enforcement of foreign key constraints.
db.execSQL("PRAGMA foreign_keys = TRUE")
}
// blocks until there is no database writer and all are reading from the most recent database snapshot.
db.query("PRAGMA wal_checkpoint(FULL)").close()
if (!db.inTransaction()) {
db.execSQL("VACUUM")
}
}
}
fun SupportSQLiteDatabase.getTableNames(
exclude: List<String> = listOf("android_metadata", "room_master_table")
): List<String> {
val cursor = query("SELECT DISTINCT tbl_name FROM sqlite_master WHERE type='table'")
val tables = mutableListOf<String>()
while (cursor.moveToNext()) {
tables.add(cursor.getString(0))
}
cursor.close()
tables.removeAll(exclude)
return tables
}
fun SupportSQLiteDatabase.hasForeignKeys(tables: List<String>? = null): Boolean {
val tableNames = tables ?: getTableNames(exclude = listOf("android_metadata", "room_master_table", "sqlite_sequence"))
for (tableName in tableNames) {
val cursor = query("PRAGMA foreign_key_list($tableName)")
if (cursor.count > 0) {
cursor.close()
return true
}
cursor.close()
}
return false
}
fun SupportSQLiteDatabase.supportsDeferForeignKeys(): Boolean {
// defer_foreign_keys is only supported on API 21+
// Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
val cursor = query("PRAGMA defer_foreign_keys")
return cursor.use { it.count > 0 }
}
I'm new to android room library. I need to migrate a Not Null column to Null,
But room migration only allow ADD or RENAME in ALTER table query. How do execute a column migration query?
#Entity(tableName = "vehicle_detail")
data class VehicleDetailsEntity(
#PrimaryKey(autoGenerate = true)
val vehicleClientId: Long = 0,
val vehicleId: String,
val updatedOn: Date,
val updatedBy: String
)
I need to change table structure into
#Entity(tableName = "vehicle_detail")
data class VehicleDetailsEntity(
#PrimaryKey(autoGenerate = true)
val vehicleClientId: Long = 0,
val vehicleId: String,
val updatedOn: Date?,
val updatedBy: String?
)
java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
You need to run a migration since SQLite doesn't allow column constraint modification.
For that migration you need to create a new temp table and copy all your previous data to it, then delete the old table and rename the temp one to the needed table name.
If you have a scheme directory, you can find your exact creation SQL query which you should copy on your migration (I just figured it out from a scheme of mine and could not be 100% correct):
val MIGRATION_1_2: Migration = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
// Create the new table
database.execSQL(
"CREATE TABLE IF NOT EXISTS VehicleDetailsEntityTmp (vehicleId TEXT NOT NULL, updatedOn TEXT, updatedBy TEXT,vehicleClientId INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL )"
)
// Copy the data
database.execSQL(
"INSERT INTO VehicleDetailsEntityTmp (vehicleId, updatedOn, updatedBy ,vehicleClientId) SELECT vehicleId, updatedOn, updatedBy ,vehicleClientId FROM VehicleDetailsEntity ")
// Remove the old table
database.execSQL("DROP TABLE VehicleDetailsEntity")
// Change the table name to the correct one
database.execSQL("ALTER TABLE VehicleDetailsEntityTmp RENAME TO VehicleDetailsEntity")
}
}
I'm designing a Database that would be implemented in Android using Room, after reading the docs I found that there is no recomendations about using Int or Long as primary keys.
In some places they define entities with int primary keys:
#Entity
data class User(
#PrimaryKey var id: Int,
var firstName: String?,
var lastName: String?
)
But in other place it says that if you want to get the ID of the last row inserted "insert" method return a long.
#Dao
interface MyDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertUsers(vararg users: User)
#Insert
fun insertBothUsers(user1: User, user2: User)
#Insert
fun insertUsersAndFriends(user: User, friends: List<User>)
}
If the #Insert method receives only 1 parameter, it can return a long, which is the new rowId for the inserted item. If the parameter is an array or a collection, it should return long[] or List instead.
So, The primary keys in room should be Int or Long?; Are there best practices about choosing one type over the other?
Both of these types will map to an INTEGER in the underlying SQLite database.
For example, with a class like this:
#Entity
data class Test(#PrimaryKey val i: Int, val l: Long)
You'd get a SQLite table defined with this query:
CREATE TABLE IF NOT EXISTS `Test` (`i` INTEGER NOT NULL, `l` INTEGER NOT NULL, PRIMARY KEY(`i`))
So you can use whichever one you'll need the magnitude of in your code. If you do decide to use an Int for some reason and you run out of values, you can even change it to a Long without having to migrate your database later on.
As for this INTEGER type, as per the SQLite documentation:
The value is a signed integer, stored in 1, 2, 3, 4, 6, or 8 bytes depending on the magnitude of the value.
Both is fine. On mobile (and most of the time in general) Int should be sufficient (it will also save you 4 bytes over Long).
Why? Using an Int you could store over 2 billion records (2_000_000_000). So you could store a record of around 1/4 of all the humans living on earth. Just for comparison: Using a Long would enable you to store over 900 quadrillion records (900_000_000_000_000_000).
I would recommend to use Long since the insert function will return a Long, or long[].