mirror of
https://github.com/sqlite/sqlite.git
synced 2025-08-08 14:02:16 +03:00
Merge latest trunk changes into this branch.
FossilOrigin-Name: 36801effa9ec67b551f58972e21794466420f10cd0420701fcd87695e6cd11ee
This commit is contained in:
@@ -53,6 +53,7 @@ struct sqlite3_session {
|
||||
int rc; /* Non-zero if an error has occurred */
|
||||
void *pFilterCtx; /* First argument to pass to xTableFilter */
|
||||
int (*xTableFilter)(void *pCtx, const char *zTab);
|
||||
sqlite3_value *pZeroBlob; /* Value containing X'' */
|
||||
sqlite3_session *pNext; /* Next session object on same db. */
|
||||
SessionTable *pTable; /* List of attached tables */
|
||||
SessionHook hook; /* APIs to grab new and old data with */
|
||||
@@ -120,6 +121,7 @@ struct SessionTable {
|
||||
SessionTable *pNext;
|
||||
char *zName; /* Local name of table */
|
||||
int nCol; /* Number of columns in table zName */
|
||||
int bStat1; /* True if this is sqlite_stat1 */
|
||||
const char **azCol; /* Column names */
|
||||
u8 *abPK; /* Array of primary key flags */
|
||||
int nEntry; /* Total number of entries in hash table */
|
||||
@@ -503,6 +505,7 @@ static int sessionPreupdateHash(
|
||||
h = sessionHashAppendBlob(h, n, z);
|
||||
}else{
|
||||
assert( eType==SQLITE_NULL );
|
||||
assert( pTab->bStat1==0 || i!=1 );
|
||||
*pbNullPK = 1;
|
||||
}
|
||||
}
|
||||
@@ -845,7 +848,6 @@ static int sessionPreupdateEqual(
|
||||
}
|
||||
if( memcmp(a, z, n) ) return 0;
|
||||
a += n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -901,9 +903,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){
|
||||
|
||||
/*
|
||||
** This function queries the database for the names of the columns of table
|
||||
** zThis, in schema zDb. It is expected that the table has nCol columns. If
|
||||
** not, SQLITE_SCHEMA is returned and none of the output variables are
|
||||
** populated.
|
||||
** zThis, in schema zDb.
|
||||
**
|
||||
** Otherwise, if they are not NULL, variable *pnCol is set to the number
|
||||
** of columns in the database table and variable *pzTab is set to point to a
|
||||
@@ -924,9 +924,7 @@ static int sessionGrowHash(int bPatchset, SessionTable *pTab){
|
||||
** *pabPK = {1, 0, 0, 1}
|
||||
**
|
||||
** All returned buffers are part of the same single allocation, which must
|
||||
** be freed using sqlite3_free() by the caller. If pazCol was not NULL, then
|
||||
** pointer *pazCol should be freed to release all memory. Otherwise, pointer
|
||||
** *pabPK. It is illegal for both pazCol and pabPK to be NULL.
|
||||
** be freed using sqlite3_free() by the caller
|
||||
*/
|
||||
static int sessionTableInfo(
|
||||
sqlite3 *db, /* Database connection */
|
||||
@@ -951,7 +949,23 @@ static int sessionTableInfo(
|
||||
assert( pazCol && pabPK );
|
||||
|
||||
nThis = sqlite3Strlen30(zThis);
|
||||
zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, 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 ){
|
||||
/* For sqlite_stat1, pretend that (tbl,idx) is the PRIMARY KEY. */
|
||||
zPragma = sqlite3_mprintf(
|
||||
"SELECT 0, 'tbl', '', 0, '', 1 UNION ALL "
|
||||
"SELECT 1, 'idx', '', 0, '', 2 UNION ALL "
|
||||
"SELECT 2, 'stat', '', 0, '', 0"
|
||||
);
|
||||
}else if( rc==SQLITE_ERROR ){
|
||||
zPragma = sqlite3_mprintf("");
|
||||
}else{
|
||||
return rc;
|
||||
}
|
||||
}else{
|
||||
zPragma = sqlite3_mprintf("PRAGMA '%q'.table_info('%q')", zDb, zThis);
|
||||
}
|
||||
if( !zPragma ) return SQLITE_NOMEM;
|
||||
|
||||
rc = sqlite3_prepare_v2(db, zPragma, -1, &pStmt, 0);
|
||||
@@ -1043,11 +1057,55 @@ static int sessionInitTable(sqlite3_session *pSession, SessionTable *pTab){
|
||||
break;
|
||||
}
|
||||
}
|
||||
if( 0==sqlite3_stricmp("sqlite_stat1", pTab->zName) ){
|
||||
pTab->bStat1 = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (pSession->rc || pTab->abPK==0);
|
||||
}
|
||||
|
||||
/*
|
||||
** Versions of the four methods in object SessionHook for use with the
|
||||
** sqlite_stat1 table. The purpose of this is to substitute a zero-length
|
||||
** blob each time a NULL value is read from the "idx" column of the
|
||||
** sqlite_stat1 table.
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** This function is only called from with a pre-update-hook reporting a
|
||||
** change on table pTab (attached to session pSession). The type of change
|
||||
@@ -1064,6 +1122,7 @@ static void sessionPreupdateOneChange(
|
||||
int iHash;
|
||||
int bNull = 0;
|
||||
int rc = SQLITE_OK;
|
||||
SessionStat1Ctx stat1 = {0};
|
||||
|
||||
if( pSession->rc ) return;
|
||||
|
||||
@@ -1083,6 +1142,25 @@ static void sessionPreupdateOneChange(
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate the hash-key for this change. If the primary key of the row
|
||||
** includes a NULL value, exit early. Such changes are ignored by the
|
||||
** session module. */
|
||||
@@ -1172,6 +1250,9 @@ static void sessionPreupdateOneChange(
|
||||
|
||||
/* If an error has occurred, mark the session object as failed. */
|
||||
error_out:
|
||||
if( pTab->bStat1 ){
|
||||
pSession->hook = stat1.hook;
|
||||
}
|
||||
if( rc!=SQLITE_OK ){
|
||||
pSession->rc = rc;
|
||||
}
|
||||
@@ -1508,7 +1589,6 @@ int sqlite3session_diff(
|
||||
if( abPK[i] ) bHasPk = 1;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
sqlite3_free((char*)azCol);
|
||||
if( bMismatch ){
|
||||
@@ -1634,6 +1714,7 @@ void sqlite3session_delete(sqlite3_session *pSession){
|
||||
}
|
||||
}
|
||||
sqlite3_mutex_leave(sqlite3_db_mutex(db));
|
||||
sqlite3ValueFree(pSession->pZeroBlob);
|
||||
|
||||
/* Delete all attached table objects. And the contents of their
|
||||
** associated hash-tables. */
|
||||
@@ -2101,28 +2182,41 @@ static int sessionSelectStmt(
|
||||
sqlite3_stmt **ppStmt /* OUT: Prepared SELECT statement */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
int i;
|
||||
const char *zSep = "";
|
||||
SessionBuffer buf = {0, 0, 0};
|
||||
char *zSql = 0;
|
||||
int nSql = -1;
|
||||
|
||||
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, " = ?", &rc);
|
||||
sessionAppendInteger(&buf, i+1, &rc);
|
||||
zSep = " AND ";
|
||||
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
|
||||
);
|
||||
}else{
|
||||
int i;
|
||||
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;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
|
||||
rc = sqlite3_prepare_v2(db, zSql, nSql, ppStmt, 0);
|
||||
}
|
||||
sqlite3_free(buf.aBuf);
|
||||
sqlite3_free(zSql);
|
||||
return rc;
|
||||
}
|
||||
|
||||
@@ -2647,13 +2741,16 @@ static int sessionReadRecord(
|
||||
if( abPK && abPK[i]==0 ) continue;
|
||||
rc = sessionInputBuffer(pIn, 9);
|
||||
if( rc==SQLITE_OK ){
|
||||
eType = pIn->aData[pIn->iNext++];
|
||||
}
|
||||
|
||||
assert( apOut[i]==0 );
|
||||
if( eType ){
|
||||
apOut[i] = sqlite3ValueNew(0);
|
||||
if( !apOut[i] ) rc = SQLITE_NOMEM;
|
||||
if( pIn->iNext>=pIn->nData ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
}else{
|
||||
eType = pIn->aData[pIn->iNext++];
|
||||
assert( apOut[i]==0 );
|
||||
if( eType ){
|
||||
apOut[i] = sqlite3ValueNew(0);
|
||||
if( !apOut[i] ) rc = SQLITE_NOMEM;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
@@ -2663,10 +2760,14 @@ static int sessionReadRecord(
|
||||
pIn->iNext += sessionVarintGet(aVal, &nByte);
|
||||
rc = sessionInputBuffer(pIn, nByte);
|
||||
if( rc==SQLITE_OK ){
|
||||
u8 enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0);
|
||||
rc = sessionValueSetStr(apOut[i],&pIn->aData[pIn->iNext],nByte,enc);
|
||||
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;
|
||||
}
|
||||
}
|
||||
pIn->iNext += nByte;
|
||||
}
|
||||
if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){
|
||||
sqlite3_int64 v = sessionGetI64(aVal);
|
||||
@@ -2706,8 +2807,12 @@ static int sessionChangesetBufferTblhdr(SessionInput *pIn, int *pnByte){
|
||||
rc = sessionInputBuffer(pIn, 9);
|
||||
if( rc==SQLITE_OK ){
|
||||
nRead += sessionVarintGet(&pIn->aData[pIn->iNext + nRead], &nCol);
|
||||
rc = sessionInputBuffer(pIn, nRead+nCol+100);
|
||||
nRead += nCol;
|
||||
if( nCol<0 ){
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
}else{
|
||||
rc = sessionInputBuffer(pIn, nRead+nCol+100);
|
||||
nRead += nCol;
|
||||
}
|
||||
}
|
||||
|
||||
while( rc==SQLITE_OK ){
|
||||
@@ -2784,11 +2889,15 @@ static int sessionChangesetReadTblhdr(sqlite3_changeset_iter *p){
|
||||
int nByte;
|
||||
int nVarint;
|
||||
nVarint = sessionVarintGet(&p->in.aData[p->in.iNext], &p->nCol);
|
||||
nCopy -= nVarint;
|
||||
p->in.iNext += nVarint;
|
||||
nByte = p->nCol * sizeof(sqlite3_value*) * 2 + nCopy;
|
||||
p->tblhdr.nBuf = 0;
|
||||
sessionBufferGrow(&p->tblhdr, nByte, &rc);
|
||||
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 ){
|
||||
@@ -2866,6 +2975,13 @@ static int sessionChangesetNext(
|
||||
op = p->in.aData[p->in.iNext++];
|
||||
}
|
||||
|
||||
if( p->zTab==0 ){
|
||||
/* The first record in the changeset is not a table header. Must be a
|
||||
** corrupt changeset. */
|
||||
assert( p->in.iNext==1 );
|
||||
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 ){
|
||||
@@ -2908,9 +3024,9 @@ static int sessionChangesetNext(
|
||||
** new.* to old.*, to accommodate the code that reads these arrays. */
|
||||
for(i=0; i<p->nCol; i++){
|
||||
assert( p->apValue[i]==0 );
|
||||
assert( p->abPK[i]==0 || p->apValue[i+p->nCol] );
|
||||
if( p->abPK[i] ){
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -3305,7 +3421,7 @@ struct SessionApplyCtx {
|
||||
int nCol; /* Size of azCol[] and abPK[] arrays */
|
||||
const char **azCol; /* Array of column names */
|
||||
u8 *abPK; /* Boolean array - true if column is in PK */
|
||||
|
||||
int bStat1; /* True if table is sqlite_stat1 */
|
||||
int bDeferConstraints; /* True to defer constraints */
|
||||
SessionBuffer constraints; /* Deferred constraints are stored here */
|
||||
};
|
||||
@@ -3475,6 +3591,7 @@ static int sessionUpdateRow(
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Formulate and prepare an SQL statement to query table zTab by primary
|
||||
** key. Assuming the following table structure:
|
||||
@@ -3536,6 +3653,47 @@ static int sessionInsertRow(
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sessionPrepare(sqlite3 *db, sqlite3_stmt **pp, const char *zSql){
|
||||
return sqlite3_prepare_v2(db, zSql, -1, pp, 0);
|
||||
}
|
||||
|
||||
/*
|
||||
** Prepare statements for applying changes to the sqlite_stat1 table.
|
||||
** These are similar to those created by sessionSelectRow(),
|
||||
** sessionInsertRow(), sessionUpdateRow() and sessionDeleteRow() for
|
||||
** other tables.
|
||||
*/
|
||||
static int sessionStat1Sql(sqlite3 *db, SessionApplyCtx *p){
|
||||
int rc = sessionSelectRow(db, "sqlite_stat1", p);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sessionPrepare(db, &p->pInsert,
|
||||
"INSERT INTO main.sqlite_stat1 VALUES(?1, "
|
||||
"CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END, "
|
||||
"?3)"
|
||||
);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sessionPrepare(db, &p->pUpdate,
|
||||
"UPDATE main.sqlite_stat1 SET "
|
||||
"tbl = CASE WHEN ?2 THEN ?3 ELSE tbl END, "
|
||||
"idx = CASE WHEN ?5 THEN ?6 ELSE idx END, "
|
||||
"stat = CASE WHEN ?8 THEN ?9 ELSE stat END "
|
||||
"WHERE tbl=?1 AND idx IS "
|
||||
"CASE WHEN length(?4)=0 AND typeof(?4)='blob' THEN NULL ELSE ?4 END "
|
||||
"AND (?10 OR ?8=0 OR stat IS ?7)"
|
||||
);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sessionPrepare(db, &p->pDelete,
|
||||
"DELETE FROM main.sqlite_stat1 WHERE tbl=?1 AND idx IS "
|
||||
"CASE WHEN length(?2)=0 AND typeof(?2)='blob' THEN NULL ELSE ?2 END "
|
||||
"AND (?4 OR stat IS ?3)"
|
||||
);
|
||||
}
|
||||
assert( rc==SQLITE_OK );
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
** A wrapper around sqlite3_bind_value() that detects an extra problem.
|
||||
** See comments in the body of this function for details.
|
||||
@@ -3593,7 +3751,13 @@ static int sessionBindRow(
|
||||
if( !abPK || abPK[i] ){
|
||||
sqlite3_value *pVal;
|
||||
(void)xValue(pIter, i, &pVal);
|
||||
rc = sessionBindValue(pStmt, i+1, pVal);
|
||||
if( pVal==0 ){
|
||||
/* The value in the changeset was "undefined". This indicates a
|
||||
** corrupt changeset blob. */
|
||||
rc = SQLITE_CORRUPT_BKPT;
|
||||
}else{
|
||||
rc = sessionBindValue(pStmt, i+1, pVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
@@ -3866,11 +4030,25 @@ static int sessionApplyOneOp(
|
||||
|
||||
}else{
|
||||
assert( op==SQLITE_INSERT );
|
||||
rc = sessionBindRow(pIter, sqlite3changeset_new, nCol, 0, p->pInsert);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
if( p->bStat1 ){
|
||||
/* Check if there is a conflicting row. For sqlite_stat1, this needs
|
||||
** to be done using a SELECT, as there is no PRIMARY KEY in the
|
||||
** database schema to throw an exception if a duplicate is inserted. */
|
||||
rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect);
|
||||
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);
|
||||
}
|
||||
|
||||
sqlite3_step(p->pInsert);
|
||||
rc = sqlite3_reset(p->pInsert);
|
||||
if( (rc&0xff)==SQLITE_CONSTRAINT ){
|
||||
rc = sessionConflictHandler(
|
||||
SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace
|
||||
@@ -4103,12 +4281,20 @@ static int sessionChangesetApply(
|
||||
}
|
||||
else{
|
||||
sApply.nCol = nCol;
|
||||
if((rc = sessionSelectRow(db, zTab, &sApply))
|
||||
|| (rc = sessionUpdateRow(db, zTab, &sApply))
|
||||
|| (rc = sessionDeleteRow(db, zTab, &sApply))
|
||||
|| (rc = sessionInsertRow(db, zTab, &sApply))
|
||||
){
|
||||
break;
|
||||
if( 0==sqlite3_stricmp(zTab, "sqlite_stat1") ){
|
||||
if( (rc = sessionStat1Sql(db, &sApply) ) ){
|
||||
break;
|
||||
}
|
||||
sApply.bStat1 = 1;
|
||||
}else{
|
||||
if((rc = sessionSelectRow(db, zTab, &sApply))
|
||||
|| (rc = sessionUpdateRow(db, zTab, &sApply))
|
||||
|| (rc = sessionDeleteRow(db, zTab, &sApply))
|
||||
|| (rc = sessionInsertRow(db, zTab, &sApply))
|
||||
){
|
||||
break;
|
||||
}
|
||||
sApply.bStat1 = 0;
|
||||
}
|
||||
}
|
||||
nTab = sqlite3Strlen30(zTab);
|
||||
|
Reference in New Issue
Block a user