1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-07-29 08:01:23 +03:00

Add the sqlite3changeset_apply() function. Does not yet handle all cases.

FossilOrigin-Name: 2b19be7bf753c7dd12e1c3b384981a3ea1bc8145
This commit is contained in:
dan
2011-03-11 19:05:52 +00:00
parent 91ddd5595b
commit d5f0767c9c
6 changed files with 924 additions and 16 deletions

View File

@ -573,6 +573,47 @@ static void sessionAppendBlob(
}
}
static void sessionAppendStr(
SessionBuffer *p,
const char *zStr,
int *pRc
){
int nStr = strlen(zStr);
if( *pRc==SQLITE_OK && 0==sessionBufferGrow(p, nStr, pRc) ){
memcpy(&p->aBuf[p->nBuf], zStr, nStr);
p->nBuf += nStr;
}
}
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 = strlen(zStr)*2 + 2 + 1;
if( *pRc==SQLITE_OK && 0==sessionBufferGrow(p, nStr, pRc) ){
char *zOut = (char *)&p->aBuf[p->nBuf];
const char *zIn = zStr;
*zOut++ = '"';
while( *zIn ){
if( *zIn=='"' ) *zOut++ = '"';
*zOut++ = *(zIn++);
}
*zOut++ = '"';
p->nBuf = ((u8 *)zOut - p->aBuf);
}
}
static void sessionAppendCol(
SessionBuffer *p,
sqlite3_stmt *pStmt,
@ -616,6 +657,7 @@ static void sessionAppendUpdate(
sqlite3_stmt *pStmt,
SessionBuffer *pBuf,
SessionChange *p,
u8 *abPK,
int *pRc
){
if( *pRc==SQLITE_OK ){
@ -667,6 +709,9 @@ static void sessionAppendUpdate(
nCopy = nAdvance;
}
}
if( abPK[i] ){
nCopy = nAdvance;
}
if( nCopy==0 ){
sessionAppendByte(pBuf, 0, pRc);
@ -686,8 +731,98 @@ static void sessionAppendUpdate(
sqlite3_free(buf2.aBuf);
}
}
}
static int sessionTableInfo(
sqlite3 *db, /* Database connection */
const char *zThis, /* Table name */
int nCol, /* Expected number of columns */
const char **pzTab, /* OUT: Copy of zThis */
const char ***pazCol, /* OUT: Array of column names for table */
u8 **pabPK /* OUT: Array of booleans - true for PK col */
){
char *zPragma;
sqlite3_stmt *pStmt;
int rc;
int nByte;
int nDbCol = 0;
int nThis;
int i;
u8 *pAlloc;
u8 *pFree = 0;
char **azCol;
u8 *abPK;
nThis = strlen(zThis);
zPragma = sqlite3_mprintf("PRAGMA main.table_info('%q')", 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;
while( SQLITE_ROW==sqlite3_step(pStmt) ){
nByte += sqlite3_column_bytes(pStmt, 1);
nDbCol++;
}
rc = sqlite3_reset(pStmt);
if( nDbCol!=nCol ){
rc = SQLITE_SCHEMA;
}
if( rc==SQLITE_OK ){
nByte += nDbCol * (sizeof(const char *) + sizeof(u8) + 1);
pAlloc = sqlite3_malloc(nByte);
if( pAlloc==0 ){
rc = SQLITE_NOMEM;
}
}
if( rc==SQLITE_OK ){
pFree = pAlloc;
if( pazCol ){
azCol = (char **)pAlloc;
pAlloc = (u8 *)&azCol[nCol];
}
if( pabPK ){
abPK = (u8 *)pAlloc;
pAlloc = &abPK[nCol];
}
if( pzTab ){
memcpy(pAlloc, zThis, nThis+1);
*pzTab = (char *)pAlloc;
pAlloc += nThis+1;
}
i = 0;
while( SQLITE_ROW==sqlite3_step(pStmt) ){
int nName = sqlite3_column_bytes(pStmt, 1);
const unsigned char *zName = sqlite3_column_text(pStmt, 1);
if( zName==0 ) break;
if( pazCol ){
memcpy(pAlloc, zName, nName+1);
azCol[i] = (char *)pAlloc;
pAlloc += nName+1;
}
if( pabPK ) abPK[i] = sqlite3_column_int(pStmt, 5);
i++;
}
rc = sqlite3_reset(pStmt);
}
if( rc==SQLITE_OK ){
if( pazCol ) *pazCol = (const char **)azCol;
if( pabPK ) *pabPK = abPK;
}else{
if( pazCol ) *pazCol = 0;
if( pabPK ) *pabPK = 0;
if( pzTab ) *pzTab = 0;
sqlite3_free(pFree);
}
sqlite3_finalize(pStmt);
return rc;
}
/*
@ -717,6 +852,7 @@ int sqlite3session_changeset(
sqlite3_stmt *pStmt = 0;
int bNoop = 1;
int nRewind = buf.nBuf;
u8 *abPK = 0;
/* Write a table header */
sessionAppendByte(&buf, 'T', &rc);
@ -740,6 +876,10 @@ int sqlite3session_changeset(
rc = SQLITE_SCHEMA;
}
if( rc==SQLITE_OK ){
rc = sessionTableInfo(db, pTab->zName, pTab->nCol, 0, 0, &abPK);
}
for(i=0; i<pTab->nChange; i++){
SessionChange *p;
for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){
@ -747,7 +887,7 @@ int sqlite3session_changeset(
if( sqlite3_step(pStmt)==SQLITE_ROW ){
int iCol;
if( p->aRecord ){
sessionAppendUpdate(pStmt, &buf, p, &rc);
sessionAppendUpdate(pStmt, &buf, p, abPK, &rc);
}else{
sessionAppendByte(&buf, SQLITE_INSERT, &rc);
for(iCol=0; iCol<pTab->nCol; iCol++){
@ -766,6 +906,7 @@ int sqlite3session_changeset(
}
sqlite3_finalize(pStmt);
sqlite3_free(abPK);
if( bNoop ){
buf.nBuf = nRewind;
@ -797,6 +938,7 @@ struct sqlite3_changeset_iter {
u8 *pNext; /* Pointer to next change within aChangeset */
int rc;
sqlite3_stmt *pConflict; /* Conflicting row, if any */
char *zTab; /* Current table */
int nCol; /* Number of columns in zTab */
int op; /* Current operation */
@ -947,9 +1089,9 @@ int sqlite3changeset_next(sqlite3_changeset_iter *p){
*/
int sqlite3changeset_op(
sqlite3_changeset_iter *pIter,
const char **pzTab, /* OUT: Pointer to table name */
int *pnCol, /* OUT: Number of columns in table */
int *pOp /* OUT: SQLITE_INSERT, DELETE or UPDATE */
const char **pzTab, /* OUT: Pointer to table name */
int *pnCol, /* OUT: Number of columns in table */
int *pOp /* OUT: SQLITE_INSERT, DELETE or UPDATE */
){
*pOp = pIter->op;
*pnCol = pIter->nCol;
@ -960,8 +1102,11 @@ int sqlite3changeset_op(
int sqlite3changeset_old(
sqlite3_changeset_iter *pIter,
int iVal,
sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
sqlite3_value **ppValue /* OUT: Old value (or NULL pointer) */
){
if( pIter->op!=SQLITE_UPDATE && pIter->op!=SQLITE_DELETE ){
return SQLITE_MISUSE;
}
if( iVal<0 || iVal>=pIter->nCol ){
return SQLITE_RANGE;
}
@ -972,8 +1117,11 @@ int sqlite3changeset_old(
int sqlite3changeset_new(
sqlite3_changeset_iter *pIter,
int iVal,
sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
sqlite3_value **ppValue /* OUT: New value (or NULL pointer) */
){
if( pIter->op!=SQLITE_UPDATE && pIter->op!=SQLITE_INSERT ){
return SQLITE_MISUSE;
}
if( iVal<0 || iVal>=pIter->nCol ){
return SQLITE_RANGE;
}
@ -981,6 +1129,21 @@ int sqlite3changeset_new(
return SQLITE_OK;
}
int sqlite3changeset_conflict(
sqlite3_changeset_iter *pIter,
int iVal,
sqlite3_value **ppValue /* OUT: Value from conflicting row */
){
if( !pIter->pConflict ){
return SQLITE_MISUSE;
}
if( iVal<0 || iVal>=sqlite3_column_count(pIter->pConflict) ){
return SQLITE_RANGE;
}
*ppValue = sqlite3_column_value(pIter->pConflict, iVal);
return SQLITE_OK;
}
/*
** Finalize an iterator allocated with sqlite3changeset_start().
**
@ -1073,5 +1236,436 @@ int sqlite3changeset_invert(
return SQLITE_OK;
}
static void sessionUpdateDeleteWhere(
SessionBuffer *pBuf, /* Buffer to append to */
int nCol, /* Number of entries in azCol and abPK */
const char **azCol, /* Column names */
u8 *abPK, /* True for PK columns */
int *pRc /* IN/OUT: Error code */
){
if( *pRc==SQLITE_OK ){
int i;
const char *zSep = "";
sessionAppendStr(pBuf, " WHERE ", pRc);
for(i=0; i<nCol; i++){
if( abPK[i] ){
sessionAppendStr(pBuf, zSep, pRc);
sessionAppendIdent(pBuf, azCol[i], pRc);
sessionAppendStr(pBuf, " = ?", pRc);
sessionAppendInteger(pBuf, i+1, pRc);
zSep = "AND ";
}
}
sessionAppendStr(pBuf, " AND (?", pRc);
sessionAppendInteger(pBuf, nCol+1, pRc);
sessionAppendStr(pBuf, " OR ", pRc);
zSep = "";
for(i=0; i<nCol; i++){
if( !abPK[i] ){
sessionAppendStr(pBuf, zSep, pRc);
sessionAppendIdent(pBuf, azCol[i], pRc);
sessionAppendStr(pBuf, " IS ?", pRc);
sessionAppendInteger(pBuf, i+1, pRc);
zSep = "AND ";
}
}
sessionAppendStr(pBuf, ")", pRc);
}
}
/*
** Formulate a statement to DELETE a row from database db. Assuming a table
** structure like this:
**
** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
**
** The DELETE statement looks like this:
**
** DELETE FROM x WHERE a = :1 AND c = :3 AND :5 OR (b IS :2 AND d IS :4)
**
** Variable :5 (nCol+1) is a boolean. It should be set to 0 if we require
** matching b and d values, or 1 otherwise. The second case comes up if the
** conflict handler is invoked with NOTFOUND and returns CHANGESET_REPLACE.
*/
static int sessionDeleteRow(
sqlite3 *db, /* Database handle */
const char *zTab, /* Table name */
int nCol, /* Number of entries in azCol and abPK */
const char **azCol, /* Column names */
u8 *abPK, /* True for PK columns */
sqlite3_stmt **ppStmt /* OUT: Compiled SELECT statement. */
){
int rc = SQLITE_OK;
if( *ppStmt==0 ){
SessionBuffer buf = {0, 0, 0};
sessionAppendStr(&buf, "DELETE FROM ", &rc);
sessionAppendIdent(&buf, zTab, &rc);
sessionUpdateDeleteWhere(&buf, nCol, azCol, abPK, &rc);
if( rc==SQLITE_OK ){
rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
}
sqlite3_free(buf.aBuf);
}
return rc;
}
/*
** Formulate and prepare a statement to UPDATE a row from database db.
** Assuming a table structure like this:
**
** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
**
** The UPDATE statement looks like this:
**
** UPDATE x SET
** a = CASE WHEN ?2 THEN ?3 ELSE a END,
** b = CASE WHEN ?5 THEN ?6 ELSE a END,
** c = CASE WHEN ?8 THEN ?9 ELSE a END,
** d = CASE WHEN ?11 THEN ?12 ELSE a END
** WHERE a = ?1 AND c = ?7 AND (?13 OR
** (?5==0 OR b IS ?4) AND (?11==0 OR b IS ?10) AND
** )
**
** For each column in the table, there are three variables to bind:
**
** ?(i*3+1) The old.* value of the column, if any.
** ?(i*3+2) A boolean flag indicating that the value is being modified.
** ?(i*3+3) The new.* value of the column, if any.
**
** Also, a boolean flag that, if set to true, causes the statement to update
** a row even if the non-PK values do not match. This is required if the
** conflict-handler is invoked with CHANGESET_DATA and returns
** CHANGESET_REPLACE. This is variable "?(nCol*3+1)".
**
*/
static int sessionUpdateRow(
sqlite3 *db, /* Database handle */
const char *zTab, /* Table name */
int nCol, /* Number of entries in azCol and abPK */
const char **azCol, /* Column names */
u8 *abPK, /* True for PK columns */
sqlite3_stmt **ppStmt /* OUT: Compiled SELECT statement. */
){
int rc = SQLITE_OK;
if( *ppStmt==0 ){
int i;
const char *zSep = "";
SessionBuffer buf = {0, 0, 0};
/* Append "UPDATE tbl SET " */
sessionAppendStr(&buf, "UPDATE ", &rc);
sessionAppendIdent(&buf, zTab, &rc);
sessionAppendStr(&buf, " SET ", &rc);
/* Append the assignments */
for(i=0; i<nCol; i++){
sessionAppendStr(&buf, zSep, &rc);
sessionAppendIdent(&buf, azCol[i], &rc);
sessionAppendStr(&buf, " = CASE WHEN ?", &rc);
sessionAppendInteger(&buf, i*3+2, &rc);
sessionAppendStr(&buf, " THEN ?", &rc);
sessionAppendInteger(&buf, i*3+3, &rc);
sessionAppendStr(&buf, " ELSE ", &rc);
sessionAppendIdent(&buf, azCol[i], &rc);
sessionAppendStr(&buf, " END", &rc);
zSep = ", ";
}
/* Append the PK part of the WHERE clause */
sessionAppendStr(&buf, " WHERE ", &rc);
for(i=0; i<nCol; i++){
if( abPK[i] ){
sessionAppendIdent(&buf, azCol[i], &rc);
sessionAppendStr(&buf, " = ?", &rc);
sessionAppendInteger(&buf, i*3+1, &rc);
sessionAppendStr(&buf, " AND ", &rc);
}
}
/* Append the non-PK part of the WHERE clause */
sessionAppendStr(&buf, " (?", &rc);
sessionAppendInteger(&buf, nCol*3+1, &rc);
sessionAppendStr(&buf, " OR 1", &rc);
for(i=0; i<nCol; i++){
if( !abPK[i] ){
sessionAppendStr(&buf, " AND (?", &rc);
sessionAppendInteger(&buf, i*3+2, &rc);
sessionAppendStr(&buf, "=0 OR ", &rc);
sessionAppendIdent(&buf, azCol[i], &rc);
sessionAppendStr(&buf, " IS ?", &rc);
sessionAppendInteger(&buf, i*3+1, &rc);
sessionAppendStr(&buf, ")", &rc);
}
}
sessionAppendStr(&buf, ")", &rc);
if( rc==SQLITE_OK ){
rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
}
sqlite3_free(buf.aBuf);
}
return rc;
}
static int sessionSelectRow(
sqlite3 *db, /* Database handle */
const char *zTab, /* Table name */
int nCol, /* Number of entries in azCol and abPK */
const char **azCol, /* Column names */
u8 *abPK, /* True for PK columns */
sqlite3_stmt **ppStmt /* OUT: Compiled SELECT statement. */
){
int rc = SQLITE_OK;
if( *ppStmt==0 ){
int i;
const char *zSep = "";
SessionBuffer buf = {0, 0, 0};
sessionAppendStr(&buf, "SELECT * FROM ", &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( rc==SQLITE_OK ){
rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
}
sqlite3_free(buf.aBuf);
}
return rc;
}
int sqlite3changeset_apply(
sqlite3 *db,
int nChangeset,
void *pChangeset,
int(*xConflict)(
void *pCtx, /* Copy of fifth arg to _apply() */
int eConflict, /* DATA, MISSING, CONFLICT, CONSTRAINT */
sqlite3_changeset_iter *p /* Handle describing change and conflict */
),
void *pCtx
){
sqlite3_changeset_iter *pIter;
int rc;
int rc2;
const char *zTab = 0; /* Name of current table */
int nTab = 0; /* Result of strlen(zTab) */
int nCol = 0; /* Number of columns in table zTab */
const char **azCol = 0; /* Array of column names */
u8 *abPK = 0; /* Boolean array - true if column is in PK */
sqlite3_stmt *pDelete = 0; /* DELETE statement */
sqlite3_stmt *pUpdate = 0; /* DELETE statement */
sqlite3_stmt *pInsert = 0; /* INSERT statement */
sqlite3_stmt *pSelect = 0; /* SELECT statement */
rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
sqlite3changeset_start(&pIter, nChangeset, pChangeset);
while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
int op;
const char *zThis;
sqlite3changeset_op(pIter, &zThis, &nCol, &op);
if( zTab==0 || sqlite3_strnicmp(zThis, zTab, nTab+1) ){
sqlite3_free(azCol);
rc = sessionTableInfo(db, zThis, nCol, &zTab, &azCol, &abPK);
nTab = strlen(zTab);
sqlite3_finalize(pDelete);
sqlite3_finalize(pUpdate);
sqlite3_finalize(pInsert);
sqlite3_finalize(pSelect);
pSelect = pUpdate = pInsert = pDelete = 0;
}
if( op==SQLITE_DELETE ){
int res;
int i;
rc = sessionDeleteRow(db, zTab, nCol, azCol, abPK, &pDelete);
for(i=0; rc==SQLITE_OK && i<nCol; i++){
sqlite3_value *pVal;
rc = sqlite3changeset_old(pIter, i, &pVal);
if( rc==SQLITE_OK ){
rc = sqlite3_bind_value(pDelete, i+1, pVal);
}
}
if( rc==SQLITE_OK ) rc = sqlite3_bind_int(pDelete, nCol+1, 0);
if( rc!=SQLITE_OK ) break;
sqlite3_step(pDelete);
rc = sqlite3_reset(pDelete);
if( rc==SQLITE_OK && sqlite3_changes(db)==0 ){
/* A NOTFOUND or DATA error. Search the table to see if it contains
** a row with a matching primary key. If so, this is a DATA conflict.
** Otherwise, if there is no primary key match, it is a NOTFOUND. */
rc = sessionSelectRow(db, zTab, nCol, azCol, abPK, &pSelect);
for(i=0; rc==SQLITE_OK && i<nCol; i++){
if( abPK[i] ){
sqlite3_value *pVal;
rc = sqlite3changeset_old(pIter, i, &pVal);
if( rc==SQLITE_OK ) sqlite3_bind_value(pSelect, i+1, pVal);
}
}
if( rc!=SQLITE_OK ) break;
if( SQLITE_ROW==sqlite3_step(pSelect) ){
pIter->pConflict = pSelect;
res = xConflict(pCtx, SQLITE_CHANGESET_DATA, pIter);
pIter->pConflict = 0;
sqlite3_reset(pSelect);
}else{
rc = sqlite3_reset(pSelect);
if( rc==SQLITE_OK ){
res = xConflict(pCtx, SQLITE_CHANGESET_NOTFOUND, pIter);
}
}
}else if( rc==SQLITE_CONSTRAINT ){
res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter);
rc = SQLITE_OK;
}
if( rc!=SQLITE_OK ) break;
}else if( op==SQLITE_UPDATE ){
int i;
int res;
rc = sessionUpdateRow(db, zTab, nCol, azCol, abPK, &pUpdate);
for(i=0; rc==SQLITE_OK && i<nCol; i++){
sqlite3_value *pOld = 0;
sqlite3_value *pNew = 0;
rc = sqlite3changeset_old(pIter, i, &pOld);
if( rc==SQLITE_OK ){
rc = sqlite3changeset_new(pIter, i, &pNew);
}
if( rc==SQLITE_OK ){
if( pOld ) sqlite3_bind_value(pUpdate, i*3+1, pOld);
sqlite3_bind_int(pUpdate, i*3+2, !!pNew);
if( pNew ) sqlite3_bind_value(pUpdate, i*3+3, pOld);
}
}
if( rc==SQLITE_OK ) rc = sqlite3_bind_int(pUpdate, nCol*3+1, 0);
if( rc!=SQLITE_OK ) break;
sqlite3_step(pUpdate);
rc = sqlite3_reset(pUpdate);
if( rc==SQLITE_OK && sqlite3_changes(db)==0 ){
/* A NOTFOUND or DATA error. Search the table to see if it contains
** a row with a matching primary key. If so, this is a DATA conflict.
** Otherwise, if there is no primary key match, it is a NOTFOUND. */
rc = sessionSelectRow(db, zTab, nCol, azCol, abPK, &pSelect);
for(i=0; rc==SQLITE_OK && i<nCol; i++){
if( abPK[i] ){
sqlite3_value *pVal;
rc = sqlite3changeset_old(pIter, i, &pVal);
if( rc==SQLITE_OK ) sqlite3_bind_value(pSelect, i+1, pVal);
}
}
if( rc!=SQLITE_OK ) break;
if( SQLITE_ROW==sqlite3_step(pSelect) ){
pIter->pConflict = pSelect;
res = xConflict(pCtx, SQLITE_CHANGESET_DATA, pIter);
pIter->pConflict = 0;
sqlite3_reset(pSelect);
}else{
rc = sqlite3_reset(pSelect);
if( rc==SQLITE_OK ){
res = xConflict(pCtx, SQLITE_CHANGESET_NOTFOUND, pIter);
}
}
}else if( rc==SQLITE_CONSTRAINT ){
assert(0);
}
}else{
int i;
assert( op==SQLITE_INSERT );
if( pInsert==0 ){
SessionBuffer buf = {0, 0, 0};
sessionAppendStr(&buf, "INSERT INTO main.", &rc);
sessionAppendIdent(&buf, zTab, &rc);
sessionAppendStr(&buf, " VALUES(?", &rc);
for(i=1; i<nCol; i++) sessionAppendStr(&buf, ", ?", &rc);
sessionAppendStr(&buf, ")", &rc);
if( rc==SQLITE_OK ){
rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &pInsert, 0);
}
sqlite3_free(buf.aBuf);
}
for(i=0; rc==SQLITE_OK && i<nCol; i++){
sqlite3_value *pVal;
rc = sqlite3changeset_new(pIter, i, &pVal);
if( rc==SQLITE_OK ){
rc = sqlite3_bind_value(pInsert, i+1, pVal);
}
}
if( rc!=SQLITE_OK ) break;
sqlite3_step(pInsert);
rc = sqlite3_reset(pInsert);
if( rc==SQLITE_CONSTRAINT && xConflict ){
int res;
/* Figure out if this is a primary key or other constraint. */
rc = sessionSelectRow(db, zTab, nCol, azCol, abPK, &pSelect);
for(i=0; rc==SQLITE_OK && i<nCol; i++){
if( abPK[i] ){
sqlite3_value *pVal;
rc = sqlite3changeset_new(pIter, i, &pVal);
if( rc==SQLITE_OK ) sqlite3_bind_value(pSelect, i+1, pVal);
}
}
if( rc!=SQLITE_OK ) break;
if( SQLITE_ROW==sqlite3_step(pSelect) ){
pIter->pConflict = pSelect;
res = xConflict(pCtx, SQLITE_CHANGESET_CONFLICT, pIter);
pIter->pConflict = 0;
sqlite3_reset(pSelect);
}else{
rc = sqlite3_reset(pSelect);
if( rc==SQLITE_OK ){
res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter);
}
}
}
}
}
rc2 = sqlite3changeset_finalize(pIter);
if( rc==SQLITE_DONE ) rc = rc2;
if( rc==SQLITE_OK ){
rc = sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
}else{
sqlite3_exec(db, "ROLLBACK TO changeset_apply", 0, 0, 0);
sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
}
sqlite3_finalize(pInsert);
sqlite3_finalize(pDelete);
sqlite3_finalize(pUpdate);
sqlite3_finalize(pSelect);
sqlite3_free(azCol);
return rc;
}
#endif /* #ifdef SQLITE_ENABLE_SESSION */