SQLite 会话模块 C/C++ 接口

此页面定义了 SQLite 会话扩展的 C 语言接口。这不是教程。这些页面设计得精确,不易阅读。教程单独提供

此页面在单个 HTML 文件中包含所有 C 语言接口信息。如果您愿意,还可以将相同的信息分成许多小页面 以便于查看。

该文档由扫描源代码文件 sqlite3session.h 中的注释的脚本创建。


对象:


常量:


功能:


sqlite3changeset_start_v2 的标志

#define SQLITE_CHANGESETSTART_INVERT        0x0002

以下标志可以通过第 4 个参数传递给 sqlite3changeset_start_v2sqlite3changeset_start_v2_strm

SQLITE_CHANGESETAPPLY_INVERT
在迭代时反转变更集。这相当于在应用之前使用 sqlite3changeset_invert() 反转变更集。用补丁集指定这个标志是错误的。


sqlite3session_config() 的值。

#define SQLITE_SESSION_CONFIG_STRMSIZE 1


变更组句柄

typedef struct sqlite3_changegroup sqlite3_changegroup;

变更组是用于组合两个或多个 变更集补丁集的对象

构造函数:sqlite3changegroup_new()

析构函数:sqlite3changegroup_delete()

方法: sqlite3changegroup_add()sqlite3changegroup_output()


变更集迭代器句柄

typedef struct sqlite3_changeset_iter sqlite3_changeset_iter;

此对象的实例充当游标,用于迭代变更集补丁集的元素。

构造函数: sqlite3changeset_start()sqlite3changeset_start_v2()


重新设置变更集

typedef struct sqlite3_rebaser sqlite3_rebaser;

重要提示:此界面是实验性的,如有更改,恕不另行通知。

假设有一个站点托管状态为 S0 的数据库。并且进行了修改,将该数据库移动到状态 S1 并记录了一个变更集(“本地”变更集)。然后,从另一个站点(“远程”变更集)接收基于 S0 的变更集并将其应用于数据库。然后数据库处于状态(S1+“远程”),其中确切的状态取决于在应用“远程”时做出的任何冲突解决决定(OMIT 或 REPLACE)。重新设置变更集的基础是更新它以将那些冲突解决决策考虑在内,这样就不必在网络的其他地方解决相同的冲突。

例如,如果本地和远程变更集都包含对“CREATE TABLE t1(a PRIMARY KEY, b)”相同键的插入:

本地:插入 t1 值(1,'v1');远程:插入 t1 值(1,'v2');

并且冲突解决方案是 REPLACE,然后 INSERT 更改从本地更改集中删除(它被覆盖)。或者,如果冲突解决方案是“OMIT”,则本地变更集被修改为包含:

更新 t1 SET b = 'v2' WHERE a=1;

本地变更集中的变更重新设置如下:

本地插入
这可能只与远程 INSERT 冲突。如果冲突解决方案是 OMIT,则将 UPDATE 更改添加到 rebased 变更集。或者,如果冲突解决方案是 REPLACE,则不向重新设置基准的变更集添加任何内容。

本地删除
这可能与远程 UPDATE 或 DELETE 冲突。在这两种情况下,唯一可能的解决方案是 OMIT。如果远程操作是 DELETE,则不向 rebased 变更集添加任何变更。如果远程操作是更新,则更改的旧*字段将更新以反映更新中的新*值。

本地更新
这可能与远程 UPDATE 或 DELETE 冲突。如果它与 DELETE 冲突,并且冲突解决方案是 OMIT,则更新将更改为 INSERT。来自更新更改的 new.* 记录中的任何未定义值都使用来自冲突 DELETE 的 old.* 值填充。或者,如果冲突解决方案是 REPLACE,则 UPDATE 更改会从重新设置的变更集中简单地省略。

如果冲突与远程更新有关且解决方案为 OMIT,则旧的 * 值将使用远程更改中的新的 * 值重新定位。或者,如果解决方案是 REPLACE,则将更改复制到重新设置基准的变更集中,同时对列的更新也由冲突的远程 UPDATE 删除。如果这意味着不会更新任何列,则忽略更改。

本地更改可以同时针对多个远程更改进行重新设置。如果单个键被多个远程变更集修改,则在本地变更集变基之前,它们将按如下方式组合:

  • 如果一个键上有一个或多个 REPLACE 决议,它会根据 REPLACE 重新定位。

  • 如果键上没有 REPLACE 解决方案,则本地变更集将根据最新的 OMIT 解决方案重新定位。

请注意,来自多个远程变更集的冲突解决方案是按字段而不是按行组合的。这意味着在多个远程 UPDATE 操作的情况下,单个本地更改的某些字段可能会为 REPLACE 重新定位,而其他字段可能会为 OMIT 重新定位。

为了 rebase 本地变更集,必须首先使用 sqlite3changeset_apply_v2() 将远程变更集应用到本地数据库,并捕获 rebase 信息的缓冲区。然后:

  1. 通过调用 sqlite3rebaser_create() 创建一个 sqlite3_rebaser 对象。
  2. 新对象配置有通过调用 sqlite3rebaser_configure() 从 sqlite3changeset_apply_v2() 获得的 rebase 缓冲区。如果要根据多个远程变更集重新设置本地变更集,则应多次调用 sqlite3rebaser_configure(),其顺序与多次调用 sqlite3changeset_apply_v2() 的顺序相同。
  3. 每个本地变更集都通过调用 sqlite3rebaser_rebase() 进行重新设置。
  4. 通过调用 sqlite3rebaser_delete() 删除 sqlite3_rebaser 对象。


会话对象句柄

typedef struct sqlite3_session sqlite3_session;

此对象的一个​​实例是一个会话,可用于记录对数据库的更改。

构造函数:sqlite3session_create()

析构函数:sqlite3session_delete()


将变更集添加到变更组

int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pData);

将缓冲区 pData(大小为 nData 字节)中的变更集(或补丁集)内的所有更改添加到变更组。

如果缓冲区包含一个补丁集,那么在同一变更组对象上对该函数的所有先前调用也必须具有指定的补丁集。或者,如果缓冲区包含变更集,则必须更早地调用此函数。否则,将返回 SQLITE_ERROR 并且不会向变更组添加任何变更。

变更集和变更组中的行由其 PRIMARY KEY 列中的值标识。如果两行具有相同的主键,则认为变更集中的变更应用于与变更组中已存在的变更相同的行。

对尚未出现在更改组中的行所做的更改将简单地复制到其中。或者,如果新的变更集和变更组都包含应用于单个行的变更,则变更组的最终内容取决于每个变更的类型,如下所示:

现有变化 新变化 输出变化
插入插入 忽略新更改。如果在变更集已经添加到变更组之后立即记录新的变更集,则不会发生这种情况。
插入更新 INSERT 更改保留在更改组中。INSERT 更改中的值被修改,就好像该行是由现有更改插入的,然后根据新更改进行更新。
插入删除 现有的 INSERT 将从更改组中删除。不添加 DELETE。
更新插入 忽略新更改。如果在变更集已经添加到变更组之后立即记录新的变更集,则不会发生这种情况。
更新更新 现有的 UPDATE 保留在更改组中。它被修改,以便伴随的值就像该行被现有更改更新一次,然后又被新更改更新一样。
更新删除 现有的 UPDATE 被变更组中的新 DELETE 取代。
删除插入 如果新更改插入的行中的一个或多个列值与现有更改删除的行中的列值不同,则现有的 DELETE 将被更改组中的 UPDATE 替换。否则,如果插入的行与删除的行完全相同,则简单地丢弃现有的 DELETE。
删除更新 忽略新更改。如果在变更集已经添加到变更组之后立即记录新的变更集,则不会发生这种情况。
删除删除 忽略新更改。如果在变更集已经添加到变更组之后立即记录新的变更集,则不会发生这种情况。

