【发布时间】: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