Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Have the sqlite3session_apply() function and its streaming equivalent retry any operations that failed with SQLITE_CONSTRAINT after all other operations on the same table have been attempted. New code is largely untested. |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | session-retry |
Files: | files | file ages | folders |
SHA1: |
1085911afb51744f32fe9db183b50e8e |
User & Date: | dan 2016-03-30 21:19:00.012 |
Context
2016-03-31
| ||
00:45 | Fix the return code in sessionRetryConstraints(). (check-in: ea917c14b9 user: drh tags: session-retry) | |
2016-03-30
| ||
21:19 | Have the sqlite3session_apply() function and its streaming equivalent retry any operations that failed with SQLITE_CONSTRAINT after all other operations on the same table have been attempted. New code is largely untested. (check-in: 1085911afb user: dan tags: session-retry) | |
16:23 | Updates for the MSVC makefiles. (check-in: 7cf0cab730 user: mistachkin tags: trunk) | |
Changes
Changes to ext/session/session1.test.
︙ | ︙ | |||
202 203 204 205 206 207 208 | INSERT INTO t1 VALUES(3, 'three'); INSERT INTO t1 VALUES(4, 'four'); INSERT INTO t1 VALUES(5, 'five'); INSERT INTO t1 VALUES(6, 'six'); INSERT INTO t1 VALUES(7, 'seven'); INSERT INTO t1 VALUES(8, NULL); } -conflicts { | < > | 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | INSERT INTO t1 VALUES(3, 'three'); INSERT INTO t1 VALUES(4, 'four'); INSERT INTO t1 VALUES(5, 'five'); INSERT INTO t1 VALUES(6, 'six'); INSERT INTO t1 VALUES(7, 'seven'); INSERT INTO t1 VALUES(8, NULL); } -conflicts { {INSERT t1 CONFLICT {i 6 t six} {i 6 t VI}} {INSERT t1 CONSTRAINT {i 8 n {}}} } do_db2_test 3.1.3 "SELECT * FROM t1" { 6 VI 3 three 4 four 5 five 7 seven } do_execsql_test 3.1.4 "SELECT * FROM t1" { 1 one 2 two 3 three 4 four 5 five 6 six 7 seven 8 {} |
︙ | ︙ | |||
268 269 270 271 272 273 274 | } 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 { | < > | 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 | } 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 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 7 8 9 x 11 12} do_execsql_test 3.3.5 { SELECT * FROM t4 } {-1 2 3 -1 5 6 {} 8 9 x 11 12} #------------------------------------------------------------------------- # This next block of tests verifies that values returned by the conflict # handler are intepreted correctly. |
︙ | ︙ |
Added ext/session/sessionG.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 | # 2016 March 30 # # 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 sessions module. # Specifically, it tests that UNIQUE constraints are dealt with correctly. # 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 sessionG forcedelete test.db2 sqlite3 db2 test.db2 do_test 1.0 { do_common_sql { CREATE TABLE t1(a PRIMARY KEY, b UNIQUE); INSERT INTO t1 VALUES(1, 'one'); INSERT INTO t1 VALUES(2, 'two'); INSERT INTO t1 VALUES(3, 'three'); } do_then_apply_sql { DELETE FROM t1 WHERE a=1; INSERT INTO t1 VALUES(4, 'one'); } compare_db db db2 } {} do_test 1.1 { do_then_apply_sql { DELETE FROM t1 WHERE a=4; INSERT INTO t1 VALUES(1, 'one'); } compare_db db db2 } {} do_test 1.2 { execsql { INSERT INTO t1 VALUES(5, 'five') } db2 do_then_apply_sql { INSERT INTO t1 VALUES(11, 'eleven'); INSERT INTO t1 VALUES(12, 'five'); } execsql { SELECT * FROM t1 } db2 } {2 two 3 three 1 one 5 five 11 eleven} do_test 1.3 { execsql { SELECT * FROM t1 } } {2 two 3 three 1 one 11 eleven 12 five} finish_test |
Changes to ext/session/sqlite3session.c.
︙ | ︙ | |||
63 64 65 66 67 68 69 70 71 72 73 74 75 76 | /* ** An object of this type is used internally as an abstraction for ** input data. Input data may be supplied either as a single large buffer ** (e.g. sqlite3changeset_start()) or using a stream function (e.g. ** sqlite3changeset_start_strm()). */ struct SessionInput { int iNext; /* Offset in aData[] of next change */ u8 *aData; /* Pointer to buffer containing changeset */ int nData; /* Number of bytes in aData */ SessionBuffer buf; /* Current read buffer */ int (*xInput)(void*, void*, int*); /* Input stream call (or NULL) */ void *pIn; /* First argument to xInput */ | > > | 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | /* ** An object of this type is used internally as an abstraction for ** input data. Input data may be supplied either as a single large buffer ** (e.g. sqlite3changeset_start()) or using a stream function (e.g. ** sqlite3changeset_start_strm()). */ struct SessionInput { int bNoDiscard; /* If true, discard no data */ int iCurrent; /* Offset in aData[] of current change */ int iNext; /* Offset in aData[] of next change */ u8 *aData; /* Pointer to buffer containing changeset */ int nData; /* Number of bytes in aData */ SessionBuffer buf; /* Current read buffer */ int (*xInput)(void*, void*, int*); /* Input stream call (or NULL) */ void *pIn; /* First argument to xInput */ |
︙ | ︙ | |||
2456 2457 2458 2459 2460 2461 2462 | pRet = (sqlite3_changeset_iter *)sqlite3_malloc(nByte); if( !pRet ) return SQLITE_NOMEM; memset(pRet, 0, sizeof(sqlite3_changeset_iter)); pRet->in.aData = (u8 *)pChangeset; pRet->in.nData = nChangeset; pRet->in.xInput = xInput; pRet->in.pIn = pIn; | < | 2458 2459 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 2470 2471 | pRet = (sqlite3_changeset_iter *)sqlite3_malloc(nByte); if( !pRet ) return SQLITE_NOMEM; memset(pRet, 0, sizeof(sqlite3_changeset_iter)); pRet->in.aData = (u8 *)pChangeset; pRet->in.nData = nChangeset; pRet->in.xInput = xInput; pRet->in.pIn = pIn; pRet->in.bEof = (xInput ? 0 : 1); /* Populate the output variable and return success. */ *pp = pRet; return SQLITE_OK; } |
︙ | ︙ | |||
2485 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 | int sqlite3changeset_start_strm( sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn ){ return sessionChangesetStart(pp, xInput, pIn, 0, 0); } /* ** Ensure that there are at least nByte bytes available in the buffer. Or, ** if there are not nByte bytes remaining in the input, that all available ** data is in the buffer. ** ** Return an SQLite error code if an error occurs, or SQLITE_OK otherwise. */ static int sessionInputBuffer(SessionInput *pIn, int nByte){ int rc = SQLITE_OK; if( pIn->xInput ){ while( !pIn->bEof && (pIn->iNext+nByte)>=pIn->nData && rc==SQLITE_OK ){ int nNew = SESSIONS_STRM_CHUNK_SIZE; | > > > > > > > > > > > > > > > > > < < < < < < | | 2486 2487 2488 2489 2490 2491 2492 2493 2494 2495 2496 2497 2498 2499 2500 2501 2502 2503 2504 2505 2506 2507 2508 2509 2510 2511 2512 2513 2514 2515 2516 2517 2518 2519 2520 2521 2522 2523 2524 2525 2526 2527 2528 2529 2530 2531 | int sqlite3changeset_start_strm( sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn ){ return sessionChangesetStart(pp, xInput, pIn, 0, 0); } /* ** If the SessionInput object passed as the only argument is a streaming ** object and the buffer is full, discard some data to free up space. */ static void sessionDiscardData(SessionInput *pIn){ if( pIn->bEof && pIn->xInput && pIn->iNext>=SESSIONS_STRM_CHUNK_SIZE ){ int nMove = pIn->buf.nBuf - pIn->iNext; assert( nMove>=0 ); if( nMove>0 ){ memmove(pIn->buf.aBuf, &pIn->buf.aBuf[pIn->iNext], nMove); } pIn->buf.nBuf -= pIn->iNext; pIn->iNext = 0; pIn->nData = pIn->buf.nBuf; } } /* ** Ensure that there are at least nByte bytes available in the buffer. Or, ** if there are not nByte bytes remaining in the input, that all available ** data is in the buffer. ** ** Return an SQLite error code if an error occurs, or SQLITE_OK otherwise. */ static int sessionInputBuffer(SessionInput *pIn, int nByte){ int rc = SQLITE_OK; if( pIn->xInput ){ while( !pIn->bEof && (pIn->iNext+nByte)>=pIn->nData && rc==SQLITE_OK ){ int nNew = SESSIONS_STRM_CHUNK_SIZE; if( pIn->bNoDiscard==0 ) sessionDiscardData(pIn); if( SQLITE_OK==sessionBufferGrow(&pIn->buf, nNew, &rc) ){ rc = pIn->xInput(pIn->pIn, &pIn->buf.aBuf[pIn->buf.nBuf], &nNew); if( nNew==0 ){ pIn->bEof = 1; }else{ pIn->buf.nBuf += nNew; } |
︙ | ︙ | |||
2814 2815 2816 2817 2818 2819 2820 2821 2822 2823 2824 2825 2826 2827 2828 2829 2830 2831 2832 | if( p->rc!=SQLITE_OK ) return p->rc; /* If the iterator is already at the end of the changeset, return DONE. */ if( p->in.iNext>=p->in.nData ){ return SQLITE_DONE; } op = p->in.aData[p->in.iNext++]; if( op=='T' || op=='P' ){ p->bPatchset = (op=='P'); if( sessionChangesetReadTblhdr(p) ) return p->rc; if( (p->rc = sessionInputBuffer(&p->in, 2)) ) return p->rc; op = p->in.aData[p->in.iNext++]; } p->op = op; p->bIndirect = p->in.aData[p->in.iNext++]; if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){ return (p->rc = SQLITE_CORRUPT_BKPT); | > > > > | 2826 2827 2828 2829 2830 2831 2832 2833 2834 2835 2836 2837 2838 2839 2840 2841 2842 2843 2844 2845 2846 2847 2848 | if( p->rc!=SQLITE_OK ) return p->rc; /* If the iterator is already at the end of the changeset, return DONE. */ if( p->in.iNext>=p->in.nData ){ return SQLITE_DONE; } sessionDiscardData(&p->in); p->in.iCurrent = p->in.iNext; op = p->in.aData[p->in.iNext++]; if( op=='T' || op=='P' ){ p->bPatchset = (op=='P'); if( sessionChangesetReadTblhdr(p) ) return p->rc; if( (p->rc = sessionInputBuffer(&p->in, 2)) ) return p->rc; p->in.iCurrent = p->in.iNext; op = p->in.aData[p->in.iNext++]; } p->op = op; p->bIndirect = p->in.aData[p->in.iNext++]; if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){ return (p->rc = SQLITE_CORRUPT_BKPT); |
︙ | ︙ | |||
3262 3263 3264 3265 3266 3267 3268 3269 3270 3271 3272 3273 3274 3275 | sqlite3_stmt *pDelete; /* DELETE statement */ sqlite3_stmt *pUpdate; /* UPDATE statement */ sqlite3_stmt *pInsert; /* INSERT statement */ sqlite3_stmt *pSelect; /* SELECT statement */ int nCol; /* Size of azCol[] and abPK[] arrays */ const char **azCol; /* Array of column names */ u8 *abPK; /* Boolean array - true if column is in PK */ }; /* ** Formulate a statement to DELETE a row from database db. Assuming a table ** structure like this: ** ** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c)); | > > > | 3278 3279 3280 3281 3282 3283 3284 3285 3286 3287 3288 3289 3290 3291 3292 3293 3294 | sqlite3_stmt *pDelete; /* DELETE statement */ sqlite3_stmt *pUpdate; /* UPDATE statement */ sqlite3_stmt *pInsert; /* INSERT statement */ sqlite3_stmt *pSelect; /* SELECT statement */ int nCol; /* Size of azCol[] and abPK[] arrays */ const char **azCol; /* Array of column names */ u8 *abPK; /* Boolean array - true if column is in PK */ int bDeferConstraints; /* True to defer constraints */ SessionBuffer constraints; /* Deferred constraints are stored here */ }; /* ** Formulate a statement to DELETE a row from database db. Assuming a table ** structure like this: ** ** CREATE TABLE x(a, b, c, d, PRIMARY KEY(a, c)); |
︙ | ︙ | |||
3512 3513 3514 3515 3516 3517 3518 | } /* ** Iterator pIter must point to an SQLITE_INSERT entry. This function ** transfers new.* values from the current iterator entry to statement ** pStmt. The table being inserted into has nCol columns. ** | | | 3531 3532 3533 3534 3535 3536 3537 3538 3539 3540 3541 3542 3543 3544 3545 | } /* ** Iterator pIter must point to an SQLITE_INSERT entry. This function ** transfers new.* values from the current iterator entry to statement ** pStmt. The table being inserted into has nCol columns. ** ** New.* value $i from the iterator is bound to variable ($i+1) of ** statement pStmt. If parameter abPK is NULL, all values from 0 to (nCol-1) ** are transfered to the statement. Otherwise, if abPK is not NULL, it points ** to an array nCol elements in size. In this case only those values for ** which abPK[$i] is true are read from the iterator and bound to the ** statement. ** ** An SQLite error code is returned if an error occurs. Otherwise, SQLITE_OK. |
︙ | ︙ | |||
3658 3659 3660 3661 3662 3663 3664 | if( rc==SQLITE_ROW ){ /* There exists another row with the new.* primary key. */ pIter->pConflict = p->pSelect; res = xConflict(pCtx, eType, pIter); pIter->pConflict = 0; rc = sqlite3_reset(p->pSelect); }else if( rc==SQLITE_OK ){ | > > > > > > > > | | | > | 3677 3678 3679 3680 3681 3682 3683 3684 3685 3686 3687 3688 3689 3690 3691 3692 3693 3694 3695 3696 3697 3698 3699 3700 3701 3702 | if( rc==SQLITE_ROW ){ /* There exists another row with the new.* primary key. */ pIter->pConflict = p->pSelect; res = xConflict(pCtx, eType, pIter); pIter->pConflict = 0; rc = sqlite3_reset(p->pSelect); }else if( rc==SQLITE_OK ){ if( p->bDeferConstraints && eType==SQLITE_CHANGESET_CONFLICT ){ /* Instead of invoking the conflict handler, append the change blob ** to the SessionApplyCtx.constraints buffer. */ u8 *aBlob = &pIter->in.aData[pIter->in.iCurrent]; int nBlob = pIter->in.iNext - pIter->in.iCurrent; sessionAppendBlob(&p->constraints, aBlob, nBlob, &rc); res = SQLITE_CHANGESET_OMIT; }else{ /* No other row with the new.* primary key. */ res = xConflict(pCtx, eType+1, pIter); if( res==SQLITE_CHANGESET_REPLACE ) rc = SQLITE_MISUSE; } } if( rc==SQLITE_OK ){ switch( res ){ case SQLITE_CHANGESET_REPLACE: assert( pbReplace ); *pbReplace = 1; |
︙ | ︙ | |||
3819 3820 3821 3822 3823 3824 3825 3826 3827 3828 3829 3830 3831 3832 | SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace ); } } return rc; } /* ** Argument pIter is a changeset iterator that has been initialized, but ** not yet passed to sqlite3changeset_next(). This function applies the ** changeset to the main database attached to handle "db". The supplied ** conflict handler callback is invoked to resolve any conflicts encountered ** while applying the change. | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 3847 3848 3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 3865 3866 3867 3868 3869 3870 3871 3872 3873 3874 3875 3876 3877 3878 3879 3880 3881 3882 3883 3884 3885 3886 3887 3888 3889 3890 3891 3892 3893 3894 3895 3896 3897 3898 3899 3900 3901 3902 3903 3904 3905 3906 3907 3908 3909 3910 3911 3912 3913 3914 3915 3916 3917 3918 3919 3920 3921 3922 3923 3924 3925 3926 3927 3928 3929 3930 3931 3932 3933 3934 3935 3936 3937 3938 3939 3940 3941 3942 3943 3944 3945 3946 3947 3948 3949 3950 | SQLITE_CHANGESET_CONFLICT, p, pIter, xConflict, pCtx, pbReplace ); } } return rc; } static int sessionApplyOneWithRetry( sqlite3 *db, /* Apply change to "main" db of this handle */ sqlite3_changeset_iter *pIter, /* Changeset iterator to read change from */ SessionApplyCtx *pApply, /* Apply context */ int(*xConflict)(void*, int, sqlite3_changeset_iter*), void *pCtx /* First argument passed to xConflict */ ){ int bReplace = 0; int bRetry = 0; int rc; rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, &bRetry); if( rc==SQLITE_OK && bRetry ){ rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, &bReplace, 0); } if( bReplace ){ assert( pIter->op==SQLITE_INSERT ); rc = sqlite3_exec(db, "SAVEPOINT replace_op", 0, 0, 0); if( rc==SQLITE_OK ){ rc = sessionBindRow(pIter, sqlite3changeset_new, pApply->nCol, pApply->abPK, pApply->pDelete); sqlite3_bind_int(pApply->pDelete, pApply->nCol+1, 1); } if( rc==SQLITE_OK ){ sqlite3_step(pApply->pDelete); rc = sqlite3_reset(pApply->pDelete); } if( rc==SQLITE_OK ){ rc = sessionApplyOneOp(pIter, pApply, xConflict, pCtx, 0, 0); } if( rc==SQLITE_OK ){ rc = sqlite3_exec(db, "RELEASE replace_op", 0, 0, 0); } } return rc; } /* ** Retry the changes accumulated in the pApply->constraints buffer. */ static int sessionRetryConstraints( sqlite3 *db, int bPatchset, const char *zTab, SessionApplyCtx *pApply, int(*xConflict)(void*, int, sqlite3_changeset_iter*), void *pCtx /* First argument passed to xConflict */ ){ int rc = SQLITE_OK; while( pApply->constraints.nBuf ){ sqlite3_changeset_iter *pIter2 = 0; SessionBuffer cons = pApply->constraints; memset(&pApply->constraints, 0, sizeof(SessionBuffer)); rc = sessionChangesetStart(&pIter2, 0, 0, cons.nBuf, cons.aBuf); if( rc==SQLITE_OK ){ int nByte = 2*pApply->nCol*sizeof(sqlite3_value*); int rc2; pIter2->bPatchset = bPatchset; pIter2->zTab = (char*)zTab; pIter2->nCol = pApply->nCol; pIter2->abPK = pApply->abPK; sessionBufferGrow(&pIter2->tblhdr, nByte, &rc); pIter2->apValue = (sqlite3_value**)pIter2->tblhdr.aBuf; if( rc==SQLITE_OK ) memset(pIter2->apValue, 0, nByte); while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter2) ){ rc = sessionApplyOneWithRetry(db, pIter2, pApply, xConflict, pCtx); } rc2 = sqlite3changeset_finalize(pIter2); if( rc==SQLITE_OK ) rc==rc2; } assert( pApply->bDeferConstraints || pApply->constraints.nBuf==0 ); sqlite3_free(cons.aBuf); if( rc!=SQLITE_OK ) break; if( pApply->constraints.nBuf>=cons.nBuf ){ /* No progress was made on the last round. */ pApply->bDeferConstraints = 0; } } return rc; } /* ** Argument pIter is a changeset iterator that has been initialized, but ** not yet passed to sqlite3changeset_next(). This function applies the ** changeset to the main database attached to handle "db". The supplied ** conflict handler callback is invoked to resolve any conflicts encountered ** while applying the change. |
︙ | ︙ | |||
3849 3850 3851 3852 3853 3854 3855 3856 3857 3858 3859 3860 3861 3862 3863 3864 | int rc; /* Return code */ const char *zTab = 0; /* Name of current table */ int nTab = 0; /* Result of sqlite3Strlen30(zTab) */ SessionApplyCtx sApply; /* changeset_apply() context object */ assert( xConflict!=0 ); memset(&sApply, 0, sizeof(sApply)); sqlite3_mutex_enter(sqlite3_db_mutex(db)); rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0); if( rc==SQLITE_OK ){ rc = sqlite3_exec(db, "PRAGMA defer_foreign_keys = 1", 0, 0, 0); } while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){ int nCol; int op; | > < < > > > > > > | 3967 3968 3969 3970 3971 3972 3973 3974 3975 3976 3977 3978 3979 3980 3981 3982 3983 3984 3985 3986 3987 3988 3989 3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 4004 4005 4006 4007 4008 4009 4010 | int rc; /* Return code */ const char *zTab = 0; /* Name of current table */ int nTab = 0; /* Result of sqlite3Strlen30(zTab) */ SessionApplyCtx sApply; /* changeset_apply() context object */ assert( xConflict!=0 ); pIter->in.bNoDiscard = 1; memset(&sApply, 0, sizeof(sApply)); sqlite3_mutex_enter(sqlite3_db_mutex(db)); rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0); if( rc==SQLITE_OK ){ rc = sqlite3_exec(db, "PRAGMA defer_foreign_keys = 1", 0, 0, 0); } while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){ int nCol; int op; const char *zNew; sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0); if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){ u8 *abPK; rc = sessionRetryConstraints( db, pIter->bPatchset, zTab, &sApply, xConflict, pCtx ); if( rc!=SQLITE_OK ) break; sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */ sqlite3_finalize(sApply.pDelete); sqlite3_finalize(sApply.pUpdate); sqlite3_finalize(sApply.pInsert); sqlite3_finalize(sApply.pSelect); memset(&sApply, 0, sizeof(sApply)); sApply.db = db; sApply.bDeferConstraints = 1; /* If an xFilter() callback was specified, invoke it now. If the ** xFilter callback returns zero, skip this table. If it returns ** non-zero, proceed. */ schemaMismatch = (xFilter && (0==xFilter(pCtx, zNew))); if( schemaMismatch ){ zTab = sqlite3_mprintf("%s", zNew); |
︙ | ︙ | |||
3929 3930 3931 3932 3933 3934 3935 | } } /* If there is a schema mismatch on the current table, proceed to the ** next change. A log message has already been issued. */ if( schemaMismatch ) continue; | < < < | | < < < | | < < < < < < < < | < < < < < > | 4052 4053 4054 4055 4056 4057 4058 4059 4060 4061 4062 4063 4064 4065 4066 4067 4068 4069 4070 4071 4072 | } } /* If there is a schema mismatch on the current table, proceed to the ** next change. A log message has already been issued. */ if( schemaMismatch ) continue; rc = sessionApplyOneWithRetry(db, pIter, &sApply, xConflict, pCtx); } if( rc==SQLITE_OK ){ rc = sessionRetryConstraints( db, pIter->bPatchset, zTab, &sApply, xConflict, pCtx ); } if( rc==SQLITE_OK ){ rc = sqlite3changeset_finalize(pIter); }else{ sqlite3changeset_finalize(pIter); } |
︙ | ︙ | |||
3990 3991 3992 3993 3994 3995 3996 3997 3998 3999 4000 4001 4002 4003 | } sqlite3_finalize(sApply.pInsert); 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; } /* ** Apply the changeset passed via pChangeset/nChangeset to the main database ** attached to handle "db". Invoke the supplied conflict handler callback | > | 4095 4096 4097 4098 4099 4100 4101 4102 4103 4104 4105 4106 4107 4108 4109 | } sqlite3_finalize(sApply.pInsert); sqlite3_finalize(sApply.pDelete); sqlite3_finalize(sApply.pUpdate); sqlite3_finalize(sApply.pSelect); sqlite3_free((char*)sApply.azCol); /* cast works around VC++ bug */ sqlite3_free((char*)sApply.constraints.aBuf); sqlite3_mutex_leave(sqlite3_db_mutex(db)); return rc; } /* ** Apply the changeset passed via pChangeset/nChangeset to the main database ** attached to handle "db". Invoke the supplied conflict handler callback |
︙ | ︙ |