如果新的变更集包含对已存在于变更组中的表的更改,则该表的列数和主键列的位置必须一致。如果不是这种情况,则此函数将失败并返回 SQLITE_SCHEMA。如果输入变更集似乎已损坏并且检测到损坏,则返回 SQLITE_CORRUPT。或者,如果在处理过程中出现内存不足的情况,此函数将返回 SQLITE_NOMEM。在所有情况下,如果发生错误,则更改组的最终内容的状态是未定义的。

如果没有错误发生,则返回 SQLITE_OK。


删除变更组对象

void sqlite3changegroup_delete(sqlite3_changegroup*);


创建一个新的变更组对象

int sqlite3changegroup_new(sqlite3_changegroup **pp);

sqlite3_changegroup 对象用于将两个或多个变更集(或补丁集)组合成一个变更集(或补丁集)。单个变更组对象可以组合变更集或补丁集,但不能同时组合两者。输出始终采用与输入相同的格式。

如果成功,此函数返回 SQLITE_OK 并在返回前用指向新 sqlite3_changegroup 对象的指针填充 (*pp)。调用者最终应该使用对 sqlite3changegroup_delete() 的调用来释放返回的对象。如果发生错误,则返回 SQLite 错误代码(即 SQLITE_NOMEM)并将 *pp 设置为 NULL。

sqlite3_changegroup 对象的常用使用模式如下:

  • 它是通过调用 sqlite3changegroup_new() 创建的。

  • 通过调用 sqlite3changegroup_add() 将零个或多个变更集(或补丁集)添加到对象中。

  • 应用程序通过调用 sqlite3changegroup_output() 获得将所有输入变更集组合在一起的结果。

  • 使用对 sqlite3changegroup_delete() 的调用删除该对象。

在对 new() 和 delete() 的调用之间可以以任何顺序对 add() 和 output() 进行任意次数的调用。

除了常规的 sqlite3changegroup_add() 和 sqlite3changegroup_output() 函数外,还提供流式版本 sqlite3changegroup_add_strm() 和 sqlite3changegroup_output_strm()。


从变更组中获取复合变更集

int sqlite3changegroup_output(
  sqlite3_changegroup*,
  int *pnData,                    /* OUT: Size of output buffer in bytes */
  void **ppData                   /* OUT: Pointer to output buffer */
);

获取包含代表变更组当前内容的变更集(或补丁集)的缓冲区。如果变更组的输入本身就是变更集,那么输出就是一个变更集。或者,如果输入是补丁集,则输出也是补丁集。

与 sqlite3session_changeset() 和 sqlite3session_patchset() 函数的输出一样,与单个表相关的所有更改都在此函数的输出中组合在一起。表的出现顺序与添加到变更组的第一个变更集的顺序相同。如果添加到变更组的第二个或后续变更集包含未出现在第一个变更集中的表的变更,则它们将附加到输出变更集的末尾,同样按照它们第一次遇到的顺序。

如果发生错误,则返回 SQLite 错误代码并将输出变量 (*pnData) 和 (*ppData) 设置为 0。否则,返回 SQLITE_OK 并将输出变量设置为输出的大小和指向输出的指针缓冲区,分别。在这种情况下,调用者有责任使用对 sqlite3_free() 的调用最终释放缓冲区。


连接两个变更集对象

int sqlite3changeset_concat(
  int nA,                         /* Number of bytes in buffer pA */
  void *pA,                       /* Pointer to buffer containing changeset A */
  int nB,                         /* Number of bytes in buffer pB */
  void *pB,                       /* Pointer to buffer containing changeset B */
  int *pnOut,                     /* OUT: Number of bytes in output changeset */
  void **ppOut                    /* OUT: Buffer containing output changeset */
);

此函数用于将两个变更集 A 和 B 连接成一个变更集。结果是一个变更集,相当于先应用变更集 A,然后再应用变更集 B。

此函数使用 sqlite3_changegroup 对象组合两个输入变更集。调用它会产生与以下代码片段类似的结果:

  sqlite3_changegroup *pGrp;
  rc = sqlite3_changegroup_new(&pGrp);
  if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nA, pA);
  if( rc==SQLITE_OK ) rc = sqlite3changegroup_add(pGrp, nB, pB);
  if( rc==SQLITE_OK ){
    rc = sqlite3changegroup_output(pGrp, pnOut, ppOut);
  }else{
    *ppOut = 0;
    *pnOut = 0;
  }

有关详细信息,请参阅下面的 sqlite3_changegroup 文档。


从变更集迭代器获取冲突的行值

int sqlite3changeset_conflict(
  sqlite3_changeset_iter *pIter,  /* Changeset iterator */
  int iVal,                       /* Column number */
  sqlite3_value **ppValue         /* OUT: Value from conflicting row */
);

此函数只能与通过sqlite3changeset_apply()使用 SQLITE_CHANGESET_DATASQLITE_CHANGESET_CONFLICT传递给冲突处理程序回调的迭代器对象一起使用。如果在任何其他迭代器上调用此函数,则返回SQLITE_MISUSE并将 *ppValue 设置为 NULL。

参数 iVal 必须大于或等于 0,并且小于表中受当前更改影响的列数。否则, 返回SQLITE_RANGE并将 *ppValue 设置为 NULL。

如果成功,此函数将 *ppValue 设置为指向受保护的 sqlite3_value 对象,该对象包含与当前冲突处理程序回调关联的“冲突行”中的第 iVal 个值,并返回 SQLITE_OK。

如果发生其他错误(例如 OOM 条件),则返回 SQLite 错误代码并将 *ppValue 设置为 NULL。


完成变更集迭代器

int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter);

此函数用于完成使用 sqlite3changeset_start()分配的迭代器。

只能在使用 sqlite3changeset_start()函数创建的迭代器上调用此函数。如果应用程序使用通过sqlite3changeset_apply()传递给冲突处理程序的迭代器调用此函数 SQLITE_MISUSE将立即返回并且调用无效。

