--- /dev/null
+# 2016 August 23
+#
+# The author disclaims copyright to this source code. In place of
+# a legal notice, here is a blessing:
+#
+# May you do good and not evil.
+# May you find forgiveness for yourself and forgive others.
+# May you share freely, never taking more than you give.
+#
+#***********************************************************************
+# This file implements regression tests for SQLite library.
+#
+
+if {![info exists testdir]} {
+ set testdir [file join [file dirname [info script]] .. .. test]
+}
+source $testdir/tester.tcl
+ifcapable !session {finish_test; return}
+
+set testprefix changebatch1
+
+proc do_changebatch_test {tn args} {
+ set C [list]
+ foreach a $args {
+ sqlite3session S db main
+ S attach *
+ execsql $a
+ lappend C [S changeset]
+ S delete
+ }
+
+ sqlite3changebatch cb db
+ set i 1
+ foreach ::cs [lrange $C 0 end-1] {
+ do_test $tn.$i { cb add [set ::cs] } SQLITE_OK
+ incr i
+ }
+
+ set ::cs [lindex $C end]
+ do_test $tn.$i { cb add [set ::cs] } SQLITE_CONSTRAINT
+
+ cb delete
+}
+
+do_execsql_test 1.0 {
+ CREATE TABLE t1(a PRIMARY KEY, b);
+}
+
+do_changebatch_test 1.1 {
+ INSERT INTO t1 VALUES(1, 1);
+} {
+ DELETE FROM t1 WHERE a=1;
+}
+
+finish_test
--- /dev/null
+
+#if !defined(SQLITE_TEST) || (defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK))
+
+#include "sqlite3session.h"
+#include "sqlite3changebatch.h"
+
+#include <assert.h>
+#include <string.h>
+
+typedef struct BatchTable BatchTable;
+typedef struct BatchIndex BatchIndex;
+typedef struct BatchIndexEntry BatchIndexEntry;
+typedef struct BatchHash BatchHash;
+
+struct sqlite3_changebatch {
+ sqlite3 *db; /* Database handle used to read schema */
+ BatchTable *pTab; /* First in linked list of tables */
+ int iChangesetId; /* Current changeset id */
+ int iNextIdxId; /* Next available index id */
+ int nEntry; /* Number of entries in hash table */
+ int nHash; /* Number of hash buckets */
+ BatchIndexEntry **apHash; /* Array of hash buckets */
+};
+
+struct BatchTable {
+ BatchIndex *pIdx; /* First in linked list of UNIQUE indexes */
+ BatchTable *pNext; /* Next table */
+ char zTab[1]; /* Table name */
+};
+
+struct BatchIndex {
+ BatchIndex *pNext; /* Next index on same table */
+ int iId; /* Index id (assigned internally) */
+ int bPk; /* True for PK index */
+ int nCol; /* Size of aiCol[] array */
+ int *aiCol; /* Array of columns that make up index */
+};
+
+struct BatchIndexEntry {
+ BatchIndexEntry *pNext; /* Next colliding hash table entry */
+ int iChangesetId; /* Id of associated changeset */
+ int iIdxId; /* Id of index this key is from */
+ int szRecord;
+ char aRecord[1];
+};
+
+/*
+** Allocate and zero a block of nByte bytes. Must be freed using cbFree().
+*/
+static void *cbMalloc(int *pRc, int nByte){
+ void *pRet;
+
+ if( *pRc ){
+ pRet = 0;
+ }else{
+ pRet = sqlite3_malloc(nByte);
+ if( pRet ){
+ memset(pRet, 0, nByte);
+ }else{
+ *pRc = SQLITE_NOMEM;
+ }
+ }
+
+ return pRet;
+}
+
+/*
+** Free an allocation made by cbMalloc().
+*/
+static void cbFree(void *p){
+ sqlite3_free(p);
+}
+
+/*
+** Return the hash bucket that pEntry belongs in.
+*/
+static int cbHash(sqlite3_changebatch *p, BatchIndexEntry *pEntry){
+ unsigned int iHash = (unsigned int)pEntry->iIdxId;
+ unsigned char *pEnd = (unsigned char*)&pEntry->aRecord[pEntry->szRecord];
+ unsigned char *pIter;
+
+ for(pIter=pEntry->aRecord; pIter<pEnd; pIter++){
+ iHash += (iHash << 7) + *pIter;
+ }
+
+ return (int)(iHash % p->nHash);
+}
+
+/*
+** Resize the hash table.
+*/
+static int cbHashResize(sqlite3_changebatch *p){
+ int rc = SQLITE_OK;
+ BatchIndexEntry **apNew;
+ int nNew = (p->nHash ? p->nHash*2 : 512);
+ int i;
+
+ apNew = cbMalloc(&rc, sizeof(BatchIndexEntry*) * nNew);
+ if( rc==SQLITE_OK ){
+ int nHash = p->nHash;
+ p->nHash = nNew;
+ for(i=0; i<nHash; i++){
+ BatchIndexEntry *pEntry;
+ while( pEntry=p->apHash[i] ){
+ int iHash = cbHash(p, pEntry);
+ p->apHash[i] = pEntry->pNext;
+ pEntry->pNext = apNew[iHash];
+ apNew[iHash] = pEntry;
+ }
+ }
+
+ cbFree(p->apHash);
+ p->apHash = apNew;
+ }
+
+ return rc;
+}
+
+
+/*
+** Allocate a new sqlite3_changebatch object.
+*/
+int sqlite3changebatch_new(sqlite3 *db, sqlite3_changebatch **pp){
+ sqlite3_changebatch *pRet;
+ int rc = SQLITE_OK;
+ *pp = pRet = (sqlite3_changebatch*)cbMalloc(&rc, sizeof(sqlite3_changebatch));
+ if( pRet ){
+ pRet->db = db;
+ }
+ return rc;
+}
+
+/*
+** Add a BatchIndex entry for index zIdx to table pTab.
+*/
+static int cbAddIndex(
+ sqlite3_changebatch *p,
+ BatchTable *pTab,
+ const char *zIdx,
+ int bPk
+){
+ int nCol = 0;
+ sqlite3_stmt *pIndexInfo = 0;
+ BatchIndex *pNew = 0;
+ int rc;
+ char *zIndexInfo;
+
+ zIndexInfo = (char*)sqlite3_mprintf("PRAGMA main.index_info = %Q", zIdx);
+ if( zIndexInfo ){
+ rc = sqlite3_prepare_v2(p->db, zIndexInfo, -1, &pIndexInfo, 0);
+ sqlite3_free(zIndexInfo);
+ }else{
+ rc = SQLITE_NOMEM;
+ }
+
+ if( rc==SQLITE_OK ){
+ int rc2;
+ while( SQLITE_ROW==sqlite3_step(pIndexInfo) ){ nCol++; }
+ rc2 = sqlite3_reset(pIndexInfo);
+ if( rc==SQLITE_OK ) rc = rc2;
+ }
+
+ pNew = (BatchIndex*)cbMalloc(&rc, sizeof(BatchIndex) + sizeof(int) * nCol);
+ if( rc==SQLITE_OK ){
+ int rc2;
+ pNew->nCol = nCol;
+ pNew->bPk = bPk;
+ pNew->aiCol = (int*)&pNew[1];
+ pNew->iId = p->iNextIdxId++;
+ while( SQLITE_ROW==sqlite3_step(pIndexInfo) ){
+ int i = sqlite3_column_int(pIndexInfo, 0);
+ int j = sqlite3_column_int(pIndexInfo, 1);
+ pNew->aiCol[i] = j;
+ }
+ rc2 = sqlite3_reset(pIndexInfo);
+ if( rc==SQLITE_OK ) rc = rc2;
+ }
+
+ if( rc==SQLITE_OK ){
+ pNew->pNext = pTab->pIdx;
+ pTab->pIdx = pNew;
+ }else{
+ cbFree(pNew);
+ }
+ sqlite3_finalize(pIndexInfo);
+
+ return rc;
+}
+
+/*
+** Find or create the BatchTable object named zTab.
+*/
+static int cbFindTable(
+ sqlite3_changebatch *p,
+ const char *zTab,
+ BatchTable **ppTab
+){
+ BatchTable *pRet = 0;
+ int rc = SQLITE_OK;
+
+ for(pRet=p->pTab; pRet; pRet=pRet->pNext){
+ if( 0==sqlite3_stricmp(zTab, pRet->zTab) ) break;
+ }
+
+ if( pRet==0 ){
+ int nTab = strlen(zTab);
+ pRet = (BatchTable*)cbMalloc(&rc, nTab + sizeof(BatchTable));
+ if( pRet ){
+ sqlite3_stmt *pIndexList = 0;
+ char *zIndexList = 0;
+ int rc2;
+ memcpy(pRet->zTab, zTab, nTab);
+
+ zIndexList = sqlite3_mprintf("PRAGMA main.index_list = %Q", zTab);
+ if( zIndexList==0 ){
+ rc = SQLITE_NOMEM;
+ }else{
+ rc = sqlite3_prepare_v2(p->db, zIndexList, -1, &pIndexList, 0);
+ sqlite3_free(zIndexList);
+ }
+
+ while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pIndexList) ){
+ if( sqlite3_column_int(pIndexList, 2) ){
+ const char *zIdx = (const char*)sqlite3_column_text(pIndexList, 1);
+ const char *zTyp = (const char*)sqlite3_column_text(pIndexList, 3);
+ rc = cbAddIndex(p, pRet, zIdx, (zTyp[0]=='p'));
+ }
+ }
+ rc2 = sqlite3_finalize(pIndexList);
+ if( rc==SQLITE_OK ) rc = rc2;
+
+ if( rc==SQLITE_OK ){
+ pRet->pNext = p->pTab;
+ p->pTab = pRet;
+ }
+ }
+ }
+
+ *ppTab = pRet;
+ return rc;
+}
+
+static int cbAddToHash(
+ sqlite3_changebatch *p,
+ sqlite3_changeset_iter *pIter,
+ BatchIndex *pIdx,
+ int (*xVal)(sqlite3_changeset_iter*,int,sqlite3_value**),
+ int *pbConf
+){
+ BatchIndexEntry *pNew;
+ int sz = pIdx->nCol;
+ int i;
+ int iOut = 0;
+ int rc = SQLITE_OK;
+
+ for(i=0; rc==SQLITE_OK && i<pIdx->nCol; i++){
+ sqlite3_value *pVal;
+ rc = xVal(pIter, pIdx->aiCol[i], &pVal);
+ if( rc==SQLITE_OK ){
+ int eType = 0;
+ if( pVal ){
+ eType = sqlite3_value_type(pVal);
+ }
+ switch( eType ){
+ case 0:
+ case SQLITE_NULL:
+ return SQLITE_OK;
+
+ case SQLITE_INTEGER:
+ sz += 8;
+ break;
+ case SQLITE_FLOAT:
+ sz += 8;
+ break;
+
+ default:
+ assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
+ sz += sqlite3_value_bytes(pVal);
+ break;
+ }
+ }
+ }
+
+ pNew = cbMalloc(&rc, sizeof(BatchIndexEntry) + sz);
+ if( pNew ){
+ pNew->iChangesetId = p->iChangesetId;
+ pNew->iIdxId = pIdx->iId;
+ pNew->szRecord = sz;
+
+ for(i=0; rc==SQLITE_OK && i<pIdx->nCol; i++){
+ sqlite3_value *pVal;
+ rc = xVal(pIter, pIdx->aiCol[i], &pVal);
+ if( rc==SQLITE_OK ){
+ int eType = sqlite3_value_type(pVal);
+ pNew->aRecord[iOut++] = eType;
+ switch( eType ){
+ case SQLITE_INTEGER: {
+ sqlite3_int64 i64 = sqlite3_value_int64(pVal);
+ memcpy(&pNew->aRecord[iOut], &i64, 8);
+ iOut += 8;
+ break;
+ }
+ case SQLITE_FLOAT: {
+ double d64 = sqlite3_value_double(pVal);
+ memcpy(&pNew->aRecord[iOut], &d64, sizeof(double));
+ iOut += sizeof(double);
+ break;
+ }
+
+ default: {
+ int nByte = sqlite3_value_bytes(pVal);
+ const char *z = (const char*)sqlite3_value_blob(pVal);
+ memcpy(&pNew->aRecord[iOut], z, nByte);
+ iOut += nByte;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if( rc==SQLITE_OK && p->nEntry>=(p->nHash/2) ){
+ rc = cbHashResize(p);
+ }
+
+ if( rc==SQLITE_OK ){
+ BatchIndexEntry *pIter;
+ int iHash = cbHash(p, pNew);
+
+ assert( iHash>=0 && iHash<p->nHash );
+ for(pIter=p->apHash[iHash]; pIter; pIter=pIter->pNext){
+ if( pNew->szRecord==pIter->szRecord
+ && 0==memcmp(pNew->aRecord, pIter->aRecord, pNew->szRecord)
+ ){
+ if( pNew->iChangesetId!=pIter->iChangesetId ){
+ *pbConf = 1;
+ }
+ cbFree(pNew);
+ pNew = 0;
+ break;
+ }
+ }
+
+ if( pNew ){
+ pNew->pNext = p->apHash[iHash];
+ p->apHash[iHash] = pNew;
+ p->nEntry++;
+ }
+ }
+
+ p->iChangesetId++;
+ return rc;
+}
+
+
+/*
+** Add a changeset to the current batch.
+*/
+int sqlite3changebatch_add(sqlite3_changebatch *p, void *pBuf, int nBuf){
+ sqlite3_changeset_iter *pIter; /* Iterator opened on pBuf/nBuf */
+ int rc; /* Return code */
+ int bConf = 0; /* Conflict was detected */
+
+ rc = sqlite3changeset_start(&pIter, nBuf, pBuf);
+ if( rc==SQLITE_OK ){
+ int rc2;
+ for(rc2 = sqlite3changeset_next(pIter);
+ rc2==SQLITE_ROW;
+ rc2 = sqlite3changeset_next(pIter)
+ ){
+ BatchTable *pTab;
+ BatchIndex *pIdx;
+ const char *zTab; /* Table this change applies to */
+ int nCol; /* Number of columns in table */
+ int op; /* UPDATE, INSERT or DELETE */
+
+ sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
+ assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
+
+ rc = cbFindTable(p, zTab, &pTab);
+ for(pIdx=pTab->pIdx; pIdx && rc==SQLITE_OK; pIdx=pIdx->pNext){
+ if( op==SQLITE_UPDATE && pIdx->bPk ) continue;
+ if( op==SQLITE_UPDATE || op==SQLITE_DELETE ){
+ rc = cbAddToHash(p, pIter, pIdx, sqlite3changeset_old, &bConf);
+ }
+ if( op==SQLITE_UPDATE || op==SQLITE_INSERT ){
+ rc = cbAddToHash(p, pIter, pIdx, sqlite3changeset_new, &bConf);
+ }
+ }
+ if( rc!=SQLITE_OK ) break;
+ }
+
+ rc2 = sqlite3changeset_finalize(pIter);
+ if( rc==SQLITE_OK ) rc = rc2;
+ }
+
+ if( rc==SQLITE_OK && bConf ){
+ rc = SQLITE_CONSTRAINT;
+ }
+ return rc;
+}
+
+/*
+** Zero an existing changebatch object.
+*/
+void sqlite3changebatch_zero(sqlite3_changebatch *p){
+ int i;
+ for(i=0; i<p->nHash; i++){
+ BatchIndexEntry *pEntry;
+ BatchIndexEntry *pNext;
+ for(pEntry=p->apHash[i]; pEntry; pEntry=pNext){
+ pNext = pEntry->pNext;
+ cbFree(pEntry);
+ }
+ }
+ cbFree(p->apHash);
+ p->nHash = 0;
+ p->apHash = 0;
+}
+
+/*
+** Delete a changebatch object.
+*/
+void sqlite3changebatch_delete(sqlite3_changebatch *p){
+ BatchTable *pTab;
+ BatchTable *pTabNext;
+
+ sqlite3changebatch_zero(p);
+ for(pTab=p->pTab; pTab; pTab=pTabNext){
+ BatchIndex *pIdx;
+ BatchIndex *pIdxNext;
+ for(pIdx=pTab->pIdx; pIdx; pIdx=pIdxNext){
+ pIdxNext = pIdx->pNext;
+ cbFree(pIdx);
+ }
+ pTabNext = pTab->pNext;
+ cbFree(pTab);
+ }
+ cbFree(p);
+}
+
+#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
--- /dev/null
+
+#if !defined(SQLITECHANGEBATCH_H_)
+#define SQLITECHANGEBATCH_H_ 1
+
+typedef struct sqlite3_changebatch sqlite3_changebatch;
+
+/*
+** Create a new changebatch object for detecting conflicts between
+** changesets associated with a schema equivalent to that of the "main"
+** database of the open database handle db passed as the first
+** parameter. It is the responsibility of the caller to ensure that
+** the database handle is not closed until after the changebatch
+** object has been deleted.
+**
+** A changebatch object is used to detect batches of non-conflicting
+** changesets. Changesets that do not conflict may be applied to the
+** target database in any order without affecting the final state of
+** the database.
+**
+** The changebatch object only works reliably if PRIMARY KEY and UNIQUE
+** constraints on tables affected by the changesets use collation
+** sequences that are equivalent to built-in collation sequence
+** BINARY for the == operation.
+**
+** If successful, SQLITE_OK is returned and (*pp) set to point to
+** the new changebatch object. If an error occurs, an SQLite error
+** code is returned and the final value of (*pp) is undefined.
+*/
+int sqlite3changebatch_new(sqlite3 *db, sqlite3_changebatch **pp);
+
+/*
+** Argument p points to a buffer containing a changeset n bytes in
+** size. Assuming no error occurs, this function returns SQLITE_OK
+** if the changeset does not conflict with any changeset passed
+** to an sqlite3changebatch_add() call made on the same
+** sqlite3_changebatch* handle since the most recent call to
+** sqlite3changebatch_zero(). If the changeset does conflict with
+** an earlier such changeset, SQLITE_CONSTRAINT is returned. Or,
+** if an error occurs, some other SQLite error code may be returned.
+**
+** One changeset is said to conflict with another if
+** either:
+**
+** * the two changesets contain operations (INSERT, UPDATE or
+** DELETE) on the same row, identified by primary key, or
+**
+** * the two changesets contain operations (INSERT, UPDATE or
+** DELETE) on rows with identical values in any combination
+** of fields constrained by a UNIQUE constraint.
+**
+** Even if this function returns SQLITE_CONFLICT, the current
+** changeset is added to the internal data structures - so future
+** calls to this function may conflict with it. If this function
+** returns any result code other than SQLITE_OK or SQLITE_CONFLICT,
+** the result of any future call to sqlite3changebatch_add() is
+** undefined.
+**
+** Only changesets may be passed to this function. Passing a
+** patchset to this function results in an SQLITE_MISUSE error.
+*/
+int sqlite3changebatch_add(sqlite3_changebatch*, void *p, int n);
+
+/*
+** Zero a changebatch object. This causes the records of all earlier
+** calls to sqlite3changebatch_add() to be discarded.
+*/
+void sqlite3changebatch_zero(sqlite3_changebatch*);
+
+/*
+** Delete a changebatch object.
+*/
+void sqlite3changebatch_delete(sqlite3_changebatch*);
+
+#endif /* !defined(SQLITECHANGEBATCH_H_) */
+
Tcl_DecrRefCount(pEval);
return res;
-}
+}
static int test_conflict_handler(
void *pCtx, /* Pointer to TestConflictHandler structure */
return TCL_OK;
}
+#include "sqlite3changebatch.h"
+
+typedef struct TestChangebatch TestChangebatch;
+struct TestChangebatch {
+ sqlite3_changebatch *pChangebatch;
+};
+
+/*
+** Tclcmd: $changebatch add BLOB
+** $changebatch zero
+** $changebatch delete
+*/
+static int SQLITE_TCLAPI test_changebatch_cmd(
+ void *clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ TestChangebatch *p = (TestChangebatch*)clientData;
+ sqlite3_changebatch *pChangebatch = p->pChangebatch;
+ struct SessionSubcmd {
+ const char *zSub;
+ int nArg;
+ const char *zMsg;
+ int iSub;
+ } aSub[] = {
+ { "add", 1, "CHANGESET", }, /* 0 */
+ { "zero", 0, "", }, /* 1 */
+ { "delete", 0, "", }, /* 2 */
+ { 0 }
+ };
+ int iSub;
+ int rc;
+
+ if( objc<2 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ...");
+ return TCL_ERROR;
+ }
+ rc = Tcl_GetIndexFromObjStruct(interp,
+ objv[1], aSub, sizeof(aSub[0]), "sub-command", 0, &iSub
+ );
+ if( rc!=TCL_OK ) return rc;
+ if( objc!=2+aSub[iSub].nArg ){
+ Tcl_WrongNumArgs(interp, 2, objv, aSub[iSub].zMsg);
+ return TCL_ERROR;
+ }
+
+ switch( iSub ){
+ case 0: { /* add */
+ int nArg;
+ unsigned char *pArg = Tcl_GetByteArrayFromObj(objv[2], &nArg);
+ rc = sqlite3changebatch_add(pChangebatch, pArg, nArg);
+ if( rc!=SQLITE_OK && rc!=SQLITE_CONSTRAINT ){
+ return test_session_error(interp, rc, 0);
+ }else{
+ extern const char *sqlite3ErrName(int);
+ Tcl_SetObjResult(interp, Tcl_NewStringObj(sqlite3ErrName(rc), -1));
+ }
+ break;
+ }
+
+ case 1: { /* zero */
+ sqlite3changebatch_zero(pChangebatch);
+ break;
+ }
+
+ case 2: /* delete */
+ Tcl_DeleteCommand(interp, Tcl_GetString(objv[0]));
+ break;
+ }
+
+ return TCL_OK;
+}
+
+static void SQLITE_TCLAPI test_changebatch_del(void *clientData){
+ TestChangebatch *p = (TestChangebatch*)clientData;
+ sqlite3changebatch_delete(p->pChangebatch);
+ ckfree((char*)p);
+}
+
+/*
+** Tclcmd: sqlite3changebatch CMD DB-HANDLE
+*/
+static int SQLITE_TCLAPI test_sqlite3changebatch(
+ void * clientData,
+ Tcl_Interp *interp,
+ int objc,
+ Tcl_Obj *CONST objv[]
+){
+ sqlite3 *db;
+ Tcl_CmdInfo info;
+ int rc; /* sqlite3session_create() return code */
+ TestChangebatch *p; /* New wrapper object */
+
+ if( objc!=3 ){
+ Tcl_WrongNumArgs(interp, 1, objv, "CMD DB-HANDLE");
+ return TCL_ERROR;
+ }
+
+ if( 0==Tcl_GetCommandInfo(interp, Tcl_GetString(objv[2]), &info) ){
+ Tcl_AppendResult(interp, "no such handle: ", Tcl_GetString(objv[2]), 0);
+ return TCL_ERROR;
+ }
+ db = *(sqlite3 **)info.objClientData;
+
+ p = (TestChangebatch*)ckalloc(sizeof(TestChangebatch));
+ memset(p, 0, sizeof(TestChangebatch));
+ rc = sqlite3changebatch_new(db, &p->pChangebatch);
+ if( rc!=SQLITE_OK ){
+ ckfree((char*)p);
+ return test_session_error(interp, rc, 0);
+ }
+
+ Tcl_CreateObjCommand(
+ interp, Tcl_GetString(objv[1]), test_changebatch_cmd, (ClientData)p,
+ test_changebatch_del
+ );
+ Tcl_SetObjResult(interp, objv[1]);
+ return TCL_OK;
+}
+
int TestSession_Init(Tcl_Interp *interp){
Tcl_CreateObjCommand(interp, "sqlite3session", test_sqlite3session, 0, 0);
Tcl_CreateObjCommand(
interp, "sqlite3changeset_apply_replace_all",
test_sqlite3changeset_apply_replace_all, 0, 0
);
+
+ Tcl_CreateObjCommand(
+ interp, "sqlite3changebatch", test_sqlite3changebatch, 0, 0
+ );
return TCL_OK;
}
$(TOP)/ext/fts3/fts3_write.c \
$(TOP)/ext/async/sqlite3async.c \
$(TOP)/ext/session/sqlite3session.c \
+ $(TOP)/ext/session/sqlite3changebatch.c \
$(TOP)/ext/session/test_session.c
# Header files used by all library source files.
-C Enhance\sthe\sVACUUM\scommand\sso\sthat\sit\scan\soperate\son\san\sattached\sdatabase.
-D 2016-08-19T15:15:55.612
+C Add\san\sexperimental\smodule\sto\sdetect\sconflicts\sbetween\ssessions\schangesets.
+D 2016-08-22T20:49:06.286
F Makefile.in cfd8fb987cd7a6af046daa87daa146d5aad0e088
F Makefile.linux-gcc 7bc79876b875010e8c8f9502eb935ca92aa3c434
F Makefile.msc d66d0395c38571aab3804f8db0fa20707ae4609a
F ext/rtree/sqlite3rtree.h 9c5777af3d2921c7b4ae4954e8e5697502289d28
F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
+F ext/session/changebatch1.test 5a69388b91ad02544ef2d9ae27771dbf0f9cea58
F ext/session/changeset.c 4ccbaa4531944c24584bf6a61ba3a39c62b6267a
F ext/session/session1.test 98f384736e2bc21ccf5ed81bdadcff4ad863393b
F ext/session/session2.test 284de45abae4cc1082bc52012ee81521d5ac58e0
F ext/session/session_common.tcl a1293167d14774b5e728836720497f40fe4ea596
F ext/session/sessionfault.test da273f2712b6411e85e71465a1733b8501dbf6f7
F ext/session/sessionfault2.test 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0
+F ext/session/sqlite3changebatch.c 37fb1c87d7b3ccaff194411ff8344d9cc056bd98
+F ext/session/sqlite3changebatch.h 50a302e4fc535324309607b13a1993bca074758b
F ext/session/sqlite3session.c 37485891b4add26cf61495df193c419f36556a32
F ext/session/sqlite3session.h 69bf73cfd71e58f2ae5d2aa935b2c1a541aee555
-F ext/session/test_session.c 2caed9a659586428c63ca46e4900347b374487d4
+F ext/session/test_session.c 24968972a5709d2ccf5d570f1a13ce009fbc0d86
F ext/userauth/sqlite3userauth.h 19cb6f0e31316d0ee4afdfb7a85ef9da3333a220
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
F ext/userauth/userauth.c 5fa3bdb492f481bbc1709fc83c91ebd13460c69e
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895 x
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
F magic.txt 8273bf49ba3b0c8559cb2774495390c31fd61c60
-F main.mk 1883ecab643b136e8ab3fdc33785e6ea8b5ceb46
+F main.mk de9447348ea580282aa47dbffd20b042bfbec4e1
F mkso.sh fd21c06b063bb16a5d25deea1752c2da6ac3ed83
F mptest/config01.test 3c6adcbc50b991866855f1977ff172eb6d901271
F mptest/config02.test 4415dfe36c48785f751e16e32c20b077c28ae504
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P cb9865e14db1c0076618f13400151112f84960cb ad35ef116296e5d6aaeb9ef260bf35bee3bd6d20
-R abd29f9f3d87c2af53d085425233b2a6
-T +closed ad35ef116296e5d6aaeb9ef260bf35bee3bd6d20
-U drh
-Z a6de375e7bb722769082d6e640b9c49b
+P 083f9e6270fa4faa402b91231271da4f3915c79f
+R c89955dc116a299d44f6360524b79a8a
+T *branch * changebatch
+T *sym-changebatch *
+T -sym-trunk *
+U dan
+Z f9a59b56ef4c3ca7ba5584bf19e5e811
-083f9e6270fa4faa402b91231271da4f3915c79f
\ No newline at end of file
+0c9fd6b723041955b5182caa430312e5124fdc83
\ No newline at end of file