0

I'm new to SQLite, I'm trying to create an application where a user can create tasks and attach reminders to them. I'm using SQLite database to save these items. Everything works perfectly, now i want to implement a full text search feature, i have read on SQLite documentations that using FTS4 VIRTUAL TABLE is a way better than the ordinary one.

  • so in order to keep the virtual table synced i had to use triggers. but an error occurs after calling execSQL("//*Trigger code*//")

here are my triggers (using them in the same order as mentioned in the documentations) :

object SQLiteTriggerUtils {

    fun getBeforeDeleteTrigger(mainTable : String,
                               ftsTable : String,
                               rowId : Int?) : String {

        return "CREATE TRIGGER table_bd" +
                " BEFORE DELETE ON $mainTable" +
                " BEGIN DELETE FROM $ftsTable" +
                " WHERE docid=$rowId END;"
    }

    fun getBeforeUpdateTrigger(mainTable: String,
                               ftsTable: String,
                               rowId: Int?) : String {

        return "CREATE TRIGGER table_bu" +
                " BEFORE UPDATE ON $mainTable" +
                " BEGIN DELETE FROM $ftsTable" +
                " WHERE docid=$rowId END;"
    }

    fun getAfterUpdateTrigger(
        mainTable: String,
        ftsTable: String,
        rowId: Int?,
        updatedField: String,
        updatedValue: String?
    ) : String {

        return "CREATE TRIGGER table_au" +
                " AFTER UPDATE ON $mainTable" +
                " BEGIN INSERT INTO $ftsTable(docid, $updatedField)" +
                " VALUES($rowId, $updatedValue) END;"
    }

    fun getAfterInsertTrigger(
        mainTable: String,
        ftsTable: String,
        rowId: Int?,
        updatedField: String,
        updatedValue: String?
    ) : String {

        return "CREATE TRIGGER table_ai" +
                " AFTER INSERT ON $mainTable" +
                " BEGIN INSERT INTO $ftsTable(docid, $updatedField)" +
                " VALUES($rowId, $updatedValue) END;"
    }
}

here's my database onCreate() method :

override fun onCreate(db: SQLiteDatabase) {
        val CREATION_TABLE = ("CREATE TABLE $TABLE_NAME ( "
                + "$KEY_ID INTEGER PRIMARY KEY AUTOINCREMENT, "
                + "$KEY_LABEL TEXT, "
                + "$KEY_DESCRIPTION TEXT, "
                + "$KEY_IMPORTANCE INTEGER,"
                + "$KEY_LOGO INTEGER,"
                + "$KEY_TO_DO_DATE TEXT,"
                + "$KEY_CREATION_DATE TEXT)")
        val FTS_CREATION_TABLE = ("CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts4 (content='$TABLE_NAME', $KEY_LABEL)")
        db.execSQL(CREATION_TABLE)
        db.execSQL(FTS_CREATION_TABLE)
    } 

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
        db.execSQL("DROP TABLE IF EXISTS $FTS_TABLE_NAME")
        onCreate(db)
    }

example excuting triggers onDeleteItem() method :

override fun deleteItem(itemId: Int): Boolean {
        var success : Boolean
        writableDatabase.apply {
            execSQL(SQLiteTriggerUtils.getBeforeDeleteTrigger(TABLE_NAME, FTS_TABLE_NAME, itemId))
            success = delete(TABLE_NAME, "id = ?", arrayOf(itemId.toString())) > 0
            close()
        }
        return success
    }

returned error :

android.database.sqlite.SQLiteException: near "END": syntax error (Sqlite code 1): , while compiling: CREATE TRIGGER table_bd BEFORE DELETE ON todo_tasks BEGIN DELETE FROM fts_todo_tasks WHERE docid=1 END;, (OS error - 2:No such file or directory)
Tamim Attafi
  • 2,253
  • 2
  • 17
  • 34

1 Answers1

2

I think your issues after correcting the syntax error of following the triggered action(s) that is coded between BEGIN and END with a semi-colon as per :-

enter image description here

May have been that you tried adding the triggers before you knew the value (might not be so would depend upon the code).

Is that a trigger is an entity, like a view,table,index etc that forms part of the schema and therefore requires a unique name. So CREATE TRIGGER the_trigger_name ...... requires that the_trigger_name is unique. It appears that you may be trying to create the same trigger every time the action that would invoke the trigger is about to be actioned and would then fail as the trigger already exists.

You could use CREATE TRIGGER IF NOT EXISTS the_trigger_name ......, however that existing trigger would then be used.

Therefore you would probably want to DROP TRIGGER the_trigger_name before creating the trigger.

