I want my table names to be specific as I am migrating from Native Android Room database to Flutter using Drift library for database.
Room take a table name i.e. class name and creates a table:
e.g. the following will create table 'User' with field names as 'firstName' and lastName' in native Android using Room database support
#Entity
data class User(
#PrimaryKey val id: Int,
val firstName: String?,
val lastName: String?
)
When I try to replicate this in Flutter, I write:
#DataClassName('User') // To avoid drift changing it to 'user'
data class User(
TextColumn get firstName => text().named('firstName')();
TextColumn get lastName => text().named('lastName')();
// The above '.named()' makes sure drift does not change names to 'first_name' & 'last_name'
)
By doing above I am making sure the databases match exactly! (please correct me if I am wrong here)
Now when I try to replicate the migration statements in drift I am writing:
...
if (from < 4) await m.addColumn(Note, Note.path);
if (from < 5) await m.addColumn(Profile, Profile.squarePicture);
...
As you can see that I am writing note as a 'Note' because I have defined my table name as 'Note' and not 'note' same goes with profile! But the code is highlighting the following error:
The argument type 'Type' can't be assigned to the parameter type
'TableInfo<Table, dynamic>'
When I change table names to small alphabets i.e. 'n' and 'p' the error goes away but I do not understand WHY? My table names are explicitly with capital letters in the start.
I do not want to change anything in the database when my users will upgrade their app which was previously made in Native Android using Kotlin and Room database to the Flutter version. I want them to have a seamless experience with no loss of data!
The #DataClassName() annotation is used to change the name of the class which is generated by the drift. This generated class is what the drift will use when querying the database and getting the result.
By default, the generated class name will be the same as the class name except the drift will strip any last 's' character. For example, if your class name is "Users" the drift will generate a class with the name "User" with the 's' at the end stripped.
Coming to your question #DataClassName() has nothing to do with the table name in your database or any migration strategy. If you want to change the table name you can do something like this,
class User extends Table {
///This property will change the table name.
String get tableName => 'User';
...
}
Official Documentation - Dart table Names
Drift also generates a class that has an identical name to our table class. In your case, there will be a class generated $User which extends our table class, and a mixin TableInfo. This class's object is automatically created by the drift in the database generated code.
So drift will generate a variable named user which will be object of class $User. This variable needs to be used inside migration stregeies so that drift can successfully migrate a table.
Related
so in my application, when the user clicks add on something I should create an Entity A to carry the values which the user provides, this Entity A have an autoincremented-primary-key, also along the way of constructing Entity A there're another Entities that carry the key of Entity A as a foreign key as well as part of their composite key.
so my problem is that room prevents me from creating the other entities without providing the key of Entity A in their constructor annotating it with #NonNull as it's part of their composite key and it can't null.
now I don't know how to approach this problem,
was it a mistake from the beginning to work with my entities as custom classes along my application and I should separate entities from custom classes ? (though they would be having the same fields)
whenever the user clicks the add option, should I just push/insert an empty entity/row/tuple to get an autogenerated key so I could create the entities along the way?
please tell me your thoughts about this as it's my first time to work with a database embedded in an application so I don't know what should regarding it.
this Entity A have an autoincremented-primary-key
AUTOINCREMENT, in Room autoGenerate = true as part of the #PrimaryKey annotation, does not actually result in auto generation. Rather it is a constraint rule that forces the next automatically generated rowid to be greater than any that exist or have existed (for that table).
Without AUTOINCREMENT if the column is INTEGER PRIMARY KEY (or implied via a table level definition of such a column as PRIMARY KEY) then the column is made an alias of the always existing rowid (except for the rarely used WITHOUT ROWID table (unable to do so in Room via entities, there is no annotation for such a table)).
The rowid is always unique and always automatically generated and will typically be greater (typically 1 greater) anyway. It is only (unless purposefully manipulated) when the max (9223372036854775807th rowid) is reached when AUTOINCREMENT comes into play. In which case with AUTOINCREMENT you get an SQLITE_FULL exception, without SQLITE will try to find a lower unused/free rowid.
Due to the unnecessary overheads see I personally never use autoGenerate = true.
What AUTOINCREMENT does, is have a system table sqlite_sequence with a row per table that has AUTOINCREMENT where it stores/maintains the highest allocated rowid for the table. With AUTOINCREMENT it then uses the higher of the sqlite_sequence value and the highest rowid value and then adds 1 (without it just uses the highest rowid and adds 1).
was it a mistake from the beginning to work with my entities as custom classes along my application and I should separate entities from custom classes ?
There should be no need to have separate classes an Entity can be used as a stand-alone class, the room annotations being ignored.
whenever the user clicks the add option, should I just push/insert an empty entity/row/tuple to get an autogenerated key so I could create the entities along the way?
It is very easy to get the generated key and #Insert for a single insert returns the key (id) as a long so the #Dao #Insert abstract fun(entityA: EntityA): Long (long in Java) returns the key or -1 if the insert did not insert a row.
If you use the list/varargs for of #Insert then it returns a and array of Longs, each element returning the key (id) of the insert or -1.
So considering what I believe is your issue consider the following 3 Entities (not if Java then use Long rather than long for the key as primitives can't be null).
#Entity
data class EntityA(
#PrimaryKey
var entityAKey: Long? = null,
var otherAdata: String
)
No AUTOINCREMENT via autoGenerate = true.
No #NOTNULL annotations
then :-
#Entity
data class EntityB(
#PrimaryKey
var entityBKey: Long?= null,
var otherBdata: String
)
and :-
#Entity(
primaryKeys = ["entityBRef","entityARef","otherPartOfPrimaryKey"]
)
data class EntityC(
var entityBRef: Long,
var entityARef: Long,
var otherPartOfPrimaryKey: Long,
var otherCData: String
)
add some Dao's :-
#Insert
abstract fun insert(entityA: EntityA): Long
#Insert
abstract fun insert(entityB: EntityB): Long
#Insert
abstract fun insert(entityC: EntityC): Long
NOTE the Long return value (always Long doesn't compile if Int) and generated keys should always be long anyway as they can exceed what an Int can hold.
Finally consider :-
db = TheDatabase.getInstance(this)
dao = db.getDao()
var myfirstA = EntityA(otherAdata = "First")
var myfirstB = EntityB(otherBdata = "The first B")
var a1 = dao.insert(myfirstA)
var b1 = dao.insert(myfirstB)
dao.insert(EntityC(b1,a1,100L,"The first C using id's from the first A and the first B"))
run on the main thread via allowMainThreadQueries()
And the database :-
You could even do :-
dao.insert(EntityC(
dao.insert(EntityB(otherBdata = "Second B")),
dao.insert(EntityA(otherAdata = "A's second")),
200,
"blah")
)
obviously this would likely be of limited use as you'd need to know the values up front.
And the result is :-
Database snapshots obtained via Android studio's App Inspector (formerly Database Inspector).
You could also do/use :-
var my3rdA = EntityA(otherAdata = "3rd")
my3rdA.entityAKey = dao.insert(my3rdA)
Of course whenever you extract from the database then the object will include the key (id) (unless you purposefully chose not to).
I know it's not the best practice to store an image in DB directly and we should store a path instead. But in my case this is a must.
I am able to store list of images perfectly fine defined as:
#ColumnInfo(name = "picture")
var picture: ByteArray? = null
I came across solutions that suggests using (typeAffinity = ColumnInfo.BLOB). So I changed my column to:
#ColumnInfo(name = "picture", typeAffinity = ColumnInfo.BLOB)
var picture: ByteArray? = null
I haven't noticed anything significant in performance. I wonder what are the possible advantages of using typeAffinity or disadvantages of not using it?
It maybe worth mentioning my images are always under 1 megabytes.
There is no real advantage/disadvantage, certainly not at run time perhaps marginally at compile time.
That is all that using typeAffinity=? does is override the typeAffinity being determined by the type of the field/column of the variable.
As you have var picture: ByteArray this would be resolved to a column type of BLOB anyway.
If you wished you could compile with both and see the resultant SQL used by looking at the generated java.
Perhaps consider the following Entity that uses both:-
#Entity(tableName = "user")
data class UserEntity(
#PrimaryKey
val userid: Long = 0,
var picture1: ByteArray? = null,
#ColumnInfo(typeAffinity = ColumnInfo.BLOB)
var pitcure2: ByteArray? = null
)
In the generated Java (use Android view as highlihted) then in the #Database class (UserDatabase in the example) suffixed by _Impl (so UserDatabase_Impl in the example) the following is a screen shot of the generated Java :-
Android highlighted indicates where to select the Android view.
The highlight in the code explorer shows the respective code (UserDatabase_Impl) in the expanded java (generated) directory
The createAllTables method is the method used to create the table(s)
room_master_table is a room specific table used for verification of an existing table with the schema to detect if there are differences.
The code (SQL) generated for the creation of the table is :-
_db.execSQL("CREATE TABLE IF NOT EXISTS `user` (`userid` INTEGER NOT NULL, `picture1` BLOB, `pitcure2` BLOB, PRIMARY KEY(`userid`))");
i.e. the definition of the columns picture1 and picture2 are identical bar the column names.
NOTE please heed the WARNING in regards to not changing the generated code.
I have a SQLite DB which I cannot change or migrate and need to map a NUMERIC column to my Kotlin class using Room. What do I need to use as a data type of the field in Kotlin class? When using Boolean, Integer, Double, String, it fails with similar error message:
Expected:
TableInfo{name='calendars', columns={monday=Column{name='monday', type='REAL', affinity='4', ...}
Found:
TableInfo{name='calendars', columns={monday=Column{name='monday', type='NUMERIC', affinity='
EDIT: I suppose it's possible to update the database and change column type. But I still want to know if there is a way to map NUMERIC with Room.
If you have an existing database, open it with SQLite Browser, change the datatype there by selecting the table and editing it (changing the type to REAL). This preserves the data and afterwards you can use the database again.
Currently, Room seems to have problems mapping these datatypes, so you have to explicitly use REAL in your database.
Also it might be a good idea to annotate your field in your data object respectively.
#ColumnInfo(typeAffinity = ColumnInfo.REAL)
public double yourField;
I am trying to implement Realm into a project of mine. I keep running into this
java.lang.IllegalArgumentException: Invalid query: field '_id' not found in class 'messages'.
Note: MESSAGE.COLUMN_ID = "_id"
Query
The Logcat prints true for hasField.
Model
The same thing works for other tables fine.
Typed Realm queries use the field name as the argument, and are mapped over internally to the internal schema field name.
Therefore, your query ought to look like this:
MessageDto messageDto = realmInstance.where(MessageDto.class)
.equalTo("mLocalId", query.getMessageId())
.findFirst();
While playing with the Room Persistence Library I came to know that there is no methodology to set a data class field with NOT NULL and also UNIQUE constraints. whether SQLite supports those constraints. Isn't it a problem to migrate old database where those constraints are used? Can anyone give a suggestion for this issue?
I came to know that there is no methodology to set a data class field with NOT NULL and also UNIQUE constraints
A #NonNull annotation on an #Entity field will cause that field's column to have NOT NULL applied to it.
unique=true on an #Index will enforce a uniqueness constraint (e.g., #Entity(indices={#Index(value="something", unique=true)}). However, you are correct that a plain UNIQUE constraint on a column, other than via an index, is not supported.
Isn't it a problem to migrate old database where those constraints are used?
Room is not designed to support existing database structures, particularly in the now-current alpha state. Over time, I would expect Room to support a higher percentage of SQLite features, though I will be stunned if it ever reaches 100%.
Complementary answer about NOT NULL for those using Kotlin:
please note that marking a type as non optional will automatically make it not null (and an optional type will not do that).
You can check it in the schema generated by room with #Database(exportSchema = true) on your database.
For example I have something like that:
#Entity(tableName = "messages")
data class Message (
#PrimaryKey
val messageId: UUID = UUID.randomUUID(),
val date: Date = Date(),
val receivedDate: Date? = null
)
And in the generated schema I can read:
"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`messageId` TEXT NOT NULL, `date` INTEGER NOT NULL, `receivedDate` INTEGER, PRIMARY KEY(`messageId`))"
(Note: the Date type is here an Int and the UUID a string due to converters I use elsewhere)
If you have multiple item that is to be marked unique & based on that you want to insert in db then you can use composite primary key.
For Not null, Room has provided "#NonNull" annotation that is added over the field that cannot be null.
In the below mentioned eg. roll number is unique in each class but 2 different class can have same roll number. So we can use class & rollNumber as composite primary key & insert in db uniquely.
Example:
#Entity(primaryKeys = {"rollNumber", "class"})
class Student {
#NonNull
private int rollNumber;
private String firstName;
private String lastName;
private int class;
}
for a null able field you can use wrapper primitive type java. for example use Integer instance int in your Room Table.
as in wrapper primitive type java this can bee null but primitive type class cant bee null. and in generation of this SQL code for primitive field that use notNull=true but when use Integer in generayion SQL code use notNull=false