如果在调用 sqlite3changeset_xxx() 函数时遇到错误(例如sqlite3changeset_next()中的SQLITE_CORRUPT或 sqlite3changeset_new() 中的 SQLITE_NOMEM 此函数将返回与该错误对应的错误代码。否则,返回 SQLITE_OK。这是为了允许以下模式(伪代码):

  sqlite3changeset_start();
  while( SQLITE_ROW==sqlite3changeset_next() ){
    // Do something with change.
  }
  rc = sqlite3changeset_finalize();
  if( rc!=SQLITE_OK ){
    // An error has occurred 
  }


确定违反外键约束的次数

int sqlite3changeset_fk_conflicts(
  sqlite3_changeset_iter *pIter,  /* Changeset iterator */
  int *pnOut                      /* OUT: Number of FK violations */
);

只能使用传递给 SQLITE_CHANGESET_FOREIGN_KEY 冲突处理程序回调的迭代器调用此函数。在这种情况下,它将输出变量设置为目标数据库中已知外键违规的总数,并返回 SQLITE_OK。

在所有其他情况下,此函数返回 SQLITE_MISUSE。


反转变更集

int sqlite3changeset_invert(
  int nIn, const void *pIn,       /* Input changeset */
  int *pnOut, void **ppOut        /* OUT: Inverse of input */
);

此函数用于“反转”变更集对象。将反向变更集应用于数据库会逆转应用未反向变更集的效果。具体来说:

  • 每个 DELETE 更改都更改为 INSERT,并且
  • 每个 INSERT 更改都更改为 DELETE,并且
  • 对于每个 UPDATE 更改,old.* 和 new.* 值都会被交换。

此函数不会更改变更集中出现的顺序。它只是颠倒了每个单独变化的意义。

如果成功,指向包含反向变更集的缓冲区的指针将存储在 *ppOut 中,相同缓冲区的大小存储在 *pnOut 中,并返回 SQLITE_OK。如果发生错误,*pnOut 和 *ppOut 都会清零并返回 SQLite 错误代码。

成功调用此函数后,调用者有责任最终在 *ppOut 指针上调用 sqlite3_free() 以释放缓冲区分配。

警告/待办事项:此函数当前假定输入是有效的变更集。如果不是,则结果未定义。


从变更集迭代器中获取新的*值

int sqlite3changeset_new(
  sqlite3_changeset_iter *pIter,  /* Changeset iterator */
  int iVal,                       /* Column number */
  sqlite3_value **ppValue         /* OUT: New value (or NULL pointer) */
);

传递给此函数的 pIter 参数可以是通过 sqlite3changeset_apply() 传递给冲突处理程序的迭代器,也可以是通过sqlite3changeset_start()创建的迭代器在后一种情况下,最近调用sqlite3changeset_next()必须返回 SQLITE_ROW。此外,只有当迭代器当前指向的更改类型是SQLITE_UPDATESQLITE_INSERT时,才可以调用它否则,此函数返回SQLITE_MISUSE并将 *ppValue 设置为 NULL。

参数 iVal 必须大于或等于 0,并且小于表中受当前更改影响的列数。否则, 返回SQLITE_RANGE并将 *ppValue 设置为 NULL。

如果成功,此函数将 *ppValue 设置为指向受保护的 sqlite3_value 对象,该对象包含作为 UPDATE 或 INSERT 更改的一部分存储的新行值向量中的第 iVal 个值,并返回 SQLITE_OK。如果更改是 UPDATE 并且不包括请求列的新值,则 *ppValue 设置为 NULL 并返回 SQLITE_OK。该函数的名称来自于它类似于可用于更新或删除触发器的“new.*”列。

如果发生其他错误(例如 OOM 条件),则返回 SQLite 错误代码并将 *ppValue 设置为 NULL。


推进变更集迭代器

int sqlite3changeset_next(sqlite3_changeset_iter *pIter);

此函数只能与函数 sqlite3changeset_start()创建的迭代器一起使用。如果在通过sqlite3changeset_apply()传递给冲突处理程序回调的迭代器上调用它,则返回 SQLITE_MISUSE 并且调用无效。

在 sqlite3changeset_start() 创建迭代器之后,它不会立即指向变更集中的任何更改。假设变更集不为空,第一次调用此函数会使迭代器指向变更集中的第一个变更。每个后续调用都会使迭代器前进以指向变更集中的下一个变更(如果有的话)。如果在调用 sqlite3changeset_next() 后没有错误发生并且迭代器指向有效更改,则返回 SQLITE_ROW。否则,如果已经访问了变更集中的所有变更,则返回 SQLITE_DONE。

如果发生错误,则返回 SQLite 错误代码。可能的错误代码包括 SQLITE_CORRUPT(如果变更集缓冲区已损坏)或 SQLITE_NOMEM。


从变更集迭代器中获取 old.* 值

int sqlite3changeset_old(
  sqlite3_changeset_iter *pIter,  /* Changeset iterator */
  int iVal,                       /* Column number */
  sqlite3_value **ppValue         /* OUT: Old value (or NULL pointer) */
);

传递给此函数的 pIter 参数可以是通过 sqlite3changeset_apply() 传递给冲突处理程序的迭代器,也可以是通过sqlite3changeset_start()创建的迭代器在后一种情况下,最近调用sqlite3changeset_next()必须返回 SQLITE_ROW。此外,只有当迭代器当前指向的更改类型是SQLITE_DELETESQLITE_UPDATE时,才可以调用它否则,此函数返回SQLITE_MISUSE并将 *ppValue 设置为 NULL。

参数 iVal 必须大于或等于 0,并且小于表中受当前更改影响的列数。否则, 返回SQLITE_RANGE并将 *ppValue 设置为 NULL。

如果成功,此函数将 *ppValue 设置为指向受保护的 sqlite3_value 对象,该对象包含作为 UPDATE 或 DELETE 更改的一部分存储的原始行值向量中的第 iVal 个值,并返回 SQLITE_OK。该函数的名称来自于它类似于可用于更新或删除触发器的“old.*”列。

如果发生其他错误(例如 OOM 条件),则返回 SQLite 错误代码并将 *ppValue 设置为 NULL。


从变更集迭代器获取当前操作

int sqlite3changeset_op(
  sqlite3_changeset_iter *pIter,  /* Iterator object */
  const char **pzTab,             /* OUT: Pointer to table name */
  int *pnCol,                     /* OUT: Number of columns in table */
  int *pOp,                       /* OUT: SQLITE_INSERT, DELETE or UPDATE */
  int *pbIndirect                 /* OUT: True for an 'indirect' change */
);

传递给此函数的 pIter 参数可以是通过 sqlite3changeset_apply() 传递给冲突处理程序的迭代器,也可以是通过sqlite3changeset_start()创建的迭代器在后一种情况下,最近一次调用sqlite3changeset_next()必须返回SQLITE_ROW如果不是这种情况,则此函数返回SQLITE_MISUSE

参数 pOp、​​pnCol 和 pzTab 不能为 NULL。返回时,通过这些指针设置三个输出:

*pOp 设置为SQLITE_INSERTSQLITE_DELETESQLITE_UPDATE 之一,具体取决于迭代器当前指向的更改类型;

*pnCol 设置为表中受更改影响的列数;

*pzTab 设置为指向一个以 nul 结尾的 utf-8 编码字符串,其中包含受当前更改影响的表的名称。缓冲区保持有效,直到在迭代器上调用 sqlite3changeset_next() 或直到冲突处理函数返回。

如果 pbIndirect 不为 NULL,则如果更改是间接更改,则 *pbIndirect 设置为真 (1),否则设置为假 (0)。有关直接和间接更改的描述,请参阅 sqlite3session_indirect()的文档。

如果没有错误发生,则返回 SQLITE_OK。如果确实发生错误,则返回 SQLite 错误代码。在这种情况下,输出变量的值可能不可信。


获取表的主键定义

int sqlite3changeset_pk(
  sqlite3_changeset_iter *pIter,  /* Iterator object */
  unsigned char **pabPK,          /* OUT: Array of boolean - true for PK cols */
  int *pnCol                      /* OUT: Number of entries in output array */
);

对于每个修改后的表,变更集包括以下内容:

  • 表中的列数,以及
  • 这些列中的哪些构成了表的 PRIMARY KEY。

此函数用于查找哪些列包含由迭代器 pIter 当前指向的更改修改的表的 PRIMARY KEY。如果成功,*pabPK 将设置为指向 nCol 条目数组,其中 nCol 是表中的列数。如果对应的列是表主键的一部分,则 *pabPK 的元素设置为 0x01,否则设置为 0x00。

如果参数 pnCol 不为 NULL,则 *pnCol 设置为表中的列数。

如果在迭代器未指向有效条目时调用此函数,则返回 SQLITE_MISUSE 并将输出变量清零。否则,返回 SQLITE_OK 并如上所述填充输出变量。


配置变更集 rebaser 对象。

int sqlite3rebaser_configure(
  sqlite3_rebaser*, 
  int nRebase, const void *pRebase
); 

重要提示:此界面是实验性的,如有更改,恕不另行通知。

根据缓冲区 pRebase(大小为 nRebase 字节)描述的冲突解决方案配置变更集 rebaser 对象以对变更集进行 rebase,这必须是从先前调用 sqlite3changeset_apply_v2() 获得的。


创建一个变更集 rebaser 对象。

int sqlite3rebaser_create(sqlite3_rebaser **ppNew);

重要提示:此界面是实验性的,如有更改,恕不另行通知。

分配一个新的变更集 rebaser 对象。如果成功,将 (*ppNew) 设置为指向新对象并返回 SQLITE_OK。否则,如果发生错误,则返回 SQLite 错误代码(例如 SQLITE_NOMEM)并将 (*ppNew) 设置为 NULL。


删除变更集 rebaser 对象。

void sqlite3rebaser_delete(sqlite3_rebaser *p); 

重要提示:此界面是实验性的,如有更改,恕不另行通知。

删除变更集 rebaser 对象和所有关联的资源。每次成功调用 sqlite3rebaser_create() 都应该调用此函数一次。


变基变更集

int sqlite3rebaser_rebase(
  sqlite3_rebaser*,
  int nIn, const void *pIn, 
  int *pnOut, void **ppOut 
);

重要提示:此界面是实验性的,如有更改,恕不另行通知。

参数 pIn 必须指向包含大小为 nIn 字节的变更集的缓冲区。此函数根据作为第一个参数传递的 rebaser 对象的配置,使用 rebased 的变更集副本分配并填充缓冲区。如果成功,将 (*ppOut) 设置为指向包含重新设置基数的变更集的新缓冲区,并将 (*pnOut) 设置为其大小(以字节为单位)并返回 SQLITE_OK。调用者有责任使用 sqlite3_free() 最终释放新缓冲区。否则,如果发生错误,(*ppOut) 和 (*pnOut) 将设置为零并返回 SQLite 错误代码。


将表附加到会话对象

int sqlite3session_attach(
  sqlite3_session *pSession,      /* Session object */
  const char *zTab                /* Table name */
);

如果参数 zTab 不为 NULL,则它是附加到作为第一个参数传递的会话对象的表的名称。将记录启用会话对象时对表所做的所有后续更改。有关详细信息,请参阅sqlite3session_changeset()的文档。

或者,如果参数 zTab 为 NULL,则会记录数据库中所有表的更改。如果在进行此调用后将其他表添加到数据库(通过执行“CREATE TABLE”语句),则还会记录新表的更改。

只能为具有 PRIMARY KEY 明确定义为其 CREATE TABLE 语句的一部分的表记录更改。PRIMARY KEY 是否为“INTEGER PRIMARY KEY”(rowid 别名)并不重要。PRIMARY KEY 可以由单个列组成,也可以是复合键。

如果指定的表在数据库中不存在,这不是错误。如果命名表没有 PRIMARY KEY 也不是错误。但是,在这两种情况下都不会记录任何更改。

对于在一个或多个 PRIMARY KEY 列中存储了 NULL 值的单个行,不会记录更改。

如果调用完成且没有错误,则返回 SQLITE_OK。或者,如果发生错误,则返回 SQLite 错误代码(例如 SQLITE_NOMEM)。

特殊的sqlite_stat1处理

从 SQLite 版本 3.22.0 开始,“sqlite_stat1”表是上述某些规则的例外。在 SQLite 中,sqlite_stat1 的模式是:

       CREATE TABLE sqlite_stat1(tbl,idx,stat)  
 

即使 sqlite_stat1 没有 PRIMARY KEY,也会为它记录更改,就好像 PRIMARY KEY 是 (tbl,idx) 一样。此外,还会记录 (idx IS NULL) 为真的行的更改。但是,对于此类行,零长度的 blob(SQL 值 X'')将存储在变更集或补丁集中,而不是 NULL 值。这允许通过 sqlite3changeset_invert()、concat() 和类似的遗留实现来操作此类变更集。

sqlite3changeset_apply() 函数在更新 sqlite_stat1 表时自动将零长度 blob 转换回 NULL 值。但是,如果应用程序直接在变更集迭代器上调用 sqlite3changeset_new()、sqlite3changeset_old() 或 sqlite3changeset_conflict(包括在传递给冲突处理程序回调的变更集迭代器上),则返回 X'' 值。如果需要,应用程序必须自行将 X'' 转换为 NULL。

会话模块的旧版(早于 3.22.0)无法捕获对 sqlite_stat1 表所做的更改。sqlite3changeset_apply() 函数的旧版本会默默地忽略对属于变更集或补丁集的 sqlite_stat1 表的任何修改。


从会话对象生成变更集

int sqlite3session_changeset(
  sqlite3_session *pSession,      /* Session object */
  int *pnChangeset,               /* OUT: Size of buffer at *ppChangeset */
  void **ppChangeset              /* OUT: Buffer containing changeset */
);

获取一个变更集,其中包含对附加到作为第一个参数传递的会话对象的表的更改。如果成功,在返回 SQLITE_OK 之前,将 *ppChangeset 设置为指向包含变更集的缓冲区,并将 *pnChangeset 设置为变更集的大小(以字节为单位)。如果发生错误,请将 *ppChangeset 和 *pnChangeset 都设置为零并返回 SQLite 错误代码。

变更集由零个或多个 INSERT、UPDATE 和/或 DELETE 变更组成,每个变更代表对附加表的单行的变更。INSERT 更改包含新数据库行的每个字段的值。DELETE 包含已删除数据库行的每个字段的原始值。UPDATE 更改包含更新的数据库行的每个字段的原始值以及每个更新的非主键列的更新值。UPDATE 更改不可能表示修改主键列值的更改。如果进行了这样的更改,它在变更集中表示为 DELETE 后跟 INSERT。

对于在一个或多个 PRIMARY KEY 列中存储了 NULL 值的行,不会记录更改。如果插入或删除这样的行,则此函数返回的变更集中不存在相应的更改。如果在 PRIMARY KEY 列中存储了一个或多个 NULL 值的现有行被更新,以便所有 PRIMARY KEY 列都是非 NULL,则只有 INSERT 出现在变更集中。类似地,如果更新具有非 NULL PRIMARY KEY 值的现有行,使其一个或多个 PRIMARY KEY 列设置为 NULL,则生成的变更集仅包含 DELETE 变更。

可以使用使用sqlite3changeset_start() API 创建的迭代器遍历变更集的内容。可以使用sqlite3changeset_apply() API将变更集应用于具有兼容模式的数据库。

在此函数生成的变更集中,与单个表相关的所有变更都组合在一起。换句话说,当遍历变更集或将变更集应用于数据库时,与单个表相关的所有变更都会在移动到下一个表之前得到处理。表的排序顺序与它们附加(或自动附加)到 sqlite3_session 对象的顺序相同。与单个表相关的更改的存储顺序是未定义的。

成功调用此函数后,调用者有责任使用sqlite3_free()最终释放 *ppChangeset 指向的缓冲区

变更集生成

一旦将表附加到会话对象,会话对象就会记录插入到表中的所有新行的主键值。它还记录任何已删除或已更新行的原始主键和其他列值。对于每个唯一的主键值,数据只记录一次——在会话的生命周期中第一次插入、更新或删除具有所述主键的行。

上一段有一个例外:当插入、更新或删除一行时,如果它的一个或多个主键列包含 NULL 值,则不会记录更改。

因此,会话对象累积两种类型的记录——仅由主键值组成的记录(在用户插入新记录时创建)和由主键值和其他表列的原始值组成的记录(在用户插入新记录时创建)删除或更新记录)。

