SQLite预更新钩子重入

时间:2016-05-31 12:26:41

标签: c++ database sqlite callback

我试图使用SQLite的新C接口preupdate hook:

https://www.sqlite.org/c3ref/preupdate_count.html

现在我的问题: pre_update API签名如下:

void *sqlite3_preupdate_hook(
  sqlite3 *db,
  void(*xPreUpdate)(
    void *pCtx,                   /* Copy of third arg to preupdate_hook() */
    sqlite3 *db,                  /* Database handle */
    int op,                       /* SQLITE_UPDATE, DELETE or INSERT */
    char const *zDb,              /* Database name */
    char const *zName,            /* Table name */
    sqlite3_int64 iKey1,          /* Rowid of row about to be deleted/updated */
    sqlite3_int64 iKey2           /* New rowid value (for a rowid UPDATE) */
  ),
  void*
);

正如您所看到的,它向回调注入了一个指向注册钩子的数据库连接的指针。 根据我的经验和SQLite文档,我知道更新/提交/回滚挂钩不是可重入的,这意味着它们无法修改导致挂钩调用的连接。

我想使用这个pre_update回调来从数据库中读取和写入。

现在我有两个问题:

1)SQLite pre_update回调是否可重入并获得了从回调范围修改和读取数据库的支持?

2)如果确实如此,有人可以解释下列内容是如何实现的吗?

我创建了一个新数据库并使用SQLite shell运行以下内容:

sqlite> CREATE TABLE Parent(_index INTEGER PRIMARY KEY);
sqlite> INSERT INTO "Parent" VALUES(1);
sqlite> CREATE TABLE Child(_index INTEGER PRIMARY KEY, CONSTRAINT ch_fk FOREIGN KEY(_index) REFERENCES Parent(_index) DEFERRABLE INITIALLY DEFERRED);
sqlite> INSERT INTO "Child" VALUES(1);

现在我正在尝试创建一个自动操作,该操作应该在事务内部工作,并在每次父项更改其密钥时更新子项,因此它不会成为约束违规。

注意:我知道我可以使用triggers / SQLite外键UPDATE机制实现此行为,但我愿意测试此API的稳定性。

所以代码:

int main(int argc, char *argv[]){

    int rc;
    char *err_msg;
    sqlite3 *sqlite_connection;

    rc = sqlite3_open("__database__.db",&sqlite_connection);
    rc += rc = sqlite3_exec(sqlite_connection, "PRAGMA foreign_keys=ON;", 0, 0, &err_msg);/* set foreign keys mechanism on */
    if(rc != SQLITE_OK){
        printf("Error initializing connection : %s\n",err_msg);
        exit(rc);
    }

    sqlite3_preupdate_hook(sqlite_connection,pre_hook,NULL);

    //Watch tables content before
    rc = sqlite3_exec(sqlite_connection, "SELECT * FROM PARENT;", callback, (void*)NULL, &err_msg);
    rc = sqlite3_exec(sqlite_connection, "SELECT * FROM CHILD;", callback, (void*)NULL, &err_msg);

    //BEGIN TRANSACTION
    rc = sqlite3_exec(sqlite_connection, "BEGIN", 0, 0, &err_msg);

    /* Update table to invoke callback */
    rc += sqlite3_exec(sqlite_connection, "UPDATE Parent SET _index = 2 WHERE _index = 1", 0, 0, &err_msg); /* Update table to invoke callback */

    //Watch tables content after
    rc = sqlite3_exec(sqlite_connection, "SELECT * FROM PARENT;", callback, (void*)NULL, &err_msg);
    rc = sqlite3_exec(sqlite_connection, "SELECT * FROM CHILD;", callback, (void*)NULL, &err_msg);

    rc += sqlite3_exec(sqlite_connection, "COMMIT;", 0, 0, &err_msg);

    if(rc != SQLITE_OK){
        printf("Error updating the database : %s\n",err_msg);
        rc = sqlite3_exec(sqlite_connection, "ROLLBACK", 0, 0, &err_msg);
        if(rc != SQLITE_OK){
            printf("Error updating the database : %s\n",err_msg);
        }
    }


    sqlite3_close(sqlite_connection);

    return rc;

}

和pre_hook:

void pre_hook(
        void *pCtx,                   /* Copy of third arg to preupdate_hook() */
        sqlite3 *db,                  /* Database handle */
        int op,                       /* SQLITE_UPDATE, DELETE or INSERT */
        char const *zDb,              /* Database name */
        char const *zName,            /* Table name */
        sqlite3_int64 iKey1,          /* Rowid of row about to be deleted/updated */
        sqlite3_int64 iKey2){
             char query_buffer[100];
             char *err_msg;
             if((strcmp("Parent",zName) == 0) && (SQLITE_UPDATE == op)){
                 sprintf(query_buffer,"UPDATE Child SET _index = %d WHERE _index = %d",(int)iKey2,(int)iKey1);
                 if(sqlite3_exec(db,query_buffer , 0, 0, &err_msg) != SQLITE_OK){
                     printf("Error executin trigger\n");
                 }

             }
        }

然后我得到输出:

//操作前: 家长: _indx:1

孩子: _indx:1

//操作后: 家长: _indx:2

孩子: _indx:2 更新数据库时出错:FOREIGN KEY约束违规

如你所见,根本没有违规行为!。

但是,当我按如下方式更改主要功能时:

1)取消签署pre_hook回调。

2)在回调之外操作子更新(在我们的情况下,在父更新之后)。

我突然得到相同的输出而没有错误。

我认为这意味着pre_update回调不是可重入的,但我寻求这个问题的专业答案。

1 个答案:

答案 0 :(得分:0)

来自documentation

  

更新钩子实现不能做任何会修改的事情   调用更新挂钩的数据库连接。任何行动   修改数据库连接必须延迟到之后   完成触发更新挂钩的sqlite3_step()调用。   请注意,sqlite3_prepare_v2()和sqlite3_step()都会修改它们   数据库连接的含义"修改"在这一段中。