在Android上使用SQLite触发器返回错误

时间:2019-04-27 13:53:22

标签: java android sqlite kotlin fts4

我是SQLite的新手,我正在尝试创建一个应用程序,用户可以在其中创建任务并向其添加提醒。我正在使用SQLite数据库保存这些项目。一切工作正常,现在我想实现全文搜索功能,我已经阅读了SQLite个文档,其中介绍了使用FTS4 VIRTUAL TABLE比普通文档更好的方法。

  • 因此,为了保持虚拟表同步,我不得不使用触发器。但是调用execSQL("//*Trigger code*//")
  • 后发生错误

这是我的触发器(按照文档中提到的顺序使用它们)

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;"
    }
}

这是我的数据库onCreate()方法

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

示例执行onDeleteItem()方法的触发器

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
    }

返回的错误

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)

1 个答案:

答案 0 :(得分:2)

我认为在纠正了在BEGIN和END之间使用分号按如下方式编码的已触发动作之后的语法错误后,您的问题:-

enter image description here

可能是您在知道值之前尝试添加触发器(可能并非如此,取决于代码)。

是触发器是一个实体,例如视图,表,索引等,它们构成架构的一部分,因此需要唯一的名称。因此CREATE TRIGGER the_trigger_name ......要求 the_trigger_name 是唯一的。似乎您每次尝试执行将触发该触发器的操作时都会尝试创建相同的触发器,但由于该触发器已经存在,因此将失败。

您可以使用CREATE TRIGGER IF NOT EXISTS the_trigger_name ......,但是将使用现有触发器。

因此,您可能需要先创建DROP TRIGGER the_trigger_name,然后再创建触发器。

但是!

说触发器的预期用途是在事件(UPDATE,DELETE或INSERT)发生时(实际上紧接在此之前或之后)自动执行类似的操作。因此,触发器可以访问该行的列,从而触发该触发器(触发操作)。

如果触发动作是INSERT,则可以使用new.column引用要插入的行的列,并将其用于触发动作(在BEGIN和END之间指定的动作)。

如果触发操作为DELETE,则old.column可用于引用要删除的行的列。

如果触发操作是UPDATE,则new.column和old.column都可以被引用。

  • 其中.column被相应的列替换。

documentation说:-

  

代替分别写入全文索引和内容   表,一些用户可能希望使用数据库触发器来保持   关于存储的文档集的最新全文索引   在内容表中。例如,使用之前的表格   例子:

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;

您会看到它使用 old.docid new.rowid

因此old.docid将是要删除的行的docid,而new.rowid将是要插入 $ TABLE_NAME 的行的行ID。因此,每个触发器只需通用一次即可定义一次。

因此,我相信您可以使用:-

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

以上内容将替换(即不再需要以下内容):-

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;"
    }
}

,此外,由于TRIGGERS自动运行

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
    }

可以替换为(和其他类似的):-

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

注意以上是原理代码,尚未经过测试或运行。因此,它可能包含错误。

  • 我还建议触发器名称而不是使用au(更新后)来使用更具描述性的名称,因此命名约定没有那么严格(例如,您想在更新触发器后使用另一个吗?)。
  • li>

工作示例

以下是一个有效的示例,演示了触发器(请注意一点Kotlin经验,因此代码可能不是最好的)

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()
    }
}

结果(日志)

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: <<<<<

即FTS已为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: <<<<<

即FTS表已更新,以反映TEST001已更改为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: <<<<<

从非fts表中删除后均为空