调用此函数时,将使用累积记录和数据库文件的当前内容创建请求的变更集。具体来说:

  • 对于插入生成的每条记录,都会在数据库中查询具有匹配主键的行。如果找到,则会将 INSERT 更改添加到更改集中。如果没有找到这样的行,则不会向变更集添加任何变更。

  • 对于更新或删除生成的每条记录,查询数据库以查找具有匹配主键的行。如果找到这样的一行并且一个或多个非主键字段已从其原始值进行了修改,则将 UPDATE 更改添加到更改集中。或者,如果在表中找不到这样的行,则将 DELETE 更改添加到更改集中。如果数据库中有一行具有匹配的主键,但所有字段都包含其原始值,则不会向变更集添加任何更改。

这意味着,除其他事项外,如果在会话对象处于活动状态时插入一行然后稍后将其删除,则插入和删除都不会出现在变更集中。或者,如果删除一行,然后在会话对象处于活动状态时插入具有相同主键值的行,则生成的变更集将包含 UPDATE 更改,而不是 DELETE 和 INSERT。

当禁用会话对象时(请参阅sqlite3session_enable() API),它不会在插入、更新或删除行时累积记录。如果在会话期间不止一次写入单个行,这可能会产生一些违反直觉的效果。例如,如果在启用会话对象时插入一行,然后在禁用同一会话对象时删除行,则即使删除发生在禁用会话时,变更集中也不会出现 INSERT 记录。或者,如果在禁用会话时更新行的一个字段,而在启用会话时更新同一行的另一个字段,则生成的变更集将包含更新这两个字段的 UPDATE 变更。


