| |
|
| | #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 |
| |
|