#if defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK)
#include "sqlite3session.h"
#include <assert.h>
#include <string.h>
#ifndef SQLITE_AMALGAMATION
# include "sqliteInt.h"
# include "vdbeInt.h"
#endif
typedef struct SessionTable SessionTable;
typedef struct SessionChange SessionChange;
typedef struct SessionBuffer SessionBuffer;
typedef struct SessionInput SessionInput;
#ifndef SESSIONS_STRM_CHUNK_SIZE
# ifdef SQLITE_TEST
# define SESSIONS_STRM_CHUNK_SIZE 64
# else
# define SESSIONS_STRM_CHUNK_SIZE 1024
# endif
#endif
#define SESSIONS_ROWID "_rowid_"
static int sessions_strm_chunk_size = SESSIONS_STRM_CHUNK_SIZE;
typedef struct SessionHook SessionHook;
struct SessionHook {
void *pCtx;
int (*xOld)(void*,int,sqlite3_value**);
int (*xNew)(void*,int,sqlite3_value**);
int (*xCount)(void*);
int (*xDepth)(void*);
};
struct sqlite3_session {
sqlite3 *db;
char *zDb;
int bEnableSize;
int bEnable;
int bIndirect;
int bAutoAttach;
int bImplicitPK;
int rc;
void *pFilterCtx;
int (*xTableFilter)(void *pCtx, const char *zTab);
i64 nMalloc;
i64 nMaxChangesetSize;
sqlite3_value *pZeroBlob;
sqlite3_session *pNext;
SessionTable *pTable;
SessionHook hook;
};
struct SessionBuffer {
u8 *aBuf;
int nBuf;
int nAlloc;
};
struct SessionInput {
int bNoDiscard;
int iCurrent;
int iNext;
u8 *aData;
int nData;
SessionBuffer buf;
int (*xInput)(void*, void*, int*);
void *pIn;
int bEof;
};
struct sqlite3_changeset_iter {
SessionInput in;
SessionBuffer tblhdr;
int bPatchset;
int bInvert;
int bSkipEmpty;
int rc;
sqlite3_stmt *pConflict;
char *zTab;
int nCol;
int op;
int bIndirect;
u8 *abPK;
sqlite3_value **apValue;
};
struct SessionTable {
SessionTable *pNext;
char *zName;
int nCol;
int bStat1;
int bRowid;
const char **azCol;
u8 *abPK;
int nEntry;
int nChange;
SessionChange **apChange;
};
struct SessionChange {
u8 op;
u8 bIndirect;
int nMaxSize;
int nRecord;
u8 *aRecord;
SessionChange *pNext;
};
static int sessionVarintPut(u8 *aBuf, int iVal){
return putVarint32(aBuf, iVal);
}
static int sessionVarintLen(int iVal){
return sqlite3VarintLen(iVal);
}
static int sessionVarintGet(u8 *aBuf, int *piVal){
return getVarint32(aBuf, *piVal);
}
#define SESSION_UINT32(x) (((u32)(x)[0]<<24)|((x)[1]<<16)|((x)[2]<<8)|(x)[3])
static sqlite3_int64 sessionGetI64(u8 *aRec){
u64 x = SESSION_UINT32(aRec);
u32 y = SESSION_UINT32(aRec+4);
x = (x<<32) + y;
return (sqlite3_int64)x;
}
static void sessionPutI64(u8 *aBuf, sqlite3_int64 i){
aBuf[0] = (i>>56) & 0xFF;
aBuf[1] = (i>>48) & 0xFF;
aBuf[2] = (i>>40) & 0xFF;
aBuf[3] = (i>>32) & 0xFF;
aBuf[4] = (i>>24) & 0xFF;
aBuf[5] = (i>>16) & 0xFF;
aBuf[6] = (i>> 8) & 0xFF;
aBuf[7] = (i>> 0) & 0xFF;
}
static int sessionSerializeValue(
u8 *aBuf,
sqlite3_value *pValue,
sqlite3_int64 *pnWrite
){
int nByte;
if( pValue ){
int eType;
eType = sqlite3_value_type(pValue);
if( aBuf ) aBuf[0] = eType;
switch( eType ){
case SQLITE_NULL:
nByte = 1;
break;
case SQLITE_INTEGER:
case SQLITE_FLOAT:
if( aBuf ){
u64 i;
if( eType==SQLITE_INTEGER ){
i = (u64)sqlite3_value_int64(pValue);
}else{
double r;
assert( sizeof(double)==8 && sizeof(u64)==8 );
r = sqlite3_value_double(pValue);
memcpy(&i, &r, 8);
}
sessionPutI64(&aBuf[1], i);
}
nByte = 9;
break;
default: {
u8 *z;
int n;
int nVarint;
assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
if( eType==SQLITE_TEXT ){
z = (u8 *)sqlite3_value_text(pValue);
}else{
z = (u8 *)sqlite3_value_blob(pValue);
}
n = sqlite3_value_bytes(pValue);
if( z==0 && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
nVarint = sessionVarintLen(n);
if( aBuf ){
sessionVarintPut(&aBuf[1], n);
if( n>0 ) memcpy(&aBuf[nVarint + 1], z, n);
}
nByte = 1 + nVarint + n;
break;
}
}
}else{
nByte = 1;
if( aBuf ) aBuf[0] = '\0';
}
if( pnWrite ) *pnWrite += nByte;
return SQLITE_OK;
}
static void *sessionMalloc64(sqlite3_session *pSession, i64 nByte){
void *pRet = sqlite3_malloc64(nByte);
if( pSession ) pSession->nMalloc += sqlite3_msize(pRet);
return pRet;
}
static void sessionFree(sqlite3_session *pSession, void *pFree){
if( pSession ) pSession->nMalloc -= sqlite3_msize(pFree);
sqlite3_free(pFree);
}
#define HASH_APPEND(hash, add) ((hash) << 3) ^ (hash) ^ (unsigned int)(add)
static unsigned int sessionHashAppendI64(unsigned int h, i64 i){
h = HASH_APPEND(h, i & 0xFFFFFFFF);
return HASH_APPEND(h, (i>>32)&0xFFFFFFFF);
}
static unsigned int sessionHashAppendBlob(unsigned int h, int n, const u8 *z){
int i;
for(i=0; i<n; i++) h = HASH_APPEND(h, z[i]);
return h;
}
static unsigned int sessionHashAppendType(unsigned int h, int eType){
return HASH_APPEND(h, eType);
}
static int sessionPreupdateHash(
sqlite3_session *pSession,
i64 iRowid,
SessionTable *pTab,
int bNew,
int *piHash,
int *pbNullPK
){
unsigned int h = 0;
int i;
if( pTab->bRowid ){
assert( pTab->nCol-1==pSession->hook.xCount(pSession->hook.pCtx) );
h = sessionHashAppendI64(h, iRowid);
}else{
assert( *pbNullPK==0 );
assert( pTab->nCol==pSession->hook.xCount(pSession->hook.pCtx) );
for(i=0; i<pTab->nCol; i++){
if( pTab->abPK[i] ){
int rc;
int eType;
sqlite3_value *pVal;
if( bNew ){
rc = pSession->hook.xNew(pSession->hook.pCtx, i, &pVal);
}else{
rc = pSession->hook.xOld(pSession->hook.pCtx, i, &pVal);
}
if( rc!=SQLITE_OK ) return rc;
eType = sqlite3_value_type(pVal);
h = sessionHashAppendType(h, eType);
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
i64 iVal;
if( eType==SQLITE_INTEGER ){
iVal = sqlite3_value_int64(pVal);
}else{
double rVal = sqlite3_value_double(pVal);
assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
memcpy(&iVal, &rVal, 8);
}
h = sessionHashAppendI64(h, iVal);
}else if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
const u8 *z;
int n;
if( eType==SQLITE_TEXT ){
z = (const u8 *)sqlite3_value_text(pVal);
}else{
z = (const u8 *)sqlite3_value_blob(pVal);
}
n = sqlite3_value_bytes(pVal);
if( !z && (eType!=SQLITE_BLOB || n>0) ) return SQLITE_NOMEM;
h = sessionHashAppendBlob(h, n, z);
}else{
assert( eType==SQLITE_NULL );
assert( pTab->bStat1==0 || i!=1 );
*pbNullPK = 1;
}
}
}
}
*piHash = (h % pTab->nChange);
return SQLITE_OK;
}
static int sessionSerialLen(u8 *a){
int e = *a;
int n;
if( e==0 || e==0xFF ) return 1;
if( e==SQLITE_NULL ) return 1;
if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9;
return sessionVarintGet(&a[1], &n) + 1 + n;
}
static unsigned int sessionChangeHash(
SessionTable *pTab,
int bPkOnly,
u8 *aRecord,
int nBucket
){
unsigned int h = 0;
int i;
u8 *a = aRecord;
for(i=0; i<pTab->nCol; i++){
int eType = *a;
int isPK = pTab->abPK[i];
if( bPkOnly && isPK==0 ) continue;
assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT
|| eType==SQLITE_TEXT || eType==SQLITE_BLOB
|| eType==SQLITE_NULL || eType==0
);
assert( !isPK || (eType!=0 && eType!=SQLITE_NULL) );
if( isPK ){
a++;
h = sessionHashAppendType(h, eType);
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
h = sessionHashAppendI64(h, sessionGetI64(a));
a += 8;
}else{
int n;
a += sessionVarintGet(a, &n);
h = sessionHashAppendBlob(h, n, a);
a += n;
}
}else{
a += sessionSerialLen(a);
}
}
return (h % nBucket);
}
static int sessionChangeEqual(
SessionTable *pTab,
int bLeftPkOnly,
u8 *aLeft,
int bRightPkOnly,
u8 *aRight
){
u8 *a1 = aLeft;
u8 *a2 = aRight;
int iCol;
for(iCol=0; iCol<pTab->nCol; iCol++){
if( pTab->abPK[iCol] ){
int n1 = sessionSerialLen(a1);
int n2 = sessionSerialLen(a2);
if( n1!=n2 || memcmp(a1, a2, n1) ){
return 0;
}
a1 += n1;
a2 += n2;
}else{
if( bLeftPkOnly==0 ) a1 += sessionSerialLen(a1);
if( bRightPkOnly==0 ) a2 += sessionSerialLen(a2);
}
}
return 1;
}
static void sessionMergeRecord(
u8 **paOut,
int nCol,
u8 *aLeft,
u8 *aRight
){
u8 *a1 = aLeft;
u8 *a2 = aRight;
u8 *aOut = *paOut;
int iCol;
for(iCol=0; iCol<nCol; iCol++){
int n1 = sessionSerialLen(a1);
int n2 = sessionSerialLen(a2);
if( *a2 ){
memcpy(aOut, a2, n2);
aOut += n2;
}else{
memcpy(aOut, a1, n1);
aOut += n1;
}
a1 += n1;
a2 += n2;
}
*paOut = aOut;
}
static u8 *sessionMergeValue(
u8 **paOne,
u8 **paTwo,
int *pnVal
){
u8 *a1 = *paOne;
u8 *a2 = *paTwo;
u8 *pRet = 0;
int n1;
assert( a1 );
if( a2 ){
int n2 = sessionSerialLen(a2);
if( *a2 ){
*pnVal = n2;
pRet = a2;
}
*paTwo = &a2[n2];
}
n1 = sessionSerialLen(a1);
if( pRet==0 ){
*pnVal = n1;
pRet = a1;
}
*paOne = &a1[n1];
return pRet;
}
static int sessionMergeUpdate(
u8 **paOut,
SessionTable *pTab,
int bPatchset,
u8 *aOldRecord1,
u8 *aOldRecord2,
u8 *aNewRecord1,
u8 *aNewRecord2
){
u8 *aOld1 = aOldRecord1;
u8 *aOld2 = aOldRecord2;
u8 *aNew1 = aNewRecord1;
u8 *aNew2 = aNewRecord2;
u8 *aOut = *paOut;
int i;
if( bPatchset==0 ){
int bRequired = 0;
assert( aOldRecord1 && aNewRecord1 );
for(i=0; i<pTab->nCol; i++){
int nOld;
u8 *aOld;
int nNew;
u8 *aNew;
aOld = sessionMergeValue(&aOld1, &aOld2, &nOld);
aNew = sessionMergeValue(&aNew1, &aNew2, &nNew);
if( pTab->abPK[i] || nOld!=nNew || memcmp(aOld, aNew, nNew) ){
if( pTab->abPK[i]==0 ) bRequired = 1;
memcpy(aOut, aOld, nOld);
aOut += nOld;
}else{
*(aOut++) = '\0';
}
}
if( !bRequired ) return 0;
}
aOld1 = aOldRecord1;
aOld2 = aOldRecord2;
aNew1 = aNewRecord1;
aNew2 = aNewRecord2;
for(i=0; i<pTab->nCol; i++){
int nOld;
u8 *aOld;
int nNew;
u8 *aNew;
aOld = sessionMergeValue(&aOld1, &aOld2, &nOld);
aNew = sessionMergeValue(&aNew1, &aNew2, &nNew);
if( bPatchset==0
&& (pTab->abPK[i] || (nOld==nNew && 0==memcmp(aOld, aNew, nNew)))
){
*(aOut++) = '\0';
}else{
memcpy(aOut, aNew, nNew);
aOut += nNew;
}
}
*paOut = aOut;
return 1;
}
static int sessionPreupdateEqual(
sqlite3_session *pSession,
i64 iRowid,
SessionTable *pTab,
SessionChange *pChange,
int op
){
int iCol;
u8 *a = pChange->aRecord;
if( pTab->bRowid ){
if( a[0]!=SQLITE_INTEGER ) return 0;
return sessionGetI64(&a[1])==iRowid;
}
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
for(iCol=0; iCol<pTab->nCol; iCol++){
if( !pTab->abPK[iCol] ){
a += sessionSerialLen(a);
}else{
sqlite3_value *pVal;
int rc;
int eType = *a++;
if( op==SQLITE_INSERT ){
rc = pSession->hook.xNew(pSession->hook.pCtx, iCol, &pVal);
}else{
rc = pSession->hook.xOld(pSession->hook.pCtx, iCol, &pVal);
}
assert( rc==SQLITE_OK );
if( sqlite3_value_type(pVal)!=eType ) return 0;
assert( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT
|| eType==SQLITE_BLOB || eType==SQLITE_TEXT
);
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
i64 iVal = sessionGetI64(a);
a += 8;
if( eType==SQLITE_INTEGER ){
if( sqlite3_value_int64(pVal)!=iVal ) return 0;
}else{
double rVal;
assert( sizeof(iVal)==8 && sizeof(rVal)==8 );
memcpy(&rVal, &iVal, 8);
if( sqlite3_value_double(pVal)!=rVal ) return 0;
}
}else{
int n;
const u8 *z;
a += sessionVarintGet(a, &n);
if( sqlite3_value_bytes(pVal)!=n ) return 0;
if( eType==SQLITE_TEXT ){
z = sqlite3_value_text(pVal);
}else{
z = sqlite3_value_blob(pVal);
}
if( n>0 && memcmp(a, z, n) ) return 0;
a += n;
}
}
}
return 1;
}
static int sessionGrowHash(
sqlite3_session *pSession,
int bPatchset,
SessionTable *pTab
){
if( pTab->nChange==0 || pTab->nEntry>=(pTab->nChange/2) ){
int i;
SessionChange **apNew;
sqlite3_int64 nNew = 2*(sqlite3_int64)(pTab->nChange ? pTab->nChange : 128);
apNew = (SessionChange**)sessionMalloc64(
pSession, sizeof(SessionChange*) * nNew
);
if( apNew==0 ){
if( pTab->nChange==0 ){
return SQLITE_ERROR;
}
return SQLITE_OK;
}
memset(apNew, 0, sizeof(SessionChange *) * nNew);
for(i=0; i<pTab->nChange; i++){
SessionChange *p;
SessionChange *pNext;
for(p=pTab->apChange[i]; p; p=pNext){
int bPkOnly = (p->op==SQLITE_DELETE && bPatchset);
int iHash = sessionChangeHash(pTab, bPkOnly, p->aRecord, nNew);
pNext = p->pNext;
p->pNext = apNew[iHash];
apNew[iHash] = p;
}
}
sessionFree(pSession, pTab->apChange);
pTab->nChange = nNew;
pTab->apChange = apNew;
}
return SQLITE_OK;
}
static int sessionTableInfo(
sqlite3_session *pSession,
sqlite3 *db,
const char *zDb,
const char *zThis,
int *pnCol,
const char **pzTab,
const char ***pazCol,
u8 **pabPK,
int *pbRowid
){
char *zPragma;
sqlite3_stmt *pStmt;
int rc;
sqlite3_int64 nByte;
int nDbCol = 0;
int nThis;
int i;
u8 *pAlloc = 0;
char **azCol = 0;
u8 *abPK = 0;
int bRowid = 0;
assert( pazCol && pabPK );
nThis = sqlite3Strlen30(zThis);
if( nThis==12 && 0==sqlite3_stricmp("sqlite_stat1", zThis) ){
rc = sqlite3_table_column_metadata(db, zDb, zThis, 0, 0, 0, 0, 0, 0);
if( rc==SQLITE_OK ){
zPragma = sqlite3_mprintf(
"SELECT 0, 'tbl', '', 0, '', 1 UNION ALL "
"SELECT 1, 'idx', '', 0, '', 2 UNION ALL "
"SELECT 2, 'stat', '', 0, '', 0"
);
}else if( rc==SQLITE_ERROR ){
zPragma = sqlite3_mprintf("");
}else{
*pazCol = 0;
*pabPK = 0;
*pnCol = 0;
if( pzTab ) *pzTab = 0;
return rc;
}
}else{
zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis);
}
if( !zPragma ){
*pazCol = 0;
*pabPK = 0;
*pnCol = 0;
if( pzTab ) *pzTab = 0;
return SQLITE_NOMEM;
}
rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0);
sqlite3_free(zPragma);
if( rc!=SQLITE_OK ){
*pazCol = 0;
*pabPK = 0;
*pnCol = 0;
if( pzTab ) *pzTab = 0;
return rc;
}
nByte = nThis + 1;
bRowid = (pbRowid!=0);
while( SQLITE_ROW==sqlite3_step(pStmt) ){
nByte += sqlite3_column_bytes(pStmt, 1);
nDbCol++;
if( sqlite3_column_int(pStmt, 5) ) bRowid = 0;
}
if( nDbCol==0 ) bRowid = 0;
nDbCol += bRowid;
nByte += strlen(SESSIONS_ROWID);
rc = sqlite3_reset(pStmt);
if( rc==SQLITE_OK ){
nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1);
pAlloc = sessionMalloc64(pSession, nByte);
if( pAlloc==0 ){
rc = SQLITE_NOMEM;
}
}
if( rc==SQLITE_OK ){
azCol = (char **)pAlloc;
pAlloc = (u8 *)&azCol[nDbCol];
abPK = (u8 *)pAlloc;
pAlloc = &abPK[nDbCol];
if( pzTab ){
memcpy(pAlloc, zThis, nThis+1);
*pzTab = (char *)pAlloc;
pAlloc += nThis+1;
}
i = 0;
if( bRowid ){
size_t nName = strlen(SESSIONS_ROWID);
memcpy(pAlloc, SESSIONS_ROWID, nName+1);
azCol[i] = (char*)pAlloc;
pAlloc += nName+1;
abPK[i] = 1;
i++;
}
while( SQLITE_ROW==sqlite3_step(pStmt) ){
int nName = sqlite3_column_bytes(pStmt, 1);
const unsigned char *zName = sqlite3_column_text(pStmt, 1);
if( zName==0 ) break;
memcpy(pAlloc, zName, nName+1);
azCol[i] = (char *)pAlloc;
pAlloc += nName+1;
abPK[i] = sqlite3_column_int(pStmt, 5);
i++;
}
rc = sqlite3_reset(pStmt);
}
if( rc==SQLITE_OK ){
*pazCol = (const char **)azCol;
*pabPK = abPK;
*pnCol = nDbCol;
}else{
*pazCol = 0;
*pabPK = 0;
*pnCol = 0;
if( pzTab ) *pzTab = 0;
sessionFree(pSession, azCol);
}
if( pbRowid ) *pbRowid = bRowid;
sqlite3_finalize(pStmt);
return rc;
}
static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
if( pTab->nCol==0 ){
u8 *abPK;
assert( pTab->azCol==0 || pTab->abPK==0 );
pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb,
pTab->zName, &pTab->nCol, 0, &pTab->azCol, &abPK,
(pSession->bImplicitPK ? &pTab->bRowid : 0)
);
if( pSession->rc==SQLITE_OK ){
int i;
for(i=0; i<pTab->nCol; i++){
if( abPK[i] ){
pTab->abPK = abPK;
break;
}
}
if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){
pTab->bStat1 = 1;
}
if( pSession->bEnableSize ){
pSession->nMaxChangesetSize += (
1 + sessionVarintLen(pTab->nCol) + pTab->nCol + strlen(pTab->zName)+1
);
}
}
}
return (pSession->rc || pTab->abPK==0);
}
typedef struct SessionStat1Ctx SessionStat1Ctx;
struct SessionStat1Ctx {
SessionHook hook;
sqlite3_session *pSession;
};
static int sessionStat1Old(void *pCtx, int iCol, sqlite3_value **ppVal){
SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
sqlite3_value *pVal = 0;
int rc = p->hook.xOld(p->hook.pCtx, iCol, &pVal);
if( rc==SQLITE_OK && iCol==1 && sqlite3_value_type(pVal)==SQLITE_NULL ){
pVal = p->pSession->pZeroBlob;
}
*ppVal = pVal;
return rc;
}
static int sessionStat1New(void *pCtx, int iCol, sqlite3_value **ppVal){
SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
sqlite3_value *pVal = 0;
int rc = p->hook.xNew(p->hook.pCtx, iCol, &pVal);
if( rc==SQLITE_OK && iCol==1 && sqlite3_value_type(pVal)==SQLITE_NULL ){
pVal = p->pSession->pZeroBlob;
}
*ppVal = pVal;
return rc;
}
static int sessionStat1Count(void *pCtx){
SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
return p->hook.xCount(p->hook.pCtx);
}
static int sessionStat1Depth(void *pCtx){
SessionStat1Ctx *p = (SessionStat1Ctx*)pCtx;
return p->hook.xDepth(p->hook.pCtx);
}
static int sessionUpdateMaxSize(
int op,
sqlite3_session *pSession,
SessionTable *pTab,
SessionChange *pC
){
i64 nNew = 2;
if( pC->op==SQLITE_INSERT ){
if( pTab->bRowid ) nNew += 9;
if( op!=SQLITE_DELETE ){
int ii;
for(ii=0; ii<pTab->nCol; ii++){
sqlite3_value *p = 0;
pSession->hook.xNew(pSession->hook.pCtx, ii, &p);
sessionSerializeValue(0, p, &nNew);
}
}
}else if( op==SQLITE_DELETE ){
nNew += pC->nRecord;
if( sqlite3_preupdate_blobwrite(pSession->db)>=0 ){
nNew += pC->nRecord;
}
}else{
int ii;
u8 *pCsr = pC->aRecord;
if( pTab->bRowid ){
nNew += 9 + 1;
pCsr += 9;
}
for(ii=pTab->bRowid; ii<pTab->nCol; ii++){
int bChanged = 1;
int nOld = 0;
int eType;
sqlite3_value *p = 0;
pSession->hook.xNew(pSession->hook.pCtx, ii-pTab->bRowid, &p);
if( p==0 ){
return SQLITE_NOMEM;
}
eType = *pCsr++;
switch( eType ){
case SQLITE_NULL:
bChanged = sqlite3_value_type(p)!=SQLITE_NULL;
break;
case SQLITE_FLOAT:
case SQLITE_INTEGER: {
if( eType==sqlite3_value_type(p) ){
sqlite3_int64 iVal = sessionGetI64(pCsr);
if( eType==SQLITE_INTEGER ){
bChanged = (iVal!=sqlite3_value_int64(p));
}else{
double dVal;
memcpy(&dVal, &iVal, 8);
bChanged = (dVal!=sqlite3_value_double(p));
}
}
nOld = 8;
pCsr += 8;
break;
}
default: {
int nByte;
nOld = sessionVarintGet(pCsr, &nByte);
pCsr += nOld;
nOld += nByte;
assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
if( eType==sqlite3_value_type(p)
&& nByte==sqlite3_value_bytes(p)
&& (nByte==0 || 0==memcmp(pCsr, sqlite3_value_blob(p), nByte))
){
bChanged = 0;
}
pCsr += nByte;
break;
}
}
if( bChanged && pTab->abPK[ii] ){
nNew = pC->nRecord + 2;
break;
}
if( bChanged ){
nNew += 1 + nOld;
sessionSerializeValue(0, p, &nNew);
}else if( pTab->abPK[ii] ){
nNew += 2 + nOld;
}else{
nNew += 2;
}
}
}
if( nNew>pC->nMaxSize ){
int nIncr = nNew - pC->nMaxSize;
pC->nMaxSize = nNew;
pSession->nMaxChangesetSize += nIncr;
}
return SQLITE_OK;
}
static void sessionPreupdateOneChange(
int op,
i64 iRowid,
sqlite3_session *pSession,
SessionTable *pTab
){
int iHash;
int bNull = 0;
int rc = SQLITE_OK;
SessionStat1Ctx stat1 = {{0,0,0,0,0},0};
if( pSession->rc ) return;
if( sessionInitTable(pSession, pTab) ) return;
if( (pTab->nCol-pTab->bRowid)!=pSession->hook.xCount(pSession->hook.pCtx) ){
pSession->rc = SQLITE_SCHEMA;
return;
}
if( sessionGrowHash(pSession, 0, pTab) ){
pSession->rc = SQLITE_NOMEM;
return;
}
if( pTab->bStat1 ){
stat1.hook = pSession->hook;
stat1.pSession = pSession;
pSession->hook.pCtx = (void*)&stat1;
pSession->hook.xNew = sessionStat1New;
pSession->hook.xOld = sessionStat1Old;
pSession->hook.xCount = sessionStat1Count;
pSession->hook.xDepth = sessionStat1Depth;
if( pSession->pZeroBlob==0 ){
sqlite3_value *p = sqlite3ValueNew(0);
if( p==0 ){
rc = SQLITE_NOMEM;
goto error_out;
}
sqlite3ValueSetStr(p, 0, "", 0, SQLITE_STATIC);
pSession->pZeroBlob = p;
}
}
rc = sessionPreupdateHash(
pSession, iRowid, pTab, op==SQLITE_INSERT, &iHash, &bNull
);
if( rc!=SQLITE_OK ) goto error_out;
if( bNull==0 ){
SessionChange *pC;
for(pC=pTab->apChange[iHash]; pC; pC=pC->pNext){
if( sessionPreupdateEqual(pSession, iRowid, pTab, pC, op) ) break;
}
if( pC==0 ){
sqlite3_int64 nByte;
int i;
assert( rc==SQLITE_OK );
pTab->nEntry++;
nByte = sizeof(SessionChange);
for(i=0; i<(pTab->nCol-pTab->bRowid); i++){
sqlite3_value *p = 0;
if( op!=SQLITE_INSERT ){
TESTONLY(int trc = ) pSession->hook.xOld(pSession->hook.pCtx, i, &p);
assert( trc==SQLITE_OK );
}else if( pTab->abPK[i] ){
TESTONLY(int trc = ) pSession->hook.xNew(pSession->hook.pCtx, i, &p);
assert( trc==SQLITE_OK );
}
rc = sessionSerializeValue(0, p, &nByte);
if( rc!=SQLITE_OK ) goto error_out;
}
if( pTab->bRowid ){
nByte += 9;
}
pC = (SessionChange *)sessionMalloc64(pSession, nByte);
if( !pC ){
rc = SQLITE_NOMEM;
goto error_out;
}else{
memset(pC, 0, sizeof(SessionChange));
pC->aRecord = (u8 *)&pC[1];
}
nByte = 0;
if( pTab->bRowid ){
pC->aRecord[0] = SQLITE_INTEGER;
sessionPutI64(&pC->aRecord[1], iRowid);
nByte = 9;
}
for(i=0; i<(pTab->nCol-pTab->bRowid); i++){
sqlite3_value *p = 0;
if( op!=SQLITE_INSERT ){
pSession->hook.xOld(pSession->hook.pCtx, i, &p);
}else if( pTab->abPK[i] ){
pSession->hook.xNew(pSession->hook.pCtx, i, &p);
}
sessionSerializeValue(&pC->aRecord[nByte], p, &nByte);
}
if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){
pC->bIndirect = 1;
}
pC->nRecord = nByte;
pC->op = op;
pC->pNext = pTab->apChange[iHash];
pTab->apChange[iHash] = pC;
}else if( pC->bIndirect ){
if( pSession->hook.xDepth(pSession->hook.pCtx)==0
&& pSession->bIndirect==0
){
pC->bIndirect = 0;
}
}
assert( rc==SQLITE_OK );
if( pSession->bEnableSize ){
rc = sessionUpdateMaxSize(op, pSession, pTab, pC);
}
}
error_out:
if( pTab->bStat1 ){
pSession->hook = stat1.hook;
}
if( rc!=SQLITE_OK ){
pSession->rc = rc;
}
}
static int sessionFindTable(
sqlite3_session *pSession,
const char *zName,
SessionTable **ppTab
){
int rc = SQLITE_OK;
int nName = sqlite3Strlen30(zName);
SessionTable *pRet;
for(pRet=pSession->pTable; pRet; pRet=pRet->pNext){
if( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) ) break;
}
if( pRet==0 && pSession->bAutoAttach ){
if( pSession->xTableFilter==0
|| pSession->xTableFilter(pSession->pFilterCtx, zName)
){
rc = sqlite3session_attach(pSession, zName);
if( rc==SQLITE_OK ){
pRet = pSession->pTable;
while( ALWAYS(pRet) && pRet->pNext ){
pRet = pRet->pNext;
}
assert( pRet!=0 );
assert( 0==sqlite3_strnicmp(pRet->zName, zName, nName+1) );
}
}
}
assert( rc==SQLITE_OK || pRet==0 );
*ppTab = pRet;
return rc;
}
static void xPreUpdate(
void *pCtx,
sqlite3 *db,
int op,
char const *zDb,
char const *zName,
sqlite3_int64 iKey1,
sqlite3_int64 iKey2
){
sqlite3_session *pSession;
int nDb = sqlite3Strlen30(zDb);
assert( sqlite3_mutex_held(db->mutex) );
(void)iKey1;
(void)iKey2;
for(pSession=(sqlite3_session *)pCtx; pSession; pSession=pSession->pNext){
SessionTable *pTab;
if( pSession->bEnable==0 ) continue;
if( pSession->rc ) continue;
if( sqlite3_strnicmp(zDb, pSession->zDb, nDb+1) ) continue;
pSession->rc = sessionFindTable(pSession, zName, &pTab);
if( pTab ){
assert( pSession->rc==SQLITE_OK );
assert( op==SQLITE_UPDATE || iKey1==iKey2 );
sessionPreupdateOneChange(op, iKey1, pSession, pTab);
if( op==SQLITE_UPDATE ){
sessionPreupdateOneChange(SQLITE_INSERT, iKey2, pSession, pTab);
}
}
}
}
static int sessionPreupdateOld(void *pCtx, int iVal, sqlite3_value **ppVal){
return sqlite3_preupdate_old((sqlite3*)pCtx, iVal, ppVal);
}
static int sessionPreupdateNew(void *pCtx, int iVal, sqlite3_value **ppVal){
return sqlite3_preupdate_new((sqlite3*)pCtx, iVal, ppVal);
}
static int sessionPreupdateCount(void *pCtx){
return sqlite3_preupdate_count((sqlite3*)pCtx);
}
static int sessionPreupdateDepth(void *pCtx){
return sqlite3_preupdate_depth((sqlite3*)pCtx);
}
static void sessionPreupdateHooks(
sqlite3_session *pSession
){
pSession->hook.pCtx = (void*)pSession->db;
pSession->hook.xOld = sessionPreupdateOld;
pSession->hook.xNew = sessionPreupdateNew;
pSession->hook.xCount = sessionPreupdateCount;
pSession->hook.xDepth = sessionPreupdateDepth;
}
typedef struct SessionDiffCtx SessionDiffCtx;
struct SessionDiffCtx {
sqlite3_stmt *pStmt;
int bRowid;
int nOldOff;
};
static int sessionDiffOld(void *pCtx, int iVal, sqlite3_value **ppVal){
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
*ppVal = sqlite3_column_value(p->pStmt, iVal+p->nOldOff+p->bRowid);
return SQLITE_OK;
}
static int sessionDiffNew(void *pCtx, int iVal, sqlite3_value **ppVal){
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
*ppVal = sqlite3_column_value(p->pStmt, iVal+p->bRowid);
return SQLITE_OK;
}
static int sessionDiffCount(void *pCtx){
SessionDiffCtx *p = (SessionDiffCtx*)pCtx;
return (p->nOldOff ? p->nOldOff : sqlite3_column_count(p->pStmt)) - p->bRowid;
}
static int sessionDiffDepth(void *pCtx){
(void)pCtx;
return 0;
}
static void sessionDiffHooks(
sqlite3_session *pSession,
SessionDiffCtx *pDiffCtx
){
pSession->hook.pCtx = (void*)pDiffCtx;
pSession->hook.xOld = sessionDiffOld;
pSession->hook.xNew = sessionDiffNew;
pSession->hook.xCount = sessionDiffCount;
pSession->hook.xDepth = sessionDiffDepth;
}
static char *sessionExprComparePK(
int nCol,
const char *zDb1, const char *zDb2,
const char *zTab,
const char **azCol, u8 *abPK
){
int i;
const char *zSep = "";
char *zRet = 0;
for(i=0; i<nCol; i++){
if( abPK[i] ){
zRet = sqlite3_mprintf("%z%s\"%w\".\"%w\".\"%w\"=\"%w\".\"%w\".\"%w\"",
zRet, zSep, zDb1, zTab, azCol[i], zDb2, zTab, azCol[i]
);
zSep = " AND ";
if( zRet==0 ) break;
}
}
return zRet;
}
static char *sessionExprCompareOther(
int nCol,
const char *zDb1, const char *zDb2,
const char *zTab,
const char **azCol, u8 *abPK
){
int i;
const char *zSep = "";
char *zRet = 0;
int bHave = 0;
for(i=0; i<nCol; i++){
if( abPK[i]==0 ){
bHave = 1;
zRet = sqlite3_mprintf(
"%z%s\"%w\".\"%w\".\"%w\" IS NOT \"%w\".\"%w\".\"%w\"",
zRet, zSep, zDb1, zTab, azCol[i], zDb2, zTab, azCol[i]
);
zSep = " OR ";
if( zRet==0 ) break;
}
}
if( bHave==0 ){
assert( zRet==0 );
zRet = sqlite3_mprintf("0");
}
return zRet;
}
static char *sessionSelectFindNew(
const char *zDb1,
const char *zDb2,
int bRowid,
const char *zTbl,
const char *zExpr
){
const char *zSel = (bRowid ? SESSIONS_ROWID ", *" : "*");
char *zRet = sqlite3_mprintf(
"SELECT %s FROM \"%w\".\"%w\" WHERE NOT EXISTS ("
" SELECT 1 FROM \"%w\".\"%w\" WHERE %s"
")",
zSel, zDb1, zTbl, zDb2, zTbl, zExpr
);
return zRet;
}
static int sessionDiffFindNew(
int op,
sqlite3_session *pSession,
SessionTable *pTab,
const char *zDb1,
const char *zDb2,
char *zExpr
){
int rc = SQLITE_OK;
char *zStmt = sessionSelectFindNew(
zDb1, zDb2, pTab->bRowid, pTab->zName, zExpr
);
if( zStmt==0 ){
rc = SQLITE_NOMEM;
}else{
sqlite3_stmt *pStmt;
rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0);
if( rc==SQLITE_OK ){
SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx;
pDiffCtx->pStmt = pStmt;
pDiffCtx->nOldOff = 0;
pDiffCtx->bRowid = pTab->bRowid;
while( SQLITE_ROW==sqlite3_step(pStmt) ){
i64 iRowid = (pTab->bRowid ? sqlite3_column_int64(pStmt, 0) : 0);
sessionPreupdateOneChange(op, iRowid, pSession, pTab);
}
rc = sqlite3_finalize(pStmt);
}
sqlite3_free(zStmt);
}
return rc;
}
static char *sessionAllCols(
const char *zDb,
SessionTable *pTab
){
int ii;
char *zRet = 0;
for(ii=0; ii<pTab->nCol; ii++){
zRet = sqlite3_mprintf("%z%s\"%w\".\"%w\".\"%w\"",
zRet, (zRet ? ", " : ""), zDb, pTab->zName, pTab->azCol[ii]
);
if( !zRet ) break;
}
return zRet;
}
static int sessionDiffFindModified(
sqlite3_session *pSession,
SessionTable *pTab,
const char *zFrom,
const char *zExpr
){
int rc = SQLITE_OK;
char *zExpr2 = sessionExprCompareOther(pTab->nCol,
pSession->zDb, zFrom, pTab->zName, pTab->azCol, pTab->abPK
);
if( zExpr2==0 ){
rc = SQLITE_NOMEM;
}else{
char *z1 = sessionAllCols(pSession->zDb, pTab);
char *z2 = sessionAllCols(zFrom, pTab);
char *zStmt = sqlite3_mprintf(
"SELECT %s,%s FROM \"%w\".\"%w\", \"%w\".\"%w\" WHERE %s AND (%z)",
z1, z2, pSession->zDb, pTab->zName, zFrom, pTab->zName, zExpr, zExpr2
);
if( zStmt==0 || z1==0 || z2==0 ){
rc = SQLITE_NOMEM;
}else{
sqlite3_stmt *pStmt;
rc = sqlite3_prepare(pSession->db, zStmt, -1, &pStmt, 0);
if( rc==SQLITE_OK ){
SessionDiffCtx *pDiffCtx = (SessionDiffCtx*)pSession->hook.pCtx;
pDiffCtx->pStmt = pStmt;
pDiffCtx->nOldOff = pTab->nCol;
while( SQLITE_ROW==sqlite3_step(pStmt) ){
i64 iRowid = (pTab->bRowid ? sqlite3_column_int64(pStmt, 0) : 0);
sessionPreupdateOneChange(SQLITE_UPDATE, iRowid, pSession, pTab);
}
rc = sqlite3_finalize(pStmt);
}
}
sqlite3_free(zStmt);
sqlite3_free(z1);
sqlite3_free(z2);
}
return rc;
}
int sqlite3session_diff(
sqlite3_session *pSession,
const char *zFrom,
const char *zTbl,
char **pzErrMsg
){
const char *zDb = pSession->zDb;
int rc = pSession->rc;
SessionDiffCtx d;
memset(&d, 0, sizeof(d));
sessionDiffHooks(pSession, &d);
sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
if( pzErrMsg ) *pzErrMsg = 0;
if( rc==SQLITE_OK ){
char *zExpr = 0;
sqlite3 *db = pSession->db;
SessionTable *pTo;
rc = sessionFindTable(pSession, zTbl, &pTo);
if( pTo==0 ) goto diff_out;
if( sessionInitTable(pSession, pTo) ){
rc = pSession->rc;
goto diff_out;
}
if( rc==SQLITE_OK ){
int bHasPk = 0;
int bMismatch = 0;
int nCol;
int bRowid = 0;
u8 *abPK;
const char **azCol = 0;
rc = sessionTableInfo(0, db, zFrom, zTbl, &nCol, 0, &azCol, &abPK,
pSession->bImplicitPK ? &bRowid : 0
);
if( rc==SQLITE_OK ){
if( pTo->nCol!=nCol ){
bMismatch = 1;
}else{
int i;
for(i=0; i<nCol; i++){
if( pTo->abPK[i]!=abPK[i] ) bMismatch = 1;
if( sqlite3_stricmp(azCol[i], pTo->azCol[i]) ) bMismatch = 1;
if( abPK[i] ) bHasPk = 1;
}
}
}
sqlite3_free((char*)azCol);
if( bMismatch ){
if( pzErrMsg ){
*pzErrMsg = sqlite3_mprintf("table schemas do not match");
}
rc = SQLITE_SCHEMA;
}
if( bHasPk==0 ){
goto diff_out;
}
}
if( rc==SQLITE_OK ){
zExpr = sessionExprComparePK(pTo->nCol,
zDb, zFrom, pTo->zName, pTo->azCol, pTo->abPK
);
}
if( rc==SQLITE_OK ){
rc = sessionDiffFindNew(SQLITE_INSERT, pSession, pTo, zDb, zFrom, zExpr);
}
if( rc==SQLITE_OK ){
rc = sessionDiffFindNew(SQLITE_DELETE, pSession, pTo, zFrom, zDb, zExpr);
}
if( rc==SQLITE_OK ){
rc = sessionDiffFindModified(pSession, pTo, zFrom, zExpr);
}
sqlite3_free(zExpr);
}
diff_out:
sessionPreupdateHooks(pSession);
sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
return rc;
}
int sqlite3session_create(
sqlite3 *db,
const char *zDb,
sqlite3_session **ppSession
){
sqlite3_session *pNew;
sqlite3_session *pOld;
int nDb = sqlite3Strlen30(zDb);
*ppSession = 0;
pNew = (sqlite3_session *)sqlite3_malloc64(sizeof(sqlite3_session) + nDb + 1);
if( !pNew ) return SQLITE_NOMEM;
memset(pNew, 0, sizeof(sqlite3_session));
pNew->db = db;
pNew->zDb = (char *)&pNew[1];
pNew->bEnable = 1;
memcpy(pNew->zDb, zDb, nDb+1);
sessionPreupdateHooks(pNew);
sqlite3_mutex_enter(sqlite3_db_mutex(db));
pOld = (sqlite3_session*)sqlite3_preupdate_hook(db, xPreUpdate, (void*)pNew);
pNew->pNext = pOld;
sqlite3_mutex_leave(sqlite3_db_mutex(db));
*ppSession = pNew;
return SQLITE_OK;
}
static void sessionDeleteTable(sqlite3_session *pSession, SessionTable *pList){
SessionTable *pNext;
SessionTable *pTab;
for(pTab=pList; pTab; pTab=pNext){
int i;
pNext = pTab->pNext;
for(i=0; i<pTab->nChange; i++){
SessionChange *p;
SessionChange *pNextChange;
for(p=pTab->apChange[i]; p; p=pNextChange){
pNextChange = p->pNext;
sessionFree(pSession, p);
}
}
sessionFree(pSession, (char*)pTab->azCol);
sessionFree(pSession, pTab->apChange);
sessionFree(pSession, pTab);
}
}
void sqlite3session_delete(sqlite3_session *pSession){
sqlite3 *db = pSession->db;
sqlite3_session *pHead;
sqlite3_session **pp;
sqlite3_mutex_enter(sqlite3_db_mutex(db));
pHead = (sqlite3_session*)sqlite3_preupdate_hook(db, 0, 0);
for(pp=&pHead; ALWAYS((*pp)!=0); pp=&((*pp)->pNext)){
if( (*pp)==pSession ){
*pp = (*pp)->pNext;
if( pHead ) sqlite3_preupdate_hook(db, xPreUpdate, (void*)pHead);
break;
}
}
sqlite3_mutex_leave(sqlite3_db_mutex(db));
sqlite3ValueFree(pSession->pZeroBlob);
sessionDeleteTable(pSession, pSession->pTable);
assert( pSession->nMalloc==0 );
sqlite3_free(pSession);
}
void sqlite3session_table_filter(
sqlite3_session *pSession,
int(*xFilter)(void*, const char*),
void *pCtx
){
pSession->bAutoAttach = 1;
pSession->pFilterCtx = pCtx;
pSession->xTableFilter = xFilter;
}
int sqlite3session_attach(
sqlite3_session *pSession,
const char *zName
){
int rc = SQLITE_OK;
sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
if( !zName ){
pSession->bAutoAttach = 1;
}else{
SessionTable *pTab;
int nName;
nName = sqlite3Strlen30(zName);
for(pTab=pSession->pTable; pTab; pTab=pTab->pNext){
if( 0==sqlite3_strnicmp(pTab->zName, zName, nName+1) ) break;
}
if( !pTab ){
int nByte = sizeof(SessionTable) + nName + 1;
pTab = (SessionTable*)sessionMalloc64(pSession, nByte);
if( !pTab ){
rc = SQLITE_NOMEM;
}else{
SessionTable **ppTab;
memset(pTab, 0, sizeof(SessionTable));
pTab->zName = (char *)&pTab[1];
memcpy(pTab->zName, zName, nName+1);
for(ppTab=&pSession->pTable; *ppTab; ppTab=&(*ppTab)->pNext);
*ppTab = pTab;
}
}
}
sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
return rc;
}
static int sessionBufferGrow(SessionBuffer *p, i64 nByte, int *pRc){
#define SESSION_MAX_BUFFER_SZ (0x7FFFFF00 - 1)
i64 nReq = p->nBuf + nByte;
if( *pRc==SQLITE_OK && nReq>p->nAlloc ){
u8 *aNew;
i64 nNew = p->nAlloc ? p->nAlloc : 128;
do {
nNew = nNew*2;
}while( nNew<nReq );
if( nNew>SESSION_MAX_BUFFER_SZ ){
nNew = SESSION_MAX_BUFFER_SZ;
if( nNew<nReq ){
*pRc = SQLITE_NOMEM;
return 1;
}
}
aNew = (u8 *)sqlite3_realloc64(p->aBuf, nNew);
if( 0==aNew ){
*pRc = SQLITE_NOMEM;
}else{
p->aBuf = aNew;
p->nAlloc = nNew;
}
}
return (*pRc!=SQLITE_OK);
}
static void sessionAppendValue(SessionBuffer *p, sqlite3_value *pVal, int *pRc){
int rc = *pRc;
if( rc==SQLITE_OK ){
sqlite3_int64 nByte = 0;
rc = sessionSerializeValue(0, pVal, &nByte);
sessionBufferGrow(p, nByte, &rc);
if( rc==SQLITE_OK ){
rc = sessionSerializeValue(&p->aBuf[p->nBuf], pVal, 0);
p->nBuf += nByte;
}else{
*pRc = rc;
}
}
}
static void sessionAppendByte(SessionBuffer *p, u8 v, int *pRc){
if( 0==sessionBufferGrow(p, 1, pRc) ){
p->aBuf[p->nBuf++] = v;
}
}
static void sessionAppendVarint(SessionBuffer *p, int v, int *pRc){
if( 0==sessionBufferGrow(p, 9, pRc) ){
p->nBuf += sessionVarintPut(&p->aBuf[p->nBuf], v);
}
}
static void sessionAppendBlob(
SessionBuffer *p,
const u8 *aBlob,
int nBlob,
int *pRc
){
if( nBlob>0 && 0==sessionBufferGrow(p, nBlob, pRc) ){
memcpy(&p->aBuf[p->nBuf], aBlob, nBlob);
p->nBuf += nBlob;
}
}
static void sessionAppendStr(
SessionBuffer *p,
const char *zStr,
int *pRc
){
int nStr = sqlite3Strlen30(zStr);
if( 0==sessionBufferGrow(p, nStr+1, pRc) ){
memcpy(&p->aBuf[p->nBuf], zStr, nStr);
p->nBuf += nStr;
p->aBuf[p->nBuf] = 0x00;
}
}
static void sessionAppendInteger(
SessionBuffer *p,
int iVal,
int *pRc
){
char aBuf[24];
sqlite3_snprintf(sizeof(aBuf)-1, aBuf, "%d", iVal);
sessionAppendStr(p, aBuf, pRc);
}
static void sessionAppendPrintf(
SessionBuffer *p,
int *pRc,
const char *zFmt,
...
){
if( *pRc==SQLITE_OK ){
char *zApp = 0;
va_list ap;
va_start(ap, zFmt);
zApp = sqlite3_vmprintf(zFmt, ap);
if( zApp==0 ){
*pRc = SQLITE_NOMEM;
}else{
sessionAppendStr(p, zApp, pRc);
}
va_end(ap);
sqlite3_free(zApp);
}
}
static void sessionAppendIdent(
SessionBuffer *p,
const char *zStr,
int *pRc
){
int nStr = sqlite3Strlen30(zStr)*2 + 2 + 2;
if( 0==sessionBufferGrow(p, nStr, pRc) ){
char *zOut = (char *)&p->aBuf[p->nBuf];
const char *zIn = zStr;
*zOut++ = '"';
while( *zIn ){
if( *zIn=='"' ) *zOut++ = '"';
*zOut++ = *(zIn++);
}
*zOut++ = '"';
p->nBuf = (int)((u8 *)zOut - p->aBuf);
p->aBuf[p->nBuf] = 0x00;
}
}
static void sessionAppendCol(
SessionBuffer *p,
sqlite3_stmt *pStmt,
int iCol,
int *pRc
){
if( *pRc==SQLITE_OK ){
int eType = sqlite3_column_type(pStmt, iCol);
sessionAppendByte(p, (u8)eType, pRc);
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
sqlite3_int64 i;
u8 aBuf[8];
if( eType==SQLITE_INTEGER ){
i = sqlite3_column_int64(pStmt, iCol);
}else{
double r = sqlite3_column_double(pStmt, iCol);
memcpy(&i, &r, 8);
}
sessionPutI64(aBuf, i);
sessionAppendBlob(p, aBuf, 8, pRc);
}
if( eType==SQLITE_BLOB || eType==SQLITE_TEXT ){
u8 *z;
int nByte;
if( eType==SQLITE_BLOB ){
z = (u8 *)sqlite3_column_blob(pStmt, iCol);
}else{
z = (u8 *)sqlite3_column_text(pStmt, iCol);
}
nByte = sqlite3_column_bytes(pStmt, iCol);
if( z || (eType==SQLITE_BLOB && nByte==0) ){
sessionAppendVarint(p, nByte, pRc);
sessionAppendBlob(p, z, nByte, pRc);
}else{
*pRc = SQLITE_NOMEM;
}
}
}
}
static int sessionAppendUpdate(
SessionBuffer *pBuf,
int bPatchset,
sqlite3_stmt *pStmt,
SessionChange *p,
u8 *abPK
){
int rc = SQLITE_OK;
SessionBuffer buf2 = {0,0,0};
int bNoop = 1;
int nRewind = pBuf->nBuf;
int i;
u8 *pCsr = p->aRecord;
assert( abPK!=0 );
sessionAppendByte(pBuf, SQLITE_UPDATE, &rc);
sessionAppendByte(pBuf, p->bIndirect, &rc);
for(i=0; i<sqlite3_column_count(pStmt); i++){
int bChanged = 0;
int nAdvance;
int eType = *pCsr;
switch( eType ){
case SQLITE_NULL:
nAdvance = 1;
if( sqlite3_column_type(pStmt, i)!=SQLITE_NULL ){
bChanged = 1;
}
break;
case SQLITE_FLOAT:
case SQLITE_INTEGER: {
nAdvance = 9;
if( eType==sqlite3_column_type(pStmt, i) ){
sqlite3_int64 iVal = sessionGetI64(&pCsr[1]);
if( eType==SQLITE_INTEGER ){
if( iVal==sqlite3_column_int64(pStmt, i) ) break;
}else{
double dVal;
memcpy(&dVal, &iVal, 8);
if( dVal==sqlite3_column_double(pStmt, i) ) break;
}
}
bChanged = 1;
break;
}
default: {
int n;
int nHdr = 1 + sessionVarintGet(&pCsr[1], &n);
assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
nAdvance = nHdr + n;
if( eType==sqlite3_column_type(pStmt, i)
&& n==sqlite3_column_bytes(pStmt, i)
&& (n==0 || 0==memcmp(&pCsr[nHdr], sqlite3_column_blob(pStmt, i), n))
){
break;
}
bChanged = 1;
}
}
if( bChanged ) bNoop = 0;
if( bPatchset==0 ){
if( bChanged || abPK[i] ){
sessionAppendBlob(pBuf, pCsr, nAdvance, &rc);
}else{
sessionAppendByte(pBuf, 0, &rc);
}
}
if( bChanged || (bPatchset && abPK[i]) ){
sessionAppendCol(&buf2, pStmt, i, &rc);
}else{
sessionAppendByte(&buf2, 0, &rc);
}
pCsr += nAdvance;
}
if( bNoop ){
pBuf->nBuf = nRewind;
}else{
sessionAppendBlob(pBuf, buf2.aBuf, buf2.nBuf, &rc);
}
sqlite3_free(buf2.aBuf);
return rc;
}
static int sessionAppendDelete(
SessionBuffer *pBuf,
int bPatchset,
SessionChange *p,
int nCol,
u8 *abPK
){
int rc = SQLITE_OK;
sessionAppendByte(pBuf, SQLITE_DELETE, &rc);
sessionAppendByte(pBuf, p->bIndirect, &rc);
if( bPatchset==0 ){
sessionAppendBlob(pBuf, p->aRecord, p->nRecord, &rc);
}else{
int i;
u8 *a = p->aRecord;
for(i=0; i<nCol; i++){
u8 *pStart = a;
int eType = *a++;
switch( eType ){
case 0:
case SQLITE_NULL:
assert( abPK[i]==0 );
break;
case SQLITE_FLOAT:
case SQLITE_INTEGER:
a += 8;
break;
default: {
int n;
a += sessionVarintGet(a, &n);
a += n;
break;
}
}
if( abPK[i] ){
sessionAppendBlob(pBuf, pStart, (int)(a-pStart), &rc);
}
}
assert( (a - p->aRecord)==p->nRecord );
}
return rc;
}
static int sessionSelectStmt(
sqlite3 *db,
int bIgnoreNoop,
const char *zDb,
const char *zTab,
int bRowid,
int nCol,
const char **azCol,
u8 *abPK,
sqlite3_stmt **ppStmt
){
int rc = SQLITE_OK;
char *zSql = 0;
const char *zSep = "";
const char *zCols = bRowid ? SESSIONS_ROWID ", *" : "*";
int nSql = -1;
int i;
SessionBuffer nooptest = {0, 0, 0};
SessionBuffer pkfield = {0, 0, 0};
SessionBuffer pkvar = {0, 0, 0};
sessionAppendStr(&nooptest, ", 1", &rc);
if( 0==sqlite3_stricmp("sqlite_stat1", zTab) ){
sessionAppendStr(&nooptest, " AND (?6 OR ?3 IS stat)", &rc);
sessionAppendStr(&pkfield, "tbl, idx", &rc);
sessionAppendStr(&pkvar,
"?1, (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", &rc
);
zCols = "tbl, ?2, stat";
}else{
for(i=0; i<nCol; i++){
if( abPK[i] ){
sessionAppendStr(&pkfield, zSep, &rc);
sessionAppendStr(&pkvar, zSep, &rc);
zSep = ", ";
sessionAppendIdent(&pkfield, azCol[i], &rc);
sessionAppendPrintf(&pkvar, &rc, "?%d", i+1);
}else{
sessionAppendPrintf(&nooptest, &rc,
" AND (?%d OR ?%d IS %w.%w)", i+1+nCol, i+1, zTab, azCol[i]
);
}
}
}
if( rc==SQLITE_OK ){
zSql = sqlite3_mprintf(
"SELECT %s%s FROM %Q.%Q WHERE (%s) IS (%s)",
zCols, (bIgnoreNoop ? (char*)nooptest.aBuf : ""),
zDb, zTab, (char*)pkfield.aBuf, (char*)pkvar.aBuf
);
if( zSql==0 ) rc = SQLITE_NOMEM;
}
#if 0#endif
if( rc==SQLITE_OK ){
rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0);
}
sqlite3_free(zSql);
sqlite3_free(nooptest.aBuf);
sqlite3_free(pkfield.aBuf);
sqlite3_free(pkvar.aBuf);
return rc;
}
static int sessionSelectBind(
sqlite3_stmt *pSelect,
int nCol,
u8 *abPK,
SessionChange *pChange
){
int i;
int rc = SQLITE_OK;
u8 *a = pChange->aRecord;
for(i=0; i<nCol && rc==SQLITE_OK; i++){
int eType = *a++;
switch( eType ){
case 0:
case SQLITE_NULL:
assert( abPK[i]==0 );
break;
case SQLITE_INTEGER: {
if( abPK[i] ){
i64 iVal = sessionGetI64(a);
rc = sqlite3_bind_int64(pSelect, i+1, iVal);
}
a += 8;
break;
}
case SQLITE_FLOAT: {
if( abPK[i] ){
double rVal;
i64 iVal = sessionGetI64(a);
memcpy(&rVal, &iVal, 8);
rc = sqlite3_bind_double(pSelect, i+1, rVal);
}
a += 8;
break;
}
case SQLITE_TEXT: {
int n;
a += sessionVarintGet(a, &n);
if( abPK[i] ){
rc = sqlite3_bind_text(pSelect, i+1, (char *)a, n, SQLITE_TRANSIENT);
}
a += n;
break;
}
default: {
int n;
assert( eType==SQLITE_BLOB );
a += sessionVarintGet(a, &n);
if( abPK[i] ){
rc = sqlite3_bind_blob(pSelect, i+1, a, n, SQLITE_TRANSIENT);
}
a += n;
break;
}
}
}
return rc;
}
static void sessionAppendTableHdr(
SessionBuffer *pBuf,
int bPatchset,
SessionTable *pTab,
int *pRc
){
sessionAppendByte(pBuf, (bPatchset ? 'P' : 'T'), pRc);
sessionAppendVarint(pBuf, pTab->nCol, pRc);
sessionAppendBlob(pBuf, pTab->abPK, pTab->nCol, pRc);
sessionAppendBlob(pBuf, (u8 *)pTab->zName, (int)strlen(pTab->zName)+1, pRc);
}
static int sessionGenerateChangeset(
sqlite3_session *pSession,
int bPatchset,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut,
int *pnChangeset,
void **ppChangeset
){
sqlite3 *db = pSession->db;
SessionTable *pTab;
SessionBuffer buf = {0,0,0};
int rc;
assert( xOutput==0 || (pnChangeset==0 && ppChangeset==0) );
assert( xOutput!=0 || (pnChangeset!=0 && ppChangeset!=0) );
if( xOutput==0 ){
assert( pnChangeset!=0 && ppChangeset!=0 );
*pnChangeset = 0;
*ppChangeset = 0;
}
if( pSession->rc ) return pSession->rc;
rc = sqlite3_exec(pSession->db, "SAVEPOINT changeset", 0, 0, 0);
if( rc!=SQLITE_OK ) return rc;
sqlite3_mutex_enter(sqlite3_db_mutex(db));
for(pTab=pSession->pTable; rc==SQLITE_OK && pTab; pTab=pTab->pNext){
if( pTab->nEntry ){
const char *zName = pTab->zName;
int nCol = 0;
u8 *abPK = 0;
const char **azCol = 0;
int i;
sqlite3_stmt *pSel = 0;
int nRewind = buf.nBuf;
int nNoop;
int bRowid = 0;
rc = sessionTableInfo(
0, db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK,
(pSession->bImplicitPK ? &bRowid : 0)
);
if( rc==SQLITE_OK && (
pTab->nCol!=nCol
|| pTab->bRowid!=bRowid
|| memcmp(abPK, pTab->abPK, nCol)
)){
rc = SQLITE_SCHEMA;
}
sessionAppendTableHdr(&buf, bPatchset, pTab, &rc);
if( rc==SQLITE_OK ){
rc = sessionSelectStmt(
db, 0, pSession->zDb, zName, bRowid, nCol, azCol, abPK, &pSel
);
}
nNoop = buf.nBuf;
for(i=0; i<pTab->nChange && rc==SQLITE_OK; i++){
SessionChange *p;
for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){
rc = sessionSelectBind(pSel, nCol, abPK, p);
if( rc!=SQLITE_OK ) continue;
if( sqlite3_step(pSel)==SQLITE_ROW ){
if( p->op==SQLITE_INSERT ){
int iCol;
sessionAppendByte(&buf, SQLITE_INSERT, &rc);
sessionAppendByte(&buf, p->bIndirect, &rc);
for(iCol=0; iCol<nCol; iCol++){
sessionAppendCol(&buf, pSel, iCol, &rc);
}
}else{
assert( abPK!=0 );
rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, abPK);
}
}else if( p->op!=SQLITE_INSERT ){
rc = sessionAppendDelete(&buf, bPatchset, p, nCol, abPK);
}
if( rc==SQLITE_OK ){
rc = sqlite3_reset(pSel);
}
if( xOutput
&& rc==SQLITE_OK
&& buf.nBuf>nNoop
&& buf.nBuf>sessions_strm_chunk_size
){
rc = xOutput(pOut, (void*)buf.aBuf, buf.nBuf);
nNoop = -1;
buf.nBuf = 0;
}
}
}
sqlite3_finalize(pSel);
if( buf.nBuf==nNoop ){
buf.nBuf = nRewind;
}
sqlite3_free((char*)azCol);
}
}
if( rc==SQLITE_OK ){
if( xOutput==0 ){
*pnChangeset = buf.nBuf;
*ppChangeset = buf.aBuf;
buf.aBuf = 0;
}else if( buf.nBuf>0 ){
rc = xOutput(pOut, (void*)buf.aBuf, buf.nBuf);
}
}
sqlite3_free(buf.aBuf);
sqlite3_exec(db, "RELEASE changeset", 0, 0, 0);
sqlite3_mutex_leave(sqlite3_db_mutex(db));
return rc;
}
int sqlite3session_changeset(
sqlite3_session *pSession,
int *pnChangeset,
void **ppChangeset
){
int rc;
if( pnChangeset==0 || ppChangeset==0 ) return SQLITE_MISUSE;
rc = sessionGenerateChangeset(pSession, 0, 0, 0, pnChangeset, ppChangeset);
assert( rc || pnChangeset==0
|| pSession->bEnableSize==0 || *pnChangeset<=pSession->nMaxChangesetSize
);
return rc;
}
int sqlite3session_changeset_strm(
sqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
){
if( xOutput==0 ) return SQLITE_MISUSE;
return sessionGenerateChangeset(pSession, 0, xOutput, pOut, 0, 0);
}
int sqlite3session_patchset_strm(
sqlite3_session *pSession,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
){
if( xOutput==0 ) return SQLITE_MISUSE;
return sessionGenerateChangeset(pSession, 1, xOutput, pOut, 0, 0);
}
int sqlite3session_patchset(
sqlite3_session *pSession,
int *pnPatchset,
void **ppPatchset
){
if( pnPatchset==0 || ppPatchset==0 ) return SQLITE_MISUSE;
return sessionGenerateChangeset(pSession, 1, 0, 0, pnPatchset, ppPatchset);
}
int sqlite3session_enable(sqlite3_session *pSession, int bEnable){
int ret;
sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
if( bEnable>=0 ){
pSession->bEnable = bEnable;
}
ret = pSession->bEnable;
sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
return ret;
}
int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect){
int ret;
sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
if( bIndirect>=0 ){
pSession->bIndirect = bIndirect;
}
ret = pSession->bIndirect;
sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
return ret;
}
int sqlite3session_isempty(sqlite3_session *pSession){
int ret = 0;
SessionTable *pTab;
sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db));
for(pTab=pSession->pTable; pTab && ret==0; pTab=pTab->pNext){
ret = (pTab->nEntry>0);
}
sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db));
return (ret==0);
}
sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession){
return pSession->nMalloc;
}
int sqlite3session_object_config(sqlite3_session *pSession, int op, void *pArg){
int rc = SQLITE_OK;
switch( op ){
case SQLITE_SESSION_OBJCONFIG_SIZE: {
int iArg = *(int*)pArg;
if( iArg>=0 ){
if( pSession->pTable ){
rc = SQLITE_MISUSE;
}else{
pSession->bEnableSize = (iArg!=0);
}
}
*(int*)pArg = pSession->bEnableSize;
break;
}
case SQLITE_SESSION_OBJCONFIG_ROWID: {
int iArg = *(int*)pArg;
if( iArg>=0 ){
if( pSession->pTable ){
rc = SQLITE_MISUSE;
}else{
pSession->bImplicitPK = (iArg!=0);
}
}
*(int*)pArg = pSession->bImplicitPK;
break;
}
default:
rc = SQLITE_MISUSE;
}
return rc;
}
sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession){
return pSession->nMaxChangesetSize;
}
static int sessionChangesetStart(
sqlite3_changeset_iter **pp,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn,
int nChangeset,
void *pChangeset,
int bInvert,
int bSkipEmpty
){
sqlite3_changeset_iter *pRet;
int nByte;
assert( xInput==0 || (pChangeset==0 && nChangeset==0) );
*pp = 0;
nByte = sizeof(sqlite3_changeset_iter);
pRet = (sqlite3_changeset_iter *)sqlite3_malloc(nByte);
if( !pRet ) return SQLITE_NOMEM;
memset(pRet, 0, sizeof(sqlite3_changeset_iter));
pRet->in.aData = (u8 *)pChangeset;
pRet->in.nData = nChangeset;
pRet->in.xInput = xInput;
pRet->in.pIn = pIn;
pRet->in.bEof = (xInput ? 0 : 1);
pRet->bInvert = bInvert;
pRet->bSkipEmpty = bSkipEmpty;
*pp = pRet;
return SQLITE_OK;
}
int sqlite3changeset_start(
sqlite3_changeset_iter **pp,
int nChangeset,
void *pChangeset
){
return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, 0, 0);
}
int sqlite3changeset_start_v2(
sqlite3_changeset_iter **pp,
int nChangeset,
void *pChangeset,
int flags
){
int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT);
return sessionChangesetStart(pp, 0, 0, nChangeset, pChangeset, bInvert, 0);
}
int sqlite3changeset_start_strm(
sqlite3_changeset_iter **pp,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
){
return sessionChangesetStart(pp, xInput, pIn, 0, 0, 0, 0);
}
int sqlite3changeset_start_v2_strm(
sqlite3_changeset_iter **pp,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn,
int flags
){
int bInvert = !!(flags & SQLITE_CHANGESETSTART_INVERT);
return sessionChangesetStart(pp, xInput, pIn, 0, 0, bInvert, 0);
}
static void sessionDiscardData(SessionInput *pIn){
if( pIn->xInput && pIn->iNext>=sessions_strm_chunk_size ){
int nMove = pIn->buf.nBuf - pIn->iNext;
assert( nMove>=0 );
if( nMove>0 ){
memmove(pIn->buf.aBuf, &pIn->buf.aBuf[pIn->iNext], nMove);
}
pIn->buf.nBuf -= pIn->iNext;
pIn->iNext = 0;
pIn->nData = pIn->buf.nBuf;
}
}
static int sessionInputBuffer(SessionInput *pIn, int nByte){
int rc = SQLITE_OK;
if( pIn->xInput ){
while( !pIn->bEof && (pIn->iNext+nByte)>=pIn->nData && rc==SQLITE_OK ){
int nNew = sessions_strm_chunk_size;
if( pIn->bNoDiscard==0 ) sessionDiscardData(pIn);
if( SQLITE_OK==sessionBufferGrow(&pIn->buf, nNew, &rc) ){
rc = pIn->xInput(pIn->pIn, &pIn->buf.aBuf[pIn->buf.nBuf], &nNew);
if( nNew==0 ){
pIn->bEof = 1;
}else{
pIn->buf.nBuf += nNew;
}
}
pIn->aData = pIn->buf.aBuf;
pIn->nData = pIn->buf.nBuf;
}
}
return rc;
}
static void sessionSkipRecord(
u8 **ppRec,
int nCol
){
u8 *aRec = *ppRec;
int i;
for(i=0; i<nCol; i++){
int eType = *aRec++;
if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
int nByte;
aRec += sessionVarintGet((u8*)aRec, &nByte);
aRec += nByte;
}else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
aRec += 8;
}
}
*ppRec = aRec;
}
static int sessionValueSetStr(
sqlite3_value *pVal,
u8 *aData,
int nData,
u8 enc
){
u8 *aCopy = sqlite3_malloc64((sqlite3_int64)nData+1);
if( aCopy==0 ) return SQLITE_NOMEM;
memcpy(aCopy, aData, nData);
sqlite3ValueSetStr(pVal, nData, (char*)aCopy, enc, sqlite3_free);
return SQLITE_OK;
}
static int sessionReadRecord(
SessionInput *pIn,
int nCol,
u8 *abPK,
sqlite3_value **apOut,
int *pbEmpty
){
int i;
int rc = SQLITE_OK;
assert( pbEmpty==0 || *pbEmpty==0 );
if( pbEmpty ) *pbEmpty = 1;
for(i=0; i<nCol && rc==SQLITE_OK; i++){
int eType = 0;
if( abPK && abPK[i]==0 ) continue;
rc = sessionInputBuffer(pIn, 9);
if( rc==SQLITE_OK ){
if( pIn->iNext>=pIn->nData ){
rc = SQLITE_CORRUPT_BKPT;
}else{
eType = pIn->aData[pIn->iNext++];
assert( apOut[i]==0 );
if( eType ){
if( pbEmpty ) *pbEmpty = 0;
apOut[i] = sqlite3ValueNew(0);
if( !apOut[i] ) rc = SQLITE_NOMEM;
}
}
}
if( rc==SQLITE_OK ){
u8 *aVal = &pIn->aData[pIn->iNext];
if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
int nByte;
pIn->iNext += sessionVarintGet(aVal, &nByte);
rc = sessionInputBuffer(pIn, nByte);
if( rc==SQLITE_OK ){
if( nByte<0 || nByte>pIn->nData-pIn->iNext ){
rc = SQLITE_CORRUPT_BKPT;
}else{
u8 enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0);
rc = sessionValueSetStr(apOut[i],&pIn->aData[pIn->iNext],nByte,enc);
pIn->iNext += nByte;
}
}
}
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
sqlite3_int64 v = sessionGetI64(aVal);
if( eType==SQLITE_INTEGER ){
sqlite3VdbeMemSetInt64(apOut[i], v);
}else{
double d;
memcpy(&d, &v, 8);
sqlite3VdbeMemSetDouble(apOut[i], d);
}
pIn->iNext += 8;
}
}
}
return rc;
}
static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){
int rc = SQLITE_OK;
int nCol = 0;
int nRead = 0;
rc = sessionInputBuffer(pIn, 9);
if( rc==SQLITE_OK ){
nRead += sessionVarintGet(&pIn->aData[pIn->iNext + nRead], &nCol);
if( nCol<0 || nCol>65536 ){
rc = SQLITE_CORRUPT_BKPT;
}else{
rc = sessionInputBuffer(pIn, nRead+nCol+100);
nRead += nCol;
}
}
while( rc==SQLITE_OK ){
while( (pIn->iNext + nRead)<pIn->nData && pIn->aData[pIn->iNext + nRead] ){
nRead++;
}
if( (pIn->iNext + nRead)<pIn->nData ) break;
rc = sessionInputBuffer(pIn, nRead + 100);
}
*pnByte = nRead+1;
return rc;
}
static int sessionChangesetBufferRecord(
SessionInput *pIn,
int nCol,
int *pnByte
){
int rc = SQLITE_OK;
int nByte = 0;
int i;
for(i=0; rc==SQLITE_OK && i<nCol; i++){
int eType;
rc = sessionInputBuffer(pIn, nByte + 10);
if( rc==SQLITE_OK ){
eType = pIn->aData[pIn->iNext + nByte++];
if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){
int n;
nByte += sessionVarintGet(&pIn->aData[pIn->iNext+nByte], &n);
nByte += n;
rc = sessionInputBuffer(pIn, nByte);
}else if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
nByte += 8;
}
}
}
*pnByte = nByte;
return rc;
}
static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
int rc;
int nCopy;
assert( p->rc==SQLITE_OK );
rc = sessionChangesetBufferTblhdr(&p->in, &nCopy);
if( rc==SQLITE_OK ){
int nByte;
int nVarint;
nVarint = sessionVarintGet(&p->in.aData[p->in.iNext], &p->nCol);
if( p->nCol>0 ){
nCopy -= nVarint;
p->in.iNext += nVarint;
nByte = p->nCol * sizeof(sqlite3_value*) * 2 + nCopy;
p->tblhdr.nBuf = 0;
sessionBufferGrow(&p->tblhdr, nByte, &rc);
}else{
rc = SQLITE_CORRUPT_BKPT;
}
}
if( rc==SQLITE_OK ){
size_t iPK = sizeof(sqlite3_value*)*p->nCol*2;
memset(p->tblhdr.aBuf, 0, iPK);
memcpy(&p->tblhdr.aBuf[iPK], &p->in.aData[p->in.iNext], nCopy);
p->in.iNext += nCopy;
}
p->apValue = (sqlite3_value**)p->tblhdr.aBuf;
if( p->apValue==0 ){
p->abPK = 0;
p->zTab = 0;
}else{
p->abPK = (u8*)&p->apValue[p->nCol*2];
p->zTab = p->abPK ? (char*)&p->abPK[p->nCol] : 0;
}
return (p->rc = rc);
}
static int sessionChangesetNextOne(
sqlite3_changeset_iter *p,
u8 **paRec,
int *pnRec,
int *pbNew,
int *pbEmpty
){
int i;
u8 op;
assert( (paRec==0 && pnRec==0) || (paRec && pnRec) );
assert( pbEmpty==0 || *pbEmpty==0 );
if( p->rc!=SQLITE_OK ) return p->rc;
if( p->apValue ){
for(i=0; i<p->nCol*2; i++){
sqlite3ValueFree(p->apValue[i]);
}
memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2);
}
p->rc = sessionInputBuffer(&p->in, 2);
if( p->rc!=SQLITE_OK ) return p->rc;
if( p->in.iNext>=p->in.nData ){
return SQLITE_DONE;
}
sessionDiscardData(&p->in);
p->in.iCurrent = p->in.iNext;
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;
p->in.iCurrent = p->in.iNext;
if( p->in.iNext>=p->in.nData ) return SQLITE_DONE;
op = p->in.aData[p->in.iNext++];
}
if( p->zTab==0 || (p->bPatchset && p->bInvert) ){
assert( p->in.iNext==1 || p->zTab );
return (p->rc = SQLITE_CORRUPT_BKPT);
}
p->op = op;
p->bIndirect = p->in.aData[p->in.iNext++];
if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){
return (p->rc = SQLITE_CORRUPT_BKPT);
}
if( paRec ){
int nVal;
if( p->bPatchset==0 && op==SQLITE_UPDATE ){
nVal = p->nCol * 2;
}else if( p->bPatchset && op==SQLITE_DELETE ){
nVal = 0;
for(i=0; i<p->nCol; i++) if( p->abPK[i] ) nVal++;
}else{
nVal = p->nCol;
}
p->rc = sessionChangesetBufferRecord(&p->in, nVal, pnRec);
if( p->rc!=SQLITE_OK ) return p->rc;
*paRec = &p->in.aData[p->in.iNext];
p->in.iNext += *pnRec;
}else{
sqlite3_value **apOld = (p->bInvert ? &p->apValue[p->nCol] : p->apValue);
sqlite3_value **apNew = (p->bInvert ? p->apValue : &p->apValue[p->nCol]);
if( p->op!=SQLITE_INSERT && (p->bPatchset==0 || p->op==SQLITE_DELETE) ){
u8 *abPK = p->bPatchset ? p->abPK : 0;
p->rc = sessionReadRecord(&p->in, p->nCol, abPK, apOld, 0);
if( p->rc!=SQLITE_OK ) return p->rc;
}
if( p->op!=SQLITE_DELETE ){
p->rc = sessionReadRecord(&p->in, p->nCol, 0, apNew, pbEmpty);
if( p->rc!=SQLITE_OK ) return p->rc;
}
if( (p->bPatchset || p->bInvert) && p->op==SQLITE_UPDATE ){
for(i=0; i<p->nCol; i++){
assert( p->bPatchset==0 || p->apValue[i]==0 );
if( p->abPK[i] ){
assert( p->apValue[i]==0 );
p->apValue[i] = p->apValue[i+p->nCol];
if( p->apValue[i]==0 ) return (p->rc = SQLITE_CORRUPT_BKPT);
p->apValue[i+p->nCol] = 0;
}
}
}else if( p->bInvert ){
if( p->op==SQLITE_INSERT ) p->op = SQLITE_DELETE;
else if( p->op==SQLITE_DELETE ) p->op = SQLITE_INSERT;
}
if( p->bPatchset==0 && p->op==SQLITE_UPDATE){
for(i=0; i<p->nCol; i++){
if( p->abPK[i]==0 && p->apValue[i+p->nCol]==0 ){
sqlite3ValueFree(p->apValue[i]);
p->apValue[i] = 0;
}
}
}
}
return SQLITE_ROW;
}
static int sessionChangesetNext(
sqlite3_changeset_iter *p,
u8 **paRec,
int *pnRec,
int *pbNew
){
int bEmpty;
int rc;
do {
bEmpty = 0;
rc = sessionChangesetNextOne(p, paRec, pnRec, pbNew, &bEmpty);
}while( rc==SQLITE_ROW && p->bSkipEmpty && bEmpty);
return rc;
}
int sqlite3changeset_next(sqlite3_changeset_iter *p){
return sessionChangesetNext(p, 0, 0, 0);
}
int sqlite3changeset_op(
sqlite3_changeset_iter *pIter,
const char **pzTab,
int *pnCol,
int *pOp,
int *pbIndirect
){
*pOp = pIter->op;
*pnCol = pIter->nCol;
*pzTab = pIter->zTab;
if( pbIndirect ) *pbIndirect = pIter->bIndirect;
return SQLITE_OK;
}
int sqlite3changeset_pk(
sqlite3_changeset_iter *pIter,
unsigned char **pabPK,
int *pnCol
){
*pabPK = pIter->abPK;
if( pnCol ) *pnCol = pIter->nCol;
return SQLITE_OK;
}
int sqlite3changeset_old(
sqlite3_changeset_iter *pIter,
int iVal,
sqlite3_value **ppValue
){
if( pIter->op!=SQLITE_UPDATE && pIter->op!=SQLITE_DELETE ){
return SQLITE_MISUSE;
}
if( iVal<0 || iVal>=pIter->nCol ){
return SQLITE_RANGE;
}
*ppValue = pIter->apValue[iVal];
return SQLITE_OK;
}
int sqlite3changeset_new(
sqlite3_changeset_iter *pIter,
int iVal,
sqlite3_value **ppValue
){
if( pIter->op!=SQLITE_UPDATE && pIter->op!=SQLITE_INSERT ){
return SQLITE_MISUSE;
}
if( iVal<0 || iVal>=pIter->nCol ){
return SQLITE_RANGE;
}
*ppValue = pIter->apValue[pIter->nCol+iVal];
return SQLITE_OK;
}
#define sessionChangesetNew(pIter, iVal) (pIter)->apValue[(pIter)->nCol+(iVal)]
#define sessionChangesetOld(pIter, iVal) (pIter)->apValue[(iVal)]
int sqlite3changeset_conflict(
sqlite3_changeset_iter *pIter,
int iVal,
sqlite3_value **ppValue
){
if( !pIter->pConflict ){
return SQLITE_MISUSE;
}
if( iVal<0 || iVal>=pIter->nCol ){
return SQLITE_RANGE;
}
*ppValue = sqlite3_column_value(pIter->pConflict, iVal);
return SQLITE_OK;
}
int sqlite3changeset_fk_conflicts(
sqlite3_changeset_iter *pIter,
int *pnOut
){
if( pIter->pConflict || pIter->apValue ){
return SQLITE_MISUSE;
}
*pnOut = pIter->nCol;
return SQLITE_OK;
}
int sqlite3changeset_finalize(sqlite3_changeset_iter *p){
int rc = SQLITE_OK;
if( p ){
int i;
rc = p->rc;
if( p->apValue ){
for(i=0; i<p->nCol*2; i++) sqlite3ValueFree(p->apValue[i]);
}
sqlite3_free(p->tblhdr.aBuf);
sqlite3_free(p->in.buf.aBuf);
sqlite3_free(p);
}
return rc;
}
static int sessionChangesetInvert(
SessionInput *pInput,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut,
int *pnInverted,
void **ppInverted
){
int rc = SQLITE_OK;
SessionBuffer sOut;
int nCol = 0;
u8 *abPK = 0;
sqlite3_value **apVal = 0;
SessionBuffer sPK = {0, 0, 0};
memset(&sOut, 0, sizeof(SessionBuffer));
if( ppInverted ){
*ppInverted = 0;
*pnInverted = 0;
}
while( 1 ){
u8 eType;
if( (rc = sessionInputBuffer(pInput, 2)) ) goto finished_invert;
if( pInput->iNext>=pInput->nData ) break;
eType = pInput->aData[pInput->iNext];
switch( eType ){
case 'T': {
int nByte;
int nVar;
pInput->iNext++;
if( (rc = sessionChangesetBufferTblhdr(pInput, &nByte)) ){
goto finished_invert;
}
nVar = sessionVarintGet(&pInput->aData[pInput->iNext], &nCol);
sPK.nBuf = 0;
sessionAppendBlob(&sPK, &pInput->aData[pInput->iNext+nVar], nCol, &rc);
sessionAppendByte(&sOut, eType, &rc);
sessionAppendBlob(&sOut, &pInput->aData[pInput->iNext], nByte, &rc);
if( rc ) goto finished_invert;
pInput->iNext += nByte;
sqlite3_free(apVal);
apVal = 0;
abPK = sPK.aBuf;
break;
}
case SQLITE_INSERT:
case SQLITE_DELETE: {
int nByte;
int bIndirect = pInput->aData[pInput->iNext+1];
int eType2 = (eType==SQLITE_DELETE ? SQLITE_INSERT : SQLITE_DELETE);
pInput->iNext += 2;
assert( rc==SQLITE_OK );
rc = sessionChangesetBufferRecord(pInput, nCol, &nByte);
sessionAppendByte(&sOut, eType2, &rc);
sessionAppendByte(&sOut, bIndirect, &rc);
sessionAppendBlob(&sOut, &pInput->aData[pInput->iNext], nByte, &rc);
pInput->iNext += nByte;
if( rc ) goto finished_invert;
break;
}
case SQLITE_UPDATE: {
int iCol;
if( 0==apVal ){
apVal = (sqlite3_value **)sqlite3_malloc64(sizeof(apVal[0])*nCol*2);
if( 0==apVal ){
rc = SQLITE_NOMEM;
goto finished_invert;
}
memset(apVal, 0, sizeof(apVal[0])*nCol*2);
}
sessionAppendByte(&sOut, eType, &rc);
sessionAppendByte(&sOut, pInput->aData[pInput->iNext+1], &rc);
pInput->iNext += 2;
rc = sessionReadRecord(pInput, nCol, 0, &apVal[0], 0);
if( rc==SQLITE_OK ){
rc = sessionReadRecord(pInput, nCol, 0, &apVal[nCol], 0);
}
for(iCol=0; iCol<nCol; iCol++){
sqlite3_value *pVal = apVal[iCol + (abPK[iCol] ? 0 : nCol)];
sessionAppendValue(&sOut, pVal, &rc);
}
for(iCol=0; iCol<nCol; iCol++){
sqlite3_value *pVal = (abPK[iCol] ? 0 : apVal[iCol]);
sessionAppendValue(&sOut, pVal, &rc);
}
for(iCol=0; iCol<nCol*2; iCol++){
sqlite3ValueFree(apVal[iCol]);
}
memset(apVal, 0, sizeof(apVal[0])*nCol*2);
if( rc!=SQLITE_OK ){
goto finished_invert;
}
break;
}
default:
rc = SQLITE_CORRUPT_BKPT;
goto finished_invert;
}
assert( rc==SQLITE_OK );
if( xOutput && sOut.nBuf>=sessions_strm_chunk_size ){
rc = xOutput(pOut, sOut.aBuf, sOut.nBuf);
sOut.nBuf = 0;
if( rc!=SQLITE_OK ) goto finished_invert;
}
}
assert( rc==SQLITE_OK );
if( pnInverted && ALWAYS(ppInverted) ){
*pnInverted = sOut.nBuf;
*ppInverted = sOut.aBuf;
sOut.aBuf = 0;
}else if( sOut.nBuf>0 && ALWAYS(xOutput!=0) ){
rc = xOutput(pOut, sOut.aBuf, sOut.nBuf);
}
finished_invert:
sqlite3_free(sOut.aBuf);
sqlite3_free(apVal);
sqlite3_free(sPK.aBuf);
return rc;
}
int sqlite3changeset_invert(
int nChangeset,
const void *pChangeset,
int *pnInverted,
void **ppInverted
){
SessionInput sInput;
memset(&sInput, 0, sizeof(SessionInput));
sInput.nData = nChangeset;
sInput.aData = (u8*)pChangeset;
return sessionChangesetInvert(&sInput, 0, 0, pnInverted, ppInverted);
}
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
){
SessionInput sInput;
int rc;
memset(&sInput, 0, sizeof(SessionInput));
sInput.xInput = xInput;
sInput.pIn = pIn;
rc = sessionChangesetInvert(&sInput, xOutput, pOut, 0, 0);
sqlite3_free(sInput.buf.aBuf);
return rc;
}
typedef struct SessionUpdate SessionUpdate;
struct SessionUpdate {
sqlite3_stmt *pStmt;
u32 *aMask;
SessionUpdate *pNext;
};
typedef struct SessionApplyCtx SessionApplyCtx;
struct SessionApplyCtx {
sqlite3 *db;
sqlite3_stmt *pDelete;
sqlite3_stmt *pInsert;
sqlite3_stmt *pSelect;
int nCol;
const char **azCol;
u8 *abPK;
u32 *aUpdateMask;
SessionUpdate *pUp;
int bStat1;
int bDeferConstraints;
int bInvertConstraints;
SessionBuffer constraints;
SessionBuffer rebase;
u8 bRebaseStarted;
u8 bRebase;
u8 bIgnoreNoop;
int bRowid;
};
#define SESSION_UPDATE_CACHE_SZ 12
static int sessionUpdateFind(
sqlite3_changeset_iter *pIter,
SessionApplyCtx *p,
int bPatchset,
sqlite3_stmt **ppStmt
){
int rc = SQLITE_OK;
SessionUpdate *pUp = 0;
int nCol = pIter->nCol;
int nU32 = (pIter->nCol+33)/32;
int ii;
if( p->aUpdateMask==0 ){
p->aUpdateMask = sqlite3_malloc(nU32*sizeof(u32));
if( p->aUpdateMask==0 ){
rc = SQLITE_NOMEM;
}
}
if( rc==SQLITE_OK ){
memset(p->aUpdateMask, 0, nU32*sizeof(u32));
rc = SQLITE_CORRUPT;
for(ii=0; ii<pIter->nCol; ii++){
if( sessionChangesetNew(pIter, ii) ){
p->aUpdateMask[ii/32] |= (1<<(ii%32));
rc = SQLITE_OK;
}
}
}
if( rc==SQLITE_OK ){
if( bPatchset ) p->aUpdateMask[nCol/32] |= (1<<(nCol%32));
if( p->pUp ){
int nUp = 0;
SessionUpdate **pp = &p->pUp;
while( 1 ){
nUp++;
if( 0==memcmp(p->aUpdateMask, (*pp)->aMask, nU32*sizeof(u32)) ){
pUp = *pp;
*pp = pUp->pNext;
pUp->pNext = p->pUp;
p->pUp = pUp;
break;
}
if( (*pp)->pNext ){
pp = &(*pp)->pNext;
}else{
if( nUp>=SESSION_UPDATE_CACHE_SZ ){
sqlite3_finalize((*pp)->pStmt);
sqlite3_free(*pp);
*pp = 0;
}
break;
}
}
}
if( pUp==0 ){
int nByte = sizeof(SessionUpdate) * nU32*sizeof(u32);
int bStat1 = (sqlite3_stricmp(pIter->zTab, "sqlite_stat1")==0);
pUp = (SessionUpdate*)sqlite3_malloc(nByte);
if( pUp==0 ){
rc = SQLITE_NOMEM;
}else{
const char *zSep = "";
SessionBuffer buf;
memset(&buf, 0, sizeof(buf));
pUp->aMask = (u32*)&pUp[1];
memcpy(pUp->aMask, p->aUpdateMask, nU32*sizeof(u32));
sessionAppendStr(&buf, "UPDATE main.", &rc);
sessionAppendIdent(&buf, pIter->zTab, &rc);
sessionAppendStr(&buf, " SET ", &rc);
for(ii=0; ii<pIter->nCol; ii++){
if( p->abPK[ii]==0 && sessionChangesetNew(pIter, ii) ){
sessionAppendStr(&buf, zSep, &rc);
sessionAppendIdent(&buf, p->azCol[ii], &rc);
sessionAppendStr(&buf, " = ?", &rc);
sessionAppendInteger(&buf, ii*2+1, &rc);
zSep = ", ";
}
}
zSep = "";
sessionAppendStr(&buf, " WHERE ", &rc);
for(ii=0; ii<pIter->nCol; ii++){
if( p->abPK[ii] || (bPatchset==0 && sessionChangesetOld(pIter, ii)) ){
sessionAppendStr(&buf, zSep, &rc);
if( bStat1 && ii==1 ){
assert( sqlite3_stricmp(p->azCol[ii], "idx")==0 );
sessionAppendStr(&buf,
"idx IS CASE "
"WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL "
"ELSE ?4 END ", &rc
);
}else{
sessionAppendIdent(&buf, p->azCol[ii], &rc);
sessionAppendStr(&buf, " IS ?", &rc);
sessionAppendInteger(&buf, ii*2+2, &rc);
}
zSep = " AND ";
}
}
if( rc==SQLITE_OK ){
char *zSql = (char*)buf.aBuf;
rc = sqlite3_prepare_v2(p->db, zSql, buf.nBuf, &pUp->pStmt, 0);
}
if( rc!=SQLITE_OK ){
sqlite3_free(pUp);
pUp = 0;
}else{
pUp->pNext = p->pUp;
p->pUp = pUp;
}
sqlite3_free(buf.aBuf);
}
}
}
assert( (rc==SQLITE_OK)==(pUp!=0) );
if( pUp ){
*ppStmt = pUp->pStmt;
}else{
*ppStmt = 0;
}
return rc;
}
static void sessionUpdateFree(SessionApplyCtx *p){
SessionUpdate *pUp;
SessionUpdate *pNext;
for(pUp=p->pUp; pUp; pUp=pNext){
pNext = pUp->pNext;
sqlite3_finalize(pUp->pStmt);
sqlite3_free(pUp);
}
p->pUp = 0;
sqlite3_free(p->aUpdateMask);
p->aUpdateMask = 0;
}
static int sessionDeleteRow(
sqlite3 *db,
const char *zTab,
SessionApplyCtx *p
){
int i;
const char *zSep = "";
int rc = SQLITE_OK;
SessionBuffer buf = {0, 0, 0};
int nPk = 0;
sessionAppendStr(&buf, "DELETE FROM main.", &rc);
sessionAppendIdent(&buf, zTab, &rc);
sessionAppendStr(&buf, " WHERE ", &rc);
for(i=0; i<p->nCol; i++){
if( p->abPK[i] ){
nPk++;
sessionAppendStr(&buf, zSep, &rc);
sessionAppendIdent(&buf, p->azCol[i], &rc);
sessionAppendStr(&buf, " = ?", &rc);
sessionAppendInteger(&buf, i+1, &rc);
zSep = " AND ";
}
}
if( nPk<p->nCol ){
sessionAppendStr(&buf, " AND (?", &rc);
sessionAppendInteger(&buf, p->nCol+1, &rc);
sessionAppendStr(&buf, " OR ", &rc);
zSep = "";
for(i=0; i<p->nCol; i++){
if( !p->abPK[i] ){
sessionAppendStr(&buf, zSep, &rc);
sessionAppendIdent(&buf, p->azCol[i], &rc);
sessionAppendStr(&buf, " IS ?", &rc);
sessionAppendInteger(&buf, i+1, &rc);
zSep = "AND ";
}
}
sessionAppendStr(&buf, ")", &rc);
}
if( rc==SQLITE_OK ){
rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0);
}
sqlite3_free(buf.aBuf);
return rc;
}
static int sessionSelectRow(
sqlite3 *db,
const char *zTab,
SessionApplyCtx *p
){
return sessionSelectStmt(db, p->bIgnoreNoop,
"main", zTab, p->bRowid, p->nCol, p->azCol, p->abPK, &p->pSelect
);
}
static int sessionInsertRow(
sqlite3 *db,
const char *zTab,
SessionApplyCtx *p
){
int rc = SQLITE_OK;
int i;
SessionBuffer buf = {0, 0, 0};
sessionAppendStr(&buf, "INSERT INTO main.", &rc);
sessionAppendIdent(&buf, zTab, &rc);
sessionAppendStr(&buf, "(", &rc);
for(i=0; i<p->nCol; i++){
if( i!=0 ) sessionAppendStr(&buf, ", ", &rc);
sessionAppendIdent(&buf, p->azCol[i], &rc);
}
sessionAppendStr(&buf, ") VALUES(?", &rc);
for(i=1; i<p->nCol; i++){
sessionAppendStr(&buf, ", ?", &rc);
}
sessionAppendStr(&buf, ")", &rc);
if( rc==SQLITE_OK ){
rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0);
}
sqlite3_free(buf.aBuf);
return rc;
}
static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){
return sqlite3_prepare_v2(db, zSql, -1, pp, 0);
}
static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){
int rc = sessionSelectRow(db, "sqlite_stat1", p);
if( rc==SQLITE_OK ){
rc = sessionPrepare(db, &p->pInsert,
"INSERT INTO main.sqlite_stat1 VALUES(?1, "
"CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, "
"?3)"
);
}
if( rc==SQLITE_OK ){
rc = sessionPrepare(db, &p->pDelete,
"DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS "
"CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END "
"AND (?4 OR stat IS ?3)"
);
}
return rc;
}
static int sessionBindValue(
sqlite3_stmt *pStmt,
int i,
sqlite3_value *pVal
){
int eType = sqlite3_value_type(pVal);
if( (eType==SQLITE_TEXT || eType==SQLITE_BLOB) && pVal->z==0 ){
return SQLITE_NOMEM;
}
return sqlite3_bind_value(pStmt, i, pVal);
}
static int sessionBindRow(
sqlite3_changeset_iter *pIter,
int(*xValue)(sqlite3_changeset_iter *, int, sqlite3_value **),
int nCol,
u8 *abPK,
sqlite3_stmt *pStmt
){
int i;
int rc = SQLITE_OK;
assert( xValue==sqlite3changeset_old || xValue==sqlite3changeset_new );
for(i=0; rc==SQLITE_OK && i<nCol; i++){
if( !abPK || abPK[i] ){
sqlite3_value *pVal = 0;
(void)xValue(pIter, i, &pVal);
if( pVal==0 ){
rc = SQLITE_CORRUPT_BKPT;
}else{
rc = sessionBindValue(pStmt, i+1, pVal);
}
}
}
return rc;
}
static int sessionSeekToRow(
sqlite3_changeset_iter *pIter,
SessionApplyCtx *p
){
sqlite3_stmt *pSelect = p->pSelect;
int rc;
int nCol;
int op;
const char *zDummy;
sqlite3_clear_bindings(pSelect);
sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
rc = sessionBindRow(pIter,
op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old,
nCol, p->abPK, pSelect
);
if( op!=SQLITE_DELETE && p->bIgnoreNoop ){
int ii;
for(ii=0; rc==SQLITE_OK && ii<nCol; ii++){
if( p->abPK[ii]==0 ){
sqlite3_value *pVal = 0;
sqlite3changeset_new(pIter, ii, &pVal);
sqlite3_bind_int(pSelect, ii+1+nCol, (pVal==0));
if( pVal ) rc = sessionBindValue(pSelect, ii+1, pVal);
}
}
}
if( rc==SQLITE_OK ){
rc = sqlite3_step(pSelect);
if( rc!=SQLITE_ROW ) rc = sqlite3_reset(pSelect);
}
return rc;
}
static int sessionRebaseAdd(
SessionApplyCtx *p,
int eType,
sqlite3_changeset_iter *pIter
){
int rc = SQLITE_OK;
if( p->bRebase ){
int i;
int eOp = pIter->op;
if( p->bRebaseStarted==0 ){
const char *zTab = pIter->zTab;
sessionAppendByte(&p->rebase, 'T', &rc);
sessionAppendVarint(&p->rebase, p->nCol, &rc);
sessionAppendBlob(&p->rebase, p->abPK, p->nCol, &rc);
sessionAppendBlob(&p->rebase, (u8*)zTab, (int)strlen(zTab)+1, &rc);
p->bRebaseStarted = 1;
}
assert( eType==SQLITE_CHANGESET_REPLACE||eType==SQLITE_CHANGESET_OMIT );
assert( eOp==SQLITE_DELETE || eOp==SQLITE_INSERT || eOp==SQLITE_UPDATE );
sessionAppendByte(&p->rebase,
(eOp==SQLITE_DELETE ? SQLITE_DELETE : SQLITE_INSERT), &rc
);
sessionAppendByte(&p->rebase, (eType==SQLITE_CHANGESET_REPLACE), &rc);
for(i=0; i<p->nCol; i++){
sqlite3_value *pVal = 0;
if( eOp==SQLITE_DELETE || (eOp==SQLITE_UPDATE && p->abPK[i]) ){
sqlite3changeset_old(pIter, i, &pVal);
}else{
sqlite3changeset_new(pIter, i, &pVal);
}
sessionAppendValue(&p->rebase, pVal, &rc);
}
}
return rc;
}
static int sessionConflictHandler(
int eType,
SessionApplyCtx *p,
sqlite3_changeset_iter *pIter,
int(*xConflict)(void *, int, sqlite3_changeset_iter*),
void *pCtx,
int *pbReplace
){
int res = 0;
int rc;
int nCol;
int op;
const char *zDummy;
sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA );
assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT );
assert( SQLITE_CHANGESET_DATA+1==SQLITE_CHANGESET_NOTFOUND );
if( pbReplace ){
rc = sessionSeekToRow(pIter, p);
}else{
rc = SQLITE_OK;
}
if( rc==SQLITE_ROW ){
if( p->bIgnoreNoop
&& sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1)
){
res = SQLITE_CHANGESET_OMIT;
}else{
pIter->pConflict = p->pSelect;
res = xConflict(pCtx, eType, pIter);
pIter->pConflict = 0;
}
rc = sqlite3_reset(p->pSelect);
}else if( rc==SQLITE_OK ){
if( p->bDeferConstraints && eType==SQLITE_CHANGESET_CONFLICT ){
u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent];
int nBlob = pIter->in.iNext - pIter->in.iCurrent;
sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc);
return SQLITE_OK;
}else{
res = xConflict(pCtx, eType+1, pIter);
if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE;
}
}
if( rc==SQLITE_OK ){
switch( res ){
case SQLITE_CHANGESET_REPLACE:
assert( pbReplace );
*pbReplace = 1;
break;
case SQLITE_CHANGESET_OMIT:
break;
case SQLITE_CHANGESET_ABORT:
rc = SQLITE_ABORT;
break;
default:
rc = SQLITE_MISUSE;
break;
}
if( rc==SQLITE_OK ){
rc = sessionRebaseAdd(p, res, pIter);
}
}
return rc;
}
static int sessionApplyOneOp(
sqlite3_changeset_iter *pIter,
SessionApplyCtx *p,
int(*xConflict)(void *, int, sqlite3_changeset_iter *),
void *pCtx,
int *pbReplace,
int *pbRetry
){
const char *zDummy;
int op;
int nCol;
int rc = SQLITE_OK;
assert( p->pDelete && p->pInsert && p->pSelect );
assert( p->azCol && p->abPK );
assert( !pbReplace || *pbReplace==0 );
sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0);
if( op==SQLITE_DELETE ){
u8 *abPK = (pIter->bPatchset ? p->abPK : 0);
rc = sessionBindRow(pIter, sqlite3changeset_old, nCol, abPK, p->pDelete);
if( rc==SQLITE_OK && sqlite3_bind_parameter_count(p->pDelete)>nCol ){
rc = sqlite3_bind_int(p->pDelete, nCol+1, (pbRetry==0 || abPK));
}
if( rc!=SQLITE_OK ) return rc;
sqlite3_step(p->pDelete);
rc = sqlite3_reset(p->pDelete);
if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 && p->bIgnoreNoop==0 ){
rc = sessionConflictHandler(
SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry
);
}else if( (rc&0xff)==SQLITE_CONSTRAINT ){
rc = sessionConflictHandler(
SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0
);
}
}else if( op==SQLITE_UPDATE ){
int i;
sqlite3_stmt *pUp = 0;
int bPatchset = (pbRetry==0 || pIter->bPatchset);
rc = sessionUpdateFind(pIter, p, bPatchset, &pUp);
for(i=0; rc==SQLITE_OK && i<nCol; i++){
sqlite3_value *pOld = sessionChangesetOld(pIter, i);
sqlite3_value *pNew = sessionChangesetNew(pIter, i);
if( p->abPK[i] || (bPatchset==0 && pOld) ){
rc = sessionBindValue(pUp, i*2+2, pOld);
}
if( rc==SQLITE_OK && pNew ){
rc = sessionBindValue(pUp, i*2+1, pNew);
}
}
if( rc!=SQLITE_OK ) return rc;
sqlite3_step(pUp);
rc = sqlite3_reset(pUp);
if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){
rc = sessionConflictHandler(
SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry
);
}else if( (rc&0xff)==SQLITE_CONSTRAINT ){
rc = sessionConflictHandler(
SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0
);
}
}else{
assert( op==SQLITE_INSERT );
if( p->bStat1 ){
rc = sessionSeekToRow(pIter, p);
if( rc==SQLITE_ROW ){
rc = SQLITE_CONSTRAINT;
sqlite3_reset(p->pSelect);
}
}
if( rc==SQLITE_OK ){
rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert);
if( rc!=SQLITE_OK ) return rc;
sqlite3_step(p->pInsert);
rc = sqlite3_reset(p->pInsert);
}
if( (rc&0xff)==SQLITE_CONSTRAINT ){
rc = sessionConflictHandler(
SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace
);
}
}
return rc;
}
static int sessionApplyOneWithRetry(
sqlite3 *db,
sqlite3_changeset_iter *pIter,
SessionApplyCtx *pApply,
int(*xConflict)(void*, int, sqlite3_changeset_iter*),
void *pCtx
){
int bReplace = 0;
int bRetry = 0;
int rc;
rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, &bRetry);
if( rc==SQLITE_OK ){
if( bRetry ){
assert( pIter->op==SQLITE_UPDATE || pIter->op==SQLITE_DELETE );
rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0);
}
else if( bReplace ){
assert( pIter->op==SQLITE_INSERT );
rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
if( rc==SQLITE_OK ){
rc = sessionBindRow(pIter,
sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete);
sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1);
}
if( rc==SQLITE_OK ){
sqlite3_step(pApply->pDelete);
rc = sqlite3_reset(pApply->pDelete);
}
if( rc==SQLITE_OK ){
rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0);
}
if( rc==SQLITE_OK ){
rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0);
}
}
}
return rc;
}
static int sessionRetryConstraints(
sqlite3 *db,
int bPatchset,
const char *zTab,
SessionApplyCtx *pApply,
int(*xConflict)(void*, int, sqlite3_changeset_iter*),
void *pCtx
){
int rc = SQLITE_OK;
while( pApply->constraints.nBuf ){
sqlite3_changeset_iter *pIter2 = 0;
SessionBuffer cons = pApply->constraints;
memset(&pApply->constraints, 0, sizeof(SessionBuffer));
rc = sessionChangesetStart(
&pIter2, 0, 0, cons.nBuf, cons.aBuf, pApply->bInvertConstraints, 1
);
if( rc==SQLITE_OK ){
size_t nByte = 2*pApply->nCol*sizeof(sqlite3_value*);
int rc2;
pIter2->bPatchset = bPatchset;
pIter2->zTab = (char*)zTab;
pIter2->nCol = pApply->nCol;
pIter2->abPK = pApply->abPK;
sessionBufferGrow(&pIter2->tblhdr, nByte, &rc);
pIter2->apValue = (sqlite3_value**)pIter2->tblhdr.aBuf;
if( rc==SQLITE_OK ) memset(pIter2->apValue, 0, nByte);
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter2) ){
rc = sessionApplyOneWithRetry(db, pIter2, pApply, xConflict, pCtx);
}
rc2 = sqlite3changeset_finalize(pIter2);
if( rc==SQLITE_OK ) rc = rc2;
}
assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 );
sqlite3_free(cons.aBuf);
if( rc!=SQLITE_OK ) break;
if( pApply->constraints.nBuf>=cons.nBuf ){
pApply->bDeferConstraints = 0;
}
}
return rc;
}
static int sessionChangesetApply(
sqlite3 *db,
sqlite3_changeset_iter *pIter,
int(*xFilter)(
void *pCtx,
const char *zTab
),
int(*xConflict)(
void *pCtx,
int eConflict,
sqlite3_changeset_iter *p
),
void *pCtx,
void **ppRebase, int *pnRebase,
int flags
){
int schemaMismatch = 0;
int rc = SQLITE_OK;
const char *zTab = 0;
int nTab = 0;
SessionApplyCtx sApply;
int bPatchset;
assert( xConflict!=0 );
pIter->in.bNoDiscard = 1;
memset(&sApply, 0, sizeof(sApply));
sApply.bRebase = (ppRebase && pnRebase);
sApply.bInvertConstraints = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
sApply.bIgnoreNoop = !!(flags & SQLITE_CHANGESETAPPLY_IGNORENOOP);
sqlite3_mutex_enter(sqlite3_db_mutex(db));
if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){
rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
}
if( rc==SQLITE_OK ){
rc = sqlite3_exec(db, "PRAGMA defer_foreign_keys = 1", 0, 0, 0);
}
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){
int nCol;
int op;
const char *zNew;
sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0);
if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){
u8 *abPK;
rc = sessionRetryConstraints(
db, pIter->bPatchset, zTab, &sApply, xConflict, pCtx
);
if( rc!=SQLITE_OK ) break;
sessionUpdateFree(&sApply);
sqlite3_free((char*)sApply.azCol);
sqlite3_finalize(sApply.pDelete);
sqlite3_finalize(sApply.pInsert);
sqlite3_finalize(sApply.pSelect);
sApply.db = db;
sApply.pDelete = 0;
sApply.pInsert = 0;
sApply.pSelect = 0;
sApply.nCol = 0;
sApply.azCol = 0;
sApply.abPK = 0;
sApply.bStat1 = 0;
sApply.bDeferConstraints = 1;
sApply.bRebaseStarted = 0;
sApply.bRowid = 0;
memset(&sApply.constraints, 0, sizeof(SessionBuffer));
schemaMismatch = (xFilter && (0==xFilter(pCtx, zNew)));
if( schemaMismatch ){
zTab = sqlite3_mprintf("%s", zNew);
if( zTab==0 ){
rc = SQLITE_NOMEM;
break;
}
nTab = (int)strlen(zTab);
sApply.azCol = (const char **)zTab;
}else{
int nMinCol = 0;
int i;
sqlite3changeset_pk(pIter, &abPK, 0);
rc = sessionTableInfo(0, db, "main", zNew,
&sApply.nCol, &zTab, &sApply.azCol, &sApply.abPK, &sApply.bRowid
);
if( rc!=SQLITE_OK ) break;
for(i=0; i<sApply.nCol; i++){
if( sApply.abPK[i] ) nMinCol = i+1;
}
if( sApply.nCol==0 ){
schemaMismatch = 1;
sqlite3_log(SQLITE_SCHEMA,
"sqlite3changeset_apply(): no such table: %s", zTab
);
}
else if( sApply.nCol<nCol ){
schemaMismatch = 1;
sqlite3_log(SQLITE_SCHEMA,
"sqlite3changeset_apply(): table %s has %d columns, "
"expected %d or more",
zTab, sApply.nCol, nCol
);
}
else if( nCol<nMinCol || memcmp(sApply.abPK, abPK, nCol)!=0 ){
schemaMismatch = 1;
sqlite3_log(SQLITE_SCHEMA, "sqlite3changeset_apply(): "
"primary key mismatch for table %s", zTab
);
}
else{
sApply.nCol = nCol;
if( 0==sqlite3_stricmp(zTab, "sqlite_stat1") ){
if( (rc = sessionStat1Sql(db, &sApply) ) ){
break;
}
sApply.bStat1 = 1;
}else{
if( (rc = sessionSelectRow(db, zTab, &sApply))
|| (rc = sessionDeleteRow(db, zTab, &sApply))
|| (rc = sessionInsertRow(db, zTab, &sApply))
){
break;
}
sApply.bStat1 = 0;
}
}
nTab = sqlite3Strlen30(zTab);
}
}
if( schemaMismatch ) continue;
rc = sessionApplyOneWithRetry(db, pIter, &sApply, xConflict, pCtx);
}
bPatchset = pIter->bPatchset;
if( rc==SQLITE_OK ){
rc = sqlite3changeset_finalize(pIter);
}else{
sqlite3changeset_finalize(pIter);
}
if( rc==SQLITE_OK ){
rc = sessionRetryConstraints(db, bPatchset, zTab, &sApply, xConflict, pCtx);
}
if( rc==SQLITE_OK ){
int nFk, notUsed;
sqlite3_db_status(db, SQLITE_DBSTATUS_DEFERRED_FKS, &nFk, ¬Used, 0);
if( nFk!=0 ){
int res = SQLITE_CHANGESET_ABORT;
sqlite3_changeset_iter sIter;
memset(&sIter, 0, sizeof(sIter));
sIter.nCol = nFk;
res = xConflict(pCtx, SQLITE_CHANGESET_FOREIGN_KEY, &sIter);
if( res!=SQLITE_CHANGESET_OMIT ){
rc = SQLITE_CONSTRAINT;
}
}
}
sqlite3_exec(db, "PRAGMA defer_foreign_keys = 0", 0, 0, 0);
if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){
if( rc==SQLITE_OK ){
rc = sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
}else{
sqlite3_exec(db, "ROLLBACK TO changeset_apply", 0, 0, 0);
sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
}
}
assert( sApply.bRebase || sApply.rebase.nBuf==0 );
if( rc==SQLITE_OK && bPatchset==0 && sApply.bRebase ){
*ppRebase = (void*)sApply.rebase.aBuf;
*pnRebase = sApply.rebase.nBuf;
sApply.rebase.aBuf = 0;
}
sessionUpdateFree(&sApply);
sqlite3_finalize(sApply.pInsert);
sqlite3_finalize(sApply.pDelete);
sqlite3_finalize(sApply.pSelect);
sqlite3_free((char*)sApply.azCol);
sqlite3_free((char*)sApply.constraints.aBuf);
sqlite3_free((char*)sApply.rebase.aBuf);
sqlite3_mutex_leave(sqlite3_db_mutex(db));
return rc;
}
int sqlite3changeset_apply_v2(
sqlite3 *db,
int nChangeset,
void *pChangeset,
int(*xFilter)(
void *pCtx,
const char *zTab
),
int(*xConflict)(
void *pCtx,
int eConflict,
sqlite3_changeset_iter *p
),
void *pCtx,
void **ppRebase, int *pnRebase,
int flags
){
sqlite3_changeset_iter *pIter;
int bInv = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
int rc = sessionChangesetStart(&pIter, 0, 0, nChangeset, pChangeset, bInv, 1);
if( rc==SQLITE_OK ){
rc = sessionChangesetApply(
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
);
}
return rc;
}
int sqlite3changeset_apply(
sqlite3 *db,
int nChangeset,
void *pChangeset,
int(*xFilter)(
void *pCtx,
const char *zTab
),
int(*xConflict)(
void *pCtx,
int eConflict,
sqlite3_changeset_iter *p
),
void *pCtx
){
return sqlite3changeset_apply_v2(
db, nChangeset, pChangeset, xFilter, xConflict, pCtx, 0, 0, 0
);
}
int sqlite3changeset_apply_v2_strm(
sqlite3 *db,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn,
int(*xFilter)(
void *pCtx,
const char *zTab
),
int(*xConflict)(
void *pCtx,
int eConflict,
sqlite3_changeset_iter *p
),
void *pCtx,
void **ppRebase, int *pnRebase,
int flags
){
sqlite3_changeset_iter *pIter;
int bInverse = !!(flags & SQLITE_CHANGESETAPPLY_INVERT);
int rc = sessionChangesetStart(&pIter, xInput, pIn, 0, 0, bInverse, 1);
if( rc==SQLITE_OK ){
rc = sessionChangesetApply(
db, pIter, xFilter, xConflict, pCtx, ppRebase, pnRebase, flags
);
}
return rc;
}
int sqlite3changeset_apply_strm(
sqlite3 *db,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn,
int(*xFilter)(
void *pCtx,
const char *zTab
),
int(*xConflict)(
void *pCtx,
int eConflict,
sqlite3_changeset_iter *p
),
void *pCtx
){
return sqlite3changeset_apply_v2_strm(
db, xInput, pIn, xFilter, xConflict, pCtx, 0, 0, 0
);
}
struct sqlite3_changegroup {
int rc;
int bPatch;
SessionTable *pList;
};
static int sessionChangeMerge(
SessionTable *pTab,
int bRebase,
int bPatchset,
SessionChange *pExist,
int op2,
int bIndirect,
u8 *aRec,
int nRec,
SessionChange **ppNew
){
SessionChange *pNew = 0;
int rc = SQLITE_OK;
if( !pExist ){
pNew = (SessionChange *)sqlite3_malloc64(sizeof(SessionChange) + nRec);
if( !pNew ){
return SQLITE_NOMEM;
}
memset(pNew, 0, sizeof(SessionChange));
pNew->op = op2;
pNew->bIndirect = bIndirect;
pNew->aRecord = (u8*)&pNew[1];
if( bIndirect==0 || bRebase==0 ){
pNew->nRecord = nRec;
memcpy(pNew->aRecord, aRec, nRec);
}else{
int i;
u8 *pIn = aRec;
u8 *pOut = pNew->aRecord;
for(i=0; i<pTab->nCol; i++){
int nIn = sessionSerialLen(pIn);
if( *pIn==0 ){
*pOut++ = 0;
}else if( pTab->abPK[i]==0 ){
*pOut++ = 0xFF;
}else{
memcpy(pOut, pIn, nIn);
pOut += nIn;
}
pIn += nIn;
}
pNew->nRecord = pOut - pNew->aRecord;
}
}else if( bRebase ){
if( pExist->op==SQLITE_DELETE && pExist->bIndirect ){
*ppNew = pExist;
}else{
sqlite3_int64 nByte = nRec + pExist->nRecord + sizeof(SessionChange);
pNew = (SessionChange*)sqlite3_malloc64(nByte);
if( pNew==0 ){
rc = SQLITE_NOMEM;
}else{
int i;
u8 *a1 = pExist->aRecord;
u8 *a2 = aRec;
u8 *pOut;
memset(pNew, 0, nByte);
pNew->bIndirect = bIndirect || pExist->bIndirect;
pNew->op = op2;
pOut = pNew->aRecord = (u8*)&pNew[1];
for(i=0; i<pTab->nCol; i++){
int n1 = sessionSerialLen(a1);
int n2 = sessionSerialLen(a2);
if( *a1==0xFF || (pTab->abPK[i]==0 && bIndirect) ){
*pOut++ = 0xFF;
}else if( *a2==0 ){
memcpy(pOut, a1, n1);
pOut += n1;
}else{
memcpy(pOut, a2, n2);
pOut += n2;
}
a1 += n1;
a2 += n2;
}
pNew->nRecord = pOut - pNew->aRecord;
}
sqlite3_free(pExist);
}
}else{
int op1 = pExist->op;
if( (op1==SQLITE_INSERT && op2==SQLITE_INSERT)
|| (op1==SQLITE_UPDATE && op2==SQLITE_INSERT)
|| (op1==SQLITE_DELETE && op2==SQLITE_UPDATE)
|| (op1==SQLITE_DELETE && op2==SQLITE_DELETE)
){
pNew = pExist;
}else if( op1==SQLITE_INSERT && op2==SQLITE_DELETE ){
sqlite3_free(pExist);
assert( pNew==0 );
}else{
u8 *aExist = pExist->aRecord;
sqlite3_int64 nByte;
u8 *aCsr;
nByte = sizeof(SessionChange) + pExist->nRecord + nRec;
pNew = (SessionChange *)sqlite3_malloc64(nByte);
if( !pNew ){
sqlite3_free(pExist);
return SQLITE_NOMEM;
}
memset(pNew, 0, sizeof(SessionChange));
pNew->bIndirect = (bIndirect && pExist->bIndirect);
aCsr = pNew->aRecord = (u8 *)&pNew[1];
if( op1==SQLITE_INSERT ){
u8 *a1 = aRec;
assert( op2==SQLITE_UPDATE );
pNew->op = SQLITE_INSERT;
if( bPatchset==0 ) sessionSkipRecord(&a1, pTab->nCol);
sessionMergeRecord(&aCsr, pTab->nCol, aExist, a1);
}else if( op1==SQLITE_DELETE ){
assert( op2==SQLITE_INSERT );
pNew->op = SQLITE_UPDATE;
if( bPatchset ){
memcpy(aCsr, aRec, nRec);
aCsr += nRec;
}else{
if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aExist, 0,aRec,0) ){
sqlite3_free(pNew);
pNew = 0;
}
}
}else if( op2==SQLITE_UPDATE ){
u8 *a1 = aExist;
u8 *a2 = aRec;
assert( op1==SQLITE_UPDATE );
if( bPatchset==0 ){
sessionSkipRecord(&a1, pTab->nCol);
sessionSkipRecord(&a2, pTab->nCol);
}
pNew->op = SQLITE_UPDATE;
if( 0==sessionMergeUpdate(&aCsr, pTab, bPatchset, aRec, aExist,a1,a2) ){
sqlite3_free(pNew);
pNew = 0;
}
}else{
assert( op1==SQLITE_UPDATE && op2==SQLITE_DELETE );
pNew->op = SQLITE_DELETE;
if( bPatchset ){
memcpy(aCsr, aRec, nRec);
aCsr += nRec;
}else{
sessionMergeRecord(&aCsr, pTab->nCol, aRec, aExist);
}
}
if( pNew ){
pNew->nRecord = (int)(aCsr - pNew->aRecord);
}
sqlite3_free(pExist);
}
}
*ppNew = pNew;
return rc;
}
static int sessionChangesetToHash(
sqlite3_changeset_iter *pIter,
sqlite3_changegroup *pGrp,
int bRebase
){
u8 *aRec;
int nRec;
int rc = SQLITE_OK;
SessionTable *pTab = 0;
while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, 0) ){
const char *zNew;
int nCol;
int op;
int iHash;
int bIndirect;
SessionChange *pChange;
SessionChange *pExist = 0;
SessionChange **pp;
if( pGrp->pList==0 ){
pGrp->bPatch = pIter->bPatchset;
}else if( pIter->bPatchset!=pGrp->bPatch ){
rc = SQLITE_ERROR;
break;
}
sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect);
if( !pTab || sqlite3_stricmp(zNew, pTab->zName) ){
int nNew = (int)strlen(zNew);
u8 *abPK;
sqlite3changeset_pk(pIter, &abPK, 0);
for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){
if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break;
}
if( !pTab ){
SessionTable **ppTab;
pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nNew+1);
if( !pTab ){
rc = SQLITE_NOMEM;
break;
}
memset(pTab, 0, sizeof(SessionTable));
pTab->nCol = nCol;
pTab->abPK = (u8*)&pTab[1];
memcpy(pTab->abPK, abPK, nCol);
pTab->zName = (char*)&pTab->abPK[nCol];
memcpy(pTab->zName, zNew, nNew+1);
for(ppTab=&pGrp->pList; *ppTab; ppTab=&(*ppTab)->pNext);
*ppTab = pTab;
}else if( pTab->nCol!=nCol || memcmp(pTab->abPK, abPK, nCol) ){
rc = SQLITE_SCHEMA;
break;
}
}
if( sessionGrowHash(0, pIter->bPatchset, pTab) ){
rc = SQLITE_NOMEM;
break;
}
iHash = sessionChangeHash(
pTab, (pIter->bPatchset && op==SQLITE_DELETE), aRec, pTab->nChange
);
for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){
int bPkOnly1 = 0;
int bPkOnly2 = 0;
if( pIter->bPatchset ){
bPkOnly1 = (*pp)->op==SQLITE_DELETE;
bPkOnly2 = op==SQLITE_DELETE;
}
if( sessionChangeEqual(pTab, bPkOnly1, (*pp)->aRecord, bPkOnly2, aRec) ){
pExist = *pp;
*pp = (*pp)->pNext;
pTab->nEntry--;
break;
}
}
rc = sessionChangeMerge(pTab, bRebase,
pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange
);
if( rc ) break;
if( pChange ){
pChange->pNext = pTab->apChange[iHash];
pTab->apChange[iHash] = pChange;
pTab->nEntry++;
}
}
if( rc==SQLITE_OK ) rc = pIter->rc;
return rc;
}
static int sessionChangegroupOutput(
sqlite3_changegroup *pGrp,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut,
int *pnOut,
void **ppOut
){
int rc = SQLITE_OK;
SessionBuffer buf = {0, 0, 0};
SessionTable *pTab;
assert( xOutput==0 || (ppOut==0 && pnOut==0) );
for(pTab=pGrp->pList; rc==SQLITE_OK && pTab; pTab=pTab->pNext){
int i;
if( pTab->nEntry==0 ) continue;
sessionAppendTableHdr(&buf, pGrp->bPatch, pTab, &rc);
for(i=0; i<pTab->nChange; i++){
SessionChange *p;
for(p=pTab->apChange[i]; p; p=p->pNext){
sessionAppendByte(&buf, p->op, &rc);
sessionAppendByte(&buf, p->bIndirect, &rc);
sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc);
if( rc==SQLITE_OK && xOutput && buf.nBuf>=sessions_strm_chunk_size ){
rc = xOutput(pOut, buf.aBuf, buf.nBuf);
buf.nBuf = 0;
}
}
}
}
if( rc==SQLITE_OK ){
if( xOutput ){
if( buf.nBuf>0 ) rc = xOutput(pOut, buf.aBuf, buf.nBuf);
}else if( ppOut ){
*ppOut = buf.aBuf;
if( pnOut ) *pnOut = buf.nBuf;
buf.aBuf = 0;
}
}
sqlite3_free(buf.aBuf);
return rc;
}
int sqlite3changegroup_new(sqlite3_changegroup **pp){
int rc = SQLITE_OK;
sqlite3_changegroup *p;
p = (sqlite3_changegroup*)sqlite3_malloc(sizeof(sqlite3_changegroup));
if( p==0 ){
rc = SQLITE_NOMEM;
}else{
memset(p, 0, sizeof(sqlite3_changegroup));
}
*pp = p;
return rc;
}
int sqlite3changegroup_add(sqlite3_changegroup *pGrp, int nData, void *pData){
sqlite3_changeset_iter *pIter;
int rc;
rc = sqlite3changeset_start(&pIter, nData, pData);
if( rc==SQLITE_OK ){
rc = sessionChangesetToHash(pIter, pGrp, 0);
}
sqlite3changeset_finalize(pIter);
return rc;
}
int sqlite3changegroup_output(
sqlite3_changegroup *pGrp,
int *pnData,
void **ppData
){
return sessionChangegroupOutput(pGrp, 0, 0, pnData, ppData);
}
int sqlite3changegroup_add_strm(
sqlite3_changegroup *pGrp,
int (*xInput)(void *pIn, void *pData, int *pnData),
void *pIn
){
sqlite3_changeset_iter *pIter;
int rc;
rc = sqlite3changeset_start_strm(&pIter, xInput, pIn);
if( rc==SQLITE_OK ){
rc = sessionChangesetToHash(pIter, pGrp, 0);
}
sqlite3changeset_finalize(pIter);
return rc;
}
int sqlite3changegroup_output_strm(
sqlite3_changegroup *pGrp,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut
){
return sessionChangegroupOutput(pGrp, xOutput, pOut, 0, 0);
}
void sqlite3changegroup_delete(sqlite3_changegroup *pGrp){
if( pGrp ){
sessionDeleteTable(0, pGrp->pList);
sqlite3_free(pGrp);
}
}
int sqlite3changeset_concat(
int nLeft,
void *pLeft,
int nRight ,
void *pRight,
int *pnOut,
void **ppOut
){
sqlite3_changegroup *pGrp;
int rc;
rc = sqlite3changegroup_new(&pGrp);
if( rc==SQLITE_OK ){
rc = sqlite3changegroup_add(pGrp, nLeft, pLeft);
}
if( rc==SQLITE_OK ){
rc = sqlite3changegroup_add(pGrp, nRight, pRight);
}
if( rc==SQLITE_OK ){
rc = sqlite3changegroup_output(pGrp, pnOut, ppOut);
}
sqlite3changegroup_delete(pGrp);
return rc;
}
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
){
sqlite3_changegroup *pGrp;
int rc;
rc = sqlite3changegroup_new(&pGrp);
if( rc==SQLITE_OK ){
rc = sqlite3changegroup_add_strm(pGrp, xInputA, pInA);
}
if( rc==SQLITE_OK ){
rc = sqlite3changegroup_add_strm(pGrp, xInputB, pInB);
}
if( rc==SQLITE_OK ){
rc = sqlite3changegroup_output_strm(pGrp, xOutput, pOut);
}
sqlite3changegroup_delete(pGrp);
return rc;
}
struct sqlite3_rebaser {
sqlite3_changegroup grp;
};
static void sessionAppendRecordMerge(
SessionBuffer *pBuf,
int nCol,
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( *a1==0 || *a1==0xFF ){
memcpy(pOut, a2, nn2);
pOut += nn2;
}else{
memcpy(pOut, a1, nn1);
pOut += nn1;
}
a1 += nn1;
a2 += nn2;
}
pBuf->nBuf = pOut-pBuf->aBuf;
assert( pBuf->nBuf<=pBuf->nAlloc );
}
}
static void sessionAppendPartialUpdate(
SessionBuffer *pBuf,
sqlite3_changeset_iter *pIter,
u8 *aRec, int nRec,
u8 *aChange, int nChange,
int *pRc
){
sessionBufferGrow(pBuf, 2+nRec+nChange, pRc);
if( *pRc==SQLITE_OK ){
int bData = 0;
u8 *pOut = &pBuf->aBuf[pBuf->nBuf];
int i;
u8 *a1 = aRec;
u8 *a2 = aChange;
*pOut++ = SQLITE_UPDATE;
*pOut++ = pIter->bIndirect;
for(i=0; i<pIter->nCol; i++){
int n1 = sessionSerialLen(a1);
int n2 = sessionSerialLen(a2);
if( pIter->abPK[i] || a2[0]==0 ){
if( !pIter->abPK[i] && a1[0] ) bData = 1;
memcpy(pOut, a1, n1);
pOut += n1;
}else if( a2[0]!=0xFF && a1[0] ){
bData = 1;
memcpy(pOut, a2, n2);
pOut += n2;
}else{
*pOut++ = '\0';
}
a1 += n1;
a2 += n2;
}
if( bData ){
a2 = aChange;
for(i=0; i<pIter->nCol; i++){
int n1 = sessionSerialLen(a1);
int n2 = sessionSerialLen(a2);
if( pIter->abPK[i] || a2[0]!=0xFF ){
memcpy(pOut, a1, n1);
pOut += n1;
}else{
*pOut++ = '\0';
}
a1 += n1;
a2 += n2;
}
pBuf->nBuf = (pOut - pBuf->aBuf);
}
}
}
static int sessionRebase(
sqlite3_rebaser *p,
sqlite3_changeset_iter *pIter,
int (*xOutput)(void *pOut, const void *pData, int nData),
void *pOut,
int *pnOut,
void **ppOut
){
int rc = SQLITE_OK;
u8 *aRec = 0;
int nRec = 0;
int bNew = 0;
SessionTable *pTab = 0;
SessionBuffer sOut = {0,0,0};
while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec, &bNew) ){
SessionChange *pChange = 0;
int bDone = 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;
if( pIter->bPatchset ){
rc = SQLITE_ERROR;
}
sessionAppendByte(&sOut, pIter->bPatchset ? 'P' : 'T', &rc);
sessionAppendVarint(&sOut, pIter->nCol, &rc);
sessionAppendBlob(&sOut, pIter->abPK, pIter->nCol, &rc);
sessionAppendBlob(&sOut,(u8*)pIter->zTab,(int)strlen(pIter->zTab)+1,&rc);
}
if( pTab && rc==SQLITE_OK ){
int iHash = sessionChangeHash(pTab, 0, aRec, pTab->nChange);
for(pChange=pTab->apChange[iHash]; pChange; pChange=pChange->pNext){
if( sessionChangeEqual(pTab, 0, aRec, 0, pChange->aRecord) ){
break;
}
}
}
if( pChange ){
assert( pChange->op==SQLITE_DELETE || pChange->op==SQLITE_INSERT );
switch( pIter->op ){
case SQLITE_INSERT:
if( pChange->op==SQLITE_INSERT ){
bDone = 1;
if( pChange->bIndirect==0 ){
sessionAppendByte(&sOut, SQLITE_UPDATE, &rc);
sessionAppendByte(&sOut, pIter->bIndirect, &rc);
sessionAppendBlob(&sOut, pChange->aRecord, pChange->nRecord, &rc);
sessionAppendBlob(&sOut, aRec, nRec, &rc);
}
}
break;
case SQLITE_UPDATE:
bDone = 1;
if( pChange->op==SQLITE_DELETE ){
if( pChange->bIndirect==0 ){
u8 *pCsr = aRec;
sessionSkipRecord(&pCsr, pIter->nCol);
sessionAppendByte(&sOut, SQLITE_INSERT, &rc);
sessionAppendByte(&sOut, pIter->bIndirect, &rc);
sessionAppendRecordMerge(&sOut, pIter->nCol,
pCsr, nRec-(pCsr-aRec),
pChange->aRecord, pChange->nRecord, &rc
);
}
}else{
sessionAppendPartialUpdate(&sOut, pIter,
aRec, nRec, pChange->aRecord, pChange->nRecord, &rc
);
}
break;
default:
assert( pIter->op==SQLITE_DELETE );
bDone = 1;
if( pChange->op==SQLITE_INSERT ){
sessionAppendByte(&sOut, SQLITE_DELETE, &rc);
sessionAppendByte(&sOut, pIter->bIndirect, &rc);
sessionAppendRecordMerge(&sOut, pIter->nCol,
pChange->aRecord, pChange->nRecord, aRec, nRec, &rc
);
}
break;
}
}
if( bDone==0 ){
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 if( ppOut ){
*ppOut = (void*)sOut.aBuf;
*pnOut = sOut.nBuf;
sOut.aBuf = 0;
}
}
sqlite3_free(sOut.aBuf);
return rc;
}
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;
}else{
memset(pNew, 0, sizeof(sqlite3_rebaser));
}
*ppNew = pNew;
return rc;
}
int sqlite3rebaser_configure(
sqlite3_rebaser *p,
int nRebase, const void *pRebase
){
sqlite3_changeset_iter *pIter = 0;
int rc;
rc = sqlite3changeset_start(&pIter, nRebase, (void*)pRebase);
if( rc==SQLITE_OK ){
rc = sessionChangesetToHash(pIter, &p->grp, 1);
}
sqlite3changeset_finalize(pIter);
return rc;
}
int sqlite3rebaser_rebase(
sqlite3_rebaser *p,
int nIn, const void *pIn,
int *pnOut, void **ppOut
){
sqlite3_changeset_iter *pIter = 0;
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;
}
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;
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;
}
void sqlite3rebaser_delete(sqlite3_rebaser *p){
if( p ){
sessionDeleteTable(0, p->grp.pList);
sqlite3_free(p);
}
}
int sqlite3session_config(int op, void *pArg){
int rc = SQLITE_OK;
switch( op ){
case SQLITE_SESSION_CONFIG_STRMSIZE: {
int *pInt = (int*)pArg;
if( *pInt>0 ){
sessions_strm_chunk_size = *pInt;
}
*pInt = sessions_strm_chunk_size;
break;
}
default:
rc = SQLITE_MISUSE;
break;
}
return rc;
}
#endif