However!

Saying that the expected use of triggers is to perform a similar action automatically when the event (UPDATE, DELETE or INSERT) happens (actually immediately before or after). As such the trigger has access to the columns of the row which causes the trigger to be actioned (triggering action).

If the triggering action is INSERT then the columns of the row being inserted can be referenced using new.column and used in the triggered action (the action(s) specified between the BEGIN and END).

If the triggering action is DELETE then old.column can be used to referenced the columns of the row being deleted.

If the triggering action is UPDATE then both new.column and old.column can be referenced.

  • where .column is replaced by the respective column.

As such when the documentation says :-

Instead of writing separately to the full-text index and the content table, some users may wish to use database triggers to keep the full-text index up to date with respect to the set of documents stored in the content table. For example, using the tables from earlier examples:

CREATE TRIGGER t2_bu BEFORE UPDATE ON t2 BEGIN
  DELETE FROM t3 WHERE docid=old.rowid;
END;
CREATE TRIGGER t2_bd BEFORE DELETE ON t2 BEGIN
  DELETE FROM t3 WHERE docid=old.rowid;
END;

CREATE TRIGGER t2_au AFTER UPDATE ON t2 BEGIN
  INSERT INTO t3(docid, b, c) VALUES(new.rowid, new.b, new.c);
END;
CREATE TRIGGER t2_ai AFTER INSERT ON t2 BEGIN
  INSERT INTO t3(docid, b, c) VALUES(new.rowid, new.b, new.c);
END;

You will see that it uses old.docid and new.rowid

Thus old.docid will be the docid of the row being deleted updated and new.rowid will be the rowid of the row being inserted into the $TABLE_NAME. As such each trigger only needs to be defined once as it is generic.

As such I believe that you could use :-

val TRG_BD = "trigger_bd" //<<<<<<<<<< ADDED >>>>>>>>>>
val TRG_BU = "trigger_bu" //<<<<<<<<<< ADDED >>>>>>>>>>
val TRG_AU = "trigger_au" //<<<<<<<<<< ADDED >>>>>>>>>>
val TRG_AI = "trigger_ai" //<<<<<<<<<< ADDED >>>>>>>>>>

