Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Start adding the sqlite3changeset_concat() function to the session module. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | sessions |
Files: | files | file ages | folders |
SHA1: |
8927b2260b8d84f53776cb29e1d2fa41 |
User & Date: | dan 2011-04-14 11:16:21.630 |
Context
2011-04-14
| ||
18:01 | Add further tests for the sqlite3changeset_concat() function. Also fixes. (check-in: 1fc3f15d88 user: dan tags: sessions) | |
11:16 | Start adding the sqlite3changeset_concat() function to the session module. (check-in: 8927b2260b user: dan tags: sessions) | |
2011-04-09
| ||
18:07 | Merge the latest trunk changes into the sessions branch. (check-in: 83705e90a5 user: drh tags: sessions) | |
Changes
Added ext/session/session5.test.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | # 2011 April 13 # # The author disclaims copyright to this source code. In place of # a legal notice, here is a blessing: # # May you do good and not evil. # May you find forgiveness for yourself and forgive others. # May you share freely, never taking more than you give. # #*********************************************************************** # This file implements regression tests for the session module. # Specifically, for the sqlite3changeset_concat() command. # if {![info exists testdir]} { set testdir [file join [file dirname [info script]] .. .. test] } source [file join [file dirname [info script]] session_common.tcl] source $testdir/tester.tcl ifcapable !session {finish_test; return} set testprefix session5 proc do_concat_test {tn sql1 sql2} { sqlite3session S1 db main ; S1 attach * sqlite3session S2 db main ; S2 attach * execsql $sql1 set C1 [S1 changeset] S1 delete sqlite3session S1 db main ; S1 attach * execsql $sql2 set C2 [S1 changeset] S1 delete set C3 [S2 changeset] S2 delete set C4 [sqlite3changeset_concat $C1 $C2] set c3 [list] set c4 [list] sqlite3session_foreach c $C3 { lappend c3 $c } sqlite3session_foreach c $C4 { lappend c4 $c } set c3 [lsort $c3] set c4 [lsort $c4] do_test $tn [list set {} $c4] $c3 } do_execsql_test 1.0 { CREATE TABLE t1(a PRIMARY KEY, b); } do_concat_test 1.1.1 { INSERT INTO t1 VALUES(1, 'one'); } { INSERT INTO t1 VALUES(2, 'two'); } do_concat_test 1.1.2 { UPDATE t1 SET b = 'five' WHERE a = 1; } { UPDATE t1 SET b = 'six' WHERE a = 2; } do_concat_test 1.1.3 { DELETE FROM t1 WHERE a = 1; } { DELETE FROM t1 WHERE a = 2; } # 1.2.1: INSERT + DELETE -> (none) # 1.2.2: INSERT + UPDATE -> INSERT # # 1.2.3: DELETE + INSERT (matching data) -> (none) # 1.2.4: DELETE + INSERT (non-matching data) -> UPDATE # # 1.2.5: UPDATE + UPDATE (matching data) -> (none) # 1.2.6: UPDATE + UPDATE (non-matching data) -> UPDATE # 1.2.7: UPDATE + DELETE -> DELETE # do_concat_test 1.2.1 { INSERT INTO t1 VALUES('x', 'y'); } { DELETE FROM t1 WHERE a = 'x'; } do_concat_test 1.2.2 { INSERT INTO t1 VALUES(5.0, 'five'); } { UPDATE t1 SET b = 'six' WHERE a = 5.0; } do_execsql_test 1.2.3.1 "INSERT INTO t1 VALUES('I', 'one')" do_concat_test 1.2.3.2 { DELETE FROM t1 WHERE a = 'I'; } { INSERT INTO t1 VALUES('I', 'one'); } do_concat_test 1.2.4 { DELETE FROM t1 WHERE a = 'I'; } { INSERT INTO t1 VALUES('I', 'two'); } do_concat_test 1.2.5 { UPDATE t1 SET b = 'five' WHERE a = 'I'; } { UPDATE t1 SET b = 'two' WHERE a = 'I'; } do_concat_test 1.2.6 { UPDATE t1 SET b = 'six' WHERE a = 'I'; } { UPDATE t1 SET b = 'seven' WHERE a = 'I'; } do_concat_test 1.2.7 { UPDATE t1 SET b = 'eight' WHERE a = 'I'; } { DELETE FROM t1 WHERE a = 'I'; } finish_test |
Changes to ext/session/sqlite3session.c.
︙ | ︙ | |||
122 123 124 125 126 127 128 129 130 131 132 133 134 135 | ** 1 byte: Constant 0x54 (capital 'T') ** Varint: Big-endian integer set to the number of columns in the table. ** N bytes: Unqualified table name (encoded using UTF-8). Nul-terminated. ** ** Followed by one or more changes to the table. ** ** 1 byte: Either SQLITE_INSERT, UPDATE or DELETE. ** old.* record: (delete and update only) ** new.* record: (insert and update only) */ /* ** For each row modified during a session, there exists a single instance of ** this structure stored in a SessionTable.aChange[] hash table. | > | 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | ** 1 byte: Constant 0x54 (capital 'T') ** Varint: Big-endian integer set to the number of columns in the table. ** N bytes: Unqualified table name (encoded using UTF-8). Nul-terminated. ** ** Followed by one or more changes to the table. ** ** 1 byte: Either SQLITE_INSERT, UPDATE or DELETE. ** 1 byte: The "indirect-change" flag. ** old.* record: (delete and update only) ** new.* record: (insert and update only) */ /* ** For each row modified during a session, there exists a single instance of ** this structure stored in a SessionTable.aChange[] hash table. |
︙ | ︙ | |||
349 350 351 352 353 354 355 | } *piHash = (h % pTab->nChange); return SQLITE_OK; } /* | | < | | | 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 | } *piHash = (h % pTab->nChange); return SQLITE_OK; } /* ** Based on the primary key values stored in change aRecord, calculate a ** hash key, assuming the has table has nBucket buckets. The hash keys ** calculated by this function are compatible with those calculated by ** sessionPreupdateHash(). */ static unsigned int sessionChangeHash( SessionTable *pTab, /* Table handle */ u8 *aRecord, /* Change record */ int nBucket /* Assume this many buckets in hash table */ ){ unsigned int h = 0; /* Value to return */ int i; /* Used to iterate through columns */ u8 *a = aRecord; /* Used to iterate through change record */ for(i=0; i<pTab->nCol; i++){ int eType = *a++; int isPK = pTab->abPK[i]; /* It is not possible for eType to be SQLITE_NULL here. The session ** module does not record changes for rows with NULL values stored in |
︙ | ︙ | |||
388 389 390 391 392 393 394 395 396 397 398 399 400 401 | a += sessionVarintGet(a, &n); if( isPK ) h = sessionHashAppendBlob(h, n, a); a += n; } } return (h % nBucket); } static int sessionPreupdateEqual( sqlite3 *db, SessionTable *pTab, SessionChange *pChange, int bNew, int *pbEqual | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 | a += sessionVarintGet(a, &n); if( isPK ) h = sessionHashAppendBlob(h, n, a); a += n; } } return (h % nBucket); } static int sessionSerialLen(u8 *a){ int e = *a; int n; if( e==0 ) return 1; if( e==SQLITE_NULL ) return 1; if( e==SQLITE_INTEGER || e==SQLITE_FLOAT ) return 9; return sessionVarintGet(&a[1], &n) + 1 + n; } static int sessionChangeEqual( SessionTable *pTab, u8 *aLeft, /* Change record */ u8 *aRight /* Change record */ ){ u8 *a1 = aLeft; u8 *a2 = aRight; int i; for(i=0; i<pTab->nCol; i++){ int n1 = sessionSerialLen(a1); int n2 = sessionSerialLen(a2); if( pTab->abPK[i] && (n1!=n2 || memcmp(a1, a2, n1)) ){ return 0; } a1 += n1; a2 += n1; } return 1; } static void sessionMergeRecord( u8 **paOut, SessionTable *pTab, u8 *aLeft, u8 *aRight ){ u8 *a1 = aLeft; u8 *a2 = aRight; u8 *aOut = *paOut; int i; for(i=0; i<pTab->nCol; i++){ int n1 = sessionSerialLen(a1); int n2 = sessionSerialLen(a2); if( *a2 ){ memcpy(aOut, a2, n2); aOut += n2; }else{ memcpy(aOut, a1, n1); aOut += n1; } a1 += n1; a2 += n2; } *paOut = aOut; } static u8 *sessionMergeValue( u8 **paOne, u8 **paTwo, int *pnVal ){ u8 *a1 = *paOne; u8 *a2 = *paTwo; u8 *pRet = 0; int n1; assert( a1 ); if( a2 ){ int n2 = sessionSerialLen(a2); if( *a2 ){ *pnVal = n2; pRet = a2; } *paTwo = &a2[n2]; } n1 = sessionSerialLen(a1); if( pRet==0 ){ *pnVal = n1; pRet = a1; } *paOne = &a1[n1]; return pRet; } static int sessionMergeUpdate( u8 **paOut, SessionTable *pTab, u8 *aOldRecord1, u8 *aOldRecord2, u8 *aNewRecord1, u8 *aNewRecord2 ){ u8 *aOld1 = aOldRecord1; u8 *aOld2 = aOldRecord2; u8 *aNew1 = aNewRecord1; u8 *aNew2 = aNewRecord2; u8 *aOut = *paOut; int i; int bRequired = 0; assert( aOldRecord1 && aNewRecord1 ); /* Write the old.* vector first. */ for(i=0; i<pTab->nCol; i++){ int nOld; u8 *aOld; int nNew; u8 *aNew; aOld = sessionMergeValue(&aOld1, &aOld2, &nOld); aNew = sessionMergeValue(&aNew1, &aNew2, &nNew); if( pTab->abPK[i] || nOld!=nNew || memcmp(aOld, aNew, nNew) ){ if( pTab->abPK[i]==0 ) bRequired = 1; memcpy(aOut, aOld, nOld); aOut += nOld; }else{ *(aOut++) = '\0'; } } if( !bRequired ) return 0; /* Write the new.* vector */ aOld1 = aOldRecord1; aOld2 = aOldRecord2; aNew1 = aNewRecord1; aNew2 = aNewRecord2; for(i=0; i<pTab->nCol; i++){ int nOld; u8 *aOld; int nNew; u8 *aNew; aOld = sessionMergeValue(&aOld1, &aOld2, &nOld); aNew = sessionMergeValue(&aNew1, &aNew2, &nNew); if( pTab->abPK[i] || (nOld==nNew && 0==memcmp(aOld, aNew, nNew)) ){ *(aOut++) = '\0'; }else{ memcpy(aOut, aNew, nNew); aOut += nNew; } } *paOut = aOut; return 1; } static int sessionPreupdateEqual( sqlite3 *db, SessionTable *pTab, SessionChange *pChange, int bNew, int *pbEqual |
︙ | ︙ | |||
476 477 478 479 480 481 482 | ** SQLITE_OK. ** ** It is possible that a non-fatal OOM error occurs in this function. In ** that case the hash-table does not grow, but SQLITE_OK is returned anyway. ** Growing the hash table in this case is a performance optimization only, ** it is not required for correct operation. */ | | < | | 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 | ** SQLITE_OK. ** ** It is possible that a non-fatal OOM error occurs in this function. In ** that case the hash-table does not grow, but SQLITE_OK is returned anyway. ** Growing the hash table in this case is a performance optimization only, ** it is not required for correct operation. */ static int sessionGrowHash(SessionTable *pTab){ if( pTab->nChange==0 || pTab->nEntry>=(pTab->nChange/2) ){ int i; SessionChange **apNew; int nNew = (pTab->nChange ? pTab->nChange : 128) * 2; apNew = (SessionChange **)sqlite3_malloc(sizeof(SessionChange *) * nNew); if( apNew==0 ){ if( pTab->nChange==0 ){ return SQLITE_ERROR; } return SQLITE_OK; } memset(apNew, 0, sizeof(SessionChange *) * nNew); for(i=0; i<pTab->nChange; i++){ SessionChange *p; SessionChange *pNext; for(p=pTab->apChange[i]; p; p=pNext){ int iHash = sessionChangeHash(pTab, p->aRecord, nNew); pNext = p->pNext; p->pNext = apNew[iHash]; apNew[iHash] = p; } } sqlite3_free(pTab->apChange); |
︙ | ︙ | |||
673 674 675 676 677 678 679 | if( pSession->rc ) return; /* Load table details if required */ if( sessionInitTable(pSession, pTab) ) return; /* Grow the hash table if required */ | | > > > | | | 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 | if( pSession->rc ) return; /* Load table details if required */ if( sessionInitTable(pSession, pTab) ) return; /* Grow the hash table if required */ if( sessionGrowHash(pTab) ){ pSession->rc = SQLITE_NOMEM; return; } /* Search the hash table for an existing entry for rowid=iKey2. If ** one is found, store a pointer to it in pChange and unlink it from ** the hash table. Otherwise, set pChange to NULL. */ rc = sessionPreupdateHash(db, pTab, op==SQLITE_INSERT, &iHash, &bNullPk); if( rc==SQLITE_OK && bNullPk==0 ){ SessionChange *pC; for(pC=pTab->apChange[iHash]; rc==SQLITE_OK && pC; pC=pC->pNext){ int bEqual; rc = sessionPreupdateEqual(db, pTab, pC, op==SQLITE_INSERT, &bEqual); if( bEqual ) break; } if( pC==0 ){ /* Create a new change object containing all the old values (if ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK ** values (if this is an INSERT). */ SessionChange *pChange; /* New change object */ int nByte; /* Number of bytes to allocate */ int i; /* Used to iterate through columns */ assert( rc==SQLITE_OK ); pTab->nEntry++; |
︙ | ︙ | |||
846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 | pOld = (sqlite3_session*)sqlite3_preupdate_hook(db, xPreUpdate, (void*)pNew); pNew->pNext = pOld; sqlite3_mutex_leave(sqlite3_db_mutex(db)); *ppSession = pNew; return SQLITE_OK; } /* ** Delete a session object previously allocated using sqlite3session_create(). */ void sqlite3session_delete(sqlite3_session *pSession){ sqlite3 *db = pSession->db; sqlite3_session *pHead; sqlite3_session **pp; /* Unlink the session from the linked list of sessions attached to the ** database handle. Hold the db mutex while doing so. */ sqlite3_mutex_enter(sqlite3_db_mutex(db)); pHead = (sqlite3_session*)sqlite3_preupdate_hook(db, 0, 0); for(pp=&pHead; (*pp)!=pSession; pp=&((*pp)->pNext)); *pp = (*pp)->pNext; if( pHead ) sqlite3_preupdate_hook(db, xPreUpdate, (void *)pHead); sqlite3_mutex_leave(sqlite3_db_mutex(db)); /* Delete all attached table objects. And the contents of their ** associated hash-tables. */ | > > > > > > > > > > > > > > > > > > > > > | < < < < < < < < < < < < < < < | 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 | pOld = (sqlite3_session*)sqlite3_preupdate_hook(db, xPreUpdate, (void*)pNew); pNew->pNext = pOld; sqlite3_mutex_leave(sqlite3_db_mutex(db)); *ppSession = pNew; return SQLITE_OK; } void sessionDeleteTable(SessionTable *pList){ SessionTable *pNext; SessionTable *pTab; for(pTab=pList; pTab; pTab=pNext){ int i; pNext = pTab->pNext; for(i=0; i<pTab->nChange; i++){ SessionChange *p; SessionChange *pNext; for(p=pTab->apChange[i]; p; p=pNext){ pNext = p->pNext; sqlite3_free(p); } } sqlite3_free((char*)pTab->azCol); /* cast works around VC++ bug */ sqlite3_free(pTab->apChange); sqlite3_free(pTab); } } /* ** Delete a session object previously allocated using sqlite3session_create(). */ void sqlite3session_delete(sqlite3_session *pSession){ sqlite3 *db = pSession->db; sqlite3_session *pHead; sqlite3_session **pp; /* Unlink the session from the linked list of sessions attached to the ** database handle. Hold the db mutex while doing so. */ sqlite3_mutex_enter(sqlite3_db_mutex(db)); pHead = (sqlite3_session*)sqlite3_preupdate_hook(db, 0, 0); for(pp=&pHead; (*pp)!=pSession; pp=&((*pp)->pNext)); *pp = (*pp)->pNext; if( pHead ) sqlite3_preupdate_hook(db, xPreUpdate, (void *)pHead); sqlite3_mutex_leave(sqlite3_db_mutex(db)); /* Delete all attached table objects. And the contents of their ** associated hash-tables. */ sessionDeleteTable(pSession->pTable); /* Free the session object itself. */ sqlite3_free(pSession); } /* ** Attach a table to a session. All subsequent changes made to the table |
︙ | ︙ | |||
1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 | break; } } } return rc; } /* ** Obtain a changeset object containing all changes recorded by the ** session object passed as the first argument. ** ** It is the responsibility of the caller to eventually free the buffer ** using sqlite3_free(). | > > > > > > > > > > > > | 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491 1492 1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 | break; } } } return rc; } static void sessionAppendTableHdr( SessionBuffer *pBuf, SessionTable *pTab, int *pRc ){ /* Write a table header */ sessionAppendByte(pBuf, 'T', pRc); sessionAppendVarint(pBuf, pTab->nCol, pRc); sessionAppendBlob(pBuf, pTab->abPK, pTab->nCol, pRc); sessionAppendBlob(pBuf, (u8 *)pTab->zName, strlen(pTab->zName)+1, pRc); } /* ** Obtain a changeset object containing all changes recorded by the ** session object passed as the first argument. ** ** It is the responsibility of the caller to eventually free the buffer ** using sqlite3_free(). |
︙ | ︙ | |||
1365 1366 1367 1368 1369 1370 1371 | /* Check the table schema is still Ok. */ rc = sessionTableInfo(db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK); if( !rc && (pTab->nCol!=nCol || memcmp(abPK, pTab->abPK, nCol)) ){ rc = SQLITE_SCHEMA; } /* Write a table header */ | | < < < | 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 | /* Check the table schema is still Ok. */ rc = sessionTableInfo(db, pSession->zDb, zName, &nCol, 0, &azCol, &abPK); if( !rc && (pTab->nCol!=nCol || memcmp(abPK, pTab->abPK, nCol)) ){ rc = SQLITE_SCHEMA; } /* Write a table header */ sessionAppendTableHdr(&buf, pTab, &rc); /* Build and compile a statement to execute: */ if( rc==SQLITE_OK ){ rc = sessionSelectStmt( db, pSession->zDb, zName, nCol, azCol, abPK, &pSel); } |
︙ | ︙ | |||
1521 1522 1523 1524 1525 1526 1527 | if( apOut ){ apOut[i] = sqlite3ValueNew(0); if( !apOut[i] ) return SQLITE_NOMEM; } if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ int nByte; | < > | 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 | if( apOut ){ apOut[i] = sqlite3ValueNew(0); if( !apOut[i] ) return SQLITE_NOMEM; } if( eType==SQLITE_TEXT || eType==SQLITE_BLOB ){ int nByte; aRec += sessionVarintGet(aRec, &nByte); if( apOut ){ int enc = (eType==SQLITE_TEXT ? SQLITE_UTF8 : 0); sqlite3ValueSetStr(apOut[i], nByte, aRec, enc, SQLITE_STATIC); } aRec += nByte; } if( eType==SQLITE_INTEGER || eType==SQLITE_FLOAT ){ if( apOut ){ sqlite3_int64 v = sessionGetI64(aRec); |
︙ | ︙ | |||
1548 1549 1550 1551 1552 1553 1554 | } } *paChange = aRec; return SQLITE_OK; } | < < < < < < | < | > > > > > | < | > | | | | | | | | | > > | | | < > > | | > > > > > > > > > > > > > > | 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 | } } *paChange = aRec; return SQLITE_OK; } static int sessionChangesetNext( sqlite3_changeset_iter *p, u8 **paRec, int *pnRec ){ u8 *aChange; int i; u8 c; assert( (paRec==0 && pnRec==0) || (paRec && pnRec) ); /* If the iterator is in the error-state, return immediately. */ if( p->rc!=SQLITE_OK ) return p->rc; /* Free the current contents of p->apValue[], if any. */ if( p->apValue ){ for(i=0; i<p->nCol*2; i++){ sqlite3ValueFree(p->apValue[i]); } memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2); } /* If the iterator is already at the end of the changeset, return DONE. */ if( p->pNext>=&p->aChangeset[p->nChangeset] ){ return SQLITE_DONE; } aChange = p->pNext; if( aChange[0]=='T' ){ int nByte; /* Bytes to allocate for apValue */ aChange++; aChange += sessionVarintGet(aChange, &p->nCol); p->abPK = (u8 *)aChange; aChange += p->nCol; p->zTab = (char *)aChange; aChange += (sqlite3Strlen30((char *)aChange) + 1); if( paRec==0 ){ sqlite3_free(p->apValue); nByte = sizeof(sqlite3_value *) * p->nCol * 2; p->apValue = (sqlite3_value **)sqlite3_malloc(nByte); if( !p->apValue ){ return (p->rc = SQLITE_NOMEM); } memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2); } } p->op = *(aChange++); p->bIndirect = *(aChange++); if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){ return (p->rc = SQLITE_CORRUPT); } if( paRec ){ *paRec = aChange; } /* If this is an UPDATE or DELETE, read the old.* record. */ if( p->op!=SQLITE_INSERT ){ p->rc = sessionReadRecord(&aChange, p->nCol, paRec?0:p->apValue); if( p->rc!=SQLITE_OK ) return p->rc; } /* If this is an INSERT or UPDATE, read the new.* record. */ if( p->op!=SQLITE_DELETE ){ p->rc = sessionReadRecord(&aChange, p->nCol, paRec?0:&p->apValue[p->nCol]); if( p->rc!=SQLITE_OK ) return p->rc; } if( pnRec ){ *pnRec = aChange - *paRec; } p->pNext = aChange; return SQLITE_ROW; } /* ** Advance an iterator created by sqlite3changeset_start() to the next ** change in the changeset. This function may return SQLITE_ROW, SQLITE_DONE ** or SQLITE_CORRUPT. ** ** This function may not be called on iterators passed to a conflict handler ** callback by changeset_apply(). */ int sqlite3changeset_next(sqlite3_changeset_iter *p){ return sessionChangesetNext(p, 0, 0); } /* ** The following function extracts information on the current change ** from a changeset iterator. They may only be called after changeset_next() ** has returned SQLITE_ROW. */ |
︙ | ︙ | |||
2530 2531 2532 2533 2534 2535 2536 2537 2538 | sqlite3_finalize(sApply.pDelete); sqlite3_finalize(sApply.pUpdate); sqlite3_finalize(sApply.pSelect); sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */ sqlite3_mutex_leave(sqlite3_db_mutex(db)); return rc; } #endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */ | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 2716 2717 2718 2719 2720 2721 2722 2723 2724 2725 2726 2727 2728 2729 2730 2731 2732 2733 2734 2735 2736 2737 2738 2739 2740 2741 2742 2743 2744 2745 2746 2747 2748 2749 2750 2751 2752 2753 2754 2755 2756 2757 2758 2759 2760 2761 2762 2763 2764 2765 2766 2767 2768 2769 2770 2771 2772 2773 2774 2775 2776 2777 2778 2779 2780 2781 2782 2783 2784 2785 2786 2787 2788 2789 2790 2791 2792 2793 2794 2795 2796 2797 2798 2799 2800 2801 2802 2803 2804 2805 2806 2807 2808 2809 2810 2811 2812 2813 2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 2849 2850 2851 2852 2853 2854 2855 2856 2857 2858 2859 2860 2861 2862 2863 2864 2865 2866 2867 2868 2869 2870 2871 2872 2873 2874 2875 2876 2877 2878 2879 2880 2881 2882 2883 2884 2885 2886 2887 2888 2889 2890 2891 2892 2893 2894 2895 2896 2897 2898 2899 2900 2901 2902 2903 2904 2905 2906 2907 2908 2909 2910 2911 2912 2913 2914 2915 2916 2917 2918 2919 2920 2921 2922 2923 2924 2925 2926 2927 2928 2929 2930 2931 2932 2933 2934 2935 2936 2937 2938 2939 2940 2941 2942 2943 2944 2945 2946 2947 2948 2949 2950 2951 2952 2953 2954 2955 2956 2957 2958 2959 2960 2961 2962 2963 2964 2965 | sqlite3_finalize(sApply.pDelete); sqlite3_finalize(sApply.pUpdate); sqlite3_finalize(sApply.pSelect); sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */ sqlite3_mutex_leave(sqlite3_db_mutex(db)); return rc; } static int sessionChangeMerge( SessionTable *pTab, SessionChange *pExist, int op2, int bIndirect, u8 *aRec, int nRec, SessionChange **ppNew ){ SessionChange *pNew = 0; if( !pExist ){ pNew = (SessionChange *)sqlite3_malloc(sizeof(SessionChange)); if( !pNew ){ return SQLITE_NOMEM; } memset(pNew, 0, sizeof(SessionChange)); pNew->bInsert = op2; pNew->bIndirect = bIndirect; pNew->nRecord = nRec; pNew->aRecord = aRec; }else{ int op1 = pExist->bInsert; /* ** op1=INSERT, op2=INSERT -> Unsupported. Discard op2. ** op1=INSERT, op2=UPDATE -> INSERT. ** op1=INSERT, op2=DELETE -> (none) ** ** op1=UPDATE, op2=INSERT -> Unsupported. Discard op2. ** op1=UPDATE, op2=UPDATE -> UPDATE. ** op1=UPDATE, op2=DELETE -> DELETE. ** ** op1=DELETE, op2=INSERT -> UPDATE. ** op1=DELETE, op2=UPDATE -> Unsupported. Discard op2. ** op1=DELETE, op2=DELETE -> Unsupported. Discard op2. */ if( (op1==SQLITE_INSERT && op2==SQLITE_INSERT) || (op1==SQLITE_UPDATE && op2==SQLITE_INSERT) || (op1==SQLITE_DELETE && op2==SQLITE_UPDATE) || (op1==SQLITE_DELETE && op2==SQLITE_DELETE) ){ pNew = pExist; }else if( op1==SQLITE_INSERT && op2==SQLITE_DELETE ){ sqlite3_free(pExist); assert( pNew==0 ); }else{ int nByte; u8 *aCsr; nByte = sizeof(SessionChange) + pExist->nRecord + nRec; pNew = (SessionChange *)sqlite3_malloc(nByte); if( !pNew ){ return SQLITE_NOMEM; } memset(pNew, 0, sizeof(SessionChange)); pNew->bIndirect = (bIndirect && pExist->bIndirect); aCsr = pNew->aRecord = (u8 *)&pNew[1]; if( op1==SQLITE_INSERT && op2==SQLITE_UPDATE ){ u8 *a1 = aRec; pNew->bInsert = SQLITE_INSERT; sessionReadRecord(&a1, pTab->nCol, 0); sessionMergeRecord(&aCsr, pTab, pExist->aRecord, a1); } else if( op1==SQLITE_UPDATE && op2==SQLITE_UPDATE ){ u8 *a1 = pExist->aRecord; u8 *a2 = aRec; sessionReadRecord(&a1, pTab->nCol, 0); sessionReadRecord(&a2, pTab->nCol, 0); pNew->bInsert = SQLITE_UPDATE; if( 0==sessionMergeUpdate(&aCsr, pTab, aRec, pExist->aRecord, a1, a2) ){ sqlite3_free(pNew); pNew = 0; } } else if( op1==SQLITE_UPDATE && op2==SQLITE_DELETE ){ pNew->bInsert = SQLITE_DELETE; sessionMergeRecord(&aCsr, pTab, aRec, pExist->aRecord); } else if( op1==SQLITE_DELETE && op2==SQLITE_INSERT ){ pNew->bInsert = SQLITE_UPDATE; if( 0==sessionMergeUpdate(&aCsr, pTab, pExist->aRecord, 0, aRec, 0) ){ sqlite3_free(pNew); pNew = 0; } } if( pNew ){ pNew->nRecord = (aCsr - pNew->aRecord); } sqlite3_free(pExist); } } *ppNew = pNew; return SQLITE_OK; } int sessionConcatChangeset( int nChangeset, void *pChangeset, SessionTable **ppTabList ){ u8 *aRec; int nRec; sqlite3_changeset_iter *pIter; int rc; SessionTable *pTab = 0; rc = sqlite3changeset_start(&pIter, nChangeset, pChangeset); if( rc!=SQLITE_OK ) return rc; while( SQLITE_ROW==sessionChangesetNext(pIter, &aRec, &nRec) ){ const char *zNew; int nCol; int op; int iHash; int bIndirect; SessionChange *pChange; SessionChange *pExist = 0; SessionChange **pp; assert( pIter->apValue==0 ); sqlite3changeset_op(pIter, &zNew, &nCol, &op, &bIndirect); if( !pTab || zNew!=pTab->zName ){ /* Search the list for a matching table */ int nNew = strlen(zNew); for(pTab = *ppTabList; pTab; pTab=pTab->pNext){ if( 0==sqlite3_strnicmp(pTab->zName, zNew, nNew+1) ) break; } if( !pTab ){ pTab = sqlite3_malloc(sizeof(SessionTable)); if( !pTab ) break; memset(pTab, 0, sizeof(SessionTable)); pTab->pNext = *ppTabList; *ppTabList = pTab; } pTab->zName = (char *)zNew; pTab->nCol = nCol; sqlite3changeset_pk(pIter, &pTab->abPK, 0); } if( sessionGrowHash(pTab) ) break; iHash = sessionChangeHash(pTab, aRec, pTab->nChange); /* Search for existing entry. If found, remove it from the hash table. ** Code below may link it back in. */ for(pp=&pTab->apChange[iHash]; *pp; pp=&(*pp)->pNext){ if( sessionChangeEqual(pTab, (*pp)->aRecord, aRec) ){ pExist = *pp; *pp = (*pp)->pNext; pTab->nEntry--; break; } } rc = sessionChangeMerge(pTab, pExist, op, bIndirect, aRec, nRec, &pChange); if( rc ) break; if( pChange ){ pChange->pNext = pTab->apChange[iHash]; pTab->apChange[iHash] = pChange; pTab->nEntry++; } } if( rc==SQLITE_OK ){ rc = sqlite3changeset_finalize(pIter); }else{ sqlite3changeset_finalize(pIter); } return rc; } /* ** 1. Iterate through the left-hand changeset. Add an entry to a table ** specific hash table for each change in the changeset. The hash table ** key is the PK of the row affected by the change. ** ** 2. Then interate through the right-hand changeset. Attempt to add an ** entry to a hash table for each component change. If a change already ** exists with the same PK values, combine the two into a single change. ** ** 3. Write an output changeset based on the contents of the hash table. */ int sqlite3changeset_concat( int nLeft, /* Number of bytes in lhs input */ void *pLeft, /* Lhs input changeset */ int nRight /* Number of bytes in rhs input */, void *pRight, /* Rhs input changeset */ int *pnOut, /* OUT: Number of bytes in output changeset */ void **ppOut /* OUT: changeset (left <concat> right) */ ){ SessionTable *pList = 0; /* List of SessionTable objects */ int rc; /* Return code */ *pnOut = 0; *ppOut = 0; rc = sessionConcatChangeset(nLeft, pLeft, &pList); if( rc==SQLITE_OK ){ rc = sessionConcatChangeset(nRight, pRight, &pList); } /* Create the serialized output changeset based on the contents of the ** hash tables attached to the SessionTable objects in list pList. */ if( rc==SQLITE_OK ){ SessionTable *pTab; SessionBuffer buf = {0, 0, 0}; for(pTab=pList; pTab; pTab=pTab->pNext){ int i; if( pTab->nEntry==0 ) continue; sessionAppendTableHdr(&buf, pTab, &rc); for(i=0; i<pTab->nChange; i++){ SessionChange *p; for(p=pTab->apChange[i]; p; p=p->pNext){ sessionAppendByte(&buf, p->bInsert, &rc); sessionAppendByte(&buf, p->bIndirect, &rc); sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc); } } } if( rc==SQLITE_OK ){ *ppOut = buf.aBuf; *pnOut = buf.nBuf; }else{ sqlite3_free(buf.aBuf); } } concat_out: sessionDeleteTable(pList); return rc; } #endif /* SQLITE_ENABLE_SESSION && SQLITE_ENABLE_PREUPDATE_HOOK */ |
Changes to ext/session/sqlite3session.h.
︙ | ︙ | |||
517 518 519 520 521 522 523 524 525 526 527 528 529 530 | ** WARNING/TODO: This function currently assumes that the input is a valid ** changeset. If it is not, the results are undefined. */ int sqlite3changeset_invert( int nIn, void *pIn, /* Input changeset */ int *pnOut, void **ppOut /* OUT: Inverse of input */ ); /* ** CAPI3REF: Apply A Changeset To A Database ** ** Apply a changeset to a database. This function attempts to update the ** "main" database attached to handle db with the changes found in the ** changeset passed via the second and third arguments. | > > > > > > | 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 | ** WARNING/TODO: This function currently assumes that the input is a valid ** changeset. If it is not, the results are undefined. */ int sqlite3changeset_invert( int nIn, void *pIn, /* Input changeset */ int *pnOut, void **ppOut /* OUT: Inverse of input */ ); int sqlite3changeset_concat( int nLeft, void *pLeft, /* Input changeset */ int nRight, void *Right, /* Input changeset */ int *pnOut, void **ppOut /* OUT: Inverse of input */ ); /* ** CAPI3REF: Apply A Changeset To A Database ** ** Apply a changeset to a database. This function attempts to update the ** "main" database attached to handle db with the changes found in the ** changeset passed via the second and third arguments. |
︙ | ︙ |
Changes to ext/session/test_session.c.
︙ | ︙ | |||
405 406 407 408 409 410 411 412 413 414 415 416 417 418 | if( rc!=SQLITE_OK ){ return test_session_error(interp, rc); } Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((unsigned char *)aOut, nOut)); sqlite3_free(aOut); return TCL_OK; } /* ** sqlite3session_foreach VARNAME CHANGESET SCRIPT */ static int test_sqlite3session_foreach( void * clientData, Tcl_Interp *interp, | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 | if( rc!=SQLITE_OK ){ return test_session_error(interp, rc); } Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((unsigned char *)aOut, nOut)); sqlite3_free(aOut); return TCL_OK; } /* ** sqlite3changeset_concat LEFT RIGHT */ static int test_sqlite3changeset_concat( void * clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[] ){ int rc; /* Return code from changeset_invert() */ void *aLeft; /* Input changeset */ int nLeft; /* Size of buffer aChangeset in bytes */ void *aRight; /* Input changeset */ int nRight; /* Size of buffer aChangeset in bytes */ void *aOut; /* Output changeset */ int nOut; /* Size of buffer aOut in bytes */ if( objc!=3 ){ Tcl_WrongNumArgs(interp, 1, objv, "LEFT RIGHT"); return TCL_ERROR; } aLeft = (void *)Tcl_GetByteArrayFromObj(objv[1], &nLeft); aRight = (void *)Tcl_GetByteArrayFromObj(objv[2], &nRight); rc = sqlite3changeset_concat(nLeft, aLeft, nRight, aRight, &nOut, &aOut); if( rc!=SQLITE_OK ){ return test_session_error(interp, rc); } Tcl_SetObjResult(interp, Tcl_NewByteArrayObj((unsigned char *)aOut, nOut)); sqlite3_free(aOut); return TCL_OK; } /* ** sqlite3session_foreach VARNAME CHANGESET SCRIPT */ static int test_sqlite3session_foreach( void * clientData, Tcl_Interp *interp, |
︙ | ︙ | |||
510 511 512 513 514 515 516 517 518 519 520 521 522 523 | Tcl_CreateObjCommand(interp, "sqlite3session", test_sqlite3session, 0, 0); Tcl_CreateObjCommand( interp, "sqlite3session_foreach", test_sqlite3session_foreach, 0, 0 ); Tcl_CreateObjCommand( interp, "sqlite3changeset_invert", test_sqlite3changeset_invert, 0, 0 ); Tcl_CreateObjCommand( interp, "sqlite3changeset_apply", test_sqlite3changeset_apply, 0, 0 ); return TCL_OK; } #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */ | > > > | 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 | Tcl_CreateObjCommand(interp, "sqlite3session", test_sqlite3session, 0, 0); Tcl_CreateObjCommand( interp, "sqlite3session_foreach", test_sqlite3session_foreach, 0, 0 ); Tcl_CreateObjCommand( interp, "sqlite3changeset_invert", test_sqlite3changeset_invert, 0, 0 ); Tcl_CreateObjCommand( interp, "sqlite3changeset_concat", test_sqlite3changeset_concat, 0, 0 ); Tcl_CreateObjCommand( interp, "sqlite3changeset_apply", test_sqlite3changeset_apply, 0, 0 ); return TCL_OK; } #endif /* SQLITE_TEST && SQLITE_SESSION && SQLITE_PREUPDATE_HOOK */ |