/ Check-in [cbbb274e]
Login

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:Fix handling of return values from the conflict handler. Document the conflict handler arguments and return codes in sqlite3session.h.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | sessions
Files: files | file ages | folders
SHA1: cbbb274e500237dbf7155a51d4f9c17583d704ea
User & Date: dan 2011-03-14 19:49:23
Context
2011-03-15
16:37
Fix some bugs and other code issues in the session module. check-in: f2930840 user: dan tags: sessions
2011-03-14
19:49
Fix handling of return values from the conflict handler. Document the conflict handler arguments and return codes in sqlite3session.h. check-in: cbbb274e user: dan tags: sessions
2011-03-12
17:22
Fix some issues with UPDATE changes in the session module. check-in: 57862efe user: dan tags: sessions
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to ext/session/sqlite3session.c.

  1275   1275           zSep = "AND ";
  1276   1276         }
  1277   1277       }
  1278   1278       sessionAppendStr(pBuf, ")", pRc);
  1279   1279     }
  1280   1280   }
  1281   1281   
         1282  +
         1283  +typedef struct SessionApplyCtx SessionApplyCtx;
         1284  +struct SessionApplyCtx {
         1285  +  sqlite3 *db;
         1286  +  sqlite3_stmt *pDelete;          /* DELETE statement */
         1287  +  sqlite3_stmt *pUpdate;          /* DELETE statement */
         1288  +  sqlite3_stmt *pInsert;          /* INSERT statement */
         1289  +  sqlite3_stmt *pSelect;          /* SELECT statement */
         1290  +  int nCol;                       /* Size of azCol[] and abPK[] arrays */
         1291  +  const char **azCol;             /* Array of column names */
         1292  +  u8 *abPK;                       /* Boolean array - true if column is in PK */
         1293  +};
         1294  +
  1282   1295   /*
  1283   1296   ** Formulate a statement to DELETE a row from database db. Assuming a table
  1284   1297   ** structure like this:
  1285   1298   **
  1286   1299   **     CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c));
  1287   1300   **
  1288   1301   ** The DELETE statement looks like this:
................................................................................
  1292   1305   ** Variable :5 (nCol+1) is a boolean. It should be set to 0 if we require
  1293   1306   ** matching b and d values, or 1 otherwise. The second case comes up if the
  1294   1307   ** conflict handler is invoked with NOTFOUND and returns CHANGESET_REPLACE.
  1295   1308   */
  1296   1309   static int sessionDeleteRow(
  1297   1310     sqlite3 *db,                    /* Database handle */
  1298   1311     const char *zTab,               /* Table name */
  1299         -  int nCol,                       /* Number of entries in azCol and abPK */
  1300         -  const char **azCol,             /* Column names */
  1301         -  u8 *abPK,                       /* True for PK columns */ 
  1302         -  sqlite3_stmt **ppStmt           /* OUT: Compiled SELECT statement. */
         1312  +  SessionApplyCtx *p              /* Session changeset-apply context */
  1303   1313   ){
  1304   1314     int rc = SQLITE_OK;
  1305         -  if( *ppStmt==0 ){
  1306         -    SessionBuffer buf = {0, 0, 0};
         1315  +  SessionBuffer buf = {0, 0, 0};
  1307   1316   
  1308         -    sessionAppendStr(&buf, "DELETE FROM ", &rc);
  1309         -    sessionAppendIdent(&buf, zTab, &rc);
         1317  +  sessionAppendStr(&buf, "DELETE FROM ", &rc);
         1318  +  sessionAppendIdent(&buf, zTab, &rc);
         1319  +  sessionUpdateDeleteWhere(&buf, p->nCol, p->azCol, p->abPK, &rc);
  1310   1320   
  1311         -    sessionUpdateDeleteWhere(&buf, nCol, azCol, abPK, &rc);
  1312         -
  1313         -    if( rc==SQLITE_OK ){
  1314         -      rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
  1315         -    }
  1316         -    sqlite3_free(buf.aBuf);
         1321  +  if( rc==SQLITE_OK ){
         1322  +    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pDelete, 0);
  1317   1323     }
         1324  +  sqlite3_free(buf.aBuf);
  1318   1325   
  1319   1326     return rc;
  1320   1327   }
  1321   1328   
  1322   1329   /*
  1323   1330   ** Formulate and prepare a statement to UPDATE a row from database db. 
  1324   1331   ** Assuming a table structure like this:
................................................................................
  1347   1354   ** conflict-handler is invoked with CHANGESET_DATA and returns
  1348   1355   ** CHANGESET_REPLACE. This is variable "?(nCol*3+1)".
  1349   1356   **
  1350   1357   */
  1351   1358   static int sessionUpdateRow(
  1352   1359     sqlite3 *db,                    /* Database handle */
  1353   1360     const char *zTab,               /* Table name */
  1354         -  int nCol,                       /* Number of entries in azCol and abPK */
  1355         -  const char **azCol,             /* Column names */
  1356         -  u8 *abPK,                       /* True for PK columns */ 
  1357         -  sqlite3_stmt **ppStmt           /* OUT: Compiled SELECT statement. */
         1361  +  SessionApplyCtx *p              /* Session changeset-apply context */
  1358   1362   ){
  1359   1363     int rc = SQLITE_OK;
  1360         -  if( *ppStmt==0 ){
  1361         -    int i;
  1362         -    const char *zSep = "";
  1363         -    SessionBuffer buf = {0, 0, 0};
  1364         -
  1365         -    /* Append "UPDATE tbl SET " */
  1366         -    sessionAppendStr(&buf, "UPDATE ", &rc);
  1367         -    sessionAppendIdent(&buf, zTab, &rc);
  1368         -    sessionAppendStr(&buf, " SET ", &rc);
  1369         -
  1370         -    /* Append the assignments */
  1371         -    for(i=0; i<nCol; i++){
  1372         -      sessionAppendStr(&buf, zSep, &rc);
  1373         -      sessionAppendIdent(&buf, azCol[i], &rc);
  1374         -      sessionAppendStr(&buf, " = CASE WHEN ?", &rc);
         1364  +  int i;
         1365  +  const char *zSep = "";
         1366  +  SessionBuffer buf = {0, 0, 0};
         1367  +
         1368  +  /* Append "UPDATE tbl SET " */
         1369  +  sessionAppendStr(&buf, "UPDATE ", &rc);
         1370  +  sessionAppendIdent(&buf, zTab, &rc);
         1371  +  sessionAppendStr(&buf, " SET ", &rc);
         1372  +
         1373  +  /* Append the assignments */
         1374  +  for(i=0; i<p->nCol; i++){
         1375  +    sessionAppendStr(&buf, zSep, &rc);
         1376  +    sessionAppendIdent(&buf, p->azCol[i], &rc);
         1377  +    sessionAppendStr(&buf, " = CASE WHEN ?", &rc);
         1378  +    sessionAppendInteger(&buf, i*3+2, &rc);
         1379  +    sessionAppendStr(&buf, " THEN ?", &rc);
         1380  +    sessionAppendInteger(&buf, i*3+3, &rc);
         1381  +    sessionAppendStr(&buf, " ELSE ", &rc);
         1382  +    sessionAppendIdent(&buf, p->azCol[i], &rc);
         1383  +    sessionAppendStr(&buf, " END", &rc);
         1384  +    zSep = ", ";
         1385  +  }
         1386  +
         1387  +  /* Append the PK part of the WHERE clause */
         1388  +  sessionAppendStr(&buf, " WHERE ", &rc);
         1389  +  for(i=0; i<p->nCol; i++){
         1390  +    if( p->abPK[i] ){
         1391  +      sessionAppendIdent(&buf, p->azCol[i], &rc);
         1392  +      sessionAppendStr(&buf, " = ?", &rc);
         1393  +      sessionAppendInteger(&buf, i*3+1, &rc);
         1394  +      sessionAppendStr(&buf, " AND ", &rc);
         1395  +    }
         1396  +  }
         1397  +
         1398  +  /* Append the non-PK part of the WHERE clause */
         1399  +  sessionAppendStr(&buf, " (?", &rc);
         1400  +  sessionAppendInteger(&buf, p->nCol*3+1, &rc);
         1401  +  sessionAppendStr(&buf, " OR 1", &rc);
         1402  +  for(i=0; i<p->nCol; i++){
         1403  +    if( !p->abPK[i] ){
         1404  +      sessionAppendStr(&buf, " AND (?", &rc);
  1375   1405         sessionAppendInteger(&buf, i*3+2, &rc);
  1376         -      sessionAppendStr(&buf, " THEN ?", &rc);
  1377         -      sessionAppendInteger(&buf, i*3+3, &rc);
  1378         -      sessionAppendStr(&buf, " ELSE ", &rc);
  1379         -      sessionAppendIdent(&buf, azCol[i], &rc);
  1380         -      sessionAppendStr(&buf, " END", &rc);
  1381         -      zSep = ", ";
  1382         -    }
  1383         -
  1384         -    /* Append the PK part of the WHERE clause */
  1385         -    sessionAppendStr(&buf, " WHERE ", &rc);
  1386         -    for(i=0; i<nCol; i++){
  1387         -      if( abPK[i] ){
  1388         -        sessionAppendIdent(&buf, azCol[i], &rc);
  1389         -        sessionAppendStr(&buf, " = ?", &rc);
  1390         -        sessionAppendInteger(&buf, i*3+1, &rc);
  1391         -        sessionAppendStr(&buf, " AND ", &rc);
  1392         -      }
  1393         -    }
  1394         -
  1395         -    /* Append the non-PK part of the WHERE clause */
  1396         -    sessionAppendStr(&buf, " (?", &rc);
  1397         -    sessionAppendInteger(&buf, nCol*3+1, &rc);
  1398         -    sessionAppendStr(&buf, " OR 1", &rc);
  1399         -    for(i=0; i<nCol; i++){
  1400         -      if( !abPK[i] ){
  1401         -        sessionAppendStr(&buf, " AND (?", &rc);
  1402         -        sessionAppendInteger(&buf, i*3+2, &rc);
  1403         -        sessionAppendStr(&buf, "=0 OR ", &rc);
  1404         -        sessionAppendIdent(&buf, azCol[i], &rc);
  1405         -        sessionAppendStr(&buf, " IS ?", &rc);
  1406         -        sessionAppendInteger(&buf, i*3+1, &rc);
  1407         -        sessionAppendStr(&buf, ")", &rc);
  1408         -      }
  1409         -    }
  1410         -    sessionAppendStr(&buf, ")", &rc);
  1411         -
  1412         -    if( rc==SQLITE_OK ){
  1413         -      rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
  1414         -    }
  1415         -    sqlite3_free(buf.aBuf);
  1416         -  }
         1406  +      sessionAppendStr(&buf, "=0 OR ", &rc);
         1407  +      sessionAppendIdent(&buf, p->azCol[i], &rc);
         1408  +      sessionAppendStr(&buf, " IS ?", &rc);
         1409  +      sessionAppendInteger(&buf, i*3+1, &rc);
         1410  +      sessionAppendStr(&buf, ")", &rc);
         1411  +    }
         1412  +  }
         1413  +  sessionAppendStr(&buf, ")", &rc);
         1414  +
         1415  +  if( rc==SQLITE_OK ){
         1416  +    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pUpdate, 0);
         1417  +  }
         1418  +  sqlite3_free(buf.aBuf);
  1417   1419   
  1418   1420     return rc;
  1419   1421   }
  1420   1422   
  1421   1423   static int sessionSelectRow(
  1422   1424     sqlite3 *db,                    /* Database handle */
  1423   1425     const char *zTab,               /* Table name */
  1424         -  int nCol,                       /* Number of entries in azCol and abPK */
  1425         -  const char **azCol,             /* Column names */
  1426         -  u8 *abPK,                       /* True for PK columns */ 
  1427         -  sqlite3_stmt **ppStmt           /* OUT: Compiled SELECT statement. */
         1426  +  SessionApplyCtx *p              /* Session changeset-apply context */
         1427  +){
         1428  +  int rc = SQLITE_OK;
         1429  +  int i;
         1430  +  const char *zSep = "";
         1431  +  SessionBuffer buf = {0, 0, 0};
         1432  +
         1433  +  sessionAppendStr(&buf, "SELECT * FROM ", &rc);
         1434  +  sessionAppendIdent(&buf, zTab, &rc);
         1435  +  sessionAppendStr(&buf, " WHERE ", &rc);
         1436  +  for(i=0; i<p->nCol; i++){
         1437  +    if( p->abPK[i] ){
         1438  +      sessionAppendStr(&buf, zSep, &rc);
         1439  +      sessionAppendIdent(&buf, p->azCol[i], &rc);
         1440  +      sessionAppendStr(&buf, " = ?", &rc);
         1441  +      sessionAppendInteger(&buf, i+1, &rc);
         1442  +      zSep = " AND ";
         1443  +    }
         1444  +  }
         1445  +  if( rc==SQLITE_OK ){
         1446  +    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pSelect, 0);
         1447  +  }
         1448  +  sqlite3_free(buf.aBuf);
         1449  +  return rc;
         1450  +}
         1451  +
         1452  +static int sessionInsertRow(
         1453  +  sqlite3 *db,                    /* Database handle */
         1454  +  const char *zTab,               /* Table name */
         1455  +  SessionApplyCtx *p              /* Session changeset-apply context */
  1428   1456   ){
  1429   1457     int rc = SQLITE_OK;
  1430         -  if( *ppStmt==0 ){
  1431         -    int i;
  1432         -    const char *zSep = "";
  1433         -    SessionBuffer buf = {0, 0, 0};
  1434         -  
  1435         -    sessionAppendStr(&buf, "SELECT * FROM ", &rc);
  1436         -    sessionAppendIdent(&buf, zTab, &rc);
  1437         -    sessionAppendStr(&buf, " WHERE ", &rc);
  1438         -  
  1439         -    for(i=0; i<nCol; i++){
  1440         -      if( abPK[i] ){
  1441         -        sessionAppendStr(&buf, zSep, &rc);
  1442         -        sessionAppendIdent(&buf, azCol[i], &rc);
  1443         -        sessionAppendStr(&buf, " = ?", &rc);
  1444         -        sessionAppendInteger(&buf, i+1, &rc);
  1445         -        zSep = " AND ";
  1446         -      }
  1447         -    }
  1448         -  
  1449         -    if( rc==SQLITE_OK ){
  1450         -      rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, ppStmt, 0);
  1451         -    }
  1452         -    sqlite3_free(buf.aBuf);
         1458  +  int i;
         1459  +  SessionBuffer buf = {0, 0, 0};
         1460  +
         1461  +  sessionAppendStr(&buf, "INSERT INTO main.", &rc);
         1462  +  sessionAppendIdent(&buf, zTab, &rc);
         1463  +  sessionAppendStr(&buf, " VALUES(?", &rc);
         1464  +  for(i=1; i<p->nCol; i++){
         1465  +    sessionAppendStr(&buf, ", ?", &rc);
         1466  +  }
         1467  +  sessionAppendStr(&buf, ")", &rc);
         1468  +
         1469  +  if( rc==SQLITE_OK ){
         1470  +    rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &p->pInsert, 0);
  1453   1471     }
         1472  +  sqlite3_free(buf.aBuf);
  1454   1473     return rc;
  1455   1474   }
  1456   1475   
  1457         -static int sessionConstraintConflict(
         1476  +static int sessionSeekToRow(
  1458   1477     sqlite3 *db,                    /* Database handle */
  1459   1478     sqlite3_changeset_iter *pIter,  /* Changeset iterator */
  1460   1479     u8 *abPK,                       /* Primary key flags array */
  1461         -  sqlite3_stmt *pSelect,          /* SELECT statement from sessionSelectRow() */
  1462         -  int(*xConflict)(void *, int, sqlite3_changeset_iter*),
  1463         -  void *pCtx
         1480  +  sqlite3_stmt *pSelect           /* SELECT statement from sessionSelectRow() */
  1464   1481   ){
  1465         -  int res;
  1466         -  int rc;
         1482  +  int rc = SQLITE_OK;
         1483  +
  1467   1484     int i;
  1468   1485     int nCol;
  1469   1486     int op;
  1470   1487     const char *zDummy;
  1471   1488   
  1472   1489     sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
  1473         -  assert( op==SQLITE_UPDATE || op==SQLITE_INSERT );
         1490  +
         1491  +  for(i=0; rc==SQLITE_OK && i<nCol; i++){
         1492  +    if( abPK[i] ){
         1493  +      sqlite3_value *pVal = 0;
         1494  +      if( op!=SQLITE_DELETE ){
         1495  +        rc = sqlite3changeset_new(pIter, i, &pVal);
         1496  +      }
         1497  +      if( pVal==0 ){
         1498  +        rc = sqlite3changeset_old(pIter, i, &pVal);
         1499  +      }
         1500  +      if( rc==SQLITE_OK ){
         1501  +        rc = sqlite3_bind_value(pSelect, i+1, pVal);
         1502  +      }
         1503  +    }
         1504  +  }
         1505  +
         1506  +  if( rc==SQLITE_OK ){
         1507  +    rc = sqlite3_step(pSelect);
         1508  +    if( rc!=SQLITE_ROW ) rc = sqlite3_reset(pSelect);
         1509  +  }
         1510  +
         1511  +  return rc;
         1512  +}
         1513  +
         1514  +static int sessionConflictHandler(
         1515  +  int eType,
         1516  +  SessionApplyCtx *p,
         1517  +  sqlite3_changeset_iter *pIter,  /* Changeset iterator */
         1518  +  int(*xConflict)(void *, int, sqlite3_changeset_iter*),
         1519  +  void *pCtx,
         1520  +  int *pbReplace
         1521  +){
         1522  +  int res;
         1523  +  int rc;
         1524  +  int nCol;
         1525  +  int op;
         1526  +  const char *zDummy;
         1527  +
         1528  +  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
         1529  +
         1530  +  assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA );
         1531  +  assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT );
         1532  +  assert( SQLITE_CHANGESET_DATA+1==SQLITE_CHANGESET_NOTFOUND );
  1474   1533   
  1475   1534     /* Bind the new.* PRIMARY KEY values to the SELECT statement. */
  1476         -  for(i=0; i<nCol; i++){
  1477         -    if( abPK[i] ){
  1478         -      sqlite3_value *pVal;
  1479         -      if( op==SQLITE_UPDATE ) rc = sqlite3changeset_old(pIter, i, &pVal);
  1480         -      else                    rc = sqlite3changeset_new(pIter, i, &pVal);
  1481         -      if( rc!=SQLITE_OK ) return rc;
  1482         -      sqlite3_bind_value(pSelect, i+1, pVal);
         1535  +  if( pbReplace ){
         1536  +    rc = sessionSeekToRow(p->db, pIter, p->abPK, p->pSelect);
         1537  +  }else{
         1538  +    rc = SQLITE_DONE;
         1539  +  }
         1540  +
         1541  +  if( rc==SQLITE_ROW ){
         1542  +    /* There exists another row with the new.* primary key. */
         1543  +    pIter->pConflict = p->pSelect;
         1544  +    res = xConflict(pCtx, eType, pIter);
         1545  +    pIter->pConflict = 0;
         1546  +    rc = sqlite3_reset(p->pSelect);
         1547  +  }else{
         1548  +    /* No other row with the new.* primary key. */
         1549  +    rc = sqlite3_reset(p->pSelect);
         1550  +    if( rc==SQLITE_OK ){
         1551  +      res = xConflict(pCtx, eType+1, pIter);
         1552  +      if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE;
         1553  +    }
         1554  +  }
         1555  +
         1556  +  if( rc==SQLITE_OK ){
         1557  +    switch( res ){
         1558  +      case SQLITE_CHANGESET_REPLACE:
         1559  +        if( pbReplace ) *pbReplace = 1;
         1560  +        break;
         1561  +
         1562  +      case SQLITE_CHANGESET_OMIT:
         1563  +        break;
         1564  +
         1565  +      case SQLITE_CHANGESET_ABORT:
         1566  +        rc = SQLITE_ABORT;
         1567  +        break;
         1568  +
         1569  +      default:
         1570  +        rc = SQLITE_MISUSE;
         1571  +        break;
  1483   1572       }
  1484   1573     }
  1485   1574   
  1486         -  if( SQLITE_ROW==sqlite3_step(pSelect) ){
  1487         -    /* There exists another row with the new.* primary key. */
  1488         -    pIter->pConflict = pSelect;
  1489         -    res = xConflict(pCtx, SQLITE_CHANGESET_CONFLICT, pIter);
  1490         -    pIter->pConflict = 0;
  1491         -    sqlite3_reset(pSelect);
         1575  +  return rc;
         1576  +}
         1577  +
         1578  +static int sessionApplyOneOp(
         1579  +  sqlite3_changeset_iter *pIter,
         1580  +  SessionApplyCtx *p,
         1581  +  int(*xConflict)(void *, int, sqlite3_changeset_iter *),
         1582  +  void *pCtx,
         1583  +  int *pbReplace,
         1584  +  int *pbRetry
         1585  +){
         1586  +  const char *zDummy;
         1587  +  int op;
         1588  +  int nCol;
         1589  +  int rc = SQLITE_OK;
         1590  +
         1591  +  assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect );
         1592  +  assert( p->azCol && p->abPK );
         1593  +  assert( !pbReplace || *pbReplace==0 );
         1594  +
         1595  +  sqlite3changeset_op(pIter, &zDummy, &nCol, &op);
         1596  +
         1597  +  if( op==SQLITE_DELETE ){
         1598  +    int i;
         1599  +
         1600  +    /* Bind values to the DELETE statement. */
         1601  +    for(i=0; rc==SQLITE_OK && i<nCol; i++){
         1602  +      sqlite3_value *pVal;
         1603  +      rc = sqlite3changeset_old(pIter, i, &pVal);
         1604  +      if( rc==SQLITE_OK ){
         1605  +        rc = sqlite3_bind_value(p->pDelete, i+1, pVal);
         1606  +      }
         1607  +    }
         1608  +    if( rc==SQLITE_OK ) rc = sqlite3_bind_int(p->pDelete, nCol+1, pbRetry==0);
         1609  +    if( rc!=SQLITE_OK ) return rc;
         1610  +
         1611  +    sqlite3_step(p->pDelete);
         1612  +    rc = sqlite3_reset(p->pDelete);
         1613  +    if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){
         1614  +      rc = sessionConflictHandler(
         1615  +          SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry
         1616  +      );
         1617  +    }else if( rc==SQLITE_CONSTRAINT ){
         1618  +      rc = sessionConflictHandler(
         1619  +          SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, 0
         1620  +      );
         1621  +    }
         1622  +
         1623  +  }else if( op==SQLITE_UPDATE ){
         1624  +    int i;
         1625  +
         1626  +    /* Bind values to the UPDATE statement. */
         1627  +    for(i=0; rc==SQLITE_OK && i<nCol; i++){
         1628  +      sqlite3_value *pOld = 0;
         1629  +      sqlite3_value *pNew = 0;
         1630  +      rc = sqlite3changeset_old(pIter, i, &pOld);
         1631  +      if( rc==SQLITE_OK ){
         1632  +        rc = sqlite3changeset_new(pIter, i, &pNew);
         1633  +      }
         1634  +      if( rc==SQLITE_OK ){
         1635  +        if( pOld ) sqlite3_bind_value(p->pUpdate, i*3+1, pOld);
         1636  +        sqlite3_bind_int(p->pUpdate, i*3+2, !!pNew);
         1637  +        if( pNew ) sqlite3_bind_value(p->pUpdate, i*3+3, pNew);
         1638  +      }
         1639  +    }
         1640  +    if( rc==SQLITE_OK ) rc = sqlite3_bind_int(p->pUpdate, nCol*3+1, pbRetry==0);
         1641  +    if( rc!=SQLITE_OK ) return rc;
         1642  +
         1643  +    /* Attempt the UPDATE. In the case of a NOTFOUND or DATA conflict,
         1644  +    ** the result will be SQLITE_OK with 0 rows modified. */
         1645  +    sqlite3_step(p->pUpdate);
         1646  +    rc = sqlite3_reset(p->pUpdate);
         1647  +
         1648  +    if( rc==SQLITE_OK && sqlite3_changes(p->db)==0 ){
         1649  +      /* A NOTFOUND or DATA error. Search the table to see if it contains
         1650  +      ** a row with a matching primary key. If so, this is a DATA conflict.
         1651  +      ** Otherwise, if there is no primary key match, it is a NOTFOUND. */
         1652  +
         1653  +      rc = sessionConflictHandler(
         1654  +          SQLITE_CHANGESET_DATA, p, pIter, xConflict, pCtx, pbRetry
         1655  +      );
         1656  +
         1657  +    }else if( rc==SQLITE_CONSTRAINT ){
         1658  +      /* This may be a CONSTRAINT or CONFLICT error. It is a CONFLICT if
         1659  +      ** the only problem is a duplicate PRIMARY KEY, or a CONSTRAINT 
         1660  +      ** otherwise. */
         1661  +      int bPKChange = 0;
         1662  +
         1663  +      /* Check if the PK has been modified. */
         1664  +      rc = SQLITE_OK;
         1665  +      for(i=0; i<nCol && rc==SQLITE_OK; i++){
         1666  +        if( p->abPK[i] ){
         1667  +          sqlite3_value *pNew;
         1668  +          rc = sqlite3changeset_new(pIter, i, &pNew);
         1669  +          if( rc==SQLITE_OK && pNew ){
         1670  +            bPKChange = 1;
         1671  +            break;
         1672  +          }
         1673  +        }
         1674  +      }
         1675  +
         1676  +      rc = sessionConflictHandler(SQLITE_CHANGESET_CONFLICT, 
         1677  +          p, pIter, xConflict, pCtx, (bPKChange ? pbReplace : 0)
         1678  +      );
         1679  +    }
         1680  +
  1492   1681     }else{
  1493         -    /* No other row with the new.* primary key. */
  1494         -    rc = sqlite3_reset(pSelect);
  1495         -    if( rc==SQLITE_OK ){
  1496         -      res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter);
         1682  +    int i;
         1683  +    assert( op==SQLITE_INSERT );
         1684  +    for(i=0; rc==SQLITE_OK && i<nCol; i++){
         1685  +      sqlite3_value *pVal;
         1686  +      rc = sqlite3changeset_new(pIter, i, &pVal);
         1687  +      if( rc==SQLITE_OK ){
         1688  +        rc = sqlite3_bind_value(p->pInsert, i+1, pVal);
         1689  +      }
         1690  +    }
         1691  +    if( rc!=SQLITE_OK ) return rc;
         1692  +
         1693  +    sqlite3_step(p->pInsert);
         1694  +    rc = sqlite3_reset(p->pInsert);
         1695  +    if( rc==SQLITE_CONSTRAINT && xConflict ){
         1696  +      rc = sessionConflictHandler(
         1697  +          SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace
         1698  +      );
  1497   1699       }
  1498   1700     }
  1499   1701   
  1500   1702     return rc;
  1501   1703   }
  1502   1704   
  1503   1705   int sqlite3changeset_apply(
................................................................................
  1507   1709     int(*xConflict)(
  1508   1710       void *pCtx,                   /* Copy of fifth arg to _apply() */
  1509   1711       int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
  1510   1712       sqlite3_changeset_iter *p     /* Handle describing change and conflict */
  1511   1713     ),
  1512   1714     void *pCtx
  1513   1715   ){
  1514         -  sqlite3_changeset_iter *pIter;
         1716  +  sqlite3_changeset_iter *pIter = 0;
  1515   1717     int rc;
  1516   1718     int rc2;
  1517   1719   
  1518   1720     const char *zTab = 0;           /* Name of current table */
  1519   1721     int nTab = 0;                   /* Result of strlen(zTab) */
  1520         -  int nCol = 0;                   /* Number of columns in table zTab */
  1521         -  const char **azCol = 0;         /* Array of column names */
  1522         -  u8 *abPK = 0;                   /* Boolean array - true if column is in PK */
  1523   1722   
  1524         -  sqlite3_stmt *pDelete = 0;      /* DELETE statement */
  1525         -  sqlite3_stmt *pUpdate = 0;      /* DELETE statement */
  1526         -  sqlite3_stmt *pInsert = 0;      /* INSERT statement */
  1527         -  sqlite3_stmt *pSelect = 0;      /* SELECT statement */
         1723  +  SessionApplyCtx sApply;
         1724  +  memset(&sApply, 0, sizeof(sApply));
         1725  +
         1726  +  sqlite3changeset_start(&pIter, nChangeset, pChangeset);
  1528   1727   
  1529   1728     rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0);
  1530         -  if( rc!=SQLITE_OK ) return rc;
  1531         -
  1532         -  sqlite3changeset_start(&pIter, nChangeset, pChangeset);
  1533         -  while( SQLITE_ROW==sqlite3changeset_next(pIter) ){
         1729  +  while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){
         1730  +    int nCol;
  1534   1731       int op;
  1535         -    const char *zThis;
  1536         -    sqlite3changeset_op(pIter, &zThis, &nCol, &op);
  1537         -    if( zTab==0 || sqlite3_strnicmp(zThis, zTab, nTab+1) ){
  1538         -      sqlite3_free(azCol);
  1539         -      rc = sessionTableInfo(db, zThis, nCol, &zTab, &azCol, &abPK);
  1540         -      nTab = strlen(zTab);
         1732  +    int bReplace = 0;
         1733  +    int bRetry = 0;
         1734  +    const char *zNew;
         1735  +    sqlite3changeset_op(pIter, &zNew, &nCol, &op);
         1736  +
         1737  +    if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){
         1738  +      sqlite3_free(sApply.azCol);
         1739  +      sqlite3_finalize(sApply.pDelete);
         1740  +      sqlite3_finalize(sApply.pUpdate); 
         1741  +      sqlite3_finalize(sApply.pInsert);
         1742  +      sqlite3_finalize(sApply.pSelect);
         1743  +      memset(&sApply, 0, sizeof(sApply));
         1744  +      sApply.db = db;
         1745  +      sApply.nCol = nCol;
  1541   1746   
  1542         -      sqlite3_finalize(pDelete);
  1543         -      sqlite3_finalize(pUpdate);
  1544         -      sqlite3_finalize(pInsert);
  1545         -      sqlite3_finalize(pSelect);
  1546         -      pSelect = pUpdate = pInsert = pDelete = 0;
         1747  +      rc = sessionTableInfo(db, zNew, nCol, &zTab, &sApply.azCol, &sApply.abPK);
  1547   1748   
  1548         -      if( (rc = sessionSelectRow(db, zTab, nCol, azCol, abPK, &pSelect))
  1549         -       || (rc = sessionUpdateRow(db, zTab, nCol, azCol, abPK, &pUpdate))
  1550         -       || (rc = sessionDeleteRow(db, zTab, nCol, azCol, abPK, &pDelete))
         1749  +      if( rc!=SQLITE_OK 
         1750  +       || (rc = sessionSelectRow(db, zTab, &sApply))
         1751  +       || (rc = sessionUpdateRow(db, zTab, &sApply))
         1752  +       || (rc = sessionDeleteRow(db, zTab, &sApply))
         1753  +       || (rc = sessionInsertRow(db, zTab, &sApply))
  1551   1754         ){
  1552   1755           break;
  1553   1756         }
         1757  +
         1758  +      nTab = strlen(zTab);
         1759  +    }
         1760  +
         1761  +    rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, &bReplace, &bRetry);
         1762  +
         1763  +    if( rc==SQLITE_OK && bRetry ){
         1764  +      rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, &bReplace, 0);
  1554   1765       }
  1555   1766   
  1556         -    if( op==SQLITE_DELETE ){
  1557         -      int res;
  1558         -      int i;
  1559         -      rc = sessionDeleteRow(db, zTab, nCol, azCol, abPK, &pDelete);
  1560         -      for(i=0; rc==SQLITE_OK && i<nCol; i++){
  1561         -        sqlite3_value *pVal;
  1562         -        rc = sqlite3changeset_old(pIter, i, &pVal);
  1563         -        if( rc==SQLITE_OK ){
  1564         -          rc = sqlite3_bind_value(pDelete, i+1, pVal);
  1565         -        }
  1566         -      }
  1567         -      if( rc==SQLITE_OK ) rc = sqlite3_bind_int(pDelete, nCol+1, 0);
  1568         -      if( rc!=SQLITE_OK ) break;
  1569         -
  1570         -      sqlite3_step(pDelete);
  1571         -      rc = sqlite3_reset(pDelete);
  1572         -      if( rc==SQLITE_OK && sqlite3_changes(db)==0 ){
  1573         -
  1574         -        /* A NOTFOUND or DATA error. Search the table to see if it contains
  1575         -        ** a row with a matching primary key. If so, this is a DATA conflict.
  1576         -        ** Otherwise, if there is no primary key match, it is a NOTFOUND. */
  1577         -        rc = sessionSelectRow(db, zTab, nCol, azCol, abPK, &pSelect);
  1578         -        for(i=0; rc==SQLITE_OK && i<nCol; i++){
  1579         -          if( abPK[i] ){
         1767  +    if( bReplace ){
         1768  +      rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0);
         1769  +      if( rc==SQLITE_OK ){
         1770  +        int i;
         1771  +        for(i=0; i<sApply.nCol; i++){
         1772  +          if( sApply.abPK[i] ){
  1580   1773               sqlite3_value *pVal;
  1581         -            rc = sqlite3changeset_old(pIter, i, &pVal);
  1582         -            if( rc==SQLITE_OK ) sqlite3_bind_value(pSelect, i+1, pVal);
  1583         -          }
  1584         -        }
  1585         -        if( rc!=SQLITE_OK ) break;
  1586         -        if( SQLITE_ROW==sqlite3_step(pSelect) ){
  1587         -          pIter->pConflict = pSelect;
  1588         -          res = xConflict(pCtx, SQLITE_CHANGESET_DATA, pIter);
  1589         -          pIter->pConflict = 0;
  1590         -          sqlite3_reset(pSelect);
  1591         -        }else{
  1592         -          rc = sqlite3_reset(pSelect);
  1593         -          if( rc==SQLITE_OK ){
  1594         -            res = xConflict(pCtx, SQLITE_CHANGESET_NOTFOUND, pIter);
  1595         -          }
  1596         -        }
  1597         -
  1598         -      }else if( rc==SQLITE_CONSTRAINT ){
  1599         -        res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter);
  1600         -        rc = SQLITE_OK;
  1601         -      }
  1602         -
  1603         -      if( rc!=SQLITE_OK ) break;
  1604         -
  1605         -    }else if( op==SQLITE_UPDATE ){
  1606         -      int i;
  1607         -      int res;
  1608         -      rc = sessionUpdateRow(db, zTab, nCol, azCol, abPK, &pUpdate);
  1609         -      for(i=0; rc==SQLITE_OK && i<nCol; i++){
  1610         -        sqlite3_value *pOld = 0;
  1611         -        sqlite3_value *pNew = 0;
  1612         -        rc = sqlite3changeset_old(pIter, i, &pOld);
  1613         -        if( rc==SQLITE_OK ){
  1614         -          rc = sqlite3changeset_new(pIter, i, &pNew);
  1615         -        }
  1616         -        if( rc==SQLITE_OK ){
  1617         -          if( pOld ) sqlite3_bind_value(pUpdate, i*3+1, pOld);
  1618         -          sqlite3_bind_int(pUpdate, i*3+2, !!pNew);
  1619         -          if( pNew ) sqlite3_bind_value(pUpdate, i*3+3, pNew);
  1620         -        }
  1621         -      }
  1622         -      if( rc==SQLITE_OK ) rc = sqlite3_bind_int(pUpdate, nCol*3+1, 0);
  1623         -      if( rc!=SQLITE_OK ) break;
  1624         -
  1625         -      sqlite3_step(pUpdate);
  1626         -      rc = sqlite3_reset(pUpdate);
  1627         -      if( rc==SQLITE_OK && sqlite3_changes(db)==0 ){
  1628         -        /* A NOTFOUND or DATA error. Search the table to see if it contains
  1629         -        ** a row with a matching primary key. If so, this is a DATA conflict.
  1630         -        ** Otherwise, if there is no primary key match, it is a NOTFOUND. */
  1631         -        rc = sessionSelectRow(db, zTab, nCol, azCol, abPK, &pSelect);
  1632         -        for(i=0; rc==SQLITE_OK && i<nCol; i++){
  1633         -          if( abPK[i] ){
  1634         -            sqlite3_value *pVal;
  1635         -            rc = sqlite3changeset_old(pIter, i, &pVal);
  1636         -            if( rc==SQLITE_OK ) sqlite3_bind_value(pSelect, i+1, pVal);
  1637         -          }
  1638         -        }
  1639         -        if( rc!=SQLITE_OK ) break;
  1640         -        if( SQLITE_ROW==sqlite3_step(pSelect) ){
  1641         -          pIter->pConflict = pSelect;
  1642         -          res = xConflict(pCtx, SQLITE_CHANGESET_DATA, pIter);
  1643         -          pIter->pConflict = 0;
  1644         -          sqlite3_reset(pSelect);
  1645         -        }else{
  1646         -          rc = sqlite3_reset(pSelect);
  1647         -          if( rc==SQLITE_OK ){
  1648         -            res = xConflict(pCtx, SQLITE_CHANGESET_NOTFOUND, pIter);
  1649         -          }
  1650         -        }
  1651         -      }else if( rc==SQLITE_CONSTRAINT ){
  1652         -        /* This may be a CONSTRAINT or CONFLICT error. It is a CONFLICT if
  1653         -        ** the only problem is a duplicate PRIMARY KEY, or a CONSTRAINT 
  1654         -        ** otherwise. */
  1655         -        int bPKChange = 0;
  1656         -
  1657         -        /* Check if the PK has been modified. */
  1658         -        rc = SQLITE_OK;
  1659         -        for(i=0; i<nCol && rc==SQLITE_OK; i++){
  1660         -          if( abPK[i] ){
  1661         -            sqlite3_value *pNew;
  1662         -            rc = sqlite3changeset_new(pIter, i, &pNew);
  1663         -            if( rc==SQLITE_OK && pNew ){
  1664         -              bPKChange = 1;
  1665         -              break;
         1774  +            rc = sqlite3changeset_new(pIter, i, &pVal);
         1775  +            if( rc==SQLITE_OK && pVal==0 ){
         1776  +              rc = sqlite3changeset_old(pIter, i, &pVal);
         1777  +            }
         1778  +            if( rc==SQLITE_OK ){
         1779  +              rc = sqlite3_bind_value(sApply.pDelete, i+1, pVal);
  1666   1780               }
  1667   1781             }
  1668   1782           }
  1669         -
  1670         -        if( bPKChange ){
  1671         -          /* See if there exists a row with a duplicate primary key. */
  1672         -          rc = sessionConstraintConflict(
  1673         -              db, pIter, abPK, pSelect, xConflict, pCtx
  1674         -          );
  1675         -        }else{
  1676         -          res = xConflict(pCtx, SQLITE_CHANGESET_CONSTRAINT, pIter);
  1677         -        }
         1783  +        sqlite3_bind_int(sApply.pDelete, sApply.nCol+1, 1);
         1784  +      }
         1785  +      if( rc==SQLITE_OK ){
         1786  +        sqlite3_step(sApply.pDelete);
         1787  +        rc = sqlite3_reset(sApply.pDelete);
         1788  +      }
         1789  +      if( rc==SQLITE_OK ){
         1790  +        rc = sessionApplyOneOp(pIter, &sApply, xConflict, pCtx, 0, 0);
  1678   1791         }
  1679         -
  1680         -    }else{
  1681         -      int i;
  1682         -      assert( op==SQLITE_INSERT );
  1683         -      if( pInsert==0 ){
  1684         -        SessionBuffer buf = {0, 0, 0};
  1685         -        sessionAppendStr(&buf, "INSERT INTO main.", &rc);
  1686         -        sessionAppendIdent(&buf, zTab, &rc);
  1687         -        sessionAppendStr(&buf, " VALUES(?", &rc);
  1688         -        for(i=1; i<nCol; i++) sessionAppendStr(&buf, ", ?", &rc);
  1689         -        sessionAppendStr(&buf, ")", &rc);
  1690         -
  1691         -        if( rc==SQLITE_OK ){
  1692         -          rc = sqlite3_prepare_v2(db, (char *)buf.aBuf, buf.nBuf, &pInsert, 0);
  1693         -        }
  1694         -        sqlite3_free(buf.aBuf);
  1695         -      }
  1696         -
  1697         -      for(i=0; rc==SQLITE_OK && i<nCol; i++){
  1698         -        sqlite3_value *pVal;
  1699         -        rc = sqlite3changeset_new(pIter, i, &pVal);
  1700         -        if( rc==SQLITE_OK ){
  1701         -          rc = sqlite3_bind_value(pInsert, i+1, pVal);
  1702         -        }
  1703         -      }
  1704         -      if( rc!=SQLITE_OK ) break;
  1705         -
  1706         -      sqlite3_step(pInsert);
  1707         -      rc = sqlite3_reset(pInsert);
  1708         -      if( rc==SQLITE_CONSTRAINT && xConflict ){
  1709         -        rc = sessionConstraintConflict(
  1710         -              db, pIter, abPK, pSelect, xConflict, pCtx
  1711         -        );
         1792  +      if( rc==SQLITE_OK ){
         1793  +        rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0);
  1712   1794         }
  1713   1795       }
  1714   1796     }
         1797  +
  1715   1798     rc2 = sqlite3changeset_finalize(pIter);
  1716         -  if( rc==SQLITE_DONE ) rc = rc2;
         1799  +  if( rc==SQLITE_OK ) rc = rc2;
  1717   1800   
  1718   1801     if( rc==SQLITE_OK ){
  1719   1802       rc = sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
  1720   1803     }else{
  1721   1804       sqlite3_exec(db, "ROLLBACK TO changeset_apply", 0, 0, 0);
  1722   1805       sqlite3_exec(db, "RELEASE changeset_apply", 0, 0, 0);
  1723   1806     }
  1724   1807   
  1725         -  sqlite3_finalize(pInsert);
  1726         -  sqlite3_finalize(pDelete);
  1727         -  sqlite3_finalize(pUpdate);
  1728         -  sqlite3_finalize(pSelect);
  1729         -  sqlite3_free(azCol);
         1808  +  sqlite3_finalize(sApply.pInsert);
         1809  +  sqlite3_finalize(sApply.pDelete);
         1810  +  sqlite3_finalize(sApply.pUpdate);
         1811  +  sqlite3_finalize(sApply.pSelect);
         1812  +  sqlite3_free(sApply.azCol);
  1730   1813     return rc;
  1731   1814   }
  1732   1815   
  1733   1816   #endif        /* #ifdef SQLITE_ENABLE_SESSION */

Changes to ext/session/sqlite3session.h.

   160    160       void *pCtx,                   /* Copy of fifth arg to _apply() */
   161    161       int eConflict,                /* DATA, MISSING, CONFLICT, CONSTRAINT */
   162    162       sqlite3_changeset_iter *p     /* Handle describing change and conflict */
   163    163     ),
   164    164     void *pCtx
   165    165   );
   166    166   
   167         -/* Values passed as the second argument to a conflict-handler */
          167  +/* 
          168  +** Values passed as the second argument to a conflict-handler.
          169  +**
          170  +** SQLITE_CHANGESET_DATA:
          171  +**   The conflict handler is invoked with CHANGESET_DATA as the second argument
          172  +**   when processing a DELETE or UPDATE change if a row with the required
          173  +**   PRIMARY KEY fields is present in the database, but one or more other 
          174  +**   (non primary-key) fields modified by the update do not contain the 
          175  +**   expected "before" values.
          176  +** 
          177  +**   The conflicting row, in this case, is the database row with the matching
          178  +**   primary key.
          179  +** 
          180  +** SQLITE_CHANGESET_NOTFOUND:
          181  +**   The conflict handler is invoked with CHANGESET_NOTFOUND as the second
          182  +**   argument when processing a DELETE or UPDATE change if a row with the
          183  +**   required PRIMARY KEY fields is not present in the database.
          184  +** 
          185  +**   There is no conflicting row in this case. The results of invoking the
          186  +**   sqlite3changeset_conflict() API are undefined.
          187  +** 
          188  +** SQLITE_CHANGESET_CONFLICT:
          189  +**   CHANGESET_CONFLICT is passed as the second argument to the conflict
          190  +**   handler while processing an UPDATE or an INSERT if the operation would
          191  +**   result in duplicate primary key values.
          192  +** 
          193  +**   The conflicting row in this case is the database row with the matching
          194  +**   primary key.
          195  +** 
          196  +** SQLITE_CHANGESET_CONSTRAINT:
          197  +**   If any other constraint violation occurs while applying a change (i.e. 
          198  +**   a FOREIGN KEY, UNIQUE, CHECK or NOT NULL constraint), the conflict 
          199  +**   handler is invoked with CHANGESET_CONSTRAINT as the second argument.
          200  +** 
          201  +**   There is no conflicting row in this case. The results of invoking the
          202  +**   sqlite3changeset_conflict() API are undefined.
          203  +*/
   168    204   #define SQLITE_CHANGESET_DATA       1
   169    205   #define SQLITE_CHANGESET_NOTFOUND   2
   170    206   #define SQLITE_CHANGESET_CONFLICT   3
   171    207   #define SQLITE_CHANGESET_CONSTRAINT 4
   172    208   
   173         -/* Valid return values from a conflict-handler */
          209  +/* 
          210  +** Valid return values from a conflict-handler.
          211  +**
          212  +** SQLITE_CHANGESET_OMIT:
          213  +**   If a conflict handler returns this value no special action is taken. The
          214  +**   change is not applied. The session module continues to the next change
          215  +**   in the changeset.
          216  +**
          217  +** SQLITE_CHANGESET_REPLACE:
          218  +**   This value may only be returned if the second argument to the conflict
          219  +**   handler was SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If this
          220  +**   is not the case, any changes applied so far are rolled back and the 
          221  +**   call to sqlite3changeset_apply() returns SQLITE_MISUSE.
          222  +**
          223  +**   If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict
          224  +**   handler, then the conflicting row is either updated or deleted, depending
          225  +**   on the type of change.
          226  +**
          227  +**   If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_CONFLICT conflict
          228  +**   handler, then the conflicting row is removed from the database and a
          229  +**   second attempt to apply the change is made. If this second attempt fails,
          230  +**   the original row is restored to the database before continuing.
          231  +**
          232  +** SQLITE_CHANGESET_ABORT:
          233  +**   If this value is returned, any changes applied so far are rolled back 
          234  +**   and the call to sqlite3changeset_apply() returns SQLITE_MISUSE.
          235  +*/
   174    236   #define SQLITE_CHANGESET_OMIT       0
   175    237   #define SQLITE_CHANGESET_REPLACE    1
   176    238   #define SQLITE_CHANGESET_ABORT      2
   177    239   
   178    240   #endif
   179    241   