返回变更集大小的上限

sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession);

默认情况下,此函数始终返回 0。为了返回有用的结果,必须使用带有 SQLITE_SESSION_OBJCONFIG_SIZE 动词的 sqlite3session_object_config() 将 sqlite3_session 对象配置为启用此 API。

启用后,此函数返回一个上限(以字节为单位),用于如果调用 sqlite3session_changeset() 可能产生的变更集大小。最终的变更集大小可能等于或小于此函数返回的字节大小。


配置全局参数

int sqlite3session_config(int op, void *pArg);

sqlite3session_config() 接口用于对会话模块进行全局配置更改,以便根据应用程序的特定需求对其进行调整。

sqlite3session_config() 接口不是线程安全的。如果在任何其他线程位于任何其他会话方法内时调用它,则结果是未定义的。此外,如果在创建任何与会话相关的对象之后调用它,则结果也是未定义的。

sqlite3session_config() 函数的第一个参数必须是下面定义的 SQLITE_SESSION_CONFIG_XXX 常量之一。作为第二个参数传递的 (void*) 值的解释以及调用此函数的效果取决于第一个参数的值。

SQLITE_SESSION_CONFIG_STRMSIZE
默认情况下,会话模块流接口尝试以大约 1 KiB 块的形式输入和输出数据。此操作数可用于设置和查询此配置设置的值。作为第二个参数传递的指针必须指向类型 (int) 的值。如果此值大于 0,则将其用作输入和输出的新流式数据块大小。在返回之前,将 pArg 指向的 (int) 值设置为流接口块大小的最终值。

如果成功,此函数返回 SQLITE_OK,否则返回 SQLite 错误代码。


创建一个新的会话对象

int sqlite3session_create(
  sqlite3 *db,                    /* Database handle */
  const char *zDb,                /* Name of db (e.g. "main") */
  sqlite3_session **ppSession     /* OUT: New session object */
);

创建附加到数据库句柄 db 的新会话对象。如果成功,指向新对象的指针将写入 *ppSession 并返回 SQLITE_OK。如果发生错误,*ppSession 设置为 NULL 并返回 SQLite 错误代码(例如 SQLITE_NOMEM)。

可以创建附加到单个数据库句柄的多个会话对象。

使用此函数创建的会话对象应该在 它们附加到的数据库句柄本身关闭之前使用sqlite3session_delete()函数删除。如果在删除会话对象之前关闭数据库句柄,则调用任何会话模块函数(包括会话对象上的sqlite3session_delete())的结果是未定义的。

因为会话模块使用sqlite3_preupdate_hook() API,所以应用程序不可能在附加了一个或多个会话对象的数据库句柄上注册预更新挂钩。也不可能创建附加到已定义预更新挂钩的数据库句柄的会话对象。尝试这些事情中的任何一个的结果是不确定的。

会话对象将用于为数据库 zDb 中的表创建变更集,其中 zDb 是“main”或“temp”,或者是附加数据库的名称。如果在创建会话对象时数据库 zDb 未附加到数据库,则不是错误。


删除会话对象

void sqlite3session_delete(sqlite3_session *pSession);

删除先前使用 sqlite3session_create()分配的会话对象。一旦会话对象被删除,尝试将 pSession 与任何其他会话模块函数一起使用的结果是不确定的。

会话对象必须在它们所附加的数据库句柄关闭之前被删除。有关详细信息,请参阅 sqlite3session_create()的文档。


将表之间的差异加载到会话中

int sqlite3session_diff(
  sqlite3_session *pSession,
  const char *zFromDb,
  const char *zTbl,
  char **pzErrMsg
);

如果它尚未附加到作为第一个参数传递的会话对象,则此函数以与 sqlite3session_attach()函数相同的方式附加表 zTbl。如果 zTbl 不存在,或者它没有主键,则此函数是空操作(但不返回错误)。

参数 zFromDb 必须是数据库的名称(“main”、“temp”等)附加到与会话对象相同的数据库句柄,该会话对象包含与此函数附加到会话的表兼容的表。一个表被认为是兼容的,如果它:

  • 有相同的名字,
  • 具有以相同顺序声明的相同列集,并且
  • 具有相同的 PRIMARY KEY 定义。

如果表不兼容,则返回 SQLITE_SCHEMA。如果表兼容但没有任何 PRIMARY KEY 列,这不是错误,但不会向会话对象添加任何更改。与其他会话 API 一样,没有 PRIMARY KEY 的表将被忽略。

此函数向会话对象添加一组更改,可用于更新数据库 zFrom 中的表(称为“from-table”),以便其内容与附加到会话对象的表相同(称为“到桌”)。具体来说:

  • 对于目标表中存在但源表中不存在的每一行(主键),将向会话对象添加一条 INSERT 记录。

  • 对于目标表中存在但源表中不存在的每一行(主键),都会将 DELETE 记录添加到会话对象中。

  • 对于存在于两个表中但具有不同非 PK 值的每一行(主键),将更新记录添加到会话中。

澄清一下,如果调用此函数然后使用sqlite3session_changeset()构造变更集,那么在将该变更集应用到数据库 zFrom 之后,两个兼容表的内容将是相同的。

如果数据库 zFrom 不存在或不包含所需的兼容表,则会出错。

如果操作成功,则返回 SQLITE_OK。否则,SQLite 错误代码。在这种情况下,如果参数 pzErrMsg 不为 NULL,*pzErrMsg 可以设置为指向包含英语错误消息的缓冲区。调用者有责任使用 sqlite3_free() 释放此缓冲区。


启用或禁用会话对象

int sqlite3session_enable(sqlite3_session *pSession, int bEnable);

启用或禁用会话对象的更改记录。启用后,会话对象会记录对数据库所做的更改。禁用时 - 它不会。启用新创建的会话对象。有关启用和禁用会话对象如何影响最终变更集的更多详细信息,请参阅sqlite3session_changeset()的文档。

将零传递给此函数将禁用会话。传递大于零的值将启用它。传递小于零的值是空操作,可用于查询会话的当前状态。

返回值指示会话对象的最终状态:如果禁用会话则为 0,如果启用则为 1。


设置或清除间接更改标志

int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect);

会话对象记录的每个更改都被标记为直接或间接。如果发生以下任一情况,则更改被标记为间接更改:

  • 会话对象“间接”标志在进行更改时设置,或者
  • 更改是由 SQL 触发器或外键操作而不是直接作为用户 SQL 语句的结果进行的。

如果单个行受到会话中多个操作的影响,则如果所有操作都满足上述间接更改的条件,则更改被视为间接更改,否则视为直接更改。

该函数用于设置、清除或查询会话对象的间接标志。如果传递给此函数的第二个参数为零,则清除间接标志。如果它大于零,则设置间接标志。传递小于零的值不会修改间接标志的当前值,并且可用于查询指定会话对象的间接标志的当前状态。

