#if !defined(SQLITE_TEST) || (defined(SQLITE_ENABLE_SESSION) && defined(SQLITE_ENABLE_PREUPDATE_HOOK))
#include "sqlite3session.h"
#include "sqlite3changebatch.h"
#include <assert.h>
#include <string.h>
typedef struct BatchTable BatchTable;
typedef struct BatchIndex BatchIndex;
typedef struct BatchIndexEntry BatchIndexEntry;
typedef struct BatchHash BatchHash;
struct sqlite3_changebatch {
sqlite3 *db; /* Database handle used to read schema */
BatchTable *pTab; /* First in linked list of tables */
int iChangesetId; /* Current changeset id */
int iNextIdxId; /* Next available index id */
int nEntry; /* Number of entries in hash table */
int nHash; /* Number of hash buckets */
BatchIndexEntry **apHash; /* Array of hash buckets */
};
struct BatchTable {
BatchIndex *pIdx; /* First in linked list of UNIQUE indexes */
BatchTable *pNext; /* Next table */
char zTab[1]; /* Table name */
};
struct BatchIndex {
BatchIndex *pNext; /* Next index on same table */
int iId; /* Index id (assigned internally) */
int bPk; /* True for PK index */
int nCol; /* Size of aiCol[] array */
int *aiCol; /* Array of columns that make up index */
};
struct BatchIndexEntry {
BatchIndexEntry *pNext; /* Next colliding hash table entry */
int iChangesetId; /* Id of associated changeset */
int iIdxId; /* Id of index this key is from */
int szRecord;
char aRecord[1];
};
/*
** Allocate and zero a block of nByte bytes. Must be freed using cbFree().
*/
static void *cbMalloc(int *pRc, int nByte){
void *pRet;
if( *pRc ){
pRet = 0;
}else{
pRet = sqlite3_malloc(nByte);
if( pRet ){
memset(pRet, 0, nByte);
}else{
*pRc = SQLITE_NOMEM;
}
}
return pRet;
}
/*
** Free an allocation made by cbMalloc().
*/
static void cbFree(void *p){
sqlite3_free(p);
}
/*
** Return the hash bucket that pEntry belongs in.
*/
static int cbHash(sqlite3_changebatch *p, BatchIndexEntry *pEntry){
unsigned int iHash = (unsigned int)pEntry->iIdxId;
unsigned char *pEnd = (unsigned char*)&pEntry->aRecord[pEntry->szRecord];
unsigned char *pIter;
for(pIter=(unsigned char*)pEntry->aRecord; pIter<pEnd; pIter++){
iHash += (iHash << 7) + *pIter;
}
return (int)(iHash % p->nHash);
}
/*
** Resize the hash table.
*/
static int cbHashResize(sqlite3_changebatch *p){
int rc = SQLITE_OK;
BatchIndexEntry **apNew;
int nNew = (p->nHash ? p->nHash*2 : 512);
int i;
apNew = cbMalloc(&rc, sizeof(BatchIndexEntry*) * nNew);
if( rc==SQLITE_OK ){
int nHash = p->nHash;
p->nHash = nNew;
for(i=0; i<nHash; i++){
BatchIndexEntry *pEntry;
while( (pEntry=p->apHash[i])!=0 ){
int iHash = cbHash(p, pEntry);
p->apHash[i] = pEntry->pNext;
pEntry->pNext = apNew[iHash];
apNew[iHash] = pEntry;
}
}
cbFree(p->apHash);
p->apHash = apNew;
}
return rc;
}
/*
** Allocate a new sqlite3_changebatch object.
*/
int sqlite3changebatch_new(sqlite3 *db, sqlite3_changebatch **pp){
sqlite3_changebatch *pRet;
int rc = SQLITE_OK;
*pp = pRet = (sqlite3_changebatch*)cbMalloc(&rc, sizeof(sqlite3_changebatch));
if( pRet ){
pRet->db = db;
}
return rc;
}
/*
** Add a BatchIndex entry for index zIdx to table pTab.
*/
static int cbAddIndex(
sqlite3_changebatch *p,
BatchTable *pTab,
const char *zIdx,
int bPk
){
int nCol = 0;
sqlite3_stmt *pIndexInfo = 0;
BatchIndex *pNew = 0;
int rc;
char *zIndexInfo;
zIndexInfo = (char*)sqlite3_mprintf("PRAGMA main.index_info = %Q", zIdx);
if( zIndexInfo ){
rc = sqlite3_prepare_v2(p->db, zIndexInfo, -1, &pIndexInfo, 0);
sqlite3_free(zIndexInfo);
}else{
rc = SQLITE_NOMEM;
}
if( rc==SQLITE_OK ){
while( SQLITE_ROW==sqlite3_step(pIndexInfo) ){ nCol++; }
rc = sqlite3_reset(pIndexInfo);
}
pNew = (BatchIndex*)cbMalloc(&rc, sizeof(BatchIndex) + sizeof(int) * nCol);
if( rc==SQLITE_OK ){
pNew->nCol = nCol;
pNew->bPk = bPk;
pNew->aiCol = (int*)&pNew[1];
pNew->iId = p->iNextIdxId++;
while( SQLITE_ROW==sqlite3_step(pIndexInfo) ){
int i = sqlite3_column_int(pIndexInfo, 0);
int j = sqlite3_column_int(pIndexInfo, 1);
pNew->aiCol[i] = j;
}
rc = sqlite3_reset(pIndexInfo);
}
if( rc==SQLITE_OK ){
pNew->pNext = pTab->pIdx;
pTab->pIdx = pNew;
}else{
cbFree(pNew);
}
sqlite3_finalize(pIndexInfo);
return rc;
}
/*
** Free the object passed as the first argument.
*/
static void cbFreeTable(BatchTable *pTab){
BatchIndex *pIdx;
BatchIndex *pIdxNext;
for(pIdx=pTab->pIdx; pIdx; pIdx=pIdxNext){
pIdxNext = pIdx->pNext;
cbFree(pIdx);
}
cbFree(pTab);
}
/*
** Find or create the BatchTable object named zTab.
*/
static int cbFindTable(
sqlite3_changebatch *p,
const char *zTab,
BatchTable **ppTab
){
BatchTable *pRet = 0;
int rc = SQLITE_OK;
for(pRet=p->pTab; pRet; pRet=pRet->pNext){
if( 0==sqlite3_stricmp(zTab, pRet->zTab) ) break;
}
if( pRet==0 ){
int nTab = strlen(zTab);
pRet = (BatchTable*)cbMalloc(&rc, nTab + sizeof(BatchTable));
if( pRet ){
sqlite3_stmt *pIndexList = 0;
char *zIndexList = 0;
int rc2;
memcpy(pRet->zTab, zTab, nTab);
zIndexList = sqlite3_mprintf("PRAGMA main.index_list = %Q", zTab);
if( zIndexList==0 ){
rc = SQLITE_NOMEM;
}else{
rc = sqlite3_prepare_v2(p->db, zIndexList, -1, &pIndexList, 0);
sqlite3_free(zIndexList);
}
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3_step(pIndexList) ){
if( sqlite3_column_int(pIndexList, 2) ){
const char *zIdx = (const char*)sqlite3_column_text(pIndexList, 1);
const char *zTyp = (const char*)sqlite3_column_text(pIndexList, 3);
rc = cbAddIndex(p, pRet, zIdx, (zTyp[0]=='p'));
}
}
rc2 = sqlite3_finalize(pIndexList);
if( rc==SQLITE_OK ) rc = rc2;
if( rc==SQLITE_OK ){
pRet->pNext = p->pTab;
p->pTab = pRet;
}else{
cbFreeTable(pRet);
pRet = 0;
}
}
}
*ppTab = pRet;
return rc;
}
/*
** Extract value iVal from the changeset iterator passed as the first
** argument. Set *ppVal to point to the value before returning.
**
** This function attempts to extract the value using function xVal
** (which is always either sqlite3changeset_new or sqlite3changeset_old).
** If the call returns SQLITE_OK but does not supply an sqlite3_value*
** pointer, an attempt to extract the value is made using the xFallback
** function.
*/
static int cbGetChangesetValue(
sqlite3_changeset_iter *pIter,
int (*xVal)(sqlite3_changeset_iter*,int,sqlite3_value**),
int (*xFallback)(sqlite3_changeset_iter*,int,sqlite3_value**),
int iVal,
sqlite3_value **ppVal
){
int rc = xVal(pIter, iVal, ppVal);
if( rc==SQLITE_OK && *ppVal==0 && xFallback ){
rc = xFallback(pIter, iVal, ppVal);
}
return rc;
}
static int cbAddToHash(
sqlite3_changebatch *p,
sqlite3_changeset_iter *pIter,
BatchIndex *pIdx,
int (*xVal)(sqlite3_changeset_iter*,int,sqlite3_value**),
int (*xFallback)(sqlite3_changeset_iter*,int,sqlite3_value**),
int *pbConf
){
BatchIndexEntry *pNew;
int sz = pIdx->nCol;
int i;
int iOut = 0;
int rc = SQLITE_OK;
for(i=0; rc==SQLITE_OK && i<pIdx->nCol; i++){
sqlite3_value *pVal;
rc = cbGetChangesetValue(pIter, xVal, xFallback, pIdx->aiCol[i], &pVal);
if( rc==SQLITE_OK ){
int eType = 0;
if( pVal ) eType = sqlite3_value_type(pVal);
switch( eType ){
case 0:
case SQLITE_NULL:
return SQLITE_OK;
case SQLITE_INTEGER:
sz += 8;
break;
case SQLITE_FLOAT:
sz += 8;
break;
default:
assert( eType==SQLITE_TEXT || eType==SQLITE_BLOB );
sz += sqlite3_value_bytes(pVal);
break;
}
}
}
pNew = cbMalloc(&rc, sizeof(BatchIndexEntry) + sz);
if( pNew ){
pNew->iChangesetId = p->iChangesetId;
pNew->iIdxId = pIdx->iId;
pNew->szRecord = sz;
for(i=0; i<pIdx->nCol; i++){
int eType;
sqlite3_value *pVal;
rc = cbGetChangesetValue(pIter, xVal, xFallback, pIdx->aiCol[i], &pVal);
if( rc!=SQLITE_OK ) break; /* coverage: condition is never true */
eType = sqlite3_value_type(pVal);
pNew->aRecord[iOut++] = eType;
switch( eType ){
case SQLITE_INTEGER: {
sqlite3_int64 i64 = sqlite3_value_int64(pVal);
memcpy(&pNew->aRecord[iOut], &i64, 8);
iOut += 8;
break;
}
case SQLITE_FLOAT: {
double d64 = sqlite3_value_double(pVal);
memcpy(&pNew->aRecord[iOut], &d64, sizeof(double));
iOut += sizeof(double);
break;
}
default: {
int nByte = sqlite3_value_bytes(pVal);
const char *z = (const char*)sqlite3_value_blob(pVal);
memcpy(&pNew->aRecord[iOut], z, nByte);
iOut += nByte;
break;
}
}
}
}
if( rc==SQLITE_OK && p->nEntry>=(p->nHash/2) ){
rc = cbHashResize(p);
}
if( rc==SQLITE_OK ){
BatchIndexEntry *pIter;
int iHash = cbHash(p, pNew);
assert( iHash>=0 && iHash<p->nHash );
for(pIter=p->apHash[iHash]; pIter; pIter=pIter->pNext){
if( pNew->szRecord==pIter->szRecord
&& 0==memcmp(pNew->aRecord, pIter->aRecord, pNew->szRecord)
){
if( pNew->iChangesetId!=pIter->iChangesetId ){
*pbConf = 1;
}
cbFree(pNew);
pNew = 0;
break;
}
}
if( pNew ){
pNew->pNext = p->apHash[iHash];
p->apHash[iHash] = pNew;
p->nEntry++;
}
}else{
cbFree(pNew);
}
return rc;
}
/*
** Add a changeset to the current batch.
*/
int sqlite3changebatch_add(sqlite3_changebatch *p, void *pBuf, int nBuf){
sqlite3_changeset_iter *pIter; /* Iterator opened on pBuf/nBuf */
int rc; /* Return code */
int bConf = 0; /* Conflict was detected */
rc = sqlite3changeset_start(&pIter, nBuf, pBuf);
if( rc==SQLITE_OK ){
int rc2;
for(rc2 = sqlite3changeset_next(pIter);
rc2==SQLITE_ROW;
rc2 = sqlite3changeset_next(pIter)
){
BatchTable *pTab;
BatchIndex *pIdx;
const char *zTab; /* Table this change applies to */
int nCol; /* Number of columns in table */
int op; /* UPDATE, INSERT or DELETE */
sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0);
assert( op==SQLITE_INSERT || op==SQLITE_UPDATE || op==SQLITE_DELETE );
rc = cbFindTable(p, zTab, &pTab);
assert( pTab || rc!=SQLITE_OK );
if( pTab ){
for(pIdx=pTab->pIdx; pIdx && rc==SQLITE_OK; pIdx=pIdx->pNext){
if( op==SQLITE_UPDATE && pIdx->bPk ) continue;
if( op==SQLITE_UPDATE || op==SQLITE_DELETE ){
rc = cbAddToHash(p, pIter, pIdx, sqlite3changeset_old, 0, &bConf);
}
if( op==SQLITE_UPDATE || op==SQLITE_INSERT ){
rc = cbAddToHash(p, pIter, pIdx,
sqlite3changeset_new, sqlite3changeset_old, &bConf
);
}
}
}
if( rc!=SQLITE_OK ) break;
}
rc2 = sqlite3changeset_finalize(pIter);
if( rc==SQLITE_OK ) rc = rc2;
}
if( rc==SQLITE_OK && bConf ){
rc = SQLITE_CONSTRAINT;
}
p->iChangesetId++;
return rc;
}
/*
** Zero an existing changebatch object.
*/
void sqlite3changebatch_zero(sqlite3_changebatch *p){
int i;
for(i=0; i<p->nHash; i++){
BatchIndexEntry *pEntry;
BatchIndexEntry *pNext;
for(pEntry=p->apHash[i]; pEntry; pEntry=pNext){
pNext = pEntry->pNext;
cbFree(pEntry);
}
}
cbFree(p->apHash);
p->nHash = 0;
p->apHash = 0;
}
/*
** Delete a changebatch object.
*/
void sqlite3changebatch_delete(sqlite3_changebatch *p){
BatchTable *pTab;
BatchTable *pTabNext;
sqlite3changebatch_zero(p);
for(pTab=p->pTab; pTab; pTab=pTabNext){
pTabNext = pTab->pNext;
cbFreeTable(pTab);
}
cbFree(p);
}
/*
** Return the db handle.
*/
sqlite3 *sqlite3changebatch_db(sqlite3_changebatch *p){
return p->db;
}
#endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */