【问题标题】:SQLite pre-update hook re-entrantSQLite 更新前挂钩可重入
【发布时间】:2016-09-29 11:16:54
【问题描述】:

我正在尝试使用 SQLite 的新 C 接口 preupdate 钩子:

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 回调来读取和写入数据库。

现在我有 2 个问题:

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

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

注意:我知道我可以使用触发器/SQLite 外键更新机制来实现此行为,但我愿意测试此 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

孩子: _索引:2 更新数据库时出错:违反 FOREIGN KEY 约束

如您所见,根本没有违规!

但是,当我将主要功能更改如下:

1)取消签署 pre_hook 回调。

2) 在回调之外操作子更新(在我们的例子中是在父更新之后)。

我突然得到相同的输出,只是没有错误。

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

【问题讨论】:

    标签: c++ database sqlite callback


    【解决方案1】:

    来自documentation

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

    【讨论】:

      猜你喜欢
      • 2018-08-13
      • 1970-01-01
      • 2014-08-02
      • 1970-01-01
      • 2021-01-15
      • 1970-01-01
      • 1970-01-01
      • 2013-12-06
      • 2021-04-13
      相关资源
      最近更新 更多