返回值指示间接标志的最终状态:如果清除则为 0,如果已设置则为 1。


测试变更集是否记录了任何变更。

int sqlite3session_isempty(sqlite3_session *pSession);

如果作为第一个参数传递的会话对象没有记录对附加表的更改,则返回非零值。否则,如果记录了一项或多项更改,则返回零。

即使此函数返回零,在会话句柄上调用 sqlite3session_changeset()仍可能返回不包含任何更改的变更集。当修改附加表中的行然后恢复原始值时,可能会发生这种情况。但是,如果此函数返回非零值,则可以保证对 sqlite3session_changeset() 的调用将返回包含零更改的变更集。


查询会话对象使用的堆内存量。

sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession);

此 API 返回作为唯一参数传递的会话对象当前使用的堆内存总量(以字节为单位)。


从会话对象生成补丁集

int sqlite3session_patchset(
  sqlite3_session *pSession,      /* Session object */
  int *pnPatchset,                /* OUT: Size of buffer at *ppPatchset */
  void **ppPatchset               /* OUT: Buffer containing patchset */
);

补丁集和变更集之间的区别在于:

  • DELETE 记录仅包含主键字段。省略其他字段的原始值。
  • UPDATE 记录中省略了任何已修改字段的原始值。

补丁集 blob 可以与所有 sqlite3changeset_xxx API 函数的最新版本一起使用,但 sqlite3changeset_invert() 除外,如果传递了补丁集,它会返回 SQLITE_CORRUPT。同样,尝试将补丁集 blob 与旧版本的 sqlite3changeset_xxx API 一起使用也会引发 SQLITE_CORRUPT 错误。

因为省略了非主键“old.*”字段,所以如果将补丁集传递给 sqlite3changeset_apply() API,则不会检测到或报告 SQLITE_CHANGESET_DATA 冲突。其他冲突类型的工作方式与变更集相同。

补丁集内的更改的排序方式与 sqlite3session_changeset() 函数生成的更改集的排序方式相同(即单个表的所有更改都分组在一起,表按照它们附加到会话对象的顺序出现)。


在会话对象上设置表过滤器。

void sqlite3session_table_filter(
  sqlite3_session *pSession,      /* Session object */
  int(*xFilter)(
    void *pCtx,                   /* Copy of third arg to _filter_table() */
    const char *zTab              /* Table name */
  ),
  void *pCtx                      /* First argument passed to xFilter */
);

第二个参数 (xFilter) 是“过滤器回调”。对于未附加到 Session 对象的表中行的更改,将调用过滤器以确定是否应跟踪对表行的更改。如果 xFilter 返回 0,则不跟踪更改。请注意,一旦附加了一个表,就不会再次调用 xFilter。


sqlite3changeset_apply_v2 的标志

#define SQLITE_CHANGESETAPPLY_NOSAVEPOINT   0x0001
#define SQLITE_CHANGESETAPPLY_INVERT        0x0002

以下标志可以通过第 9 个参数传递给 sqlite3changeset_apply_v2sqlite3changeset_apply_v2_strm

SQLITE_CHANGESETAPPLY_NOSAVEPOINT
通常,会话模块将通过一次调用 apply_v2() 或 apply_v2_strm() 执行的所有操作包含在SAVEPOINT中。如果成功应用变更集或补丁集,则提交 SAVEPOINT,如果发生错误,则回滚。指定此标志会导致会话模块忽略此保存点。在这种情况下,如果调用者在调用 apply_v2() 时有一个打开的事务或保存点,它可能会通过回滚来恢复部分应用的变更集。

SQLITE_CHANGESETAPPLY_INVERT
在应用之前反转变更集。这相当于在应用之前使用 sqlite3changeset_invert() 反转变更集。用补丁集指定这个标志是错误的。


冲突处理程序返回的常量

#define SQLITE_CHANGESET_OMIT       0
#define SQLITE_CHANGESET_REPLACE    1
#define SQLITE_CHANGESET_ABORT      2

冲突处理程序回调必须返回以下三个值之一。

SQLITE_CHANGESET_OMIT
如果冲突处理程序返回此值,则不会采取任何特殊操作。不应用导致冲突的更改。会话模块继续到变更集中的下一个变更。

SQLITE_CHANGESET_REPLACE
如果冲突处理程序的第二个参数是 SQLITE_CHANGESET_DATA 或 SQLITE_CHANGESET_CONFLICT,则只能返回此值。如果不是这种情况,则回滚到目前为止应用的任何更改,并且对 sqlite3changeset_apply() 的调用返回 SQLITE_MISUSE。

如果 CHANGESET_REPLACE 由 SQLITE_CHANGESET_DATA 冲突处理程序返回,则冲突行将被更新或删除,具体取决于更改的类型。

如果 CHANGESET_REPLACE 由 SQLITE_CHANGESET_CONFLICT 冲突处理程序返回,则冲突行将从数据库中删除,并进行第二次应用更改的尝试。如果第二次尝试失败,则在继续之前将原始行恢复到数据库中。

SQLITE_CHANGESET_ABORT
如果返回此值,则回滚到目前为止应用的任何更改,并且对 sqlite3changeset_apply() 的调用返回 SQLITE_ABORT。


传递给冲突处理程序的常量

#define SQLITE_CHANGESET_DATA        1
#define SQLITE_CHANGESET_NOTFOUND    2
#define SQLITE_CHANGESET_CONFLICT    3
#define SQLITE_CHANGESET_CONSTRAINT  4
#define SQLITE_CHANGESET_FOREIGN_KEY 5

可以作为第二个参数传递给冲突处理程序的值。

SQLITE_CHANGESET_DATA
如果数据库中存在具有所需 PRIMARY KEY 字段的行,但一个或多个其他(非主键)字段被更新修改,则在处理 DELETE 或 UPDATE 更改时,冲突处理程序将以 CHANGESET_DATA 作为第二个参数调用不包含预期的“之前”值。

在这种情况下,冲突行是具有匹配主键的数据库行。

SQLITE_CHANGESET_NOTFOUND
如果数据库中不存在具有所需 PRIMARY KEY 字段的行,则在处理 DELETE 或 UPDATE 更改时,将使用 CHANGESET_NOTFOUND 作为第二个参数调用冲突处理程序。

在这种情况下没有冲突的行。调用 sqlite3changeset_conflict() API 的结果是未定义的。

SQLITE_CHANGESET_CONFLICT
如果操作会导致重复的主键值,则 CHANGESET_CONFLICT 在处理 INSERT 更改时作为第二个参数传递给冲突处理程序。

在这种情况下,冲突行是具有匹配主键的数据库行。

SQLITE_CHANGESET_FOREIGN_KEY
如果启用了外键处理,并且应用变更集会使数据库处于包含外键违规的状态,则在提交变更集之前,将使用 CHANGESET_FOREIGN_KEY 作为第二个参数调用冲突处理程序。如果冲突处理程序返回 CHANGESET_OMIT,则提交更改,包括那些导致违反外键约束的更改。或者,如果它返回 CHANGESET_ABORT,则回滚变更集。

未提供当前或冲突的行信息。唯一可以调用提供的 sqlite3_changeset_iter 句柄的函数是 sqlite3changeset_fk_conflicts()。

SQLITE_CHANGESET_CONSTRAINT
如果在应用更改时发生任何其他约束冲突(即 UNIQUE、CHECK 或 NOT NULL 约束),将使用 CHANGESET_CONSTRAINT 作为第二个参数调用冲突处理程序。

在这种情况下没有冲突的行。调用 sqlite3changeset_conflict() API 的结果是未定义的。


API函数的流版本。

