aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/session/sqlite3session.c272
-rw-r--r--ext/session/sqlite3session.h28
-rw-r--r--ext/session/test_session.c15
-rw-r--r--manifest19
-rw-r--r--manifest.uuid2
5 files changed, 314 insertions, 22 deletions
diff --git a/ext/session/sqlite3session.c b/ext/session/sqlite3session.c
index c8d66bb5b..ad898e884 100644
--- a/ext/session/sqlite3session.c
+++ b/ext/session/sqlite3session.c
@@ -2918,7 +2918,8 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
static int sessionChangesetNext(
sqlite3_changeset_iter *p, /* Changeset iterator */
u8 **paRec, /* If non-NULL, store record pointer here */
- int *pnRec /* If non-NULL, store size of record here */
+ int *pnRec, /* If non-NULL, store size of record here */
+ int *pbNew /* If non-NULL, true if new table */
){
int i;
u8 op;
@@ -2953,6 +2954,7 @@ static int sessionChangesetNext(
op = p->in.aData[p->in.iNext++];
while( op=='T' || op=='P' ){
+ if( pbNew ) *pbNew = 1;
p->bPatchset = (op=='P');
if( sessionChangesetReadTblhdr(p) ) return p->rc;
if( (p->rc = sessionInputBuffer(&p->in, 2)) ) return p->rc;
@@ -3031,7 +3033,7 @@ static int sessionChangesetNext(
** callback by changeset_apply().
*/
int sqlite3changeset_next(sqlite3_changeset_iter *p){
- return sessionChangesetNext(p, 0, 0);
+ return sessionChangesetNext(p, 0, 0, 0);
}
/*
@@ -4522,6 +4524,7 @@ struct sqlite3_changegroup {
*/
static int sessionChangeMerge(
SessionTable *pTab, /* Table structure */
+ int bRebase, /* True for a rebase hash-table */
int bPatchset, /* True for patchsets */
SessionChange *pExist, /* Existing change */
int op2, /* Second change operation */
@@ -4543,6 +4546,8 @@ static int sessionChangeMerge(
pNew->nRecord = nRec;
pNew->aRecord = (u8*)&pNew[1];
memcpy(pNew->aRecord, aRec, nRec);
+ }else if( bRebase){
+ assert( 0 );
}else{
int op1 = pExist->op;
@@ -4645,7 +4650,8 @@ static int sessionChangeMerge(
*/
static int sessionChangesetToHash(
sqlite3_changeset_iter *pIter, /* Iterator to read from */
- sqlite3_changegroup *pGrp /* Changegroup object to add changeset to */
+ sqlite3_changegroup *pGrp, /* Changegroup object to add changeset to */
+ int bRebase /* True if hash table is for rebasing */
){
u8 *aRec;
int nRec;
@@ -4653,7 +4659,7 @@ static int sessionChangesetToHash(
SessionTable *pTab = 0;
- while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec) ){
+ while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){
const char *zNew;
int nCol;
int op;
@@ -4733,7 +4739,7 @@ static int sessionChangesetToHash(
}
}
- rc = sessionChangeMerge(pTab,
+ rc = sessionChangeMerge(pTab, bRebase,
pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange
);
if( rc ) break;
@@ -4841,7 +4847,7 @@ int sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void *pData){
rc = sqlite3changeset_start(&pIter, nData, pData);
if( rc==SQLITE_OK ){
- rc = sessionChangesetToHash(pIter, pGrp);
+ rc = sessionChangesetToHash(pIter, pGrp, 0);
}
sqlite3changeset_finalize(pIter);
return rc;
@@ -4872,7 +4878,7 @@ int sqlite3changegroup_add_strm(
rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
if( rc==SQLITE_OK ){
- rc = sessionChangesetToHash(pIter, pGrp);
+ rc = sessionChangesetToHash(pIter, pGrp, 0);
}
sqlite3changeset_finalize(pIter);
return rc;
@@ -4957,4 +4963,256 @@ int sqlite3changeset_concat_strm(
return rc;
}
+struct sqlite3_rebaser {
+ sqlite3_changegroup grp; /* Hash table */
+};
+
+/*
+** Buffers a1 and a2 must both contain a sessions module record nCol
+** fields in size. This function appends an nCol sessions module
+** record to buffer pBuf that is a copy of a1, except that:
+**
+** + If bUndefined is 0, for each field that is not "undefined" in either
+** a1[] or a2[], swap in the field from a2[].
+**
+** + If bUndefined is 1, for each field that is "undefined" in a1[]
+** swap in the field from a2[].
+*/
+static void sessionAppendRecordMerge(
+ SessionBuffer *pBuf,
+ int nCol,
+ int bUndefined,
+ u8 *a1, int n1,
+ u8 *a2, int n2,
+ int *pRc
+){
+ sessionBufferGrow(pBuf, n1+n2, pRc);
+ if( *pRc==SQLITE_OK ){
+ int i;
+ u8 *pOut = &pBuf->aBuf[pBuf->nBuf];
+ for(i=0; i<nCol; i++){
+ int nn1 = sessionSerialLen(a1);
+ int nn2 = sessionSerialLen(a2);
+ if( bUndefined==0 ){
+ if( *a1 && *a2 ){
+ memcpy(pOut, a2, nn2);
+ pOut += nn2;
+ }else{
+ memcpy(pOut, a1, nn1);
+ pOut += nn1;
+ }
+ }else{
+ if( *a1==0 ){
+ memcpy(pOut, a2, nn2);
+ pOut += nn2;
+ }else{
+ memcpy(pOut, a1, nn1);
+ pOut += nn1;
+ }
+ }
+ a1 += n1;
+ a2 += n2;
+ }
+ }
+}
+
+static int sessionRebase(
+ sqlite3_rebaser *p, /* Rebaser hash table */
+ sqlite3_changeset_iter *pIter, /* Input data */
+ int (*xOutput)(void *pOut, const void *pData, int nData),
+ void *pOut, /* Context for xOutput callback */
+ int *pnOut, /* OUT: Number of bytes in output changeset */
+ void **ppOut /* OUT: Inverse of pChangeset */
+){
+ int rc = SQLITE_OK;
+ u8 *aRec = 0;
+ int nRec = 0;
+ int bNew = 0;
+ SessionTable *pTab = 0;
+ SessionBuffer sOut = {0,0,0};
+
+ while( SQLITE_OK==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){
+ SessionChange *pChange = 0;
+
+ if( bNew ){
+ const char *zTab = pIter->zTab;
+ for(pTab=p->grp.pList; pTab; pTab=pTab->pNext){
+ if( 0==sqlite3_stricmp(pTab->zName, zTab) ) break;
+ }
+ bNew = 0;
+
+ /* Append a table header to the output for this new table */
+ sessionAppendByte(&sOut, pIter->bPatchset ? 'P' : 'T', &rc);
+ sessionAppendVarint(&sOut, pIter->nCol, &rc);
+ sessionAppendBlob(&sOut, pIter->abPK, pIter->nCol, &rc);
+ sessionAppendBlob(&sOut, (u8*)pIter->zTab, strlen(pIter->zTab)+1, &rc);
+ }
+
+ if( pTab ){
+ int bPkOnly = (pIter->bPatchset && pIter->op==SQLITE_DELETE);
+ int iHash = sessionChangeHash(pTab, bPkOnly, aRec, pTab->nChange);
+
+ for(pChange=pTab->apChange[iHash]; pChange; pChange=pChange->pNext){
+ if( sessionChangeEqual(pTab, bPkOnly, aRec, 0, pChange->aRecord) ){
+ break;
+ }
+ }
+ }
+
+ if( pChange ){
+ assert( pChange->op==SQLITE_DELETE || pChange->op==SQLITE_INSERT );
+ /* If pChange is an INSERT, then rebase the change. If it is a
+ ** DELETE, omit the change from the output altogether. */
+ if( pChange->op==SQLITE_INSERT ){
+ if( pChange->bIndirect ){
+ /* The change being rebased against was a DELETE. So, if the
+ ** input is a:
+ **
+ ** DELETE - omit the change altogether.
+ ** UPDATE - change to an INSERT,
+ ** INSERT - no change (output the record as is).
+ */
+ if( pIter->op!=SQLITE_DELETE ){
+ sessionAppendByte(&sOut, SQLITE_INSERT, &rc);
+ sessionAppendByte(&sOut, pIter->bIndirect, &rc);
+ if( pIter->op==SQLITE_INSERT ){
+ sessionAppendBlob(&sOut, aRec, nRec, &rc);
+ }else{
+ sessionAppendRecordMerge(&sOut, pIter->nCol, 1,
+ aRec, nRec, pChange->aRecord, pChange->nRecord, &rc
+ );
+ }
+ }
+ }else{
+ sessionAppendByte(&sOut, pIter->op, &rc);
+ sessionAppendByte(&sOut, pIter->bIndirect, &rc);
+ if( pIter->op==SQLITE_INSERT ){
+ sessionAppendBlob(&sOut, aRec, nRec, &rc);
+ }else{
+ u8 *pCsr = aRec;
+ sessionAppendRecordMerge(&sOut, pIter->nCol, 0,
+ aRec, nRec, pChange->aRecord, pChange->nRecord, &rc
+ );
+ if( pIter->op==SQLITE_UPDATE ){
+ sessionSkipRecord(&pCsr, pIter->nCol);
+ sessionAppendBlob(&sOut, pCsr, nRec - (pCsr-aRec), &rc);
+ }
+ }
+ }
+ }
+ }else{
+ sessionAppendByte(&sOut, pIter->op, &rc);
+ sessionAppendByte(&sOut, pIter->bIndirect, &rc);
+ sessionAppendBlob(&sOut, aRec, nRec, &rc);
+ }
+
+ if( rc==SQLITE_OK && xOutput && sOut.nBuf>SESSIONS_STRM_CHUNK_SIZE ){
+ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf);
+ sOut.nBuf = 0;
+ }
+ if( rc ) break;
+ }
+
+ if( rc!=SQLITE_OK ){
+ sqlite3_free(sOut.aBuf);
+ memset(&sOut, 0, sizeof(sOut));
+ }
+
+ if( rc==SQLITE_OK ){
+ if( xOutput ){
+ if( sOut.nBuf>0 ){
+ rc = xOutput(pOut, sOut.aBuf, sOut.nBuf);
+ }
+ }else{
+ *ppOut = (void*)sOut.aBuf;
+ *pnOut = sOut.nBuf;
+ sOut.aBuf = 0;
+ }
+ }
+ sqlite3_free(sOut.aBuf);
+ return rc;
+}
+
+/*
+** Create a new rebaser object.
+*/
+int sqlite3rebaser_create(sqlite3_rebaser **ppNew){
+ int rc = SQLITE_OK;
+ sqlite3_rebaser *pNew;
+
+ pNew = sqlite3_malloc(sizeof(sqlite3_rebaser));
+ if( pNew==0 ){
+ rc = SQLITE_NOMEM;
+ }
+ *ppNew = pNew;
+ return rc;
+}
+
+/*
+** Call this one or more times to configure a rebaser.
+*/
+int sqlite3rebaser_configure(
+ sqlite3_rebaser *p,
+ int nRebase, const void *pRebase
+){
+ sqlite3_changeset_iter *pIter = 0; /* Iterator opened on pData/nData */
+ int rc; /* Return code */
+ rc = sqlite3changeset_start(&pIter, nRebase, (void*)pRebase);
+ if( rc==SQLITE_OK ){
+ rc = sessionChangesetToHash(pIter, &p->grp, 1);
+ }
+ sqlite3changeset_finalize(pIter);
+ return rc;
+}
+
+/*
+** Rebase a changeset according to current rebaser configuration
+*/
+int sqlite3rebaser_rebase(
+ sqlite3_rebaser *p,
+ int nIn, const void *pIn,
+ int *pnOut, void **ppOut
+){
+ sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */
+ int rc = sqlite3changeset_start(&pIter, nIn, (void*)pIn);
+
+ if( rc==SQLITE_OK ){
+ rc = sessionRebase(p, pIter, 0, 0, pnOut, ppOut);
+ sqlite3changeset_finalize(pIter);
+ }
+
+ return rc;
+}
+
+/*
+** Rebase a changeset according to current rebaser configuration
+*/
+int sqlite3rebaser_rebase_strm(
+ sqlite3_rebaser *p,
+ int (*xInput)(void *pIn, void *pData, int *pnData),
+ void *pIn,
+ int (*xOutput)(void *pOut, const void *pData, int nData),
+ void *pOut
+){
+ sqlite3_changeset_iter *pIter = 0; /* Iterator to skip through input */
+ int rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
+
+ if( rc==SQLITE_OK ){
+ rc = sessionRebase(p, pIter, xOutput, pOut, 0, 0);
+ sqlite3changeset_finalize(pIter);
+ }
+
+ return rc;
+}
+
+/*
+** Destroy a rebaser object
+*/
+void sqlite3rebaser_destroy(sqlite3_rebaser *p){
+ if( p ){
+ sessionDeleteTable(p->grp.pList);
+ sqlite3_free(p);
+ }
+}
+
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */
diff --git a/ext/session/sqlite3session.h b/ext/session/sqlite3session.h
index c6a2332b8..a7281bd4e 100644
--- a/ext/session/sqlite3session.h
+++ b/ext/session/sqlite3session.h
@@ -1216,6 +1216,27 @@ int sqlite3changeset_apply_v2(
#define SQLITE_CHANGESET_REPLACE 1
#define SQLITE_CHANGESET_ABORT 2
+typedef struct sqlite3_rebaser sqlite3_rebaser;
+
+/* Create a new rebaser object */
+int sqlite3rebaser_create(sqlite3_rebaser **ppNew);
+
+/* Call this one or more times to configure a rebaser */
+int sqlite3rebaser_configure(
+ sqlite3_rebaser*,
+ int nRebase, const void *pRebase
+);
+
+/* Rebase a changeset according to current rebaser configuration */
+int sqlite3rebaser_rebase(
+ sqlite3_rebaser*,
+ int nIn, const void *pIn,
+ int *pnOut, void **ppOut
+);
+
+/* Destroy a rebaser object */
+void sqlite3rebaser_destroy(sqlite3_rebaser *p);
+
/*
** CAPI3REF: Streaming Versions of API functions.
**
@@ -1373,6 +1394,13 @@ 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
+);
/*
diff --git a/ext/session/test_session.c b/ext/session/test_session.c
index dd2d9977a..7b08267b5 100644
--- a/ext/session/test_session.c
+++ b/ext/session/test_session.c
@@ -761,9 +761,18 @@ static int SQLITE_TCLAPI testSqlite3changesetApply(
}else{
sStr.aData = (unsigned char*)pChangeset;
sStr.nData = nChangeset;
- rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
- (objc==5) ? test_filter_handler : 0, test_conflict_handler, (void *)&ctx
- );
+ if( bV2==0 ){
+ rc = sqlite3changeset_apply_strm(db, testStreamInput, (void*)&sStr,
+ (objc==5) ? test_filter_handler : 0,
+ test_conflict_handler, (void *)&ctx
+ );
+ }else{
+ rc = sqlite3changeset_apply_v2_strm(db, testStreamInput, (void*)&sStr,
+ (objc==5) ? test_filter_handler : 0,
+ test_conflict_handler, (void *)&ctx,
+ &pRebase, &nRebase
+ );
+ }
}
if( rc!=SQLITE_OK ){
diff --git a/manifest b/manifest
index 5e08ac632..b887be3c0 100644
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Add\ssqlite3_changeset_apply_v2()\sand\sapply_v2_strm()\sto\sthe\ssessions\smodule.
-D 2018-03-13T20:31:23.940
+C Add\slargely\suntested\sAPIs\sfor\srebasing\schangesets.
+D 2018-03-14T21:06:58.004
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F Makefile.in 7016fc56c6b9bfe5daac4f34be8be38d8c0b5fab79ccbfb764d3b23bf1c6fff3
@@ -403,9 +403,9 @@ F ext/session/sessionfault2.test 04aa0bc9aa70ea43d8de82c4f648db4de1e990b0
F ext/session/sessionrebase.test b4ac7545e3c69deaeab061c2bf36ad9e99aa6c38db94c340d7e48a230a9d4be8
F ext/session/sessionstat1.test 41cd97c2e48619a41cdf8ae749e1b25f34719de638689221aa43971be693bf4e
F ext/session/sessionwor.test 2f3744236dc8b170a695b7d8ddc8c743c7e79fdc
-F ext/session/sqlite3session.c 564e609f3086510d319e8abc0899fa79131d071b7dc01138e1b75bbf4a43cb03
-F ext/session/sqlite3session.h 8fe499b736633021094b814cf128691587f19c1943e372a23db37175bdeb8c67
-F ext/session/test_session.c 6c45bee58063f2c3d1edc213833d72f0a3ec651cbe27393b9e4a054e711b7c44
+F ext/session/sqlite3session.c 16561ad7eb8270bd3d2ee42c434043e68831bbd452fbea82d83922a1066a7cc8
+F ext/session/sqlite3session.h 74ba48151f3593a66a975ac095d7b53efa6c1e12fe83a903e10bf8d85a1429dd
+F ext/session/test_session.c 8c04dc8cada82bd4e12f18ada3e35b56a8fd4d8dee7caac324ae28091c2b492f
F ext/userauth/sqlite3userauth.h 7f3ea8c4686db8e40b0a0e7a8e0b00fac13aa7a3
F ext/userauth/user-auth.txt e6641021a9210364665fe625d067617d03f27b04
F ext/userauth/userauth.c 3410be31283abba70255d71fd24734e017a4497f
@@ -1713,10 +1713,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 0171d4a71ca7911a9fd409a42eeed0eda4521b6e48df5cd058364c0a736313b7
-R 1a6994480e1e2c2b73e68977f31547a3
-T *branch * sessions-rebase
-T *sym-sessions-rebase *
-T -sym-trunk *
+P 445bfe977d9f3a891e08ef33237862ed047fe83e134ef3ed8b47ee0f5abd8cd6
+R 485a23056d6edbdb25721aa9069ac291
U dan
-Z d8bbc56681a1d6ff1f4fb97dc44d1b66
+Z e2563dee69381fb6fb02a6ddaa823fec
diff --git a/manifest.uuid b/manifest.uuid
index 1e1f7573d..7b1553cdc 100644
--- a/manifest.uuid
+++ b/manifest.uuid
@@ -1 +1 @@
-445bfe977d9f3a891e08ef33237862ed047fe83e134ef3ed8b47ee0f5abd8cd6 \ No newline at end of file
+39915b683b3f8d3bf872af1dede96bf2818b488a8638a1d248395023fc4bd0ef \ No newline at end of file