mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
Fix handling of return values from the conflict handler. Document the conflict handler arguments and return codes in sqlite3session.h.
FossilOrigin-Name: cbbb274e500237dbf7155a51d4f9c17583d704ea
This commit is contained in:
@ -1279,6 +1279,19 @@ static void sessionUpdateDeleteWhere(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef struct SessionApplyCtx SessionApplyCtx;
|
||||
struct SessionApplyCtx {
|
||||
sqlite3 *db;
|
||||
sqlite3_stmt *pDelete; /* DELETE statement */
|
||||
sqlite3_stmt *pUpdate; /* DELETE statement */
|
||||
sqlite3_stmt *pInsert; /* INSERT statement */
|
||||
sqlite3_stmt *pSelect; /* SELECT statement */
|
||||
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 */
|
||||
};
|
||||
|
||||
/*
|
||||
** Formulate a statement to DELETE a row from database db. Assuming a table
|
||||
** structure like this:
|
||||
@ -1296,25 +1309,19 @@ static void sessionUpdateDeleteWhere(
|
||||
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. */
|
||||
SessionApplyCtx *p /* Session changeset-apply context */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
if( *ppStmt==0 ){
|
||||
SessionBuffer buf = {0, 0, 0};
|
||||
SessionBuffer buf = {0, 0, 0};
|
||||
|
||||
sessionAppendStr(&buf, "DELETE FROM ", &rc);
|
||||
sessionAppendIdent(&buf, zTab, &rc);
|
||||
sessionAppendStr(&buf, "DELETE FROM ", &rc);
|
||||
sessionAppendIdent(&buf, zTab, &rc);
|
||||
sessionUpdateDeleteWhere(&buf, p->nCol, p->azCol, p->abPK, &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);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0);
|
||||
}
|
||||
sqlite3_free(buf.aBuf);
|
||||
|
||||
return rc;
|
||||
}
|
||||
@ -1351,149 +1358,344 @@ static int sessionDeleteRow(
|
||||
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. */
|
||||
SessionApplyCtx *p /* Session changeset-apply context */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
if( *ppStmt==0 ){
|
||||
int i;
|
||||
const char *zSep = "";
|
||||
SessionBuffer buf = {0, 0, 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 "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);
|
||||
/* Append the assignments */
|
||||
for(i=0; i<p->nCol; i++){
|
||||
sessionAppendStr(&buf, zSep, &rc);
|
||||
sessionAppendIdent(&buf, p->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, p->azCol[i], &rc);
|
||||
sessionAppendStr(&buf, " END", &rc);
|
||||
zSep = ", ";
|
||||
}
|
||||
|
||||
/* Append the PK part of the WHERE clause */
|
||||
sessionAppendStr(&buf, " WHERE ", &rc);
|
||||
for(i=0; i<p->nCol; i++){
|
||||
if( p->abPK[i] ){
|
||||
sessionAppendIdent(&buf, p->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, p->nCol*3+1, &rc);
|
||||
sessionAppendStr(&buf, " OR 1", &rc);
|
||||
for(i=0; i<p->nCol; i++){
|
||||
if( !p->abPK[i] ){
|
||||
sessionAppendStr(&buf, " AND (?", &rc);
|
||||
sessionAppendInteger(&buf, i*3+2, &rc);
|
||||
sessionAppendStr(&buf, "=0 OR ", &rc);
|
||||
sessionAppendIdent(&buf, p->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, &p->pUpdate, 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. */
|
||||
SessionApplyCtx *p /* Session changeset-apply context */
|
||||
){
|
||||
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 ";
|
||||
}
|
||||
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<p->nCol; i++){
|
||||
if( p->abPK[i] ){
|
||||
sessionAppendStr(&buf, zSep, &rc);
|
||||
sessionAppendIdent(&buf, p->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);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pSelect, 0);
|
||||
}
|
||||
sqlite3_free(buf.aBuf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sessionConstraintConflict(
|
||||
static int sessionInsertRow(
|
||||
sqlite3 *db, /* Database handle */
|
||||
const char *zTab, /* Table name */
|
||||
SessionApplyCtx *p /* Session changeset-apply context */
|
||||
){
|
||||
int rc = SQLITE_OK;
|
||||
int i;
|
||||
SessionBuffer buf = {0, 0, 0};
|
||||
|
||||
sessionAppendStr(&buf, "INSERT INTO main.", &rc);
|
||||
sessionAppendIdent(&buf, zTab, &rc);
|
||||
sessionAppendStr(&buf, " VALUES(?", &rc);
|
||||
for(i=1; i<p->nCol; i++){
|
||||
sessionAppendStr(&buf, ", ?", &rc);
|
||||
}
|
||||
sessionAppendStr(&buf, ")", &rc);
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0);
|
||||
}
|
||||
sqlite3_free(buf.aBuf);
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sessionSeekToRow(
|
||||
sqlite3 *db, /* Database handle */
|
||||
sqlite3_changeset_iter *pIter, /* Changeset iterator */
|
||||
u8 *abPK, /* Primary key flags array */
|
||||
sqlite3_stmt *pSelect, /* SELECT statement from sessionSelectRow() */
|
||||
int(*xConflict)(void *, int, sqlite3_changeset_iter*),
|
||||
void *pCtx
|
||||
sqlite3_stmt *pSelect /* SELECT statement from sessionSelectRow() */
|
||||
){
|
||||
int res;
|
||||
int rc;
|
||||
int rc = SQLITE_OK;
|
||||
|
||||
int i;
|
||||
int nCol;
|
||||
int op;
|
||||
const char *zDummy;
|
||||
|
||||
sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
|
||||
assert( op==SQLITE_UPDATE || op==SQLITE_INSERT );
|
||||
|
||||
/* Bind the new.* PRIMARY KEY values to the SELECT statement. */
|
||||
for(i=0; i<nCol; i++){
|
||||
for(i=0; rc==SQLITE_OK && i<nCol; i++){
|
||||
if( abPK[i] ){
|
||||
sqlite3_value *pVal;
|
||||
if( op==SQLITE_UPDATE ) rc = sqlite3changeset_old(pIter, i, &pVal);
|
||||
else rc = sqlite3changeset_new(pIter, i, &pVal);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
sqlite3_bind_value(pSelect, i+1, pVal);
|
||||
sqlite3_value *pVal = 0;
|
||||
if( op!=SQLITE_DELETE ){
|
||||
rc = sqlite3changeset_new(pIter, i, &pVal);
|
||||
}
|
||||
if( pVal==0 ){
|
||||
rc = sqlite3changeset_old(pIter, i, &pVal);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_bind_value(pSelect, i+1, pVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( SQLITE_ROW==sqlite3_step(pSelect) ){
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_step(pSelect);
|
||||
if( rc!=SQLITE_ROW ) rc = sqlite3_reset(pSelect);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int sessionConflictHandler(
|
||||
int eType,
|
||||
SessionApplyCtx *p,
|
||||
sqlite3_changeset_iter *pIter, /* Changeset iterator */
|
||||
int(*xConflict)(void *, int, sqlite3_changeset_iter*),
|
||||
void *pCtx,
|
||||
int *pbReplace
|
||||
){
|
||||
int res;
|
||||
int rc;
|
||||
int nCol;
|
||||
int op;
|
||||
const char *zDummy;
|
||||
|
||||
sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
|
||||
|
||||
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 );
|
||||
|
||||
/* Bind the new.* PRIMARY KEY values to the SELECT statement. */
|
||||
if( pbReplace ){
|
||||
rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect);
|
||||
}else{
|
||||
rc = SQLITE_DONE;
|
||||
}
|
||||
|
||||
if( rc==SQLITE_ROW ){
|
||||
/* There exists another row with the new.* primary key. */
|
||||
pIter->pConflict = pSelect;
|
||||
res = xConflict(pCtx, SQLITE_CHANGESET_CONFLICT, pIter);
|
||||
pIter->pConflict = p->pSelect;
|
||||
res = xConflict(pCtx, eType, pIter);
|
||||
pIter->pConflict = 0;
|
||||
sqlite3_reset(pSelect);
|
||||
rc = sqlite3_reset(p->pSelect);
|
||||
}else{
|
||||
/* No other row with the new.* primary key. */
|
||||
rc = sqlite3_reset(pSelect);
|
||||
rc = sqlite3_reset(p->pSelect);
|
||||
if( rc==SQLITE_OK ){
|
||||
res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter);
|
||||
res = xConflict(pCtx, eType+1, pIter);
|
||||
if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE;
|
||||
}
|
||||
}
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
switch( res ){
|
||||
case SQLITE_CHANGESET_REPLACE:
|
||||
if( pbReplace ) *pbReplace = 1;
|
||||
break;
|
||||
|
||||
case SQLITE_CHANGESET_OMIT:
|
||||
break;
|
||||
|
||||
case SQLITE_CHANGESET_ABORT:
|
||||
rc = SQLITE_ABORT;
|
||||
break;
|
||||
|
||||
default:
|
||||
rc = SQLITE_MISUSE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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->pUpdate && p->pInsert && p->pSelect );
|
||||
assert( p->azCol && p->abPK );
|
||||
assert( !pbReplace || *pbReplace==0 );
|
||||
|
||||
sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
|
||||
|
||||
if( op==SQLITE_DELETE ){
|
||||
int i;
|
||||
|
||||
/* Bind values to the DELETE statement. */
|
||||
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(p->pDelete, i+1, pVal);
|
||||
}
|
||||
}
|
||||
if( rc==SQLITE_OK ) rc = sqlite3_bind_int(p->pDelete, nCol+1, pbRetry==0);
|
||||
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==SQLITE_CONSTRAINT ){
|
||||
rc = sessionConflictHandler(
|
||||
SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0
|
||||
);
|
||||
}
|
||||
|
||||
}else if( op==SQLITE_UPDATE ){
|
||||
int i;
|
||||
|
||||
/* Bind values to the UPDATE statement. */
|
||||
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(p->pUpdate, i*3+1, pOld);
|
||||
sqlite3_bind_int(p->pUpdate, i*3+2, !!pNew);
|
||||
if( pNew ) sqlite3_bind_value(p->pUpdate, i*3+3, pNew);
|
||||
}
|
||||
}
|
||||
if( rc==SQLITE_OK ) rc = sqlite3_bind_int(p->pUpdate, nCol*3+1, pbRetry==0);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
/* Attempt the UPDATE. In the case of a NOTFOUND or DATA conflict,
|
||||
** the result will be SQLITE_OK with 0 rows modified. */
|
||||
sqlite3_step(p->pUpdate);
|
||||
rc = sqlite3_reset(p->pUpdate);
|
||||
|
||||
if( rc==SQLITE_OK && sqlite3_changes(p->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 = sessionConflictHandler(
|
||||
SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry
|
||||
);
|
||||
|
||||
}else if( rc==SQLITE_CONSTRAINT ){
|
||||
/* This may be a CONSTRAINT or CONFLICT error. It is a CONFLICT if
|
||||
** the only problem is a duplicate PRIMARY KEY, or a CONSTRAINT
|
||||
** otherwise. */
|
||||
int bPKChange = 0;
|
||||
|
||||
/* Check if the PK has been modified. */
|
||||
rc = SQLITE_OK;
|
||||
for(i=0; i<nCol && rc==SQLITE_OK; i++){
|
||||
if( p->abPK[i] ){
|
||||
sqlite3_value *pNew;
|
||||
rc = sqlite3changeset_new(pIter, i, &pNew);
|
||||
if( rc==SQLITE_OK && pNew ){
|
||||
bPKChange = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc = sessionConflictHandler(SQLITE_CHANGESET_CONFLICT,
|
||||
p, pIter, xConflict, pCtx, (bPKChange ? pbReplace : 0)
|
||||
);
|
||||
}
|
||||
|
||||
}else{
|
||||
int i;
|
||||
assert( op==SQLITE_INSERT );
|
||||
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(p->pInsert, i+1, pVal);
|
||||
}
|
||||
}
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
|
||||
sqlite3_step(p->pInsert);
|
||||
rc = sqlite3_reset(p->pInsert);
|
||||
if( rc==SQLITE_CONSTRAINT && xConflict ){
|
||||
rc = sessionConflictHandler(
|
||||
SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1511,209 +1713,90 @@ int sqlite3changeset_apply(
|
||||
),
|
||||
void *pCtx
|
||||
){
|
||||
sqlite3_changeset_iter *pIter;
|
||||
sqlite3_changeset_iter *pIter = 0;
|
||||
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);
|
||||
if( rc!=SQLITE_OK ) return rc;
|
||||
SessionApplyCtx sApply;
|
||||
memset(&sApply, 0, sizeof(sApply));
|
||||
|
||||
sqlite3changeset_start(&pIter, nChangeset, pChangeset);
|
||||
while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
|
||||
|
||||
rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
|
||||
while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){
|
||||
int nCol;
|
||||
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);
|
||||
int bReplace = 0;
|
||||
int bRetry = 0;
|
||||
const char *zNew;
|
||||
sqlite3changeset_op(pIter, &zNew, &nCol, &op);
|
||||
|
||||
sqlite3_finalize(pDelete);
|
||||
sqlite3_finalize(pUpdate);
|
||||
sqlite3_finalize(pInsert);
|
||||
sqlite3_finalize(pSelect);
|
||||
pSelect = pUpdate = pInsert = pDelete = 0;
|
||||
if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){
|
||||
sqlite3_free(sApply.azCol);
|
||||
sqlite3_finalize(sApply.pDelete);
|
||||
sqlite3_finalize(sApply.pUpdate);
|
||||
sqlite3_finalize(sApply.pInsert);
|
||||
sqlite3_finalize(sApply.pSelect);
|
||||
memset(&sApply, 0, sizeof(sApply));
|
||||
sApply.db = db;
|
||||
sApply.nCol = nCol;
|
||||
|
||||
if( (rc = sessionSelectRow(db, zTab, nCol, azCol, abPK, &pSelect))
|
||||
|| (rc = sessionUpdateRow(db, zTab, nCol, azCol, abPK, &pUpdate))
|
||||
|| (rc = sessionDeleteRow(db, zTab, nCol, azCol, abPK, &pDelete))
|
||||
rc = sessionTableInfo(db, zNew, nCol, &zTab, &sApply.azCol, &sApply.abPK);
|
||||
|
||||
if( rc!=SQLITE_OK
|
||||
|| (rc = sessionSelectRow(db, zTab, &sApply))
|
||||
|| (rc = sessionUpdateRow(db, zTab, &sApply))
|
||||
|| (rc = sessionDeleteRow(db, zTab, &sApply))
|
||||
|| (rc = sessionInsertRow(db, zTab, &sApply))
|
||||
){
|
||||
break;
|
||||
}
|
||||
|
||||
nTab = strlen(zTab);
|
||||
}
|
||||
|
||||
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;
|
||||
rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, &bReplace, &bRetry);
|
||||
|
||||
sqlite3_step(pDelete);
|
||||
rc = sqlite3_reset(pDelete);
|
||||
if( rc==SQLITE_OK && sqlite3_changes(db)==0 ){
|
||||
if( rc==SQLITE_OK && bRetry ){
|
||||
rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, &bReplace, 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] ){
|
||||
if( bReplace ){
|
||||
rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
|
||||
if( rc==SQLITE_OK ){
|
||||
int i;
|
||||
for(i=0; i<sApply.nCol; i++){
|
||||
if( sApply.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, pNew);
|
||||
}
|
||||
}
|
||||
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 ){
|
||||
/* This may be a CONSTRAINT or CONFLICT error. It is a CONFLICT if
|
||||
** the only problem is a duplicate PRIMARY KEY, or a CONSTRAINT
|
||||
** otherwise. */
|
||||
int bPKChange = 0;
|
||||
|
||||
/* Check if the PK has been modified. */
|
||||
rc = SQLITE_OK;
|
||||
for(i=0; i<nCol && rc==SQLITE_OK; i++){
|
||||
if( abPK[i] ){
|
||||
sqlite3_value *pNew;
|
||||
rc = sqlite3changeset_new(pIter, i, &pNew);
|
||||
if( rc==SQLITE_OK && pNew ){
|
||||
bPKChange = 1;
|
||||
break;
|
||||
rc = sqlite3changeset_new(pIter, i, &pVal);
|
||||
if( rc==SQLITE_OK && pVal==0 ){
|
||||
rc = sqlite3changeset_old(pIter, i, &pVal);
|
||||
}
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_bind_value(sApply.pDelete, i+1, pVal);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if( bPKChange ){
|
||||
/* See if there exists a row with a duplicate primary key. */
|
||||
rc = sessionConstraintConflict(
|
||||
db, pIter, abPK, pSelect, xConflict, pCtx
|
||||
);
|
||||
}else{
|
||||
res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter);
|
||||
}
|
||||
sqlite3_bind_int(sApply.pDelete, sApply.nCol+1, 1);
|
||||
}
|
||||
|
||||
}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);
|
||||
if( rc==SQLITE_OK ){
|
||||
sqlite3_step(sApply.pDelete);
|
||||
rc = sqlite3_reset(sApply.pDelete);
|
||||
}
|
||||
|
||||
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 ){
|
||||
rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, 0, 0);
|
||||
}
|
||||
if( rc!=SQLITE_OK ) break;
|
||||
|
||||
sqlite3_step(pInsert);
|
||||
rc = sqlite3_reset(pInsert);
|
||||
if( rc==SQLITE_CONSTRAINT && xConflict ){
|
||||
rc = sessionConstraintConflict(
|
||||
db, pIter, abPK, pSelect, xConflict, pCtx
|
||||
);
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rc2 = sqlite3changeset_finalize(pIter);
|
||||
if( rc==SQLITE_DONE ) rc = rc2;
|
||||
if( rc==SQLITE_OK ) rc = rc2;
|
||||
|
||||
if( rc==SQLITE_OK ){
|
||||
rc = sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
|
||||
@ -1722,11 +1805,11 @@ int sqlite3changeset_apply(
|
||||
sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
|
||||
}
|
||||
|
||||
sqlite3_finalize(pInsert);
|
||||
sqlite3_finalize(pDelete);
|
||||
sqlite3_finalize(pUpdate);
|
||||
sqlite3_finalize(pSelect);
|
||||
sqlite3_free(azCol);
|
||||
sqlite3_finalize(sApply.pInsert);
|
||||
sqlite3_finalize(sApply.pDelete);
|
||||
sqlite3_finalize(sApply.pUpdate);
|
||||
sqlite3_finalize(sApply.pSelect);
|
||||
sqlite3_free(sApply.azCol);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
@ -164,13 +164,75 @@ int sqlite3changeset_apply(
|
||||
void *pCtx
|
||||
);
|
||||
|
||||
/* Values passed as the second argument to a conflict-handler */
|
||||
/*
|
||||
** Values passed as the second argument to a conflict-handler.
|
||||
**
|
||||
** SQLITE_CHANGESET_DATA:
|
||||
** The conflict handler is invoked with CHANGESET_DATA as the second argument
|
||||
** when processing a DELETE or UPDATE change if a row with the required
|
||||
** PRIMARY KEY fields is present in the database, but one or more other
|
||||
** (non primary-key) fields modified by the update do not contain the
|
||||
** expected "before" values.
|
||||
**
|
||||
** The conflicting row, in this case, is the database row with the matching
|
||||
** primary key.
|
||||
**
|
||||
** SQLITE_CHANGESET_NOTFOUND:
|
||||
** The conflict handler is invoked with CHANGESET_NOTFOUND as the second
|
||||
** argument when processing a DELETE or UPDATE change if a row with the
|
||||
** required PRIMARY KEY fields is not present in the database.
|
||||
**
|
||||
** There is no conflicting row in this case. The results of invoking the
|
||||
** sqlite3changeset_conflict() API are undefined.
|
||||
**
|
||||
** SQLITE_CHANGESET_CONFLICT:
|
||||
** CHANGESET_CONFLICT is passed as the second argument to the conflict
|
||||
** handler while processing an UPDATE or an INSERT if the operation would
|
||||
** result in duplicate primary key values.
|
||||
**
|
||||
** The conflicting row in this case is the database row with the matching
|
||||
** primary key.
|
||||
**
|
||||
** SQLITE_CHANGESET_CONSTRAINT:
|
||||
** If any other constraint violation occurs while applying a change (i.e.
|
||||
** a FOREIGN KEY, UNIQUE, CHECK or NOT NULL constraint), the conflict
|
||||
** handler is invoked with CHANGESET_CONSTRAINT as the second argument.
|
||||
**
|
||||
** There is no conflicting row in this case. The results of invoking the
|
||||
** sqlite3changeset_conflict() API are undefined.
|
||||
*/
|
||||
#define SQLITE_CHANGESET_DATA 1
|
||||
#define SQLITE_CHANGESET_NOTFOUND 2
|
||||
#define SQLITE_CHANGESET_CONFLICT 3
|
||||
#define SQLITE_CHANGESET_CONSTRAINT 4
|
||||
|
||||
/* Valid return values from a conflict-handler */
|
||||
/*
|
||||
** Valid return values from a conflict-handler.
|
||||
**
|
||||
** SQLITE_CHANGESET_OMIT:
|
||||
** If a conflict handler returns this value no special action is taken. The
|
||||
** change is not applied. The session module continues to the next change
|
||||
** in the changeset.
|
||||
**
|
||||
** SQLITE_CHANGESET_REPLACE:
|
||||
** This value may only be returned if the second argument to the conflict
|
||||
** handler was SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If this
|
||||
** is not the case, any changes applied so far are rolled back and the
|
||||
** call to sqlite3changeset_apply() returns SQLITE_MISUSE.
|
||||
**
|
||||
** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict
|
||||
** handler, then the conflicting row is either updated or deleted, depending
|
||||
** on the type of change.
|
||||
**
|
||||
** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_CONFLICT conflict
|
||||
** handler, then the conflicting row is removed from the database and a
|
||||
** second attempt to apply the change is made. If this second attempt fails,
|
||||
** the original row is restored to the database before continuing.
|
||||
**
|
||||
** SQLITE_CHANGESET_ABORT:
|
||||
** If this value is returned, any changes applied so far are rolled back
|
||||
** and the call to sqlite3changeset_apply() returns SQLITE_MISUSE.
|
||||
*/
|
||||
#define SQLITE_CHANGESET_OMIT 0
|
||||
#define SQLITE_CHANGESET_REPLACE 1
|
||||
#define SQLITE_CHANGESET_ABORT 2
|
||||
|
@ -174,6 +174,17 @@ struct TestConflictHandler {
|
||||
Tcl_Obj *pScript;
|
||||
};
|
||||
|
||||
static int test_obj_eq_string(Tcl_Obj *p, const char *z){
|
||||
int n;
|
||||
int nObj;
|
||||
char *zObj;
|
||||
|
||||
n = strlen(z);
|
||||
zObj = Tcl_GetStringFromObj(p, &nObj);
|
||||
|
||||
return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
|
||||
}
|
||||
|
||||
static int test_conflict_handler(
|
||||
void *pCtx, /* Pointer to TestConflictHandler structure */
|
||||
int eConf, /* DATA, MISSING, CONFLICT, CONSTRAINT */
|
||||
@ -182,6 +193,7 @@ static int test_conflict_handler(
|
||||
TestConflictHandler *p = (TestConflictHandler *)pCtx;
|
||||
Tcl_Obj *pEval;
|
||||
Tcl_Interp *interp = p->interp;
|
||||
int ret = 0; /* Return value */
|
||||
|
||||
int op; /* SQLITE_UPDATE, DELETE or INSERT */
|
||||
const char *zTab; /* Name of table conflict is on */
|
||||
@ -257,9 +269,27 @@ static int test_conflict_handler(
|
||||
|
||||
if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){
|
||||
Tcl_BackgroundError(interp);
|
||||
}else{
|
||||
Tcl_Obj *pRes = Tcl_GetObjResult(interp);
|
||||
if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){
|
||||
ret = SQLITE_CHANGESET_OMIT;
|
||||
}else if( test_obj_eq_string(pRes, "REPLACE") ){
|
||||
ret = SQLITE_CHANGESET_REPLACE;
|
||||
}else if( test_obj_eq_string(pRes, "ABORT") ){
|
||||
ret = SQLITE_CHANGESET_ABORT;
|
||||
}else{
|
||||
Tcl_IncrRefCount(pRes);
|
||||
Tcl_ResetResult(interp);
|
||||
Tcl_AppendResult(interp, "unrecognized conflict handler return: \"",
|
||||
Tcl_GetString(pRes), "\"", 0
|
||||
);
|
||||
Tcl_DecrRefCount(pRes);
|
||||
Tcl_BackgroundError(interp);
|
||||
}
|
||||
}
|
||||
|
||||
Tcl_DecrRefCount(pEval);
|
||||
return SQLITE_CHANGESET_OMIT;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
|
18
manifest
18
manifest
@ -1,5 +1,5 @@
|
||||
C Fix\ssome\sissues\swith\sUPDATE\schanges\sin\sthe\ssession\smodule.
|
||||
D 2011-03-12T17:22:46
|
||||
C Fix\shandling\sof\sreturn\svalues\sfrom\sthe\sconflict\shandler.\sDocument\sthe\sconflict\shandler\sarguments\sand\sreturn\scodes\sin\ssqlite3session.h.
|
||||
D 2011-03-14T19:49:23
|
||||
F Makefile.arm-wince-mingw32ce-gcc d6df77f1f48d690bd73162294bbba7f59507c72f
|
||||
F Makefile.in 27701a1653595a1f2187dc61c8117e00a6c1d50f
|
||||
F Makefile.linux-gcc 91d710bdc4998cb015f39edf3cb314ec4f4d7e23
|
||||
@ -98,9 +98,9 @@ F ext/rtree/rtree_util.tcl 06aab2ed5b826545bf215fff90ecb9255a8647ea
|
||||
F ext/rtree/sqlite3rtree.h 1af0899c63a688e272d69d8e746f24e76f10a3f0
|
||||
F ext/rtree/tkt3363.test 142ab96eded44a3615ec79fba98c7bde7d0f96de
|
||||
F ext/rtree/viewrtree.tcl eea6224b3553599ae665b239bd827e182b466024
|
||||
F ext/session/sqlite3session.c af63d87b8787c19b4a4b681f77331a9cc13d67af
|
||||
F ext/session/sqlite3session.h 3246613b20857e58f7419e4e26dbe9161677aff0
|
||||
F ext/session/test_session.c 1b4f278d0ae164e2d02c11f5e1f2df3a2567ba41
|
||||
F ext/session/sqlite3session.c 111a988b4734b7419f23bb07e45bf5e991510270
|
||||
F ext/session/sqlite3session.h 55ca208bddbc1284c83427f423748eb720f5c68f
|
||||
F ext/session/test_session.c 2559ef68e421c7fb83e2c19ef08a17343b70d535
|
||||
F install-sh 9d4de14ab9fb0facae2f48780b874848cbf2f895
|
||||
F ltmain.sh 3ff0879076df340d2e23ae905484d8c15d5fdea8
|
||||
F main.mk ae0868e05c76eaa8a0ae3d6927a949b1c8e810d7
|
||||
@ -639,7 +639,7 @@ F test/selectA.test 06d1032fa9009314c95394f2ca2e60d9f7ae8532
|
||||
F test/selectB.test f305cc6660804cb239aab4e2f26b0e288b59958b
|
||||
F test/selectC.test f9bf1bc4581b5b8158caa6e4e4f682acb379fb25
|
||||
F test/server1.test f5b790d4c0498179151ca8a7715a65a7802c859c
|
||||
F test/session1.test 6ec522f3491fbdbf8a5b6c27f50b9e35f24e3dba
|
||||
F test/session1.test edbd6078b86b3f6337d779552671125fbd885e19
|
||||
F test/shared.test b9114eaea7e748a3a4c8ff7b9ca806c8f95cef3e
|
||||
F test/shared2.test 7f6ad2d857d0f4e5d6a0b9a897b5e56a6b6ea18c
|
||||
F test/shared3.test d69bdd5f156580876c5345652d21dc2092e85962
|
||||
@ -913,7 +913,7 @@ F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
|
||||
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
|
||||
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
|
||||
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
|
||||
P 2b19be7bf753c7dd12e1c3b384981a3ea1bc8145
|
||||
R 222ffce3b378b2ba18950183bb251367
|
||||
P 57862efe718fdc93401998f9058511292a0e1a50
|
||||
R 17f2f6e54c1cfa1876ed6a58e92b5c4b
|
||||
U dan
|
||||
Z b642b18410bb67d03067201d5612ecd6
|
||||
Z e9add8546b195a8b303123ffee5a5609
|
||||
|
@ -1 +1 @@
|
||||
57862efe718fdc93401998f9058511292a0e1a50
|
||||
cbbb274e500237dbf7155a51d4f9c17583d704ea
|
@ -148,7 +148,9 @@ do_changeset_invert_test 2.4.3 S {}
|
||||
do_test 2.4.4 { S delete } {}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# Test the application of simple changesets.
|
||||
# Test the application of simple changesets. These tests also test that
|
||||
# the conflict callback is invoked correctly. For these tests, the
|
||||
# conflict callback always returns OMIT.
|
||||
#
|
||||
db close
|
||||
forcedelete test.db test.db2
|
||||
@ -160,6 +162,8 @@ proc xConflict {args} {
|
||||
return ""
|
||||
}
|
||||
|
||||
proc bgerror {args} { set ::background_error $args }
|
||||
|
||||
proc do_conflict_test {tn args} {
|
||||
set O(-tables) [list]
|
||||
set O(-sql) [list]
|
||||
@ -183,6 +187,9 @@ proc do_conflict_test {tn args} {
|
||||
lappend conflicts $c
|
||||
}
|
||||
|
||||
after 1 {set go 1}
|
||||
vwait go
|
||||
|
||||
uplevel do_test $tn [list { set ::xConflict }] [list $conflicts]
|
||||
S delete
|
||||
}
|
||||
@ -279,10 +286,126 @@ do_conflict_test 3.3.3 -tables t4 -sql {
|
||||
{UPDATE t4 NOTFOUND {i 4 i 5 i 6} {i -1 {} {} {} {}}}
|
||||
{UPDATE t4 CONSTRAINT {i 7 i 8 i 9} {n {} {} {} {} {}}}
|
||||
}
|
||||
|
||||
do_db2_test 3.3.4 { SELECT * FROM t4 } {0 2 3 4 5 7 7 8 9 x 11 12}
|
||||
do_execsql_test 3.3.5 { SELECT * FROM t4 } {-1 2 3 -1 5 6 {} 8 9 x 11 12}
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# This next block of tests verifies that values returned by the conflict
|
||||
# handler are intepreted correctly. The following cases are tested:
|
||||
#
|
||||
# Test case Operation Conflict Return Code
|
||||
# UPDATE DATA OMIT
|
||||
# UPDATE DATA REPLACE
|
||||
#
|
||||
|
||||
proc test_reset {} {
|
||||
db close
|
||||
db2 close
|
||||
forcedelete test.db test.db2
|
||||
sqlite3 db test.db
|
||||
sqlite3 db2 test.db2
|
||||
}
|
||||
|
||||
proc xConflict {args} {
|
||||
lappend ::xConflict $args
|
||||
return $::conflict_return
|
||||
}
|
||||
|
||||
foreach {tn conflict_return after} {
|
||||
1 OMIT {1 2 value1 4 5 7 7 8 9 10 x x}
|
||||
2 REPLACE {1 2 value1 4 5 value2 10 8 9}
|
||||
} {
|
||||
test_reset
|
||||
|
||||
do_test 4.$tn.1 {
|
||||
foreach db {db db2} {
|
||||
execsql {
|
||||
CREATE TABLE t1(a, b, c, PRIMARY KEY(a));
|
||||
INSERT INTO t1 VALUES(1, 2, 3);
|
||||
INSERT INTO t1 VALUES(4, 5, 6);
|
||||
INSERT INTO t1 VALUES(7, 8, 9);
|
||||
} $db
|
||||
}
|
||||
execsql {
|
||||
REPLACE INTO t1 VALUES(4, 5, 7);
|
||||
REPLACE INTO t1 VALUES(10, 'x', 'x');
|
||||
} db2
|
||||
} {}
|
||||
|
||||
do_conflict_test 4.$tn.2 -tables t1 -sql {
|
||||
UPDATE t1 SET c = 'value1' WHERE a = 1; -- no conflict
|
||||
UPDATE t1 SET c = 'value2' WHERE a = 4; -- DATA conflict
|
||||
UPDATE t1 SET a = 10 WHERE a = 7; -- CONFLICT conflict
|
||||
} -conflicts {
|
||||
{UPDATE t1 DATA {i 4 {} {} i 6} {{} {} {} {} t value2} {i 4 i 5 i 7}}
|
||||
{UPDATE t1 CONFLICT {i 7 {} {} {} {}} {i 10 {} {} {} {}} {i 10 t x t x}}
|
||||
}
|
||||
|
||||
do_db2_test 4.$tn.3 "SELECT * FROM t1 ORDER BY a" $after
|
||||
}
|
||||
|
||||
foreach {tn conflict_return} {
|
||||
1 OMIT
|
||||
2 REPLACE
|
||||
} {
|
||||
test_reset
|
||||
|
||||
do_test 5.$tn.1 {
|
||||
# Create an identical schema in both databases.
|
||||
set schema {
|
||||
CREATE TABLE "'foolish name'"(x, y, z, PRIMARY KEY(x, y));
|
||||
}
|
||||
execsql $schema db
|
||||
execsql $schema db2
|
||||
|
||||
# Add some rows to [db2]. These rows will cause conflicts later
|
||||
# on when the changeset from [db] is applied to it.
|
||||
execsql {
|
||||
INSERT INTO "'foolish name'" VALUES('one', 'one', 'ii');
|
||||
INSERT INTO "'foolish name'" VALUES('one', 'two', 'i');
|
||||
INSERT INTO "'foolish name'" VALUES('two', 'two', 'ii');
|
||||
} db2
|
||||
|
||||
} {}
|
||||
|
||||
do_conflict_test 5.$tn.2 -tables {{'foolish name'}} -sql {
|
||||
INSERT INTO "'foolish name'" VALUES('one', 'two', 2);
|
||||
} -conflicts {
|
||||
{INSERT {'foolish name'} CONFLICT {t one t two i 2} {t one t two t i}}
|
||||
}
|
||||
|
||||
set res(REPLACE) {one one ii one two 2 two two ii}
|
||||
set res(OMIT) {one one ii one two i two two ii}
|
||||
do_db2_test 5.$tn.3 {
|
||||
SELECT * FROM "'foolish name'" ORDER BY x, y
|
||||
} $res($conflict_return)
|
||||
|
||||
|
||||
do_test 5.$tn.1 {
|
||||
set schema {
|
||||
CREATE TABLE d1("z""z" PRIMARY KEY, y);
|
||||
INSERT INTO d1 VALUES(1, 'one');
|
||||
INSERT INTO d1 VALUES(2, 'two');
|
||||
}
|
||||
execsql $schema db
|
||||
execsql $schema db2
|
||||
|
||||
execsql {
|
||||
UPDATE d1 SET y = 'TWO' WHERE "z""z" = 2;
|
||||
} db2
|
||||
|
||||
} {}
|
||||
|
||||
do_conflict_test 5.$tn.2 -tables d1 -sql {
|
||||
DELETE FROM d1 WHERE "z""z" = 2;
|
||||
} -conflicts {
|
||||
{DELETE d1 DATA {i 2 t two} {i 2 t TWO}}
|
||||
}
|
||||
|
||||
set res(REPLACE) {1 one}
|
||||
set res(OMIT) {1 one 2 TWO}
|
||||
do_db2_test 5.$tn.3 "SELECT * FROM d1" $res($conflict_return)
|
||||
}
|
||||
|
||||
catch { db2 close }
|
||||
finish_test
|
||||
|
Reference in New Issue
Block a user