kotlin android Realm DB create a foreign key (Linking Objects) - android

I have 2 tables student and a teacher.
The student table has 3 fields, name, roll_no, subjects.
Roll_no being primary key.
Another table named marks with 4 fields subject ID, subject name, subject score and roll_no
roll_no field will be a foreign key.
How to create a relation like the marks table refers as a foreign key to student table's roll_no.

Short answer: Realm doesn't have foreign keys.
Long answer: Realm wants you to think about your data as objects, instead of tables and links (https://realm.io/docs/kotlin/latest/#relationships).
Assuming roll_no is kind of like a student_id - you could model your data like this:
open class Student(
#PrimaryKey
var id: String = "",
var markedSubjects: RealmList<MarkedSubject> = RealmList()
): RealmObject()
open class MarkedSubject(
var subject: Subject? = null,
var mark: Int? = null
): RealmObject()

Related

Migration in Room with an Android Array of Strings

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.

SQLDelight and data classes with one to many relations in Kotlin

I am trying to create a small app using Kotlin Multiplatform where i can save book titles and their authors but i'm struggling with the data classes and how to map them together so that i get the author with all of their books and the publish date.
CREATE TABLE book(
id INTEGER NOT NULL,
name TEXT NOT NULL,
publishDate INTEGER NOT NULL,
authorId INTEGER NOT NULL
)
CREATE TABLE author(
id INTEGER NOT NULL,
name TEXT NOT NULL
)
Here are my data classes:
#Serializable
data class bookEntity(
id: Int,
name: String,
authorId: Int
)
#Serializable
data class authorEntity(
id: Int,
authorName: String
books: List<bookEntity>
)
and my Query:
selectAuthors:
SELECT * FROM author
JOIN book ON book.authorId = author.id
WHERE book.authorId = author.id
i tried the following mapping but it didn't work:
private fun mapAuthor(
id: Int,
authorName: String,
bookId: String,
name: String,
publishDate: Long
): Author(
return Author (
id = id,
authorName = authorName,
book = List<BookEntity>(
id = bookId,
name = name,
publishDate = publishDate
)
)
)
How can i work with lists like this?
Every help is appreciated!
The ON clause of the JOIN is the condition which links both tables. You don't need to repeat the condition in the WHEN clause. Use WHEN to further narrow down the query, for instance if you're searching for a specific author name.
SELECT * FROM author
JOIN book ON book.authorId = author.id
WHERE author.name LIKE 'John%';
If you want to query all authors, just remove the WHEN completely.
Also, you don't need to create data classes and do the mapping yourself. SQLDelight already creates data classes for your queries.

Saving object inside another object - Room Android

I have a object saved in my room database, and I need to add one more object within it.
#Entity
data class GameProfileEntity(
#PrimaryKey val id: Long,
val level: String,
val monthlyGoalsEntity: MonthlyGoalsEntity)
And I'm don't know how to put this on room migration.
This is my migration method:
execSQL("CREATE TABLE GameProfileEntity_backup(id INTEGER NOT NULL, level TEXT NOT NULL, monthlyGoals_goalsEntity TEXT NOT NULL DEFAULT 0, synced INTEGER NOT NULL DEFAULT 0, PRIMARY KEY (id));")
execSQL("INSERT INTO GameProfileEntity_backup (id, level) SELECT id, level FROM GameProfileEntity")
execSQL("DROP TABLE GameProfileEntity")
execSQL("ALTER TABLE GameProfileEntity_backup RENAME TO GameProfileEntity")
I don't have sql expertise, only some crud stuffs, should I create a table for MonthlyGoalsEntity and use join to save inside GameProfileEntity?

Room query with column alias returning null

I've been struggling with this and I haven't found a solution.
Let's say I have this sample entity:
#Entity (tableName="test")
public class Test{
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "id")
private long id;
#ColumnInfo(name = "field1")
#NonNull
private double field1;
#Ignore
#ColumnInfo(name = "queryField")
private transient String queryField;
}
So basically my table has id and field1 while queryField is not a column table but it's used for instance on screens to show extra details from another table.
Then in my DAO
...
#Query("Select a.id, a.field1, b.column as queryField
"from test a inner join some_table on..." +
"where a.id=:someId" +
"order by i.name ASC")
LiveData<List<Test>> get(long someId);
....
So running someting like this, and the query result while it contains test.id and test.field1 info it does not fill the queryField property which remains null.
#Entity does not allow this kind of queries? Do I need to generate another class to add the new column? I'd like to mix Entities and Pojos in the project to keep things simple.

Could not migrate to Room

I decided to use Room in my current application.
Find out that there are no type for one column in current schema and Room produce IllegalStateException on migration.
java.lang.IllegalStateException: Migration didn't properly handle item.
Expected:
TableInfo{name='item', columns={optional_modifiers=Column{a_type=Column{name='a_type', type='BLOB', notNull=false, primaryKeyPosition=0}...}
Found:
TableInfo{name='item', columns={optional_modifiers=Column{a_type=Column{name='a_type', type='', notNull=false, primaryKeyPosition=0}...}
Sql script of the table creation:
"create table item ("
" id text primary key," +
" a_type, "
//...
")
Entity class:
#Entity(tableName = "item")
data class Item(
#PrimaryKey
val id: String?,
val a_type: String? // actually I used several types, but none of them is worked
)
Are there any way to resolve this issue?
Sqlite doesn't allow to edit schema. So the only possible way is create the new table with correct columns info, move data to it, delete old table.
Here is the example of the code that I used
database?.execSQL("create table table_name_tmp ( " +
" id text not null primary key"
")")
database?.execSQL("""
insert into table_name_tmp (id)
select id
from table_name
""")
database?.execSQL("drop table table_name")
database?.execSQL("alter table table_name_tmp rename to table_name")
Make modification to your class annotated with #Entity as below.
#Entity(tableName = "item")
data class Item(
#PrimaryKey
val id: String?,
#ColumnInfo(typeAffinity = ColumnInfo.BLOB)
val a_type: String?
)
This will work, because from error it is visible that your old db schema is having name='a_type', type='BLOB', so you need to add #ColumnInfo(typeAffinity = ColumnInfo.BLOB) this will instruct room to consider data type of "a_type" as BLOB.
Recently it comes to my attention that #ColumnInfo also provide "UNDEFINED" as a type Affinity so with that you can now declare your table field without any type.Documentation
please try updated changes in your project.
#Entity(tableName = "item")
data class Item(
#PrimaryKey
val id: String?,
#ColumnInfo(typeAffinity = ColumnInfo.UNDEFINED)
val a_type: String?
)

Categories

Resources