mirror of
https://github.com/sqlite/sqlite.git
synced 2025-08-04 04:42:17 +03:00
486 lines
12 KiB
C
486 lines
12 KiB
C
|
|
#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 */
|