The Session Extension
会话扩展
1.介绍
1.1. 典型用例
1.2. 获得会话扩展
1.3. 限制
2.概念
2.1. 变更集和补丁集
2.2. 冲突
2.3. 更改设置
3. 使用会话扩展
3.1. 捕获变更集
3.2. 将更改集应用于数据库
3.3. 检查变更集的内容
4. 扩展功能
1.介绍
会话扩展提供了一种机制,用于记录对SQLite数据库中部分或全部rowid表的更改,并将这些更改打包到“changeset”或“patchset”文件中,稍后可用于将相同的一组更改应用于另一个数据库具有相同的模式和兼容的起始数据。“changeset”也可能被倒置并用于“undo”会话。
本文档是对会话扩展的介绍。接口的细节在单独的会话扩展C语言接口文档中。
1.1. 典型用例
假设SQLite被用作特定设计应用程序的应用程序文件格式。两个用户,Alice和Bob,每个用户的基线设计大小都是千兆字节。他们整天都在同时工作,每个人都对设计进行自定义和调整。在一天结束时,他们希望将他们的变化合并为一个统一的设计。
会话扩展通过记录对Alice和Bob数据库的所有更改并将这些更改写入更改集或补丁集文件来实现此目的。在一天结束时,Alice可以将她的变更集发送给Bob,并且Bob可以将其应用到他的数据库中。结果(假设没有冲突)是Bob的数据库包含他的变更和Alice的变更。同样,Bob可以将其工作变更集发送给Alice,并且可以将其更改应用到她的数据库中。
换句话说,会话扩展为SQLite数据库文件提供了一个类似于unix修补程序(https://en.wikipedia.org/wiki/Patch_(Unix%29))实用程序或“merge”功能的工具的版本控制系统,如Fossil,Git或Mercurial。
1.2. 获得会话扩展
自版本3.13.0(2016-05-18)以来,会话扩展已包含在SQLite合并源分布中。默认情况下,会话扩展被禁用。要启用它,请使用以下编译器开关构建:
-DSQLITE_ENABLE_SESSION -DSQLITE_ENABLE_PREUPDATE_HOOK
或者,如果使用autoconf构建系统,则将--enable-session选项传递给configure脚本。
1.3. 限制
- 在SQLite版本3.17.0之前,会话扩展只能使用rowid表,而不能使用WITHOUT ROWID表。从3.17.0开始,支持rowid和WITHOUT ROWID表。
2. 概念
2.1. 变更集和补丁集
会话模块围绕创建和操作变更集进行。变更集是对数据库的一系列更改进行编码的一组数据。变更集中的每个更改都是以下之一:
- 一个
INSERT
。INSERT
更改包含要添加到数据库表的单个行。INSERT
更改的有效负载由新行的每个字段的值组成。
- The PRIMARY KEY values identifying the modified row,
- The new values for each modified field of the row, and
- The original values for each modified field of the row.
UPDATE更改不包含有关未被更改修改的非PRIMARY KEY字段的任何信息。UPDATE更改不能指定对PRIMARY KEY字段的修改。
单个变更集可能包含适用于多个数据库表的更改。对于每个更改集至少包含一个更改的表,它还会编码以下数据:
- 数据库表的名称,
更改集只能应用于包含与存储在变更集中的上述三个条件相匹配的表的数据库。
补丁集与变更集类似。它比变更集略微更紧凑,但提供更有限的冲突检测和分辨率选项(有关详细信息,请参阅下一节)。补丁集和变更集之间的差异在于:
- 对于
DELETE
更改,有效负载只包含PRIMARY KEY字段。其他字段的原始值不作为补丁集的一部分存储。
2.2. 冲突
将更改集或补丁集应用于数据库时,会尝试为每个INSERT更改插入一个新行,为每个DELETE更改删除一行并为每个UPDATE更改修改一行。如果目标数据库与记录变更集的原始数据库处于相同状态,则这是一件简单的事情。但是,如果目标数据库的内容不完全处于此状态,则应用更改集或修补程序集时可能会发生冲突。
处理INSERT
更改时,可能会发生以下冲突:
- 目标数据库可能已经包含具有与INSERT更改所指定的PRIMARY KEY值相同的行。
处理DELETE
更改时,可能会检测到以下冲突:
- 目标数据库可能不包含具有要删除的指定PRIMARY KEY值的行。
处理UPDATE
更改时,可能会检测到以下冲突:
- 目标数据库可能不包含具有指定PRIMARY KEY值修改的行。
根据冲突的类型,会话应用程序具有各种可配置的处理冲突的选项,从忽略冲突变更,中止整个变更集应用程序或应用更改(尽管存在冲突)。有关详细信息,请参阅sqlite3changeset_apply() API的文档。
2.3. 更改设置
会话对象配置完成后,它开始监视对其配置表的更改。但是,每当数据库中的行被修改时,它都不会记录整个更改。相反,它只记录每个插入行的PRIMARY KEY字段,以及任何更新或删除行的PRIMARY KEY和所有原始行值。如果一行通过一次会话被多次修改,则不会记录新的信息。
创建变更集或补丁集所需的其他信息是在调用sqlite3session_changeset()或sqlite3session_patchset()时从数据库文件读取的。特别,
- 对于作为INSERT操作结果记录的每个主键,会话模块会检查表中是否仍有匹配主键的行。如果是这样,则将INSERT更改添加到更改集。
其中一个含义是,如果在单个会话中进行更改并取消了更改(例如,如果插入了行然后再次删除),则会话模块根本不会报告任何更改。或者,如果某行在同一个会话中多次更新,则所有更新都会合并到任何更改集或修补程序集blob中的单个更新中。
3.使用会话扩展
本节提供了演示如何使用会话扩展的示例。
3.1. 捕获变更集
下面的示例代码演示了执行SQL命令时捕获变更集的步骤。综上所述:
- 会话对象(类型为sqlite3_session *)通过调用sqlite3session_create()API函数来创建。单个会话对象监视通过单个sqlite3 *数据库句柄对单个数据库(即“main”,“temp”或附加数据库)所做的更改。
- By explicitly specifying tables using one call to [sqlite3session\_attach()](session/sqlite3session_attach) for each table, or
- By specifying that all tables in the database should be monitored for changes using a call to [sqlite3session\_attach()](session/sqlite3session_attach) with a NULL argument, or
- By configuring a callback to be invoked the first time each table is written to that indicates to the session module whether or not changes on the table should be monitored.
下面的示例代码使用上面枚举的第二个方法 - 它监视所有数据库表上的更改。
- 通过执行SQL语句对数据库进行更改。会话对象记录这些更改。
/*
** Argument zSql points to a buffer containing an SQL script to execute
** against the database handle passed as the first argument. As well as
** executing the SQL script, this function collects a changeset recording
** all changes made to the "main" database file. Assuming no error occurs,
** output variables (*ppChangeset) and (*pnChangeset) are set to point
** to a buffer containing the changeset and the size of the changeset in
** bytes before returning SQLITE_OK. In this case it is the responsibility
** of the caller to eventually free the changeset blob by passing it to
** the sqlite3_free function.
**
** Or, if an error does occur, return an SQLite error code. The final
** value of (*pChangeset) and (*pnChangeset) are undefined in this case.
*/
int sql_exec_changeset(
sqlite3 *db, /* Database handle */
const char *zSql, /* SQL script to execute */
int *pnChangeset, /* OUT: Size of changeset blob in bytes */
void **ppChangeset /* OUT: Pointer to changeset blob */
){
sqlite3_session *pSession = 0;
int rc;
/* Create a new session object */
rc = sqlite3session_create(db, "main", &pSession
/* Configure the session object to record changes to all tables */
if( rc==SQLITE_OK ) rc = sqlite3session_attach(pSession, NULL
/* Execute the SQL script */
if( rc==SQLITE_OK ) rc = sqlite3_exec(db, zSql, 0, 0, 0
/* Collect the changeset */
if( rc==SQLITE_OK ){
rc = sqlite3session_changeset(pSession, pnChangeset, ppChangeset
}
/* Delete the session object */
sqlite3session_delete(pSession
return rc;
}
3.2. 将更改集应用于数据库
将变更集应用于数据库比捕获变更集要简单。通常,对sqlite3changeset_apply()的单个调用就足够了,如下面的示例代码所示。
在情况复杂的情况下,应用变更集的复杂性在于解决冲突。有关详细信息,请参阅上面链接的API文档。
/*
** Conflict handler callback used by apply_changeset(). See below.
*/
static int xConflict(void *pCtx, int eConflict, sqlite3_changset_iter *pIter){
int ret = (int)pCtx;
return ret;
}
/*
** Apply the changeset contained in blob pChangeset, size nChangeset bytes,
** to the main database of the database handle passed as the first argument.
** Return SQLITE_OK if successful, or an SQLite error code if an error
** occurs.
**
** If parameter bIgnoreConflicts is true, then any conflicting changes
** within the changeset are simply ignored. Or, if bIgnoreConflicts is
** false, then this call fails with an SQLTIE_ABORT error if a changeset
** conflict is encountered.
*/
int apply_changeset(
sqlite3 *db, /* Database handle */
int bIgnoreConflicts, /* True to ignore conflicting changes */
int nChangeset, /* Size of changeset in bytes */
void *pChangeset /* Pointer to changeset blob */
){
return sqlite3changeset_apply(
db,
nChangeset, pChangeset,
0, xConflict,
(void*)bIgnoreConflicts
}
3.3.检查变更集的内容
下面的示例代码演示了用于迭代并提取与变更集中所有更改相关的数据的技术。总结:
- 调用sqlite3changeset_start()API来创建和初始化迭代器以遍历变更集的内容。最初,迭代器根本没有指向任何元素。
/*
** Print the contents of the changeset to stdout.
*/
static int print_changeset(void *pChangeset, int nChangeset){
int rc;
sqlite3_changeset_iter *pIter = 0;
/* Create an iterator to iterate through the changeset */
rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset
if( rc!=SQLITE_OK ) return rc;
/* This loop runs once for each change in the changeset */
while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
const char *zTab; /* Table change applies to */
int nCol; /* Number of columns in table zTab */
int op; /* SQLITE_INSERT, UPDATE or DELETE */
sqlite3_value *pVal;
/* Print the type of operation and the table it is on */
rc = sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0
if( rc!=SQLITE_OK ) goto exit_print_changeset;
printf("%s on table %s\n",
op==SQLITE_INSERT?"INSERT" : op==SQLITE_UPDATE?"UPDATE" : "DELETE",
zTab
/* If this is an UPDATE or DELETE, print the old.* values */
if( op==SQLITE_UPDATE || op==SQLITE_DELETE ){
printf("Old values:"
for(i=0; i<nCol; i++){
rc = sqlite3changeset_old(pIter, i, &pVal
if( rc!=SQLITE_OK ) goto exit_print_changeset;
printf(" %s", pVal ? sqlite3_value_text(pVal) : "-"
}
printf("\n"
}
/* If this is an UPDATE or INSERT, print the new.* values */
if( op==SQLITE_UPDATE || op==SQLITE_INSERT ){
printf("New values:"
for(i=0; i<nCol; i++){
rc = sqlite3changeset_new(pIter, i, &pVal
if( rc!=SQLITE_OK ) goto exit_print_changeset;
printf(" %s", pVal ? sqlite3_value_text(pVal) : "-"
}
printf("\n"
}
}
/* Clean up the changeset and return an error code (or SQLITE_OK) */
exit_print_changeset:
rc2 = sqlite3changeset_finalize(pIter
if( rc==SQLITE_OK ) rc = rc2;
return rc;
}
4.扩展功能
大多数应用程序仅使用前一节中介绍的会话模块功能。但是,以下附加功能可用于更改集和补丁集blob的使用和操作:
- 两个或多个变更集/补丁集可以使用sqlite3changeset_concat()或sqlite3_changegroup接口进行组合。