FTS4 matchinfo not working with android room database - android

I've two columns (name and description) in FTS virtual table and I want to prioritize the name column in FTS using matchinfo function, but with room in android I'm getting following error:
Error retrieving data from table.: unable to use function matchinfo in the requested context (code 1 SQLITE_ERROR)
Here is my Query:
#Query("""select workout_cache.id, workout_cache.met, workout_cache.name, workout_cache.workoutDescription, matchinfo(workout_cache_fts, 'pcs') as mi
from workout_cache join workout_cache_fts on workout_cache.id = workout_cache_fts.id
where workout_cache_fts match :text group by workout_cache.id""")
abstract suspend fun query(text: String): List<WorkoutCacheEntityWithMatchInfo>
My WorkoutCacheEntityWithMatchInfo class
class WorkoutCacheEntityWithMatchInfo(
#Embedded
val workout: WorkoutCacheEntity,
#ColumnInfo(name = "mi")
val matchInfo: ByteArray
)
UPDATE: It's working fine when I don't use join clause.

It's silly. I just removed the group by clause and it worked.
Here updated Dao:
#Query("""select workout_cache.id, workout_cache.met, workout_cache.name, workout_cache.workoutDescription, matchinfo(workout_cache_fts, 'pcs') as mi
from workout_cache join workout_cache_fts on workout_cache.id = workout_cache_fts.id
where workout_cache_fts match :text""")
abstract suspend fun query(text: String): List<WorkoutCacheEntityWithMatchInfo>

Related

Do I have a way to search objects on Room Database

I am now building an Android App with Local Database
The table structure is like following (coming from API)
#Entity
data class Person(
name: Name,
... ... ...
... ... ...
)
data class Name(
legalName: String.
common: String
)
This is sql code I have tried to person with legal name
#Query("SELECT * FROM person WHERE name.legalName = :legalName")
suspend fun getPersonByName (legalName: String): Person?
This gave me compile error as we can't search by name.legalName on Room database
In addition, we have static name list of person (only legal name) in Homepage (No ID or other reasonable fields to perform search)
DO we have proper way to search Users with legalName field?
The #Entity annotation is used by Room to determine the underlying SQLite table schema. A class so annotated is an object but the individual fields/members of the object are stored as columns in the table which are not objects.
Such columns can never be anything other than specific types being either:-
integer type values (e.g. Int, Long .... Boolean) (column type of INTEGER)
string type values (e.g. String) (column type of TEXT)
decimal/floating point type values (e.g, Float, Double) (column type REAL)
bytestream type values (e.g. ByteArray) (column type BLOB)
null (column definition must not have NOT NULL constraint)
Thus, objects are NOT stored or storable directly SQLite has no concept/understanding of objects just columns grouped into tables.
In your case the name field is a Name object and Room will require 2 Type Converters:-
One that converts the object into one of the above that can represent the object (typically a json representation of the object)
The other to convert the stored data back into the Object.
This allowing an object to be represented in a single column.
As such to query a field/member of the object you need to consider how it is represented and searched accordingly.
There will not be a name.legalName column just a name column and the representation depends upon the TypConverter as then would the search (WHERE clause).
Now consider the following based upon your code:-
#Entity
data class Person(
#PrimaryKey
var id: Long?=null,
var name: Name,
#Embedded /* Alternative */
var otherName: Name
)
data class Name(
var legalName: String,
var common: String
)
PrimaryKey added as required by Room
#Embedded as an alternative that copies the fields/members (legalName and common as fields)
Thus the name column will require TypeConverters as per a class with each of the 2 annotated twith #TypeConverter (note singular), the class where the Type Converters are defined has to be defined (see the TheDatabase class below). So :-
class TheTypeConverters {
/* Using Library as per dependency implementation 'com.google.code.gson:gson:2.10.1' */
#TypeConverter
fun convertFromNameToJSONString(name: Name): String = Gson().toJson(name)
#TypeConverter
fun convertFromJSONStringToName(jsonString: String): Name = Gson().fromJson(jsonString,Name::class.java)
}
note that there are other Gson libraries that may offer better functionality.
The entities (just the one in this case) have to be defined in the #Database annotation for the abstract class that extends RoomDatabase(). so:-
#TypeConverters(value = [TheTypeConverters::class])
#Database(entities = [Person::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getTheDAOs(): TheDAOs
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries() /* For brevity convenience of the demo */
.build()
}
return instance as TheDatabase
}
}
}
The #TypeConverters annotation (plural) in addition to defining a class or classes where the TypeConverters are, also defines the scope (#Database being the most encompassing scope).
At this stage the project can be compiled (CTRL + F9) and the annotation processing will generate some code. Importantly TheDatabase_Impl in the java(generated) The name being the same as the #Database annotated class suffixed with _Impl. This includes a method createAllTables which is the SQL used when creatin the SQLite tables. The SQL for the person table is:-
CREATE TABLE IF NOT EXISTS `Person` (
`id` INTEGER,
`name` TEXT NOT NULL,
`legalName` TEXT NOT NULL,
`common` TEXT NOT NULL, PRIMARY KEY(`id`)
)
As can be seen the id column as the primary key, the name column for the converted representation of the name object and then the legal and common columns due to the name object being #Embedded via the otherName field.
Just to finish matters with the following #Dao annotated interface (allowing some data to be added):-
#Dao
interface TheDAOs {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(person: Person): Long
#Query("SELECT * FROM person")
fun getAllPersonRows(): List<Person>
}
And with MainActivity as:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: TheDAOs
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getTheDAOs()
dao.insert(Person(null, name = Name("Frederick Bloggs","Fred Bloggs"), otherName = Name("Frederick ","Fred Bloggs")))
dao.insert(Person(null, name = Name("Jane Doe","Jane Doe"), otherName = Name("Jane Doe","Jane Doe")))
}
}
and the project run and then App Inspection used to view the actual database then:-
The name column contains the string {"common":"Fred Bloggs","legalName":"Frederick Bloggs"}
So the WHERE clause to locate all legal names that start with Fred could be
WHERE instr(name,',\"legalName\":\"Fred')
or
WHERE name LIKE '%,\"legalName\":\"Fred%'
it should be noted that both due to the search being within a column requires a full scan.
Of course that assumes that there is no name that has the common name ,"legalName":"Fred or as part of the common name or some other part of entire string. i.e. it can be hard to anticipate what results may be in the future.
For the alternative #Embedded Name object, the legalName and common columns are more easily searched, the equivalent search for legal names starting with Fred could be
WHERE legalname LIKE 'Fred%'
There is no potential whatsoever for Fred appearing elsewhere meeting the criteria. The search just on the single column/value nothing else. Indexing the column would very likely improve the efficiency.
Amending the #Dao annotated interface TheDAOs to be:-
#Dao
interface TheDAOs {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(person: Person): Long
#Query("SELECT * FROM person WHERE instr(name,',\"legalName\":\"Fred')")
fun getPersonsAccordingToLegalNameInNameObject(): List<Person>
#Query("SELECT * FROM person WHERE legalName LIKE 'Fred%'")
fun getPersonsAccordingToLegalName(): List<Person>
}
And MainActivity to be:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: TheDAOs
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getTheDAOs()
dao.insert(Person(null, name = Name("Frederick Bloggs","Fred Bloggs"), otherName = Name("Frederick ","Fred Bloggs")))
dao.insert(Person(null, name = Name("Jane Doe","Jane Doe"), otherName = Name("Jane Doe","Jane Doe")))
logPersonList(dao.getPersonsAccordingToLegalNameInNameObject(),"RUN1")
logPersonList(dao.getPersonsAccordingToLegalName(),"RUN2")
}
private fun logPersonList(personList: List<Person>, suffix: String) {
for (p in personList) {
Log.d("DBINFO_${suffix}","Person ID is ${p.id} Name.legalName is ${p.name.legalName} Name.common is ${p.name.common} LegalName is ${p.otherName.legalName} Common is ${p.otherName.common}")
}
}
}
Then running (first time after install) the log contains:-
2023-01-14 11:26:03.738 D/DBINFO_RUN1: Person ID is 1 Name.legalName is Frederick Bloggs Name.common is Fred Bloggs LegalName is Frederick Common is Fred Bloggs
2023-01-14 11:26:03.740 D/DBINFO_RUN2: Person ID is 1 Name.legalName is Frederick Bloggs Name.common is Fred Bloggs LegalName is Frederick Common is Fred Bloggs
i.e. in this limited demo the expected results either way.
Note that Name.legalName and Name.common is not how the data is accessed, it is just text used to easily distinguish then similar values.

SQLiteException: no such table: database-notes (code 1 SQLITE_ERROR)

I'm trying to migrate a new database version. The only thing changed is an added column. I always get the following error:
android.database.sqlite.SQLiteException: no such table: database-notes (code 1 SQLITE_ERROR): , while compiling: ALTER TABLE 'database-notes' ADD COLUMN image TEXT
I don't understand why I get this exception, because my table is named database-notes as written in the .build() call.
This is my database class:
#Database(
version = 2,
entities = [Note::class],
exportSchema = true)
abstract class AppDatabase : RoomDatabase() {
abstract fun noteDao(): NoteDAO
companion object {
fun build(context: Context) = Room.databaseBuilder(context, AppDatabase::class.java, "database-notes")
.addMigrations(MIGRATION_1_2).build()
}
}
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE 'database-notes' ADD COLUMN image TEXT")
}
}
The database name was exactly the same in the previous version. I copied it to rule out typos.
What have I overlooked here? Thank you in advance!
because my table is named database-notes
It would appear not, due to the failure, and is probably a misunderstanding of the difference between the database name and table name(s).
A Database can have multiple tables. The database name is the name of the file itself (the container of the components such as tables, indexes, views and triggers).
In your code database-notes, as per the 3rd parameter to the Room.databaseBuilder is the name of the database (the file).
With Room the table names are derived from the classes that are both annotated with #Entity and provided, via the entities parameter of the #Database annotation. In your case the Note class.
The name of the table will be Note unless you use the tableName = parameter of the #Entity annotation to provide another name.
Example
If the following were your Note class :-
#Entity // No tableName parameter so the table name is the class name
data class Note(
#PrimaryKey
var noteId: Long? = null,
var noteText: String,
var image: String
)
Then the table name would be Note (the name of the class)
If the Note class were :-
#Entity(tableName = "notes") //<<<<< specifically names the table
data class Note(
#PrimaryKey
var noteId: Long? = null,
var noteText: String,
var image: String
)
The the table name would be notes (as specified by the tableName = parameter of the #Entity annotation).

how to query a room database by row android kotlin

I am new to Room and it's throwing me through a loop. I am trying to take a specific row of a specific data class in my Room database and compile into a list and turn this list into a String so I can do stuff with it. I can sort of do this by printing the contents of the Room database table to the console, but that's it.
I'll show you my code that I have
this is my data class.
code.kt
#Parcelize
#Entity(tableName = "code_table")
data class code(
#PrimaryKey(autoGenerate = true)
val id: Int,
val code: String //this is the item I want to isolate
): Parcelable
a snippet of my ViewModel.kt that I use.
val readAllData: LiveData<List<code>>
private val repository: respository
init {
val Dao = database.getDatabase(application).dao()
repository = respository(Dao)
readAllData = repository.readAllData
}
a snippet of my repository.kt that I use.
val readAllData: LiveData<List<code>> = dao.readAllData()
a snippet of my Dao.kt that I use.
#Query("SELECT * FROM code_table ORDER BY id ASC")
fun readAllData(): LiveData<List<code>>
How I read the Room database table
private fun dbread(){
mUserViewModel = ViewModelProvider(this).get(ViewModel::class.java)
mUserViewModel.readAllData.observe(viewLifecycleOwner, Observer { user ->
var abc = user
println(abc)
})
}
this is what it outputs
I/System.out: [code(id=2, code=textA), code(id=3, code=textB), code(id=4, code=textC)]
I am not hell-bent on only querying the code row but I need to aggregate all the data in the code row into a string or JSON array. so, in this case, that would be the textA, textB and textC (I'm worried I haven't made myself clear)
I have also tried doing the following in the Dao
#Query("SELECT code FROM code_table")
fun readdb(): LiveData<List<code>>//I've tried this with lot different types within the Parentheses
this makes the build fail: it says the following
:app:kaptDebugKotlin 1 error
java.lang.reflect.InvocationTargetException (no error message)
I would guesstimate that this error is because the SQL syntax is off but I don't think it is.
I have also tried messing around with the ViewModel and repository to see if I can get them to only output just the code row but to no avail. I'm surprised I'm the first to post about this.
thank you for your time.
You can obtain you desidered result just by using SQL.
In Android (and with Room) you're using a SQLite database, so the most important thing is writing some SQL that is compliant with SQLite.
Then you can select the concatenation of all the values in your table, just by asking to the database to do it. You can achieve it with this:
#Query("SELECT GROUP_CONCAT(code) FROM code_table")
fun readConcatenatedCode(): LiveData<String>
The returned value will be a LiveData of String, and the String will contain all the values, concatenated.

Android room inserting with a query

I know inserting data into SQlite room library can be done through #Insert annotation, but I (out of curiosity) tried inserting values through SQL statement but Android studio showed me error stating column names can't be resolved. Is it a way of forcing developer to use #Insert annotation or if I am doing something wrong here? Thanks!
Please review following screenshot -
If you omit the column list that is being highlighted and thus provide values for all columns e.g. :-
#Query("INSERT INTO TestTable VALUES(null,:a,:b)")
List<TestTable> getall(String a, String b, String columna, String columnb);
Android Studio doesn't complain but then you get a compiler error of :-
error: INSERT query type is not supported yet. You can use:SELECT, DELETE, UPDATE
So perhaps better sooner than later.
#Entity
data class ModulesRoom(
#PrimaryKey(autoGenerate = false)
var id: Int = 0,
var nav: String = "",
var name: String = "",
var imageurl: String = ""
)
for List Object
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveEmployees(modules: List<ModulesRoom>)
for single obbjet
#Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveEmployees(modules: ModulesRoom)
Ussage
val mModules = Modules()
mModules.id = values
mModules.nav = values
saveEmployees(mModules)
What I am doing is calling getOpenHelper().getWritableDatabase() on the RoomDatabase.
Then you get the SupportSQLiteDatabase object that is essentially the non-Room way.
From that you can call execSQL() or use insert() with table name and ContentValues.

How to implement SUM(), DAY(), MONTH() and YEAR() from MySQL to ROOM

I have this code in SQL:
SELECT SUM(number), DAY(date) FROM conting GROUP BY DAY(date) ORDER BY DAY(date) ASC
then I need to convert in room:
#Query("SELECT SUM(number), DAY(date) FROM Records GROUP BY DAY(date) ORDER BY DAY(date) ASC") fun getResumeData(): List<Graph>
Graph.kt
data class Graph (
#ColumnInfo(name = "DAY(date)") var index: Int,
#ColumnInfo(name = "SUM(number)") var value: Int
)
but it returns the error no such function: DAY how can I fix it?
note:
Android (and Room) uses SQLite, not MySQL, so you will need to structure your queries with functions that SQLite has available. You can do something similar with SQLite's date() and datetime() functions, see https://www.sqlite.org/lang_datefunc.html for more information.

Categories

Resources