override fun onCreate(db: SQLiteDatabase) {
    val CREATION_TABLE = ("CREATE TABLE $TABLE_NAME ( "
            + "$KEY_ID INTEGER PRIMARY KEY AUTOINCREMENT, "
            + "$KEY_LABEL TEXT, "
            + "$KEY_DESCRIPTION TEXT, "
            + "$KEY_IMPORTANCE INTEGER,"
            + "$KEY_LOGO INTEGER,"
            + "$KEY_TO_DO_DATE TEXT,"
            + "$KEY_CREATION_DATE TEXT)")
    val FTS_CREATION_TABLE = ("CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts4 (content='$TABLE_NAME', $KEY_LABEL)")

    //<<<<<<<<<< ADDED FOLLOWING LINES  >>>>>>>>
    val BD_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_BD " +
            "BEFORE DELETE ON $TABLE_NAME " +
            "BEGIN " +
            "DELETE FROM $FTS_TABLE_NAME " +
            "WHERE docid=old.rowid; " +
            "END;")
    val BU_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_BU " +
            "BEFORE UPDATE ON $TABLE_NAME " +
            "BEGIN " +
            "DELETE FROM $FTS_TABLE_NAME " +
            "WHERE docid=old.rowid; " +
            "END;")
    val AU_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_AU " +
            "AFTER UPDATE ON $TABLE_NAME " +
            "BEGIN " +
            "INSERT INTO $FTS_TABLE_NAME (docid, $KEY_LABEL) VALUES(new.$KEY_ID,new.$KEY_LABEL); " + //<<<<<<<< not sure $KEY_LABEL is correct column
            "END;")
    val AI_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_AI " +
            "AFTER INSERT ON $TABLE_NAME " +
            "BEGIN " +
            "INSERT INTO $FTS_TABLE_NAME (docid, $KEY_LABEL) VALUES(new.$KEY_ID,new.$KEY_LABEL); " +
            "END;")
    //<<<<<<<<<< END OF ADDED LINES >>>>>>>>


    db.execSQL(CREATION_TABLE)
    db.execSQL(FTS_CREATION_TABLE)
    db.execSQL(BD_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
    db.execSQL(BU_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
    db.execSQL(AU_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
    db.execSQL(AI_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
}

override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
    db.execSQL("DROP TABLE IF EXISTS $FTS_TABLE_NAME")
    db.execSQL("DROP TRIGGER IF EXISTS $TRG_BD") //<<<<<<<<<< ADDDED >>>>>>>>
    db.execSQL("DROP TRIGGER IF EXISTS $TRG_BU") //<<<<<<<<<< ADDDED >>>>>>>>
    db.execSQL("DROP TRIGGER IF EXISTS $TRG_AU") //<<<<<<<<<< ADDDED >>>>>>>>
    db.execSQL("DROP TRIGGER If EXISTS $TRG_AI") //<<<<<<<<<< ADDDED >>>>>>>>
    onCreate(db)
}

The above would replace (i.e. the following would then not be needed) :-

object SQLiteTriggerUtils {

    fun getBeforeDeleteTrigger(mainTable : String,
                               ftsTable : String,
                               rowId : Int?) : String {

        return "CREATE TRIGGER table_bd" +
                " BEFORE DELETE ON $mainTable" +
                " BEGIN DELETE FROM $ftsTable" +
                " WHERE docid=$rowId END;"
    }

    fun getBeforeUpdateTrigger(mainTable: String,
                               ftsTable: String,
                               rowId: Int?) : String {

        return "CREATE TRIGGER table_bu" +
                " BEFORE UPDATE ON $mainTable" +
                " BEGIN DELETE FROM $ftsTable" +
                " WHERE docid=$rowId END;"
    }

    fun getAfterUpdateTrigger(
        mainTable: String,
        ftsTable: String,
        rowId: Int?,
        updatedField: String,
        updatedValue: String?
    ) : String {

        return "CREATE TRIGGER table_au" +
                " AFTER UPDATE ON $mainTable" +
                " BEGIN INSERT INTO $ftsTable(docid, $updatedField)" +
                " VALUES($rowId, $updatedValue) END;"
    }

    fun getAfterInsertTrigger(
        mainTable: String,
        ftsTable: String,
        rowId: Int?,
        updatedField: String,
        updatedValue: String?
    ) : String {

        return "CREATE TRIGGER table_ai" +
                " AFTER INSERT ON $mainTable" +
                " BEGIN INSERT INTO $ftsTable(docid, $updatedField)" +
                " VALUES($rowId, $updatedValue) END;"
    }
}

and additionally, as the TRIGGERS run automatically

override fun deleteItem(itemId: Int): Boolean {
        var success : Boolean
        writableDatabase.apply {
            execSQL(SQLiteTriggerUtils.getBeforeDeleteTrigger(TABLE_NAME, FTS_TABLE_NAME, itemId))
            success = delete(TABLE_NAME, "id = ?", arrayOf(itemId.toString())) > 0
            close()
        }
        return success
    }

could be replaced with (and likewise for the others) :-

override fun deleteItem(itemId: Int): Boolean {
        var success : Boolean
        writableDatabase.apply {
            success = delete(TABLE_NAME, "id = ?", arrayOf(itemId.toString())) > 0
            close()
        }
        return success
    }

NOTE The above is in-principle code, it has not been tested or run. As such it may contain errors.

  • I would also suggest that the trigger names, rather than using au (after update) use more descriptive names and therefore that the naming convention is not as restrictive (e.g. say you wanted to use another after update trigger?).

Working Example

The following is a working example that demonstrates the triggers (note little Kotlin Experience, so the code might not be the best)

DatabaseHelper.kt

val DB_VERSION = 1;
val DB_NAME = "mydb"

public class DatabaseHelper(context: Context?) :

    SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {

    val TABLE_NAME = "mytable"
    val FTS_TABLE_NAME = "myftstable"
    val TRG_BD = "trigger_bd"
    val TRG_BU = "trigger_bu"
    val TRG_AU = "trigger_au"
    val TRG_AI = "trigger_ai"
    val KEY_ID = "id";
    val KEY_LABEL = "label"
    val KEY_DESCRIPTION = "desctription"
    val KEY_IMPORTANCE = "importance";
    val KEY_LOGO = "logo";
    val KEY_TO_DO_DATE = "todo_date"
    val KEY_CREATION_DATE = "creation_date"



    override fun onCreate(db: SQLiteDatabase) {
        val CREATION_TABLE = ("CREATE TABLE $TABLE_NAME ( "
                + "$KEY_ID INTEGER PRIMARY KEY AUTOINCREMENT, "
                + "$KEY_LABEL TEXT, "
                + "$KEY_DESCRIPTION TEXT, "
                + "$KEY_IMPORTANCE INTEGER,"
                + "$KEY_LOGO INTEGER,"
                + "$KEY_TO_DO_DATE TEXT,"
                + "$KEY_CREATION_DATE TEXT)")
        val FTS_CREATION_TABLE = ("CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts4 (content='$TABLE_NAME', $KEY_LABEL)")

        //<<<<<<<<<< ADDED FOLLOWING LINES  >>>>>>>>
        val BD_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_BD " +
                "BEFORE DELETE ON $TABLE_NAME " +
                "BEGIN " +
                "DELETE FROM $FTS_TABLE_NAME " +
                "WHERE docid=old.$KEY_ID; " +
                "END;")
        val BU_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_BU " +
                "BEFORE UPDATE ON $TABLE_NAME " +
                "BEGIN " +
                "DELETE FROM $FTS_TABLE_NAME " +
                "WHERE docid=old.$KEY_ID; " +
                "END;")
        val AU_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_AU " +
                "AFTER UPDATE ON $TABLE_NAME " +
                "BEGIN " +
                "INSERT INTO $FTS_TABLE_NAME (docid, $KEY_LABEL) VALUES(new.$KEY_ID,new.$KEY_LABEL); " + //<<<<<<<< not sure $KEY_LABEL is correct column
                "END;")
        val AI_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_AI " +
                "AFTER INSERT ON $TABLE_NAME " +
                "BEGIN " +
                "INSERT INTO $FTS_TABLE_NAME (docid, $KEY_LABEL) VALUES(new.$KEY_ID,new.$KEY_LABEL); " +
                "END;")
        db.execSQL(CREATION_TABLE)
        db.execSQL(FTS_CREATION_TABLE)
        db.execSQL(BD_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
        db.execSQL(BU_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
        db.execSQL(AU_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
        db.execSQL(AI_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
        db.execSQL("DROP TABLE IF EXISTS $FTS_TABLE_NAME")
        db.execSQL("DROP TRIGGER IF EXISTS $TRG_BD") //<<<<<<<<<< ADDDED >>>>>>>>
        db.execSQL("DROP TRIGGER IF EXISTS $TRG_BU") //<<<<<<<<<< ADDDED >>>>>>>>
        db.execSQL("DROP TRIGGER IF EXISTS $TRG_AU") //<<<<<<<<<< ADDDED >>>>>>>>
        db.execSQL("DROP TRIGGER If EXISTS $TRG_AI") //<<<<<<<<<< ADDDED >>>>>>>>
        onCreate(db)
    }

    fun insert(label: String, description: String, importance: Int, tododate: String, creationdate: String ) {

        val cv = ContentValues()
        cv.put(KEY_LABEL,label)
        cv.put(KEY_DESCRIPTION,description)
        cv.put(KEY_IMPORTANCE,importance)
        cv.put(KEY_LOGO,0)
        cv.put(KEY_TO_DO_DATE,tododate)
        cv.put(KEY_CREATION_DATE,creationdate)

        val db = this.writableDatabase
        val inserted = db.insert(TABLE_NAME, null, cv )
        Log.d("INSERT","INSERT result in an id of " + inserted + ".")
    }

    fun update(id: Long, label: String) {
        val cv = ContentValues()
        cv.put(KEY_LABEL,label)

        val db = this.writableDatabase
        val updated = db.update(TABLE_NAME,cv,"$KEY_ID =" + id,null)
        Log.d("UPDATED","UPDATE resulted in " + updated + " rows being updated.")
    }

    fun delete(id: Long) {
        val whereclause = "$KEY_ID=" + id

        val db = this.writableDatabase
        val deleted = db.delete(TABLE_NAME,whereclause,null)
        Log.d("DELETED","DELETE resulted in " + deleted + " rows being deleted.")
    }

    fun logtables() {
        val db = this.writableDatabase
        val csr1 = db.query(TABLE_NAME, null, null, null, null, null, null)
        dumpCursor(csr1)
        val csr2 = db.query(FTS_TABLE_NAME,null,null,null,null,null,null)
        dumpCursor(csr2)
        csr1.close()
        csr2.close()
    }
}

MainActivity.kt

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val dbhelper = DatabaseHelper(this)
        dbhelper.insert("TEST001","Just Testing",10,"2019-01-01","2019-01-01")
        dbhelper.logtables()
        dbhelper.update(1,"001TEST")
        dbhelper.logtables()
        dbhelper.delete(1)
        dbhelper.logtables()
    }
}

