android, room: is autogenerated primary key always monotonic - android

In my Android application I use Room library for persistency.
Assuming, I have an Entity defined like this:
#Entity(tableName = "my_entity")
public class MyEntity {
#ColumnInfo(name = "id")
#PrimaryKey(autoGenerate = true)
private int id;
//...
}
can I rely on the fact, that id will be increased monotonically, i.e. that for newly inserted row id will always be higher, than for all previously created rows?
I think, that it is unlikely, but I can imagine, that Room (or SQLite - I am not sure, who is responsible in this case) could e.g. try to reuse the IDs of the previously deleted rows...
As far as I can see, the official documentation does not tell anything about it PrimaryKey.AutoGenerate().

This answer is the expanded comment from JensV.
As suggested by JensV, the generated schema json file contains (among others):
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, ... <other fields>)"
So looking at the SQLite docs of AUTOINCREMENT we get, that it is guaranteed to be monotonic.
In fact, this flag serves exactly for this purpose: to ensure, that the generated value is monotonic (without this flag, the value still will be generated to be unique, but will not be necessarily monotonic). Taking into account, that Room uses the flag, it is strange, that they don't mention it in the documentation.

Related

Is PrimaryKey's autoGenerate exactly equivalent to SQLite's AUTOINCREMENT?

Is marking a primary key with #PrimaryKey(autoGenerate = true) exactly the same as if you had used PRIMARY KEY AUTOINCREMENT in an SQL statement?
Intuition tells me yes, but documentation seems to suggest no.
Room javadoc states:
Set to true to let SQLite generate the unique id.
as if setting it false will prevent SQLite from generating the key.
But SQLite documentation for AUTOINCREMENT states that SQLite always generates a currently-unique key if none is given when doing an INSERT, and that AUTOINCREMENT merely adds the additional behavior that SQLite will never allow an automatically generated key to overlap with any previously deleted row.
The SQLite documentation also recommends not using AUTOINCREMENT if it isn't needed (for performance reasons), and states that it is usually not needed. From the description, that seems to match my case. My table will be fine if a previously deleted row ID gets reused.
Is marking a primary key with #PrimaryKey(autoGenerate = true) exactly the same as if you had used PRIMARY KEY AUTOINCREMENT in an SQL statement?
Yes, as using autoGenerate=true adds the AUTOINCREMENT keyword.
But
as if setting it false will prevent SQLite from generating the key.
Is false.
If a class is:-
annotated with #Entity, and
the column/variable/member is annotated with #PrimaryKey, and
if the type resolves to an integer type
(byte .... double, primitive or Object (e.g. Double))
then the value can be generated (it is INTEGER PRIMARY KEY that makes the column a special column that can be generated as that column is then an alias of the rowid (a normally hidden column)).
AUTOINCREMENT is only applicable to aliases of the rowid (i.e. INTEGER PRIMARY KEY). It does not determine whether the value can be generated (in the absence of a value for the column or when the value is null).
What AUTOINCREMENT does is add an additional rule when generating the value. That rule being that the value MUST be higher than any ever used for that table.
There are subtle differences.
Without AUTOINCREMENT
deleting the row with the highest value, frees that value for subsequent use (and would be used to generate the value still higher than any other value that exists at that time), and
should the highest value (9223372036854775807) be reached SQLite will try to find a free lower value, and
lastly it is possible to double the range of values by using negative values.
With AUTOINCREMENT
deleting the row with the highest value does not free that value for subsequent use
should the highest value (9223372036854775807) be reached then subsequent attempts to insert with a generated value will fail with an SQLITE FULL error.
If you insert 1 row with a value of 9223372036854775807 then that's the only row that can be inserted.
negative values cannot be generated (can still be used)
an additional table is required (sqlite_sequence), which is automatically created by SQLite, that will have a row per table with AUTOINCREMENT. The highest used value is stored in the row. So whenever inserting when the value is to be generated requires the respective row to be retrieved and the value obtained, after insertion the value has to be updated. As such there are overheads associated with using AUTOINCREMENT.
Note the above is assuming that methods to circumvent SQLite's in-built handling are not circumvented (such as updating values in the sqlite_sequence table).
I would always advocate using (not using autoGenerate=true) e.g.
#PrimaryKey
Long id_column=null;
or
#PrimaryKey
var id_column: Long?=null
thus an #Insert (convenience insert) will autogenerate if no value is given for the id_column.
Demo
Consider the following two #Entity annotated classes (with and without autoGenerate=true) :-
AutoInc:-
#Entity
data class AutoInc(
#PrimaryKey(autoGenerate = true)
val id: Long?=null,
val other: String
)
NoAutoInc:-
#Entity
data class NoAutoInc(
#PrimaryKey
var id: Long?=null,
var other:String
)
Room (after compiling and looking at the generated java in the class that is the same name as the #Database annotated class) has the following in the createAllTables method/function:-
_db.execSQL("CREATE TABLE IF NOT EXISTS `AutoInc` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `other` TEXT NOT NULL)");
_db.execSQL("CREATE TABLE IF NOT EXISTS `NoAutoInc` (`id` INTEGER, `other` TEXT NOT NULL, PRIMARY KEY(`id`))");
i.e. the only difference is the AUTOINCREMENT keyword.
Then consider the following code :-
/* Typical where the id will be generated */
dao.insert(AutoInc(other = "A"))
dao.insert(AutoInc(null,other = "B"))
dao.insert(NoAutoInc(other ="A"))
dao.insert(NoAutoInc(null, other = "B"))
/* Beware */
/* Room interprets types different ways
here 0 is taken to be 0 as id is an Object
if long (Java) then 0 will be generated id
getters/setters are taken in to consideration when determining type
* */
dao.insert(AutoInc(0,other = "W"))
dao.insert(NoAutoInc(0,other ="W"))
/* Unusual */
dao.insert(AutoInc(-100,"X"))
dao.insert(NoAutoInc(-100,other ="X"))
dao.insert(AutoInc(9223372036854775807,"Y")) /* The maximum value for an id */
dao.insert(NoAutoInc(9223372036854775807,"Y")) /* The maximum value for an id */
When run then the tables (via Android Studio's App Inspection) are:-
AutInc:-
Note the Z row has not been added due to :-
E/SQLiteLog: (13) statement aborts at 4: [INSERT OR ABORT INTO `AutoInc` (`id`,`other`) VALUES (?,?)] database or disk is full
However, the disk isn't full as Disk Explorer shows:-
It's by no means full as Disk Explorer shows (and of course the subsequent step works inserting a row into the database):-
and
NoAutInc
Here the Z row has been added with a generated id based upon SQLite finding an unused value due to the highest allowable value for an id having been reached as opposed to the failure due to the disk/table full.

Is it possible to create a table in the Room without a primary key?

I have a table in MySql and I named it FAQs and inside the table, There are two columns, Question column and Answer column, I want to get the data who inside the FAQs table and store it in the offline database but I got this message An entity must have at least 1 field annotated with #PrimaryKey
The Table
#Entity(tableName = "FAQs")
public class FAQModel {
private String question, answer;
public String getQuestion() {
return question;
}
public String getAnswer() {
return answer;
}
}
Is it possible to create a table in the Room without a primary key?
Yes you can, with some difficulty, but not via room annotation, and even still it would have a primary key so really the answer is No.
It is possible (e.g. via a callback) to create a table that does not appear to have a primary key column e.g. CREATE TABLE IF NOT EXISTS the_table (question TEXT, answer TEXT). However,
it would have a primary key on the column rowid which is normally hidden.
such a table would not be able to be readily used, as you would have to avoid Room's compilation time SQL statement checking.
you could also not take direct advantage of Room's underlying table to/from object handling.
However, you make the comment
But in the android app I only get the question and answer column and I am not getting faqId because I don't want to use it inside my app.
So you could have a POJO class that excludes the faqId column e.g.
class FAQModelLessIdColumn {
String question,answer;
}
Assuming that FAQModel is the entity and thus the tablename then with the above you could have an #Query such as:-
#Query("SELECT * FROM faqmodel")
abstract List<FAQModelLessIdColumn> getFAQModelsLessTheFaqIdColumn();
However, over time, you will probably learn that the primary key column, is highly beneficial as it MUST uniquely identify a single row in the table. So from a single value you can perform CRUD operations efficiently.
As an analogy consider the table as a list where the questions have been written down in no particular order. To find a question and it's answer How many days in a leap year then you would have to search the entire list until you found the question. If however the list was split into lists according to the first character of the question and these were maintained in order then by skipping to the H list would likely make finding the question easier. Thus indexes can greatly reduce search times. So having the faqId available enables searches via the primary index and they will be faster.

get a primary key without inserting empty rows

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).

Get/Return the autogenerated id (as primary key) generated by Android Room database

For an android room interface, I want to get the autogenerated id (as primary key of a record just inserted), so that I can put it in the object without executing a select after insert, where the select might return the wrong record if there is no other unique attribute, or set of attributes for those record types.
For example, for 2 people having the same name being inserted into the same table. You might say generate a composite key to make a unique set. However that might involve the addition of new fields that are otherwise not required.
I've seen various links, including those below. Some mention that it is the row id that is returned if the insert method is declared to return integer (or long), and succeeds.
However it is my understanding that the row id cannot be assumed to be the same as the primary key. (Refer Rowid after Insert in Room).
I cannot comment on any posts because I don't have enough reputation points.
I appreciate any comments regarding what might be a good/typical approach to this problem.
These are the posts I have looked upon:
Android Room - Get the id of new inserted row with auto-generate
https://developer.android.com/training/data-storage/room/accessing-data
https://commonsware.com/AndroidArch/previews/the-dao-of-entities
Late answer just for anyone seeing this question in the future
from SQLite docs it says :
The PRIMARY KEY of a rowid table (if there is one) is usually not the
true primary key for the table, in the sense that it is not the unique
key used by the underlying B-tree storage engine. The exception to
this rule is when the rowid table declares an INTEGER PRIMARY KEY. In
the exception, the INTEGER PRIMARY KEY becomes an alias for the rowid.
therefore it's correct to assume that the rowId returned by insert query is the same as the autoincremented-primary-key

In ActiveAndroid, how do I order by primary key?

I read that activeandroid generates ids for every record inserted.
I want to retrieve records from latest created to earliest created. I know to use orderBy(COL_NAME, DESC), where COL_NAME is the primary key column, but what is that column name?
I know I can create a pseudo primary key:
#Column(name = "id", unique = true, onUniqueConflict = Column.ConflictAction.REPLACE)
public long id;
and do
orderBy("id DESC").execute()
but I feel it's wasteful when I could just use the real primary key
From the Active Android Github Side:
One important thing to note is that ActiveAndroid creates an id field for your tables. This field is an auto-incrementing primary key.
In your case you could delete your #Column and it will work fine.
Source:
https://github.com/pardom/ActiveAndroid/wiki/Creating-your-database-model

Categories

Resources