int sqlite3changeset_apply_strm(
  sqlite3 *db,                    /* Apply change to "main" db of this handle */
  int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
  void *pIn,                                          /* First arg for xInput */
  int(*xFilter)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    const char *zTab              /* Table name */
  ),
  int(*xConflict)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  ),
  void *pCtx                      /* First argument passed to xConflict */
);
int sqlite3changeset_apply_v2_strm(
  sqlite3 *db,                    /* Apply change to "main" db of this handle */
  int (*xInput)(void *pIn, void *pData, int *pnData), /* Input function */
  void *pIn,                                          /* First arg for xInput */
  int(*xFilter)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    const char *zTab              /* Table name */
  ),
  int(*xConflict)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  ),
  void *pCtx,                     /* First argument passed to xConflict */
  void **ppRebase, int *pnRebase,
  int flags
);
int sqlite3changeset_concat_strm(
  int (*xInputA)(void *pIn, void *pData, int *pnData),
  void *pInA,
  int (*xInputB)(void *pIn, void *pData, int *pnData),
  void *pInB,
  int (*xOutput)(void *pOut, const void *pData, int nData),
  void *pOut
);
int sqlite3changeset_invert_strm(
  int (*xInput)(void *pIn, void *pData, int *pnData),
  void *pIn,
  int (*xOutput)(void *pOut, const void *pData, int nData),
  void *pOut
);
int sqlite3changeset_start_strm(
  sqlite3_changeset_iter **pp,
  int (*xInput)(void *pIn, void *pData, int *pnData),
  void *pIn
);
int sqlite3changeset_start_v2_strm(
  sqlite3_changeset_iter **pp,
  int (*xInput)(void *pIn, void *pData, int *pnData),
  void *pIn,
  int flags
);
int sqlite3session_changeset_strm(
  sqlite3_session *pSession,
  int (*xOutput)(void *pOut, const void *pData, int nData),
  void *pOut
);
int sqlite3session_patchset_strm(
  sqlite3_session *pSession,
  int (*xOutput)(void *pOut, const void *pData, int nData),
  void *pOut
);
int sqlite3changegroup_add_strm(sqlite3_changegroup*, 
    int (*xInput)(void *pIn, void *pData, int *pnData),
    void *pIn
);
int sqlite3changegroup_output_strm(sqlite3_changegroup*,
    int (*xOutput)(void *pOut, const void *pData, int nData), 
    void *pOut
);
int sqlite3rebaser_rebase_strm(
  sqlite3_rebaser *pRebaser,
  int (*xInput)(void *pIn, void *pData, int *pnData),
  void *pIn,
  int (*xOutput)(void *pOut, const void *pData, int nData),
  void *pOut
);

六个流式 API xxx_strm() 函数用于与相应的非流式 API 函数类似的目的:

串流功能非流等效
sqlite3changeset_apply_strmsqlite3changeset_apply
sqlite3changeset_apply_strm_v2sqlite3changeset_apply_v2
sqlite3changeset_concat_strmsqlite3changeset_concat
sqlite3changeset_invert_strmsqlite3changeset_invert
sqlite3changeset_start_strmsqlite3changeset_start
sqlite3session_changeset_strmsqlite3session_changeset
sqlite3session_patchset_strmsqlite3session_patchset

接受变更集(或补丁集)作为输入的非流函数要求将整个变更集存储在内存中的单个缓冲区中。类似地,返回变更集或补丁集的那些通过返回指向使用 sqlite3_malloc() 分配的单个大缓冲区的指针来实现。通常这很方便。但是,如果需要在低内存环境中运行的应用程序处理非常大的变更集,则所需的大量连续内存分配可能会变得繁重。

为了避免这个问题,而不是单个大缓冲区,输入通过回调函数传递给流式 API 函数,会话模块调用该回调函数以根据需要递增地请求输入数据。在所有情况下,一对 API 函数参数,例如

       int nChangeset,
       void *pChangeset,
 

被替换为:

       int (*xInput)(void *pIn, void *pData, int *pnData),
       void *pIn,
 

每次会话模块调用 xInput 回调时,传递的第一个参数是提供的 pIn 上下文指针的副本。第二个参数 pData 指向大小为 (*pnData) 字节的缓冲区。假设没有错误发生,xInput 方法应该将最多 (*pnData) 字节的数据复制到缓冲区中,并在返回 SQLITE_OK 之前将 (*pnData) 设置为复制的实际字节数。如果输入完全耗尽,(*pnData) 应设置为零以表明这一点。或者,如果发生错误,则应返回 SQLite 错误代码。在所有情况下,如果 xInput 回调返回错误,所有处理都将被放弃,流式 API 函数将错误代码的副本返回给调用者。

在 sqlite3changeset_start_strm() 的情况下,xInput 回调可以在迭代器生命周期内的任何时候由会话模块调用。如果此类 xInput 回调返回错误,迭代器将进入错误状态,所有后续对迭代器函数的调用将立即失败,并返回与 xInput 返回的相同错误代码。

类似地,返回变更集(或补丁集)的流式 API 函数通过回调函数而不是通过指向单个大缓冲区的指针以块的形式返回它们。在这种情况下,一对参数例如:

       int *pnChangeset,
       void **ppChangeset,
 

被替换为:

       int (*xOutput)(void *pOut, const void *pData, int nData),
       void *pOut
 

xOutput 回调被调用零次或多次以将数据返回到应用程序。传递给每个调用的第一个参数是应用程序提供的 pOut 指针的副本。第二个参数 pData 指向一个大小为 nData 字节的缓冲区,其中包含要返回的输出数据块。如果 xOutput 回调成功处理了提供的数据,它应该返回 SQLITE_OK 以指示成功。否则,它应该返回一些其他的 SQLite 错误代码。在这种情况下,处理会立即被放弃,流式 API 函数会将 xOutput 错误代码的副本返回给应用程序。

会话模块从不调用第三个参数设置为小于或等于零的值的 xOutput 回调。除此之外,不保证返回的数据块的大小。


将变更集应用于数据库

int sqlite3changeset_apply(
  sqlite3 *db,                    /* Apply change to "main" db of this handle */
  int nChangeset,                 /* Size of changeset in bytes */
  void *pChangeset,               /* Changeset blob */
  int(*xFilter)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    const char *zTab              /* Table name */
  ),
  int(*xConflict)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  ),
  void *pCtx                      /* First argument passed to xConflict */
);
int sqlite3changeset_apply_v2(
  sqlite3 *db,                    /* Apply change to "main" db of this handle */
  int nChangeset,                 /* Size of changeset in bytes */
  void *pChangeset,               /* Changeset blob */
  int(*xFilter)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    const char *zTab              /* Table name */
  ),
  int(*xConflict)(
    void *pCtx,                   /* Copy of sixth arg to _apply() */
    int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
    sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  ),
  void *pCtx,                     /* First argument passed to xConflict */
  void **ppRebase, int *pnRebase, /* OUT: Rebase data */
  int flags                       /* SESSION_CHANGESETAPPLY_* flags */
);

将变更集或补丁集应用于数据库。这些函数尝试使用通过第二个和第三个参数传递的变更集中发现的变更来更新附加到 handle db 的“主”数据库。

传递给这些函数的第四个参数 (xFilter) 是“过滤器回调”。如果它不为 NULL,则对于受变更集中至少一个更改影响的每个表,将调用过滤器回调,并将表名作为第二个参数,并将上下文指针的副本作为第六个参数作为第一个参数传递。如果“过滤器回调”返回零,则不会尝试对表应用任何更改。否则,如果返回值非零或 xFilter 参数为 NULL,则尝试与表相关的所有更改。