Changes to ext/session/test_session.c.

   169    169   }
   170    170   
   171    171   typedef struct TestConflictHandler TestConflictHandler;
   172    172   struct TestConflictHandler {
   173    173     Tcl_Interp *interp;
   174    174     Tcl_Obj *pScript;
   175    175   };
          176  +
          177  +static int test_obj_eq_string(Tcl_Obj *p, const char *z){
          178  +  int n;
          179  +  int nObj;
          180  +  char *zObj;
          181  +
          182  +  n = strlen(z);
          183  +  zObj = Tcl_GetStringFromObj(p, &nObj);
          184  +
          185  +  return (nObj==n && (n==0 || 0==memcmp(zObj, z, n)));
          186  +}
   176    187   
   177    188   static int test_conflict_handler(
   178    189     void *pCtx,                     /* Pointer to TestConflictHandler structure */
   179    190     int eConf,                      /* DATA, MISSING, CONFLICT, CONSTRAINT */
   180    191     sqlite3_changeset_iter *pIter   /* Handle describing change and conflict */
   181    192   ){
   182    193     TestConflictHandler *p = (TestConflictHandler *)pCtx;
   183    194     Tcl_Obj *pEval;
   184    195     Tcl_Interp *interp = p->interp;
          196  +  int ret = 0;                    /* Return value */
   185    197   
   186    198     int op;                         /* SQLITE_UPDATE, DELETE or INSERT */
   187    199     const char *zTab;               /* Name of table conflict is on */
   188    200     int nCol;                       /* Number of columns in table zTab */
   189    201   
   190    202     pEval = Tcl_DuplicateObj(p->pScript);
   191    203     Tcl_IncrRefCount(pEval);
................................................................................
   253    265         test_append_value(pConflict, pVal);
   254    266       }
   255    267       Tcl_ListObjAppendElement(0, pEval, pConflict);
   256    268     }
   257    269   
   258    270     if( TCL_OK!=Tcl_EvalObjEx(interp, pEval, TCL_EVAL_GLOBAL) ){
   259    271       Tcl_BackgroundError(interp);
          272  +  }else{
          273  +    Tcl_Obj *pRes = Tcl_GetObjResult(interp);
          274  +    if( test_obj_eq_string(pRes, "OMIT") || test_obj_eq_string(pRes, "") ){
          275  +      ret = SQLITE_CHANGESET_OMIT;
          276  +    }else if( test_obj_eq_string(pRes, "REPLACE") ){
          277  +      ret = SQLITE_CHANGESET_REPLACE;
          278  +    }else if( test_obj_eq_string(pRes, "ABORT") ){
          279  +      ret = SQLITE_CHANGESET_ABORT;
          280  +    }else{
          281  +      Tcl_IncrRefCount(pRes);
          282  +      Tcl_ResetResult(interp);
          283  +      Tcl_AppendResult(interp, "unrecognized conflict handler return: \"", 
          284  +          Tcl_GetString(pRes), "\"", 0
          285  +      );
          286  +      Tcl_DecrRefCount(pRes);
          287  +      Tcl_BackgroundError(interp);
          288  +    }
   260    289     }
          290  +
   261    291     Tcl_DecrRefCount(pEval);
   262         -  return SQLITE_CHANGESET_OMIT;
          292  +  return ret;
   263    293   }
   264    294   
   265    295   /*
   266    296   ** sqlite3changeset_apply DB CHANGESET SCRIPT
   267    297   */
   268    298   static int test_sqlite3changeset_apply(
   269    299     void * clientData,

Changes to test/session1.test.

   144    144     execsql { DELETE FROM t1 WHERE x = 100 }
   145    145   } {}
   146    146   do_changeset_test 2.4.2 S {}
   147    147   do_changeset_invert_test 2.4.3 S {}
   148    148   do_test 2.4.4 { S delete } {}
   149    149   
   150    150   #-------------------------------------------------------------------------
   151         -# Test the application of simple changesets.
          151  +# Test the application of simple changesets. These tests also test that
          152  +# the conflict callback is invoked correctly. For these tests, the 
          153  +# conflict callback always returns OMIT.
   152    154   #
   153    155   db close
   154    156   forcedelete test.db test.db2
   155    157   sqlite3 db test.db
   156    158   sqlite3 db2 test.db2
   157    159   
   158    160   proc xConflict {args} { 
   159    161     lappend ::xConflict $args
   160    162     return "" 
   161    163   }
          164  +
          165  +proc bgerror {args} { set ::background_error $args }
   162    166   
   163    167   proc do_conflict_test {tn args} {
   164    168     set O(-tables)    [list]
   165    169     set O(-sql)       [list]
   166    170     set O(-conflicts) [list]
   167    171   
   168    172     array set V $args
................................................................................
   178    182     set ::xConflict [list]
   179    183     sqlite3changeset_apply db2 [S changeset] xConflict
   180    184   
   181    185     set conflicts [list]
   182    186     foreach c $O(-conflicts) {
   183    187       lappend conflicts $c
   184    188     }
          189  +
          190  +  after 1 {set go 1}
          191  +  vwait go
   185    192   
   186    193     uplevel do_test $tn [list { set ::xConflict }] [list $conflicts]
   187    194     S delete
   188    195   }
   189    196   
   190    197   proc do_db2_test {testname sql {result {}}} {
   191    198     uplevel do_test $testname [list "execsql {$sql} db2"] [list [list {*}$result]]
................................................................................
   275    282     UPDATE t4 SET a = NULL WHERE c = 9;
   276    283     UPDATE t4 SET a = 'x' WHERE b = 11;
   277    284   } -conflicts {
   278    285     {UPDATE t4 DATA {i 1 i 2 i 3} {i -1 {} {} {} {}} {i 0 i 2 i 3}}
   279    286     {UPDATE t4 NOTFOUND {i 4 i 5 i 6} {i -1 {} {} {} {}}}
   280    287     {UPDATE t4 CONSTRAINT {i 7 i 8 i 9} {n {} {} {} {} {}}}
   281    288   }
   282         -
   283    289   do_db2_test     3.3.4 { SELECT * FROM t4 } {0 2 3 4 5 7 7 8 9 x 11 12}
   284    290   do_execsql_test 3.3.5 { SELECT * FROM t4 } {-1 2 3 -1 5 6 {} 8 9 x 11 12}
   285    291   
          292  +#-------------------------------------------------------------------------
          293  +# This next block of tests verifies that values returned by the conflict
          294  +# handler are intepreted correctly. The following cases are tested:
          295  +#
          296  +#     Test case   Operation   Conflict   Return Code
          297  +#                 UPDATE      DATA       OMIT
          298  +#                 UPDATE      DATA       REPLACE
          299  +#
          300  +
          301  +proc test_reset {} {
          302  +  db close
          303  +  db2 close
          304  +  forcedelete test.db test.db2
          305  +  sqlite3 db test.db
          306  +  sqlite3 db2 test.db2
          307  +}
          308  +
          309  +proc xConflict {args} {
          310  +  lappend ::xConflict $args
          311  +  return $::conflict_return
          312  +}
          313  +
          314  +foreach {tn conflict_return after} {
          315  +  1 OMIT      {1 2 value1   4 5 7       7 8 9   10 x x}
          316  +  2 REPLACE   {1 2 value1   4 5 value2          10 8 9}
          317  +} {
          318  +  test_reset
          319  +
          320  +  do_test 4.$tn.1 {
          321  +    foreach db {db db2} {
          322  +      execsql { 
          323  +        CREATE TABLE t1(a, b, c, PRIMARY KEY(a));
          324  +        INSERT INTO t1 VALUES(1, 2, 3);
          325  +        INSERT INTO t1 VALUES(4, 5, 6);
          326  +        INSERT INTO t1 VALUES(7, 8, 9);
          327  +      } $db
          328  +    }
          329  +    execsql { 
          330  +      REPLACE INTO t1 VALUES(4, 5, 7);
          331  +      REPLACE INTO t1 VALUES(10, 'x', 'x');
          332  +    } db2
          333  +  } {}
          334  +
          335  +  do_conflict_test 4.$tn.2 -tables t1 -sql {
          336  +    UPDATE t1 SET c = 'value1' WHERE a = 1;       -- no conflict
          337  +    UPDATE t1 SET c = 'value2' WHERE a = 4;       -- DATA conflict
          338  +    UPDATE t1 SET a = 10 WHERE a = 7;             -- CONFLICT conflict
          339  +  } -conflicts {
          340  +    {UPDATE t1 DATA {i 4 {} {} i 6} {{} {} {} {} t value2} {i 4 i 5 i 7}}
          341  +    {UPDATE t1 CONFLICT {i 7 {} {} {} {}} {i 10 {} {} {} {}} {i 10 t x t x}}
          342  +  }
          343  +
          344  +  do_db2_test 4.$tn.3 "SELECT * FROM t1 ORDER BY a" $after
          345  +}
          346  +
          347  +foreach {tn conflict_return} {
          348  +  1 OMIT
          349  +  2 REPLACE
          350  +} {
          351  +  test_reset
          352  +
          353  +  do_test 5.$tn.1 {
          354  +    # Create an identical schema in both databases.
          355  +    set schema {
          356  +      CREATE TABLE "'foolish name'"(x, y, z, PRIMARY KEY(x, y));
          357  +    }
          358  +    execsql $schema db
          359  +    execsql $schema db2
          360  +
          361  +    # Add some rows to [db2]. These rows will cause conflicts later
          362  +    # on when the changeset from [db] is applied to it.
          363  +    execsql { 
          364  +      INSERT INTO "'foolish name'" VALUES('one', 'one', 'ii');
          365  +      INSERT INTO "'foolish name'" VALUES('one', 'two', 'i');
          366  +      INSERT INTO "'foolish name'" VALUES('two', 'two', 'ii');
          367  +    } db2
          368  +
          369  +  } {}
          370  +
          371  +  do_conflict_test 5.$tn.2 -tables {{'foolish name'}} -sql {
          372  +    INSERT INTO "'foolish name'" VALUES('one', 'two', 2);
          373  +  } -conflicts {
          374  +    {INSERT {'foolish name'} CONFLICT {t one t two i 2} {t one t two t i}}
          375  +  }
          376  +
          377  +  set res(REPLACE) {one one ii one two 2 two two ii}
          378  +  set res(OMIT)    {one one ii one two i two two ii}
          379  +  do_db2_test 5.$tn.3 {
          380  +    SELECT * FROM "'foolish name'" ORDER BY x, y
          381  +  } $res($conflict_return)
          382  +
          383  +
          384  +  do_test 5.$tn.1 {
          385  +    set schema {
          386  +      CREATE TABLE d1("z""z" PRIMARY KEY, y);
          387  +      INSERT INTO d1 VALUES(1, 'one');
          388  +      INSERT INTO d1 VALUES(2, 'two');
          389  +    }
          390  +    execsql $schema db
          391  +    execsql $schema db2
          392  +
          393  +    execsql { 
          394  +      UPDATE d1 SET y = 'TWO' WHERE "z""z" = 2;
          395  +    } db2
          396  +
          397  +  } {}
          398  +
          399  +  do_conflict_test 5.$tn.2 -tables d1 -sql {
          400  +    DELETE FROM d1 WHERE "z""z" = 2;
          401  +  } -conflicts {
          402  +    {DELETE d1 DATA {i 2 t two} {i 2 t TWO}}
          403  +  }
          404  +
          405  +  set res(REPLACE) {1 one}
          406  +  set res(OMIT)    {1 one 2 TWO}
          407  +  do_db2_test 5.$tn.3 "SELECT * FROM d1" $res($conflict_return)
          408  +}
   286    409   
   287    410   catch { db2 close }
   288    411   finish_test
   289    412