|
|
| #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 nTotalCol; |
| int bStat1; |
| int bRowid; |
| const char **azCol; |
| const char **azDflt; |
| int *aiIdx; |
| u8 *abPK; |
| int nEntry; |
| int nChange; |
| SessionChange **apChange; |
| sqlite3_stmt *pDfltStmt; |
| }; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| |
| struct SessionChange { |
| u8 op; |
| u8 bIndirect; |
| u16 nRecordField; |
| 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(const 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; |
|
|
| assert( pTab->nTotalCol==pSession->hook.xCount(pSession->hook.pCtx) ); |
| if( pTab->bRowid ){ |
| h = sessionHashAppendI64(h, iRowid); |
| }else{ |
| assert( *pbNullPK==0 ); |
| for(i=0; i<pTab->nCol; i++){ |
| if( pTab->abPK[i] ){ |
| int rc; |
| int eType; |
| sqlite3_value *pVal; |
| int iIdx = pTab->aiIdx[i]; |
|
|
| if( bNew ){ |
| rc = pSession->hook.xNew(pSession->hook.pCtx, iIdx, &pVal); |
| }else{ |
| rc = pSession->hook.xOld(pSession->hook.pCtx, iIdx, &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(const u8 *a){ |
| int e; |
| int n; |
| assert( a!=0 ); |
| e = *a; |
| 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++; |
| int iIdx = pTab->aiIdx[iCol]; |
|
|
| |
| |
| |
| |
| |
| if( op==SQLITE_INSERT ){ |
| |
| rc = pSession->hook.xNew(pSession->hook.pCtx, iIdx, &pVal); |
| }else{ |
| |
| rc = pSession->hook.xOld(pSession->hook.pCtx, iIdx, &pVal); |
| } |
| assert( rc==SQLITE_OK ); |
| (void)rc; |
| 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, |
| int *pnTotalCol, |
| const char **pzTab, |
| const char ***pazCol, |
| const char ***pazDflt, |
| int **paiIdx, |
| 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; |
| char **azDflt = 0; |
| u8 *abPK = 0; |
| int *aiIdx = 0; |
| int bRowid = 0; |
|
|
| assert( pazCol && pabPK ); |
|
|
| *pazCol = 0; |
| *pabPK = 0; |
| *pnCol = 0; |
| if( pnTotalCol ) *pnTotalCol = 0; |
| if( paiIdx ) *paiIdx = 0; |
| if( pzTab ) *pzTab = 0; |
| if( pazDflt ) *pazDflt = 0; |
|
|
| 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, 0 UNION ALL " |
| "SELECT 1, 'idx', '', 0, '', 2, 0 UNION ALL " |
| "SELECT 2, 'stat', '', 0, '', 0, 0" |
| ); |
| }else if( rc==SQLITE_ERROR ){ |
| zPragma = sqlite3_mprintf(""); |
| }else{ |
| return rc; |
| } |
| }else{ |
| zPragma = sqlite3_mprintf("PRAGMA '%q'.table_xinfo('%q')", zDb, zThis); |
| } |
| if( !zPragma ){ |
| return SQLITE_NOMEM; |
| } |
|
|
| rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0); |
| sqlite3_free(zPragma); |
| if( rc!=SQLITE_OK ){ |
| return rc; |
| } |
|
|
| nByte = nThis + 1; |
| bRowid = (pbRowid!=0); |
| while( SQLITE_ROW==sqlite3_step(pStmt) ){ |
| nByte += sqlite3_column_bytes(pStmt, 1); |
| nByte += sqlite3_column_bytes(pStmt, 4); |
| if( sqlite3_column_int(pStmt, 6)==0 ){ |
| 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 *)*2 +sizeof(int)+sizeof(u8) + 1 + 1); |
| pAlloc = sessionMalloc64(pSession, nByte); |
| if( pAlloc==0 ){ |
| rc = SQLITE_NOMEM; |
| }else{ |
| memset(pAlloc, 0, nByte); |
| } |
| } |
| if( rc==SQLITE_OK ){ |
| azCol = (char **)pAlloc; |
| azDflt = (char**)&azCol[nDbCol]; |
| aiIdx = (int*)&azDflt[nDbCol]; |
| abPK = (u8 *)&aiIdx[nDbCol]; |
| 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; |
| aiIdx[i] = -1; |
| i++; |
| } |
| while( SQLITE_ROW==sqlite3_step(pStmt) ){ |
| if( sqlite3_column_int(pStmt, 6)==0 ){ |
| int nName = sqlite3_column_bytes(pStmt, 1); |
| int nDflt = sqlite3_column_bytes(pStmt, 4); |
| const unsigned char *zName = sqlite3_column_text(pStmt, 1); |
| const unsigned char *zDflt = sqlite3_column_text(pStmt, 4); |
|
|
| if( zName==0 ) break; |
| memcpy(pAlloc, zName, nName+1); |
| azCol[i] = (char *)pAlloc; |
| pAlloc += nName+1; |
| if( zDflt ){ |
| memcpy(pAlloc, zDflt, nDflt+1); |
| azDflt[i] = (char *)pAlloc; |
| pAlloc += nDflt+1; |
| }else{ |
| azDflt[i] = 0; |
| } |
| abPK[i] = sqlite3_column_int(pStmt, 5); |
| aiIdx[i] = sqlite3_column_int(pStmt, 0); |
| i++; |
| } |
| if( pnTotalCol ) (*pnTotalCol)++; |
| } |
| rc = sqlite3_reset(pStmt); |
| } |
|
|
| |
| |
| |
| if( rc==SQLITE_OK ){ |
| *pazCol = (const char**)azCol; |
| if( pazDflt ) *pazDflt = (const char**)azDflt; |
| *pabPK = abPK; |
| *pnCol = nDbCol; |
| if( paiIdx ) *paiIdx = aiIdx; |
| }else{ |
| sessionFree(pSession, azCol); |
| } |
| if( pbRowid ) *pbRowid = bRowid; |
| sqlite3_finalize(pStmt); |
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int sessionInitTable( |
| sqlite3_session *pSession, |
| SessionTable *pTab, |
| sqlite3 *db, |
| const char *zDb |
| ){ |
| int rc = SQLITE_OK; |
|
|
| if( pTab->nCol==0 ){ |
| u8 *abPK; |
| assert( pTab->azCol==0 || pTab->abPK==0 ); |
| sqlite3_free(pTab->azCol); |
| pTab->abPK = 0; |
| rc = sessionTableInfo(pSession, db, zDb, |
| pTab->zName, &pTab->nCol, &pTab->nTotalCol, 0, &pTab->azCol, |
| &pTab->azDflt, &pTab->aiIdx, &abPK, |
| ((pSession==0 || pSession->bImplicitPK) ? &pTab->bRowid : 0) |
| ); |
| if( 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 && pSession->bEnableSize ){ |
| pSession->nMaxChangesetSize += ( |
| 1 + sessionVarintLen(pTab->nCol) + pTab->nCol + strlen(pTab->zName)+1 |
| ); |
| } |
| } |
| } |
|
|
| if( pSession ){ |
| pSession->rc = rc; |
| return (rc || pTab->abPK==0); |
| } |
| return rc; |
| } |
|
|
| |
| |
| |
| static int sessionReinitTable(sqlite3_session *pSession, SessionTable *pTab){ |
| int nCol = 0; |
| int nTotalCol = 0; |
| const char **azCol = 0; |
| const char **azDflt = 0; |
| int *aiIdx = 0; |
| u8 *abPK = 0; |
| int bRowid = 0; |
|
|
| assert( pSession->rc==SQLITE_OK ); |
|
|
| pSession->rc = sessionTableInfo(pSession, pSession->db, pSession->zDb, |
| pTab->zName, &nCol, &nTotalCol, 0, &azCol, &azDflt, &aiIdx, &abPK, |
| (pSession->bImplicitPK ? &bRowid : 0) |
| ); |
| if( pSession->rc==SQLITE_OK ){ |
| if( pTab->nCol>nCol || pTab->bRowid!=bRowid ){ |
| pSession->rc = SQLITE_SCHEMA; |
| }else{ |
| int ii; |
| int nOldCol = pTab->nCol; |
| for(ii=0; ii<nCol; ii++){ |
| if( ii<pTab->nCol ){ |
| if( pTab->abPK[ii]!=abPK[ii] ){ |
| pSession->rc = SQLITE_SCHEMA; |
| } |
| }else if( abPK[ii] ){ |
| pSession->rc = SQLITE_SCHEMA; |
| } |
| } |
|
|
| if( pSession->rc==SQLITE_OK ){ |
| const char **a = pTab->azCol; |
| pTab->azCol = azCol; |
| pTab->nCol = nCol; |
| pTab->nTotalCol = nTotalCol; |
| pTab->azDflt = azDflt; |
| pTab->abPK = abPK; |
| pTab->aiIdx = aiIdx; |
| azCol = a; |
| } |
| if( pSession->bEnableSize ){ |
| pSession->nMaxChangesetSize += (nCol - nOldCol); |
| pSession->nMaxChangesetSize += sessionVarintLen(nCol); |
| pSession->nMaxChangesetSize -= sessionVarintLen(nOldCol); |
| } |
| } |
| } |
|
|
| sqlite3_free((char*)azCol); |
| return pSession->rc; |
| } |
|
|
| |
| |
| |
| |
| |
| static void sessionUpdateOneChange( |
| sqlite3_session *pSession, |
| int *pRc, |
| SessionChange **pp, |
| int nCol, |
| sqlite3_stmt *pDflt |
| ){ |
| SessionChange *pOld = *pp; |
|
|
| while( pOld->nRecordField<nCol ){ |
| SessionChange *pNew = 0; |
| int nByte = 0; |
| int nIncr = 0; |
| int iField = pOld->nRecordField; |
| int eType = sqlite3_column_type(pDflt, iField); |
| switch( eType ){ |
| case SQLITE_NULL: |
| nIncr = 1; |
| break; |
| case SQLITE_INTEGER: |
| case SQLITE_FLOAT: |
| nIncr = 9; |
| break; |
| default: { |
| int n = sqlite3_column_bytes(pDflt, iField); |
| nIncr = 1 + sessionVarintLen(n) + n; |
| assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB ); |
| break; |
| } |
| } |
|
|
| nByte = nIncr + (sizeof(SessionChange) + pOld->nRecord); |
| pNew = sessionMalloc64(pSession, nByte); |
| if( pNew==0 ){ |
| *pRc = SQLITE_NOMEM; |
| return; |
| }else{ |
| memcpy(pNew, pOld, sizeof(SessionChange)); |
| pNew->aRecord = (u8*)&pNew[1]; |
| memcpy(pNew->aRecord, pOld->aRecord, pOld->nRecord); |
| pNew->aRecord[pNew->nRecord++] = (u8)eType; |
| switch( eType ){ |
| case SQLITE_INTEGER: { |
| i64 iVal = sqlite3_column_int64(pDflt, iField); |
| sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal); |
| pNew->nRecord += 8; |
| break; |
| } |
| |
| case SQLITE_FLOAT: { |
| double rVal = sqlite3_column_double(pDflt, iField); |
| i64 iVal = 0; |
| memcpy(&iVal, &rVal, sizeof(rVal)); |
| sessionPutI64(&pNew->aRecord[pNew->nRecord], iVal); |
| pNew->nRecord += 8; |
| break; |
| } |
|
|
| case SQLITE_TEXT: { |
| int n = sqlite3_column_bytes(pDflt, iField); |
| const char *z = (const char*)sqlite3_column_text(pDflt, iField); |
| pNew->nRecord += sessionVarintPut(&pNew->aRecord[pNew->nRecord], n); |
| memcpy(&pNew->aRecord[pNew->nRecord], z, n); |
| pNew->nRecord += n; |
| break; |
| } |
|
|
| case SQLITE_BLOB: { |
| int n = sqlite3_column_bytes(pDflt, iField); |
| const u8 *z = (const u8*)sqlite3_column_blob(pDflt, iField); |
| pNew->nRecord += sessionVarintPut(&pNew->aRecord[pNew->nRecord], n); |
| memcpy(&pNew->aRecord[pNew->nRecord], z, n); |
| pNew->nRecord += n; |
| break; |
| } |
|
|
| default: |
| assert( eType==SQLITE_NULL ); |
| break; |
| } |
|
|
| sessionFree(pSession, pOld); |
| *pp = pOld = pNew; |
| pNew->nRecordField++; |
| pNew->nMaxSize += nIncr; |
| if( pSession ){ |
| pSession->nMaxChangesetSize += nIncr; |
| } |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| 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 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 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 int sessionPrepareDfltStmt( |
| sqlite3 *db, |
| SessionTable *pTab, |
| sqlite3_stmt **ppStmt |
| ){ |
| SessionBuffer sql = {0,0,0}; |
| int rc = SQLITE_OK; |
| const char *zSep = " "; |
| int ii = 0; |
|
|
| *ppStmt = 0; |
| sessionAppendPrintf(&sql, &rc, "SELECT"); |
| for(ii=0; ii<pTab->nCol; ii++){ |
| const char *zDflt = pTab->azDflt[ii] ? pTab->azDflt[ii] : "NULL"; |
| sessionAppendPrintf(&sql, &rc, "%s%s", zSep, zDflt); |
| zSep = ", "; |
| } |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3_prepare_v2(db, (const char*)sql.aBuf, -1, ppStmt, 0); |
| } |
| sqlite3_free(sql.aBuf); |
|
|
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| static int sessionUpdateChanges(sqlite3_session *pSession, SessionTable *pTab){ |
| sqlite3_stmt *pStmt = 0; |
| int rc = pSession->rc; |
|
|
| rc = sessionPrepareDfltStmt(pSession->db, pTab, &pStmt); |
| if( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pStmt) ){ |
| int ii = 0; |
| SessionChange **pp = 0; |
| for(ii=0; ii<pTab->nChange; ii++){ |
| for(pp=&pTab->apChange[ii]; *pp; pp=&((*pp)->pNext)){ |
| if( (*pp)->nRecordField!=pTab->nCol ){ |
| sessionUpdateOneChange(pSession, &rc, pp, pTab->nCol, pStmt); |
| } |
| } |
| } |
| } |
|
|
| pSession->rc = rc; |
| rc = sqlite3_finalize(pStmt); |
| if( pSession->rc==SQLITE_OK ) pSession->rc = rc; |
| return pSession->rc; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| 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, pTab->aiIdx[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; |
| int iIdx = pTab->aiIdx[ii]; |
| sqlite3_value *p = 0; |
| pSession->hook.xNew(pSession->hook.pCtx, iIdx, &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; |
| int nExpect = 0; |
| SessionStat1Ctx stat1 = {{0,0,0,0,0},0}; |
|
|
| if( pSession->rc ) return; |
|
|
| |
| if( sessionInitTable(pSession, pTab, pSession->db, pSession->zDb) ) return; |
|
|
| |
| |
| nExpect = pSession->hook.xCount(pSession->hook.pCtx); |
| if( pTab->nTotalCol<nExpect ){ |
| if( sessionReinitTable(pSession, pTab) ) return; |
| if( sessionUpdateChanges(pSession, pTab) ) return; |
| } |
| if( pTab->nTotalCol!=nExpect ){ |
| 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=pTab->bRowid; i<pTab->nCol; i++){ |
| int iIdx = pTab->aiIdx[i]; |
| sqlite3_value *p = 0; |
| if( op!=SQLITE_INSERT ){ |
| |
| |
| rc = pSession->hook.xOld(pSession->hook.pCtx, iIdx, &p); |
| }else if( pTab->abPK[i] ){ |
| TESTONLY(int trc = ) pSession->hook.xNew(pSession->hook.pCtx,iIdx,&p); |
| assert( trc==SQLITE_OK ); |
| } |
|
|
| if( rc==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=pTab->bRowid; i<pTab->nCol; i++){ |
| sqlite3_value *p = 0; |
| int iIdx = pTab->aiIdx[i]; |
| if( op!=SQLITE_INSERT ){ |
| pSession->hook.xOld(pSession->hook.pCtx, iIdx, &p); |
| }else if( pTab->abPK[i] ){ |
| pSession->hook.xNew(pSession->hook.pCtx, iIdx, &p); |
| } |
| sessionSerializeValue(&pC->aRecord[nByte], p, &nByte); |
| } |
|
|
| |
| if( pSession->bIndirect || pSession->hook.xDepth(pSession->hook.pCtx) ){ |
| pC->bIndirect = 1; |
| } |
| pC->nRecordField = pTab->nCol; |
| 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; |
|
|
| |
| pSession->bAutoAttach++; |
| rc = sessionFindTable(pSession, zTbl, &pTo); |
| pSession->bAutoAttach--; |
| if( pTo==0 ) goto diff_out; |
| if( sessionInitTable(pSession, pTo, pSession->db, pSession->zDb) ){ |
| rc = pSession->rc; |
| goto diff_out; |
| } |
|
|
| |
| if( rc==SQLITE_OK ){ |
| int bHasPk = 0; |
| int bMismatch = 0; |
| int nCol = 0; |
| int bRowid = 0; |
| u8 *abPK = 0; |
| const char **azCol = 0; |
| char *zDbExists = 0; |
|
|
| |
| zDbExists = sqlite3_mprintf("SELECT * FROM %Q.sqlite_schema", zFrom); |
| if( zDbExists==0 ){ |
| rc = SQLITE_NOMEM; |
| }else{ |
| sqlite3_stmt *pDbExists = 0; |
| rc = sqlite3_prepare_v2(db, zDbExists, -1, &pDbExists, 0); |
| if( rc==SQLITE_ERROR ){ |
| rc = SQLITE_OK; |
| nCol = -1; |
| } |
| sqlite3_finalize(pDbExists); |
| sqlite3_free(zDbExists); |
| } |
|
|
| if( rc==SQLITE_OK && nCol==0 ){ |
| rc = sessionTableInfo(0, db, zFrom, zTbl, |
| &nCol, 0, 0, &azCol, 0, 0, &abPK, |
| pSession->bImplicitPK ? &bRowid : 0 |
| ); |
| } |
| if( rc==SQLITE_OK ){ |
| if( pTo->nCol!=nCol ){ |
| if( nCol<=0 ){ |
| rc = SQLITE_SCHEMA; |
| if( pzErrMsg ){ |
| *pzErrMsg = sqlite3_mprintf("no such table: %s.%s", zFrom, zTbl); |
| } |
| }else{ |
| 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); |
| } |
| } |
| sqlite3_finalize(pTab->pDfltStmt); |
| 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); |
|
|
| |
| 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 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 sessionAppendInteger( |
| SessionBuffer *p, |
| int iVal, |
| int *pRc |
| ){ |
| char aBuf[24]; |
| sqlite3_snprintf(sizeof(aBuf)-1, aBuf, "%d", iVal); |
| sessionAppendStr(p, aBuf, pRc); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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++ = '"'; |
| if( zIn!=0 ){ |
| 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 sessionPrepare( |
| sqlite3 *db, |
| sqlite3_stmt **pp, |
| char **pzErrmsg, |
| const char *zSql |
| ){ |
| int rc = sqlite3_prepare_v2(db, zSql, -1, pp, 0); |
| if( pzErrmsg && rc!=SQLITE_OK ){ |
| *pzErrmsg = sqlite3_mprintf("%s", sqlite3_errmsg(db)); |
| } |
| 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, |
| char **pzErrmsg |
| ){ |
| int rc = SQLITE_OK; |
| char *zSql = 0; |
| const char *zSep = ""; |
| int i; |
|
|
| SessionBuffer cols = {0, 0, 0}; |
| 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 |
| ); |
| sessionAppendStr(&cols, "tbl, ?2, stat", &rc); |
| }else{ |
| #if 0 |
| if( bRowid ){ |
| sessionAppendStr(&cols, SESSIONS_ROWID, &rc); |
| } |
| #endif |
| for(i=0; i<nCol; i++){ |
| if( cols.nBuf ) sessionAppendStr(&cols, ", ", &rc); |
| sessionAppendIdent(&cols, azCol[i], &rc); |
| 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)", |
| (char*)cols.aBuf, (bIgnoreNoop ? (char*)nooptest.aBuf : ""), |
| zDb, zTab, (char*)pkfield.aBuf, (char*)pkvar.aBuf |
| ); |
| if( zSql==0 ) rc = SQLITE_NOMEM; |
| } |
|
|
| #if 0 |
| if( 0==sqlite3_stricmp("sqlite_stat1", zTab) ){ |
| zSql = sqlite3_mprintf( |
| "SELECT tbl, ?2, stat FROM %Q.sqlite_stat1 WHERE tbl IS ?1 AND " |
| "idx IS (CASE WHEN ?2=X'' THEN NULL ELSE ?2 END)", zDb |
| ); |
| if( zSql==0 ) rc = SQLITE_NOMEM; |
| }else{ |
| const char *zSep = ""; |
| SessionBuffer buf = {0, 0, 0}; |
|
|
| sessionAppendStr(&buf, "SELECT * FROM ", &rc); |
| sessionAppendIdent(&buf, zDb, &rc); |
| sessionAppendStr(&buf, ".", &rc); |
| sessionAppendIdent(&buf, zTab, &rc); |
| sessionAppendStr(&buf, " WHERE ", &rc); |
| for(i=0; i<nCol; i++){ |
| if( abPK[i] ){ |
| sessionAppendStr(&buf, zSep, &rc); |
| sessionAppendIdent(&buf, azCol[i], &rc); |
| sessionAppendStr(&buf, " IS ?", &rc); |
| sessionAppendInteger(&buf, i+1, &rc); |
| zSep = " AND "; |
| } |
| } |
| zSql = (char*)buf.aBuf; |
| nSql = buf.nBuf; |
| } |
| #endif |
|
|
| if( rc==SQLITE_OK ){ |
| rc = sessionPrepare(db, ppStmt, pzErrmsg, zSql); |
| } |
| sqlite3_free(zSql); |
| sqlite3_free(nooptest.aBuf); |
| sqlite3_free(pkfield.aBuf); |
| sqlite3_free(pkvar.aBuf); |
| sqlite3_free(cols.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 i; |
| sqlite3_stmt *pSel = 0; |
| int nRewind = buf.nBuf; |
| int nNoop; |
| int nOldCol = pTab->nCol; |
|
|
| |
| rc = sessionReinitTable(pSession, pTab); |
| if( rc==SQLITE_OK && pTab->nCol!=nOldCol ){ |
| rc = sessionUpdateChanges(pSession, pTab); |
| } |
|
|
| |
| sessionAppendTableHdr(&buf, bPatchset, pTab, &rc); |
|
|
| |
| if( rc==SQLITE_OK ){ |
| rc = sessionSelectStmt(db, 0, pSession->zDb, |
| zName, pTab->bRowid, pTab->nCol, pTab->azCol, pTab->abPK, &pSel, 0 |
| ); |
| } |
|
|
| 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, pTab->nCol, pTab->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<pTab->nCol; iCol++){ |
| sessionAppendCol(&buf, pSel, iCol, &rc); |
| } |
| }else{ |
| assert( pTab->abPK!=0 ); |
| rc = sessionAppendUpdate(&buf, bPatchset, pSel, p, pTab->abPK); |
| } |
| }else if( p->op!=SQLITE_INSERT ){ |
| rc = sessionAppendDelete(&buf, bPatchset, p, pTab->nCol,pTab->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; |
| } |
| } |
| } |
|
|
| 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->iCurrent>=sessions_strm_chunk_size ){ |
| int nMove = pIn->buf.nBuf - pIn->iCurrent; |
| assert( nMove>=0 ); |
| if( nMove>0 ){ |
| memmove(pIn->buf.aBuf, &pIn->buf.aBuf[pIn->iCurrent], nMove); |
| } |
| pIn->buf.nBuf -= pIn->iCurrent; |
| pIn->iNext -= pIn->iCurrent; |
| pIn->iCurrent = 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 ){ |
| if( (pIn->nData-pIn->iNext)<8 ){ |
| rc = SQLITE_CORRUPT_BKPT; |
| }else{ |
| 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; |
|
|
| p->in.iCurrent = p->in.iNext; |
| sessionDiscardData(&p->in); |
|
|
| |
| if( p->in.iNext>=p->in.nData ){ |
| return SQLITE_DONE; |
| } |
|
|
| 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; |
| char *zErr; |
| }; |
|
|
| |
| #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 = sessionPrepare(db, &p->pDelete, &p->zErr, (char*)buf.aBuf); |
| } |
| 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, &p->zErr |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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 = sessionPrepare(db, &p->pInsert, &p->zErr, (char*)buf.aBuf); |
| } |
| sqlite3_free(buf.aBuf); |
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){ |
| int rc = sessionSelectRow(db, "sqlite_stat1", p); |
| if( rc==SQLITE_OK ){ |
| rc = sessionPrepare(db, &p->pInsert, 0, |
| "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, 0, |
| "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 = SQLITE_CHANGESET_OMIT; |
| 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( 0==p->bIgnoreNoop |
| || 0==sqlite3_column_int(p->pSelect, sqlite3_column_count(p->pSelect)-1) |
| ){ |
| 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 if( p->bIgnoreNoop==0 || op!=SQLITE_DELETE |
| || eType==SQLITE_CHANGESET_CONFLICT |
| ){ |
| |
| 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 ){ |
| 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(*xFilterIter)( |
| void *pCtx, |
| sqlite3_changeset_iter *p |
| ), |
| 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; |
| u64 savedFlag = db->flags & SQLITE_FkNoAction; |
|
|
| assert( xConflict!=0 ); |
|
|
| sqlite3_mutex_enter(sqlite3_db_mutex(db)); |
| if( flags & SQLITE_CHANGESETAPPLY_FKNOACTION ){ |
| db->flags |= ((u64)SQLITE_FkNoAction); |
| db->aDb[0].pSchema->schema_cookie -= 32; |
| } |
|
|
| 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); |
| 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, 0, &zTab, &sApply.azCol, 0, 0, |
| &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; |
|
|
| |
| if( xFilterIter && 0==xFilterIter(pCtx, pIter) ) 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; |
| } |
| } |
| } |
|
|
| { |
| int rc2 = sqlite3_exec(db, "PRAGMA defer_foreign_keys = 0", 0, 0, 0); |
| if( rc==SQLITE_OK ) rc = rc2; |
| } |
|
|
| if( (flags & SQLITE_CHANGESETAPPLY_NOSAVEPOINT)==0 ){ |
| if( rc==SQLITE_OK ){ |
| rc = sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0); |
| } |
| if( rc!=SQLITE_OK ){ |
| 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 ){ |
| assert( ppRebase!=0 && pnRebase!=0 ); |
| *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); |
|
|
| if( (flags & SQLITE_CHANGESETAPPLY_FKNOACTION) && savedFlag==0 ){ |
| assert( db->flags & SQLITE_FkNoAction ); |
| db->flags &= ~((u64)SQLITE_FkNoAction); |
| db->aDb[0].pSchema->schema_cookie -= 32; |
| } |
|
|
| assert( rc!=SQLITE_OK || sApply.zErr==0 ); |
| sqlite3_set_errmsg(db, rc, sApply.zErr); |
| sqlite3_free(sApply.zErr); |
|
|
| sqlite3_mutex_leave(sqlite3_db_mutex(db)); |
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static int sessionChangesetApplyV23( |
| sqlite3 *db, |
| int nChangeset, |
| void *pChangeset, |
| int (*xInput)(void *pIn, void *pData, int *pnData), |
| void *pIn, |
| int(*xFilter)( |
| void *pCtx, |
| const char *zTab |
| ), |
| int(*xFilterIter)( |
| void *pCtx, |
| sqlite3_changeset_iter *p |
| ), |
| 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, nChangeset, pChangeset, bInverse, 1 |
| ); |
| if( rc==SQLITE_OK ){ |
| rc = sessionChangesetApply(db, pIter, |
| xFilter, xFilterIter, xConflict, pCtx, ppRebase, pnRebase, flags |
| ); |
| } |
| 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 |
| ){ |
| return sessionChangesetApplyV23(db, |
| nChangeset, pChangeset, 0, 0, |
| xFilter, 0, xConflict, pCtx, |
| ppRebase, pnRebase, flags |
| ); |
| } |
|
|
| |
| |
| |
| |
| int sqlite3changeset_apply_v3( |
| sqlite3 *db, |
| int nChangeset, |
| void *pChangeset, |
| int(*xFilter)( |
| void *pCtx, |
| sqlite3_changeset_iter *p |
| ), |
| int(*xConflict)( |
| void *pCtx, |
| int eConflict, |
| sqlite3_changeset_iter *p |
| ), |
| void *pCtx, |
| void **ppRebase, int *pnRebase, |
| int flags |
| ){ |
| return sessionChangesetApplyV23(db, |
| nChangeset, pChangeset, 0, 0, |
| 0, xFilter, xConflict, pCtx, |
| ppRebase, pnRebase, flags |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| 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 sessionChangesetApplyV23(db, |
| nChangeset, pChangeset, 0, 0, |
| xFilter, 0, xConflict, pCtx, |
| 0, 0, 0 |
| ); |
| } |
|
|
| |
| |
| |
| |
| |
| int sqlite3changeset_apply_v3_strm( |
| sqlite3 *db, |
| int (*xInput)(void *pIn, void *pData, int *pnData), |
| void *pIn, |
| int(*xFilter)( |
| void *pCtx, |
| sqlite3_changeset_iter *p |
| ), |
| int(*xConflict)( |
| void *pCtx, |
| int eConflict, |
| sqlite3_changeset_iter *p |
| ), |
| void *pCtx, |
| void **ppRebase, int *pnRebase, |
| int flags |
| ){ |
| return sessionChangesetApplyV23(db, |
| 0, 0, xInput, pIn, |
| 0, xFilter, xConflict, pCtx, |
| ppRebase, pnRebase, flags |
| ); |
| } |
| 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 |
| ){ |
| return sessionChangesetApplyV23(db, |
| 0, 0, xInput, pIn, |
| xFilter, 0, xConflict, pCtx, |
| ppRebase, pnRebase, flags |
| ); |
| } |
| 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 sessionChangesetApplyV23(db, |
| 0, 0, xInput, pIn, |
| xFilter, 0, xConflict, pCtx, |
| 0, 0, 0 |
| ); |
| } |
|
|
| |
| |
| |
| struct sqlite3_changegroup { |
| int rc; |
| int bPatch; |
| SessionTable *pList; |
| SessionBuffer rec; |
|
|
| sqlite3 *db; |
| char *zDb; |
| }; |
|
|
| |
| |
| |
| |
| |
| 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; |
| assert( aRec!=0 ); |
|
|
| 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 sessionChangesetCheckCompat( |
| SessionTable *pTab, |
| int nCol, |
| u8 *abPK |
| ){ |
| if( pTab->azCol && nCol<pTab->nCol ){ |
| int ii; |
| for(ii=0; ii<pTab->nCol; ii++){ |
| u8 bPK = (ii < nCol) ? abPK[ii] : 0; |
| if( pTab->abPK[ii]!=bPK ) return 0; |
| } |
| return 1; |
| } |
| return (pTab->nCol==nCol && 0==memcmp(abPK, pTab->abPK, nCol)); |
| } |
|
|
| static int sessionChangesetExtendRecord( |
| sqlite3_changegroup *pGrp, |
| SessionTable *pTab, |
| int nCol, |
| int op, |
| const u8 *aRec, |
| int nRec, |
| SessionBuffer *pOut |
| ){ |
| int rc = SQLITE_OK; |
| int ii = 0; |
|
|
| assert( pTab->azCol ); |
| assert( nCol<pTab->nCol ); |
|
|
| pOut->nBuf = 0; |
| if( op==SQLITE_INSERT || (op==SQLITE_DELETE && pGrp->bPatch==0) ){ |
| |
| sessionAppendBlob(pOut, aRec, nRec, &rc); |
| if( rc==SQLITE_OK && pTab->pDfltStmt==0 ){ |
| rc = sessionPrepareDfltStmt(pGrp->db, pTab, &pTab->pDfltStmt); |
| if( rc==SQLITE_OK && SQLITE_ROW!=sqlite3_step(pTab->pDfltStmt) ){ |
| rc = sqlite3_errcode(pGrp->db); |
| } |
| } |
| for(ii=nCol; rc==SQLITE_OK && ii<pTab->nCol; ii++){ |
| int eType = sqlite3_column_type(pTab->pDfltStmt, ii); |
| sessionAppendByte(pOut, eType, &rc); |
| switch( eType ){ |
| case SQLITE_FLOAT: |
| case SQLITE_INTEGER: { |
| i64 iVal; |
| if( eType==SQLITE_INTEGER ){ |
| iVal = sqlite3_column_int64(pTab->pDfltStmt, ii); |
| }else{ |
| double rVal = sqlite3_column_int64(pTab->pDfltStmt, ii); |
| memcpy(&iVal, &rVal, sizeof(i64)); |
| } |
| if( SQLITE_OK==sessionBufferGrow(pOut, 8, &rc) ){ |
| sessionPutI64(&pOut->aBuf[pOut->nBuf], iVal); |
| pOut->nBuf += 8; |
| } |
| break; |
| } |
|
|
| case SQLITE_BLOB: |
| case SQLITE_TEXT: { |
| int n = sqlite3_column_bytes(pTab->pDfltStmt, ii); |
| sessionAppendVarint(pOut, n, &rc); |
| if( eType==SQLITE_TEXT ){ |
| const u8 *z = (const u8*)sqlite3_column_text(pTab->pDfltStmt, ii); |
| sessionAppendBlob(pOut, z, n, &rc); |
| }else{ |
| const u8 *z = (const u8*)sqlite3_column_blob(pTab->pDfltStmt, ii); |
| sessionAppendBlob(pOut, z, n, &rc); |
| } |
| break; |
| } |
|
|
| default: |
| assert( eType==SQLITE_NULL ); |
| break; |
| } |
| } |
| }else if( op==SQLITE_UPDATE ){ |
| |
| |
| int iOff = 0; |
| if( pGrp->bPatch==0 ){ |
| for(ii=0; ii<nCol; ii++){ |
| iOff += sessionSerialLen(&aRec[iOff]); |
| } |
| sessionAppendBlob(pOut, aRec, iOff, &rc); |
| for(ii=0; ii<(pTab->nCol-nCol); ii++){ |
| sessionAppendByte(pOut, 0x00, &rc); |
| } |
| } |
|
|
| sessionAppendBlob(pOut, &aRec[iOff], nRec-iOff, &rc); |
| for(ii=0; ii<(pTab->nCol-nCol); ii++){ |
| sessionAppendByte(pOut, 0x00, &rc); |
| } |
| }else{ |
| assert( op==SQLITE_DELETE && pGrp->bPatch ); |
| sessionAppendBlob(pOut, aRec, nRec, &rc); |
| } |
|
|
| return rc; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| static int sessionChangesetFindTable( |
| sqlite3_changegroup *pGrp, |
| const char *zTab, |
| sqlite3_changeset_iter *pIter, |
| SessionTable **ppTab |
| ){ |
| int rc = SQLITE_OK; |
| SessionTable *pTab = 0; |
| int nTab = (int)strlen(zTab); |
| u8 *abPK = 0; |
| int nCol = 0; |
|
|
| *ppTab = 0; |
| sqlite3changeset_pk(pIter, &abPK, &nCol); |
|
|
| |
| for(pTab = pGrp->pList; pTab; pTab=pTab->pNext){ |
| if( 0==sqlite3_strnicmp(pTab->zName, zTab, nTab+1) ) break; |
| } |
|
|
| |
| if( !pTab ){ |
| SessionTable **ppNew; |
|
|
| pTab = sqlite3_malloc64(sizeof(SessionTable) + nCol + nTab+1); |
| if( !pTab ){ |
| return SQLITE_NOMEM; |
| } |
| 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, zTab, nTab+1); |
|
|
| if( pGrp->db ){ |
| pTab->nCol = 0; |
| rc = sessionInitTable(0, pTab, pGrp->db, pGrp->zDb); |
| if( rc ){ |
| assert( pTab->azCol==0 ); |
| sqlite3_free(pTab); |
| return rc; |
| } |
| } |
|
|
| |
| |
| |
| |
| for(ppNew=&pGrp->pList; *ppNew; ppNew=&(*ppNew)->pNext); |
| *ppNew = pTab; |
| } |
|
|
| |
| if( !sessionChangesetCheckCompat(pTab, nCol, abPK) ){ |
| rc = SQLITE_SCHEMA; |
| } |
|
|
| *ppTab = pTab; |
| return rc; |
| } |
|
|
| |
| |
| |
| |
| static int sessionOneChangeToHash( |
| sqlite3_changegroup *pGrp, |
| sqlite3_changeset_iter *pIter, |
| int bRebase |
| ){ |
| int rc = SQLITE_OK; |
| int nCol = 0; |
| int op = 0; |
| int iHash = 0; |
| int bIndirect = 0; |
| SessionChange *pChange = 0; |
| SessionChange *pExist = 0; |
| SessionChange **pp = 0; |
| SessionTable *pTab = 0; |
| u8 *aRec = &pIter->in.aData[pIter->in.iCurrent + 2]; |
| int nRec = (pIter->in.iNext - pIter->in.iCurrent) - 2; |
|
|
| assert( nRec>0 ); |
|
|
| |
| |
| |
| if( pGrp->pList==0 ){ |
| pGrp->bPatch = pIter->bPatchset; |
| }else if( pIter->bPatchset!=pGrp->bPatch ){ |
| rc = SQLITE_ERROR; |
| } |
|
|
| if( rc==SQLITE_OK ){ |
| const char *zTab = 0; |
| sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); |
| rc = sessionChangesetFindTable(pGrp, zTab, pIter, &pTab); |
| } |
|
|
| if( rc==SQLITE_OK && nCol<pTab->nCol ){ |
| SessionBuffer *pBuf = &pGrp->rec; |
| rc = sessionChangesetExtendRecord(pGrp, pTab, nCol, op, aRec, nRec, pBuf); |
| aRec = pBuf->aBuf; |
| nRec = pBuf->nBuf; |
| assert( pGrp->db ); |
| } |
|
|
| if( rc==SQLITE_OK && sessionGrowHash(0, pIter->bPatchset, pTab) ){ |
| rc = SQLITE_NOMEM; |
| } |
|
|
| if( rc==SQLITE_OK ){ |
| |
| |
| 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; |
| } |
| } |
| } |
|
|
| if( rc==SQLITE_OK ){ |
| rc = sessionChangeMerge(pTab, bRebase, |
| pIter->bPatchset, pExist, op, bIndirect, aRec, nRec, &pChange |
| ); |
| } |
| if( rc==SQLITE_OK && pChange ){ |
| pChange->pNext = pTab->apChange[iHash]; |
| pTab->apChange[iHash] = pChange; |
| pTab->nEntry++; |
| } |
|
|
| if( rc==SQLITE_OK ) rc = pIter->rc; |
| return rc; |
| } |
|
|
| |
| |
| |
| |
| static int sessionChangesetToHash( |
| sqlite3_changeset_iter *pIter, |
| sqlite3_changegroup *pGrp, |
| int bRebase |
| ){ |
| u8 *aRec; |
| int nRec; |
| int rc = SQLITE_OK; |
|
|
| pIter->in.bNoDiscard = 1; |
| while( SQLITE_ROW==(sessionChangesetNext(pIter, &aRec, &nRec, 0)) ){ |
| rc = sessionOneChangeToHash(pGrp, pIter, bRebase); |
| if( rc!=SQLITE_OK ) break; |
| } |
|
|
| 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_schema( |
| sqlite3_changegroup *pGrp, |
| sqlite3 *db, |
| const char *zDb |
| ){ |
| int rc = SQLITE_OK; |
|
|
| if( pGrp->pList || pGrp->db ){ |
| |
| |
| rc = SQLITE_MISUSE; |
| }else{ |
| pGrp->zDb = sqlite3_mprintf("%s", zDb); |
| if( pGrp->zDb==0 ){ |
| rc = SQLITE_NOMEM; |
| }else{ |
| pGrp->db = db; |
| } |
| } |
| 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_add_change( |
| sqlite3_changegroup *pGrp, |
| sqlite3_changeset_iter *pIter |
| ){ |
| int rc = SQLITE_OK; |
|
|
| if( pIter->in.iCurrent==pIter->in.iNext |
| || pIter->rc!=SQLITE_OK |
| || pIter->bInvert |
| ){ |
| |
| rc = SQLITE_ERROR; |
| }else{ |
| pIter->in.bNoDiscard = 1; |
| rc = sessionOneChangeToHash(pGrp, pIter, 0); |
| } |
| 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 ){ |
| sqlite3_free(pGrp->zDb); |
| sessionDeleteTable(0, pGrp->pList); |
| sqlite3_free(pGrp->rec.aBuf); |
| 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->grp.rec.aBuf); |
| 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 |
|
|