Result (log)

04-28 14:35:21.002 17810-17810/s.e.myapplication D/INSERT: INSERT result in an id of 1.
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@11697ce
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: 0 {
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out:    id=1
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out:    label=TEST001
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out:    desctription=Just Testing
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out:    importance=10
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out:    logo=0
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out:    todo_date=2019-01-01
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out:    creation_date=2019-01-01
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: }
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: <<<<<
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@3396ef
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: 0 {
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out:    label=TEST001
04-28 14:35:21.003 17810-17810/s.e.myapplication I/System.out: }
04-28 14:35:21.003 17810-17810/s.e.myapplication I/System.out: <<<<<

i.e. the FTS has had a row inserted for TEST001

04-28 14:35:21.007 17810-17810/s.e.myapplication D/UPDATED: UPDATE resulted in 1 rows being updated.
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@9ce45fc
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: 0 {
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out:    id=1
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out:    label=001TEST
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out:    desctription=Just Testing
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out:    importance=10
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out:    logo=0
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out:    todo_date=2019-01-01
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out:    creation_date=2019-01-01
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: }
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: <<<<<
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@30ec485
04-28 14:35:21.008 17810-17810/s.e.myapplication I/System.out: 0 {
04-28 14:35:21.008 17810-17810/s.e.myapplication I/System.out:    label=001TEST
04-28 14:35:21.008 17810-17810/s.e.myapplication I/System.out: }
04-28 14:35:21.008 17810-17810/s.e.myapplication I/System.out: <<<<<