对于过滤器回调未排除的每个表,此函数测试目标数据库是否包含兼容表。如果满足以下所有条件,则表被认为是兼容的:

  • 该表与变更集中记录的名称具有相同的名称,并且
  • 该表至少具有与变更集中记录的一样多的列,并且
  • 该表的主键列与变更集中记录的位置相同。

如果没有兼容的表,这不是错误,但不会应用与该表关联的任何更改。通过 sqlite3_log() 机制发出警告消息,错误代码为 SQLITE_SCHEMA。最多为变更集中的每个表发出一个这样的警告。

对于有兼容表的每个更改,都会尝试根据 UPDATE、INSERT 或 DELETE 更改修改表内容。如果不能干净地应用更改,则可以调用作为第五个参数传递给 sqlite3changeset_apply() 的冲突处理函数。下面描述了何时为每种类型的更改调用冲突处理程序的确切时间。

与 xFilter 参数不同,xConflict 不能传递 NULL。将有效函数指针以外的任何内容作为 xConflict 参数传递的结果是未定义的。

每次调用冲突处理程序函数时,它必须返回SQLITE_CHANGESET_OMITSQLITE_CHANGESET_ABORTSQLITE_CHANGESET_REPLACE之一。如果传递给冲突处理程序的第二个参数是 SQLITE_CHANGESET_DATA 或 SQLITE_CHANGESET_CONFLICT,则只能返回 SQLITE_CHANGESET_REPLACE。如果冲突处理程序返回非法值,则已进行的任何更改都将回滚并且对 sqlite3changeset_apply() 的调用返回 SQLITE_MISUSE。sqlite3changeset_apply() 根据每次调用冲突处理程序函数返回的值采取不同的操作。有关详细信息,请参阅有关三个 可用返回值的文档。

删除更改
对于每个 DELETE 更改,该函数检查目标数据库是否包含与存储在变更集中的原始行值具有相同主键值(或多个值)的行。如果是,并且存储在所有非主键列中的值也与存储在变更集中的值相匹配,则该行将从目标数据库中删除。

如果找到具有匹配主键值的行,但一个或多个非主键字段包含与存储在变更集中的原始行值不同的值,则使用SQLITE_CHANGESET_DATA作为第二个参数调用冲突处理函数。如果数据库表的列多于变更集中记录的列,则仅将那些非主键字段的值与当前数据库内容进行比较——忽略任何尾随的数据库表列。

如果在数据库中找不到具有匹配主键值的行,则调用冲突处理函数,并将SQLITE_CHANGESET_NOTFOUND 作为第二个参数传递。

如果尝试执行 DELETE 操作,但 SQLite 返回 SQLITE_CONSTRAINT(只有在违反外键约束时才会发生),将调用冲突处理函数,并将SQLITE_CHANGESET_CONSTRAINT 作为第二个参数传递。这包括尝试 DELETE 操作的情况,因为之前对冲突处理函数的调用返回了SQLITE_CHANGESET_REPLACE

插入更改
对于每个 INSERT 更改,都会尝试将新行插入数据库。如果变更集行包含的字段少于数据库表,则尾随字段将使用它们的默认值填充。

如果因为数据库已经包含具有相同主键值的行而尝试插入行失败,则调用冲突处理函数并将第二个参数设置为 SQLITE_CHANGESET_CONFLICT

如果由于某些其他约束违规(例如 NOT NULL 或 UNIQUE)而导致插入行的尝试失败,则调用冲突处理程序函数并将第二个参数设置为SQLITE_CHANGESET_CONSTRAINT这包括重新尝试 INSERT 操作的情况,因为之前对冲突处理函数的调用返回了 SQLITE_CHANGESET_REPLACE

更新更改
对于每个 UPDATE 更改,该函数检查目标数据库是否包含与存储在变更集中的原始行值具有相同主键值(或多个值)的行。如果是,并且存储在所有修改后的非主键列中的值也与存储在变更集中的值相匹配,则在目标数据库中更新该行。

如果找到具有匹配主键值的行,但一个或多个修改后的非主键字段包含与存储在变更集中的原始行值不同的值,则使用SQLITE_CHANGESET_DATA作为第二个参数调用冲突处理函数. 由于 UPDATE 更改仅包含要修改的非主键字段的值,因此只有那些字段需要与原始值匹配以避免 SQLITE_CHANGESET_DATA 冲突处理程序回调。

如果在数据库中找不到具有匹配主键值的行,则调用冲突处理函数,并将SQLITE_CHANGESET_NOTFOUND 作为第二个参数传递。

如果尝试执行 UPDATE 操作,但 SQLite 返回 SQLITE_CONSTRAINT,则会调用冲突处理函数,并将 SQLITE_CHANGESET_CONSTRAINT作为第二个参数传递。这包括在先前调用冲突处理程序函数返回SQLITE_CHANGESET_REPLACE之后尝试更新操作的情况

从 xConflict 回调中执行 SQL 语句是安全的,包括那些写入与回调相关的表的语句。这可用于进一步自定义应用程序的冲突解决策略。

这些函数所做的所有更改都包含在保存点事务中。如果发生任何其他错误(尝试写入目标数据库时约束失败除外),则回滚保存点事务,将目标数据库恢复到其原始状态,并返回 SQLite 错误代码。

如果输出参数 (ppRebase) 和 (pnRebase) 是非 NULL 并且输入是一个变更集(不是补丁集),那么 sqlite3changeset_apply_v2() 可以设置 (*ppRebase) 指向一个可以与sqlite3_rebaser APIs 在返回之前缓冲。在这种情况下,(*pnRebase) 被设置为以字节为单位的缓冲区大小。调用者有责任使用 sqlite3_free() 最终释放任何此类缓冲区。只有在应用补丁集时遇到一个或多个冲突时,才会分配和填充缓冲区。有关详细信息,请参阅围绕 sqlite3_rebaser API 的评论。

sqlite3changeset_apply_v2() 及其等效流的行为可以通过将 支持的标志组合作为第 9 个参数传递来修改。

请注意,sqlite3changeset_apply_v2() API 仍处于实验阶段 ,因此可能会发生变化。


创建迭代器以遍历变更集

int sqlite3changeset_start(
  sqlite3_changeset_iter **pp,    /* OUT: New changeset iterator handle */
  int nChangeset,                 /* Size of changeset blob in bytes */
  void *pChangeset                /* Pointer to blob containing changeset */
);
int sqlite3changeset_start_v2(
  sqlite3_changeset_iter **pp,    /* OUT: New changeset iterator handle */
  int nChangeset,                 /* Size of changeset blob in bytes */
  void *pChangeset,               /* Pointer to blob containing changeset */
  int flags                       /* SESSION_CHANGESETSTART_* flags */
);

创建用于遍历变更集内容的迭代器。如果成功,*pp 被设置为指向迭代器句柄并返回 SQLITE_OK。否则,如果发生错误,*pp 将设置为零并返回 SQLite 错误代码。

以下函数可用于推进和查询由此函数创建的变更集迭代器:

调用者有责任通过将迭代器传递给sqlite3changeset_finalize()来最终销毁迭代器。包含变更集 (pChangeset) 的缓冲区必须保持有效,直到迭代器被销毁。

假设变更集 blob 是由 sqlite3session_changeset()sqlite3changeset_concat()sqlite3changeset_invert()函数之一创建的,应用于单个表的变更集中的所有变更都组合在一起。这意味着当应用程序使用由此函数创建的迭代器遍历变更集时,将连续访问与单个表相关的所有变更。迭代器不可能访问适用于表 X 的更改,然后访问适用于表 Y 的更改,然后再访问适用于表 X 的另一个更改。

sqlite3changeset_start_v2() 及其等效流的行为可以通过将 支持的标志的组合作为第四个参数传递来修改。

请注意,sqlite3changeset_start_v2() API 仍处于实验阶段 ,因此可能会发生变化。