Index: ext/session/sqlite3session.c ================================================================== --- ext/session/sqlite3session.c +++ ext/session/sqlite3session.c @@ -665,18 +665,18 @@ int bNoop = 1; int i; u8 *pCsr = p->aRecord; sessionAppendByte(pBuf, SQLITE_UPDATE, pRc); for(i=0; inBuf -= (1 + sqlite3_column_count(pStmt)); @@ -1449,10 +1451,56 @@ } sqlite3_free(buf.aBuf); } return rc; } + +static int sessionConstraintConflict( + 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 +){ + int res; + int rc; + 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; ipConflict = pSelect; + res = xConflict(pCtx, SQLITE_CHANGESET_CONFLICT, pIter); + pIter->pConflict = 0; + sqlite3_reset(pSelect); + }else{ + /* No other row with the new.* primary key. */ + rc = sqlite3_reset(pSelect); + if( rc==SQLITE_OK ){ + res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter); + } + } + + return rc; +} int sqlite3changeset_apply( sqlite3 *db, int nChangeset, void *pChangeset, @@ -1477,10 +1525,12 @@ 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; + sqlite3changeset_start(&pIter, nChangeset, pChangeset); while( SQLITE_ROW==sqlite3changeset_next(pIter) ){ int op; const char *zThis; sqlite3changeset_op(pIter, &zThis, &nCol, &op); @@ -1492,10 +1542,17 @@ sqlite3_finalize(pDelete); sqlite3_finalize(pUpdate); sqlite3_finalize(pInsert); sqlite3_finalize(pSelect); pSelect = pUpdate = pInsert = pDelete = 0; + + 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)) + ){ + break; + } } if( op==SQLITE_DELETE ){ int res; int i; @@ -1557,11 +1614,11 @@ 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( 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; @@ -1590,11 +1647,36 @@ if( rc==SQLITE_OK ){ res = xConflict(pCtx, SQLITE_CHANGESET_NOTFOUND, pIter); } } }else if( rc==SQLITE_CONSTRAINT ){ - assert(0); + /* 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; ipConflict = 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); - } - } + rc = sessionConstraintConflict( + db, pIter, abPK, pSelect, xConflict, pCtx + ); } } } rc2 = sqlite3changeset_finalize(pIter); if( rc==SQLITE_DONE ) rc = rc2; Index: test/session1.test ================================================================== --- test/session1.test +++ test/session1.test @@ -257,26 +257,33 @@ # do_execsql_test 3.3.1 { CREATE TABLE t4(a, b, c, PRIMARY KEY(b, c)); INSERT INTO t4 VALUES(1, 2, 3); INSERT INTO t4 VALUES(4, 5, 6); + INSERT INTO t4 VALUES(7, 8, 9); + INSERT INTO t4 VALUES(10, 11, 12); } do_db2_test 3.3.2 { - CREATE TABLE t4(a, b, c, PRIMARY KEY(b, c)); + CREATE TABLE t4(a NOT NULL, b, c, PRIMARY KEY(b, c)); INSERT INTO t4 VALUES(0, 2, 3); INSERT INTO t4 VALUES(4, 5, 7); + INSERT INTO t4 VALUES(7, 8, 9); + INSERT INTO t4 VALUES(10, 11, 12); } - -do_conflict_test 3.2.3 -tables t4 -sql { +do_conflict_test 3.3.3 -tables t4 -sql { UPDATE t4 SET a = -1 WHERE b = 2; + UPDATE t4 SET a = -1 WHERE b = 5; + UPDATE t4 SET a = NULL WHERE c = 9; + UPDATE t4 SET a = 'x' WHERE b = 11; } -conflicts { - {UPDATE t4 DATA {i 1 i 2 i 3} {i -1 i 2 i 3} {i 0 i 2 i 3}} + {UPDATE t4 DATA {i 1 i 2 i 3} {i -1 {} {} {} {}} {i 0 i 2 i 3}} + {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} +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} catch { db2 close } finish_test