i.e. the FTS table has been updated to reflect TEST001 being changed to 001TEST

04-28 14:35:21.011 17810-17810/s.e.myapplication D/DELETED: DELETE resulted in 1 rows being deleted.
04-28 14:35:21.011 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@10862da
04-28 14:35:21.011 17810-17810/s.e.myapplication I/System.out: <<<<<
04-28 14:35:21.011 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@d4cb30b
04-28 14:35:21.011 17810-17810/s.e.myapplication I/System.out: <<<<<

after the deletion from the non-fts table both are empty

MikeT
  • 51,415
  • 16
  • 49
  • 68
  • First of all i have to thank you, this was the best explanation about triggers I've found around, anyway after following your answer, now I'm receiving an error, the item failed to be inserted means (success = insert.... != -1) is returning false. – Tamim Attafi Apr 28 '19 at 03:17
  • my log shows the following : Error inserting creationDate=Sun, 28 April 2019, 10:11 description=No Description. logo=null toDoDate=Sun, 28 April 2019, 10:11 importance=1 label=Unnamed. android.database.sqlite.SQLiteException: no such column: new.docid (Sqlite code 1): , while compiling: INSERT INTO todo_tasks(creationDate,description,logo,toDoDate,importance,label) VALUES (?,?,?,?,?,?), (OS error - 2:No such file or directory) – Tamim Attafi Apr 28 '19 at 03:18
  • is todo_task the fts table? I don't thinks so, looks like you might have coded new.docid into the insert into the non fts table. That is new. and old. are only available within the trigger. Not 100% sure though. Saying that trigger errors come under the triggering action. Trying to test BUT hardly ever use Kotlin. – MikeT Apr 28 '19 at 03:31
  • these are my table names : const val FTS_TABLE_NAME = "fts_todo_tasks" const val TABLE_NAME = "todo_tasks" and this is the insert line in add item method : success = insert(TABLE_NAME, null, values).toInt() != -1 triggers are declared in onCreate method as you mentioned in your answer and the whole class structure Interracts with the ordinary table only – Tamim Attafi Apr 28 '19 at 03:37
  • 1
    @TamimProduction uhhm sounds like the trigger. Does KEY_ID resolve to docid? if not change new.docid/old.docid to use $KEY_ID (I think) but leave docid when reference the FTS table as docid (I think). I assumed docid for both id columns. – MikeT Apr 28 '19 at 03:45
  • $KEY_ID references the item id in the non FTS table. changing to it from docid did the trick, Thank you! – Tamim Attafi Apr 28 '19 at 03:55
  • @TamimProduction cool, sorry I should have used $KEY_ID, will update the answer accordingly. – MikeT Apr 28 '19 at 04:02
  • in the before delete trigger, in line "WHERE docid=old.rowid; ", should i reference the old.rowid with old.$KEY_ID as well ? – Tamim Attafi Apr 28 '19 at 04:02
  • Doesn't matter as KEY_ID column is an alias of the rowid column as it's defined with `INTEGER PRIMARY KEY` (which makes it an alias of the rowid see [SQLite Autoincrement](https://sqlite.org/autoinc.html)). P.S. not using AUTOINCREMENT should be fine AND it will be more efficient (again explained in the link) (could have used `new.rowid` as well). Saying that may be more readable to use KEY_ID (*personal taste applies*) – MikeT Apr 28 '19 at 04:08
  • Thank you so much, your answers were very informative, and thank you for the links! – Tamim Attafi Apr 28 '19 at 04:11