Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Add the "indirect flag" to the changeset blob format. Also the sqlite3session_indirect() API. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | sessions |
Files: | files | file ages | folders |
SHA1: |
1feaf2d35fd9ec777319717ae2c2929d |
User & Date: | dan 2011-03-23 16:03:12 |
Context
2011-03-23
| ||
22:48 | Merge in all the latest changes from the trunk, and especially the interface changes to the SystemCall methods of the VFS. check-in: 9c3a6e47 user: drh tags: sessions | |
16:03 | Add the "indirect flag" to the changeset blob format. Also the sqlite3session_indirect() API. check-in: 1feaf2d3 user: dan tags: sessions | |
2011-03-22
| ||
18:45 | Add API function sqlite3_preupdate_depth(), for determining the depth of the trigger stack from within a pre-update callback. check-in: bdea7089 user: dan tags: sessions | |
Changes
Changes to ext/session/session1.test.
84 84 sqlite3session S db main 85 85 S attach t1 86 86 execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') } 87 87 execsql { INSERT INTO t1 VALUES(2, 'Ayutthaya') } 88 88 execsql { INSERT INTO t1 VALUES(3, 'Thonburi') } 89 89 } {} 90 90 do_changeset_test 2.1.2 S { 91 - {INSERT t1 {} {i 1 t Sukhothai}} 92 - {INSERT t1 {} {i 2 t Ayutthaya}} 93 - {INSERT t1 {} {i 3 t Thonburi}} 91 + {INSERT t1 0 {} {i 1 t Sukhothai}} 92 + {INSERT t1 0 {} {i 2 t Ayutthaya}} 93 + {INSERT t1 0 {} {i 3 t Thonburi}} 94 94 } 95 95 do_changeset_invert_test 2.1.3 S { 96 - {DELETE t1 {i 1 t Sukhothai} {}} 97 - {DELETE t1 {i 2 t Ayutthaya} {}} 98 - {DELETE t1 {i 3 t Thonburi} {}} 96 + {DELETE t1 0 {i 1 t Sukhothai} {}} 97 + {DELETE t1 0 {i 2 t Ayutthaya} {}} 98 + {DELETE t1 0 {i 3 t Thonburi} {}} 99 99 } 100 100 do_test 2.1.4 { S delete } {} 101 101 102 102 do_test 2.2.1 { 103 103 sqlite3session S db main 104 104 S attach t1 105 105 execsql { DELETE FROM t1 WHERE 1 } 106 106 } {} 107 107 do_changeset_test 2.2.2 S { 108 - {DELETE t1 {i 1 t Sukhothai} {}} 109 - {DELETE t1 {i 2 t Ayutthaya} {}} 110 - {DELETE t1 {i 3 t Thonburi} {}} 108 + {DELETE t1 0 {i 1 t Sukhothai} {}} 109 + {DELETE t1 0 {i 2 t Ayutthaya} {}} 110 + {DELETE t1 0 {i 3 t Thonburi} {}} 111 111 } 112 112 do_changeset_invert_test 2.2.3 S { 113 - {INSERT t1 {} {i 1 t Sukhothai}} 114 - {INSERT t1 {} {i 2 t Ayutthaya}} 115 - {INSERT t1 {} {i 3 t Thonburi}} 113 + {INSERT t1 0 {} {i 1 t Sukhothai}} 114 + {INSERT t1 0 {} {i 2 t Ayutthaya}} 115 + {INSERT t1 0 {} {i 3 t Thonburi}} 116 116 } 117 117 do_test 2.2.4 { S delete } {} 118 118 119 119 do_test 2.3.1 { 120 120 execsql { DELETE FROM t1 } 121 121 sqlite3session S db main 122 122 execsql { INSERT INTO t1 VALUES(1, 'Sukhothai') } ................................................................................ 127 127 UPDATE t1 SET x = 10 WHERE x = 1; 128 128 UPDATE t1 SET y = 'Surin' WHERE x = 2; 129 129 UPDATE t1 SET x = 20, y = 'Thapae' WHERE x = 3; 130 130 } 131 131 } {} 132 132 133 133 do_changeset_test 2.3.2 S { 134 - {INSERT t1 {} {i 10 t Sukhothai}} 135 - {DELETE t1 {i 1 t Sukhothai} {}} 136 - {UPDATE t1 {i 2 t Ayutthaya} {{} {} t Surin}} 137 - {DELETE t1 {i 3 t Thonburi} {}} 138 - {INSERT t1 {} {i 20 t Thapae}} 134 + {INSERT t1 0 {} {i 10 t Sukhothai}} 135 + {DELETE t1 0 {i 1 t Sukhothai} {}} 136 + {UPDATE t1 0 {i 2 t Ayutthaya} {{} {} t Surin}} 137 + {DELETE t1 0 {i 3 t Thonburi} {}} 138 + {INSERT t1 0 {} {i 20 t Thapae}} 139 139 } 140 140 141 141 do_changeset_invert_test 2.3.3 S { 142 - {DELETE t1 {i 10 t Sukhothai} {}} 143 - {INSERT t1 {} {i 1 t Sukhothai}} 144 - {UPDATE t1 {{} {} t Surin} {i 2 t Ayutthaya}} 145 - {INSERT t1 {} {i 3 t Thonburi}} 146 - {DELETE t1 {i 20 t Thapae} {}} 142 + {DELETE t1 0 {i 10 t Sukhothai} {}} 143 + {INSERT t1 0 {} {i 1 t Sukhothai}} 144 + {UPDATE t1 0 {{} {} t Surin} {i 2 t Ayutthaya}} 145 + {INSERT t1 0 {} {i 3 t Thonburi}} 146 + {DELETE t1 0 {i 20 t Thapae} {}} 147 147 } 148 148 do_test 2.3.4 { S delete } {} 149 149 150 150 do_test 2.4.1 { 151 151 sqlite3session S db main 152 152 S attach t1 153 153 execsql { INSERT INTO t1 VALUES(100, 'Bangkok') }
Changes to ext/session/session2.test.
37 37 CREATE TABLE t1(a PRIMARY KEY, b); 38 38 INSERT INTO t1 VALUES('i', 'one'); 39 39 } 40 40 do_iterator_test 1.1 t1 { 41 41 DELETE FROM t1 WHERE a = 'i'; 42 42 INSERT INTO t1 VALUES('ii', 'two'); 43 43 } { 44 - {DELETE t1 {t i t one} {}} 45 - {INSERT t1 {} {t ii t two}} 44 + {DELETE t1 0 {t i t one} {}} 45 + {INSERT t1 0 {} {t ii t two}} 46 46 } 47 47 do_iterator_test 1.2 t1 { 48 48 INSERT INTO t1 VALUES(1.5, 99.9) 49 49 } { 50 - {INSERT t1 {} {f 1.5 f 99.9}} 50 + {INSERT t1 0 {} {f 1.5 f 99.9}} 51 51 } 52 52 53 53 54 54 # Execute each of the following blocks of SQL on database [db1]. Collect 55 55 # changes using a session object. Apply the resulting changeset to 56 56 # database [db2]. Then check that the contents of the two databases are 57 57 # identical. ................................................................................ 224 224 225 225 foreach {tn sql changeset} { 226 226 1 { 227 227 INSERT INTO t1 VALUES(123); 228 228 INSERT INTO t1 VALUES(NULL); 229 229 INSERT INTO t1 VALUES(456); 230 230 } { 231 - {INSERT t1 {} {i 456}} 232 - {INSERT t1 {} {i 123}} 231 + {INSERT t1 0 {} {i 456}} 232 + {INSERT t1 0 {} {i 123}} 233 233 } 234 234 235 235 2 { 236 236 UPDATE t1 SET a = NULL; 237 237 } { 238 - {DELETE t1 {i 456} {}} 239 - {DELETE t1 {i 123} {}} 238 + {DELETE t1 0 {i 456} {}} 239 + {DELETE t1 0 {i 123} {}} 240 240 } 241 241 242 242 3 { DELETE FROM t1 } { } 243 243 244 244 4 { 245 245 INSERT INTO t3 VALUES(NULL, NULL) 246 246 } { 247 - {INSERT t3 {} {n {} i 1}} 247 + {INSERT t3 0 {} {n {} i 1}} 248 248 } 249 249 250 250 5 { INSERT INTO t2 VALUES(1, 2, NULL) } { } 251 251 6 { INSERT INTO t2 VALUES(1, NULL, 3) } { } 252 252 7 { INSERT INTO t2 VALUES(1, NULL, NULL) } { } 253 - 8 { INSERT INTO t2 VALUES(1, 2, 3) } { {INSERT t2 {} {i 1 i 2 i 3}} } 254 - 9 { DELETE FROM t2 WHERE 1 } { {DELETE t2 {i 1 i 2 i 3} {}} } 253 + 8 { INSERT INTO t2 VALUES(1, 2, 3) } { {INSERT t2 0 {} {i 1 i 2 i 3}} } 254 + 9 { DELETE FROM t2 WHERE 1 } { {DELETE t2 0 {i 1 i 2 i 3} {}} } 255 255 256 256 } { 257 257 do_iterator_test 4.$tn {t1 t2 t3} $sql $changeset 258 258 } 259 259 260 260 261 261 #------------------------------------------------------------------------- ................................................................................ 265 265 test_reset 266 266 do_execsql_test 5.0 { 267 267 CREATE TABLE t1(a PRIMARY KEY); 268 268 CREATE TABLE t2(x, y PRIMARY KEY); 269 269 } 270 270 271 271 foreach {tn sql changeset} { 272 - 1 { INSERT INTO t1 VALUES(35) } { {INSERT t1 {} {i 35}} } 273 - 2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 {} {i 36 i 37}} } 272 + 1 { INSERT INTO t1 VALUES(35) } { {INSERT t1 0 {} {i 35}} } 273 + 2 { INSERT INTO t2 VALUES(36, 37) } { {INSERT t2 0 {} {i 36 i 37}} } 274 274 3 { 275 275 DELETE FROM t1 WHERE 1; 276 276 UPDATE t2 SET x = 34; 277 277 } { 278 - {UPDATE t2 {i 36 i 37} {i 34 {} {}}} 279 - {DELETE t1 {i 35} {}} 278 + {UPDATE t2 0 {i 36 i 37} {i 34 {} {}}} 279 + {DELETE t1 0 {i 35} {}} 280 280 } 281 281 } { 282 282 do_iterator_test 5.$tn * $sql $changeset 283 283 } 284 284 285 -finish_test 285 +#------------------------------------------------------------------------- 286 +# The next block of tests verify that the "indirect" flag is set 287 +# correctly within changesets. The indirect flag is set for a change 288 +# if either of the following are true: 289 +# 290 +# * The sqlite3session_indirect() API has been used to set the session 291 +# indirect flag to true, or 292 +# * The change was made by a trigger. 293 +# 294 +# If the same row is updated more than once during a session, then the 295 +# change is considered indirect only if all changes meet the criteria 296 +# above. 297 +# 298 +test_reset 299 +db function indirect [list S indirect] 300 + 301 +do_execsql_test 6.0 { 302 + CREATE TABLE t1(a PRIMARY KEY, b, c); 303 + 304 + CREATE TABLE t2(x PRIMARY KEY, y); 305 + CREATE TRIGGER AFTER INSERT ON t2 WHEN new.x%2 BEGIN 306 + INSERT INTO t2 VALUES(new.x+1, NULL); 307 + END; 308 +} 309 + 310 +do_iterator_test 6.1.1 * { 311 + INSERT INTO t1 VALUES(1, 'one', 'i'); 312 + SELECT indirect(1); 313 + INSERT INTO t1 VALUES(2, 'two', 'ii'); 314 + SELECT indirect(0); 315 + INSERT INTO t1 VALUES(3, 'three', 'iii'); 316 +} { 317 + {INSERT t1 0 {} {i 1 t one t i}} 318 + {INSERT t1 1 {} {i 2 t two t ii}} 319 + {INSERT t1 0 {} {i 3 t three t iii}} 320 +} 321 + 322 +do_iterator_test 6.1.2 * { 323 + SELECT indirect(1); 324 + UPDATE t1 SET c = 'I' WHERE a = 1; 325 + SELECT indirect(0); 326 +} { 327 + {UPDATE t1 1 {i 1 {} {} t i} {{} {} {} {} t I}} 328 +} 329 +do_iterator_test 6.1.3 * { 330 + SELECT indirect(1); 331 + UPDATE t1 SET c = '.' WHERE a = 1; 332 + SELECT indirect(0); 333 + UPDATE t1 SET c = 'o' WHERE a = 1; 334 +} { 335 + {UPDATE t1 0 {i 1 {} {} t I} {{} {} {} {} t o}} 336 +} 337 +do_iterator_test 6.1.4 * { 338 + SELECT indirect(0); 339 + UPDATE t1 SET c = 'x' WHERE a = 1; 340 + SELECT indirect(1); 341 + UPDATE t1 SET c = 'i' WHERE a = 1; 342 +} { 343 + {UPDATE t1 0 {i 1 {} {} t o} {{} {} {} {} t i}} 344 +} 345 +do_iterator_test 6.1.4 * { 346 + SELECT indirect(1); 347 + UPDATE t1 SET c = 'y' WHERE a = 1; 348 + SELECT indirect(1); 349 + UPDATE t1 SET c = 'I' WHERE a = 1; 350 +} { 351 + {UPDATE t1 1 {i 1 {} {} t i} {{} {} {} {} t I}} 352 +} 353 + 354 +do_iterator_test 6.1.5 * { 355 + INSERT INTO t2 VALUES(1, 'x'); 356 +} { 357 + {INSERT t2 0 {} {i 1 t x}} 358 + {INSERT t2 1 {} {i 2 n {}}} 359 +} 360 + 361 +do_iterator_test 6.1.6 * { 362 + SELECT indirect(1); 363 + INSERT INTO t2 VALUES(3, 'x'); 364 + SELECT indirect(0); 365 + UPDATE t2 SET y = 'y' WHERE x>2; 366 +} { 367 + {INSERT t2 0 {} {i 3 t y}} 368 + {INSERT t2 0 {} {i 4 t y}} 369 +} 370 + 371 +do_iterator_test 6.1.7 * { 372 + SELECT indirect(1); 373 + DELETE FROM t2 WHERE x = 4; 374 + SELECT indirect(0); 375 + INSERT INTO t2 VALUES(4, 'new'); 376 +} { 377 + {UPDATE t2 0 {i 4 t y} {{} {} t new}} 378 +} 286 379 380 +finish_test
Changes to ext/session/sessionfault.test.
34 34 #------------------------------------------------------------------------- 35 35 # Test OOM error handling when collecting and applying a simple changeset. 36 36 # 37 37 # Test 1.1 attaches tables individually by name to the session object. 38 38 # Whereas test 1.2 passes NULL to sqlite3session_attach() to attach all 39 39 # tables. 40 40 # 41 -do_faultsim_test pagerfault-1.1 -faults oom-* -prep { 41 +do_faultsim_test 1.1 -faults oom-* -prep { 42 42 catch {db2 close} 43 43 catch {db close} 44 44 faultsim_restore_and_reopen 45 45 sqlite3 db2 test.db2 46 46 } -body { 47 47 do_then_apply_sql { 48 48 INSERT INTO t1 VALUES(7, 8, 9); ................................................................................ 51 51 } 52 52 } -test { 53 53 faultsim_test_result {0 {}} {1 SQLITE_NOMEM} 54 54 faultsim_integrity_check 55 55 if {$testrc==0} { compare_db db db2 } 56 56 } 57 57 58 -do_faultsim_test pagerfault-1.2 -faults oom-* -prep { 58 +do_faultsim_test 1.2 -faults oom-* -prep { 59 59 catch {db2 close} 60 60 catch {db close} 61 61 faultsim_restore_and_reopen 62 62 } -body { 63 63 sqlite3session S db main 64 64 S attach * 65 65 execsql { ................................................................................ 78 78 sqlite3 db2 test.db2 79 79 sqlite3changeset_apply db2 $::changeset xConflict 80 80 compare_db db db2 81 81 } 82 82 } 83 83 84 84 #------------------------------------------------------------------------- 85 -# The following block of tests - pagerfault-2.* - are designed to check 85 +# The following block of tests - 2.* - are designed to check 86 86 # the handling of faults in the sqlite3changeset_apply() function. 87 87 # 88 88 catch {db close} 89 89 catch {db2 close} 90 90 forcedelete test.db2 test.db 91 91 sqlite3 db2 test.db2 92 92 sqlite3 db test.db ................................................................................ 106 106 3 OMIT { UPDATE t1 SET c = 'banana' WHERE b = 'orange' } {} 107 107 4 REPLACE { INSERT INTO t2 VALUES('keyvalue', 'value 1') } { 108 108 INSERT INTO t2 VALUES('keyvalue', 'value 2'); 109 109 } 110 110 } { 111 111 proc xConflict args [list return $conflict_policy] 112 112 113 - do_faultsim_test pagerfault-2.$tn -faults oom-transient -prep { 113 + do_faultsim_test 2.$tn -faults oom-transient -prep { 114 114 catch {db2 close} 115 115 catch {db close} 116 116 faultsim_restore_and_reopen 117 117 set ::changeset [changeset_from_sql $::sql] 118 118 sqlite3 db2 test.db2 119 119 sqlite3_db_config_lookaside db2 0 0 0 120 120 execsql $::sql2 db2 ................................................................................ 128 128 } 129 129 130 130 #------------------------------------------------------------------------- 131 131 # This test case is designed so that a malloc() failure occurs while 132 132 # resizing the session object hash-table from 256 to 512 buckets. This 133 133 # is not an error, just a sub-optimal condition. 134 134 # 135 -do_faultsim_test pagerfault-3 -faults oom-* -prep { 135 +do_faultsim_test 3 -faults oom-* -prep { 136 136 catch {db2 close} 137 137 catch {db close} 138 138 faultsim_restore_and_reopen 139 139 sqlite3 db2 test.db2 140 140 141 141 sqlite3session S db main 142 142 S attach t1 ................................................................................ 187 187 INSERT INTO t1 VALUES(4, 16); 188 188 } db2 189 189 } {} 190 190 191 191 faultsim_save_and_close 192 192 db2 close 193 193 194 -do_faultsim_test pagerfault-4 -faults oom-* -prep { 194 +do_faultsim_test 4 -faults oom-* -prep { 195 195 catch {db2 close} 196 196 catch {db close} 197 197 faultsim_restore_and_reopen 198 198 sqlite3 db2 test.db2 199 199 sqlite3session S db main 200 200 S attach t1 201 201 execsql { ................................................................................ 230 230 set changeset [changeset_from_sql { 231 231 INSERT INTO t1 VALUES('xxx', 'yyy'); 232 232 DELETE FROM t1 WHERE a = 'string'; 233 233 UPDATE t1 SET a = 20 WHERE b = 2; 234 234 }] 235 235 db close 236 236 237 -do_faultsim_test pagerfault-5 -faults oom* -body { 237 +do_faultsim_test 5 -faults oom* -body { 238 238 set ::inverse [sqlite3changeset_invert $::changeset] 239 239 set {} {} 240 240 } -test { 241 241 faultsim_test_result {0 {}} {1 SQLITE_NOMEM} 242 242 if {$testrc==0} { 243 243 set x [list] 244 244 sqlite3session_foreach c $::inverse { lappend x $c } 245 245 foreach c { 246 - {DELETE t1 {t xxx t yyy} {}} 247 - {INSERT t1 {} {t string i 1}} 248 - {UPDATE t1 {i 20 {} {}} {i 4 i 2}} 246 + {DELETE t1 0 {t xxx t yyy} {}} 247 + {INSERT t1 0 {} {t string i 1}} 248 + {UPDATE t1 0 {i 20 {} {}} {i 4 i 2}} 249 249 } { lappend y $c } 250 250 if {$x != $y} { error "changeset no good" } 251 251 } 252 252 } 253 253 254 254 finish_test
Changes to ext/session/sqlite3session.c.
15 15 /* 16 16 ** Session handle structure. 17 17 */ 18 18 struct sqlite3_session { 19 19 sqlite3 *db; /* Database handle session is attached to */ 20 20 char *zDb; /* Name of database session is attached to */ 21 21 int bEnable; /* True if currently recording */ 22 + int bIndirect; /* True if all changes are indirect */ 22 23 int bAutoAttach; /* True to auto-attach tables */ 23 24 int rc; /* Non-zero if an error has occurred */ 24 25 sqlite3_session *pNext; /* Next session object on same db. */ 25 26 SessionTable *pTable; /* List of attached tables */ 26 27 }; 27 28 28 29 /* ................................................................................ 33 34 int nChangeset; /* Number of bytes in aChangeset */ 34 35 u8 *pNext; /* Pointer to next change within aChangeset */ 35 36 int rc; /* Iterator error code */ 36 37 sqlite3_stmt *pConflict; /* Points to conflicting row, if any */ 37 38 char *zTab; /* Current table */ 38 39 int nCol; /* Number of columns in zTab */ 39 40 int op; /* Current operation */ 41 + int bIndirect; /* True if current change was indirect */ 40 42 sqlite3_value **apValue; /* old.* and new.* values */ 41 43 }; 42 44 43 45 /* 44 46 ** Each session object maintains a set of the following structures, one 45 47 ** for each table the session object is monitoring. The structures are 46 48 ** stored in a linked list starting at sqlite3_session.pTable. ................................................................................ 127 129 128 130 /* 129 131 ** For each row modified during a session, there exists a single instance of 130 132 ** this structure stored in a SessionTable.aChange[] hash table. 131 133 */ 132 134 struct SessionChange { 133 135 int bInsert; /* True if row was inserted this session */ 136 + int bIndirect; /* True if this change is "indirect" */ 134 137 int nRecord; /* Number of bytes in buffer aRecord[] */ 135 138 u8 *aRecord; /* Buffer containing old.* record */ 136 139 SessionChange *pNext; /* For hash-table collisions */ 137 140 }; 138 141 139 142 /* 140 143 ** Instances of this structure are used to build strings or binary records. ................................................................................ 656 659 657 660 static void sessionPreupdateOneChange( 658 661 int op, 659 662 sqlite3_session *pSession, 660 663 SessionTable *pTab 661 664 ){ 662 665 sqlite3 *db = pSession->db; 663 - SessionChange *pChange; 664 - SessionChange *pC; 665 666 int iHash; 666 667 int bNullPk = 0; 667 668 int rc = SQLITE_OK; 668 669 669 670 if( pSession->rc ) return; 670 671 671 672 /* Load table details if required */ ................................................................................ 675 676 if( sessionGrowHash(pSession, pTab) ) return; 676 677 677 678 /* Search the hash table for an existing entry for rowid=iKey2. If 678 679 ** one is found, store a pointer to it in pChange and unlink it from 679 680 ** the hash table. Otherwise, set pChange to NULL. 680 681 */ 681 682 rc = sessionPreupdateHash(db, pTab, op==SQLITE_INSERT, &iHash, &bNullPk); 682 - if( bNullPk==0 ){ 683 + if( rc==SQLITE_OK && bNullPk==0 ){ 684 + SessionChange *pC; 683 685 for(pC=pTab->apChange[iHash]; rc==SQLITE_OK && pC; pC=pC->pNext){ 684 686 int bEqual; 685 687 rc = sessionPreupdateEqual(db, pTab, pC, op==SQLITE_INSERT, &bEqual); 686 688 if( bEqual ) break; 687 689 } 688 690 if( pC==0 ){ 689 691 /* Create a new change object containing all the old values (if 690 692 ** this is an SQLITE_UPDATE or SQLITE_DELETE), or just the PK 691 693 ** values (if this is an INSERT). */ 694 + SessionChange *pChange; /* New change object */ 692 695 int nByte; /* Number of bytes to allocate */ 693 696 int i; /* Used to iterate through columns */ 694 697 698 + assert( rc==SQLITE_OK ); 695 699 pTab->nEntry++; 696 700 697 701 /* Figure out how large an allocation is required */ 698 702 nByte = sizeof(SessionChange); 699 703 for(i=0; i<pTab->nCol && rc==SQLITE_OK; i++){ 700 704 sqlite3_value *p = 0; 701 705 if( op!=SQLITE_INSERT ){ ................................................................................ 728 732 } 729 733 if( p && rc==SQLITE_OK ){ 730 734 rc = sessionSerializeValue(&pChange->aRecord[nByte], p, &nByte); 731 735 } 732 736 } 733 737 if( rc==SQLITE_OK ){ 734 738 /* Add the change back to the hash-table */ 739 + if( pSession->bIndirect || sqlite3_preupdate_depth(pSession->db) ){ 740 + pChange->bIndirect = 1; 741 + } 735 742 pChange->nRecord = nByte; 736 743 pChange->bInsert = (op==SQLITE_INSERT); 737 744 pChange->pNext = pTab->apChange[iHash]; 738 745 pTab->apChange[iHash] = pChange; 739 746 }else{ 740 747 sqlite3_free(pChange); 748 + } 749 + }else if( rc==SQLITE_OK && pC->bIndirect ){ 750 + /* If the existing change is considered "indirect", but this current 751 + ** change is "direct", mark the change object as direct. */ 752 + if( sqlite3_preupdate_depth(pSession->db)==0 && pSession->bIndirect==0 ){ 753 + pC->bIndirect = 0; 741 754 } 742 755 } 743 756 } 744 757 745 758 /* If an error has occurred, mark the session object as failed. */ 746 759 if( rc!=SQLITE_OK ){ 747 760 pSession->rc = rc; ................................................................................ 1132 1145 SessionBuffer buf2 = {0,0,0}; /* Buffer to accumulate new.* record in */ 1133 1146 int bNoop = 1; /* Set to zero if any values are modified */ 1134 1147 int nRewind = pBuf->nBuf; /* Set to zero if any values are modified */ 1135 1148 int i; /* Used to iterate through columns */ 1136 1149 u8 *pCsr = p->aRecord; /* Used to iterate through old.* values */ 1137 1150 1138 1151 sessionAppendByte(pBuf, SQLITE_UPDATE, pRc); 1152 + sessionAppendByte(pBuf, p->bIndirect, pRc); 1139 1153 for(i=0; i<sqlite3_column_count(pStmt); i++){ 1140 1154 int bChanged = 0; 1141 1155 int nAdvance; 1142 1156 int eType = *pCsr; 1143 1157 switch( eType ){ 1144 1158 case SQLITE_NULL: 1145 1159 nAdvance = 1; ................................................................................ 1362 1376 for(p=pTab->apChange[i]; rc==SQLITE_OK && p; p=p->pNext){ 1363 1377 rc = sessionSelectBind(pSel, nCol, abPK, p->aRecord, p->nRecord); 1364 1378 if( rc==SQLITE_OK ){ 1365 1379 if( sqlite3_step(pSel)==SQLITE_ROW ){ 1366 1380 int iCol; 1367 1381 if( p->bInsert ){ 1368 1382 sessionAppendByte(&buf, SQLITE_INSERT, &rc); 1383 + sessionAppendByte(&buf, p->bIndirect, &rc); 1369 1384 for(iCol=0; iCol<nCol; iCol++){ 1370 1385 sessionAppendCol(&buf, pSel, iCol, &rc); 1371 1386 } 1372 1387 }else{ 1373 1388 sessionAppendUpdate(&buf, pSel, p, abPK, &rc); 1374 1389 } 1375 1390 }else if( !p->bInsert ){ 1376 1391 /* A DELETE change */ 1377 1392 sessionAppendByte(&buf, SQLITE_DELETE, &rc); 1393 + sessionAppendByte(&buf, p->bIndirect, &rc); 1378 1394 sessionAppendBlob(&buf, p->aRecord, p->nRecord, &rc); 1379 1395 } 1380 1396 if( rc==SQLITE_OK ){ 1381 1397 rc = sqlite3_reset(pSel); 1382 1398 } 1383 1399 } 1384 1400 } ................................................................................ 1411 1427 if( bEnable>=0 ){ 1412 1428 pSession->bEnable = bEnable; 1413 1429 } 1414 1430 ret = pSession->bEnable; 1415 1431 sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db)); 1416 1432 return ret; 1417 1433 } 1434 + 1435 +/* 1436 +** Enable or disable the session object passed as the first argument. 1437 +*/ 1438 +int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect){ 1439 + int ret; 1440 + sqlite3_mutex_enter(sqlite3_db_mutex(pSession->db)); 1441 + if( bIndirect>=0 ){ 1442 + pSession->bIndirect = bIndirect; 1443 + } 1444 + ret = pSession->bIndirect; 1445 + sqlite3_mutex_leave(sqlite3_db_mutex(pSession->db)); 1446 + return ret; 1447 +} 1418 1448 1419 1449 /* 1420 1450 ** Create an iterator used to iterate through the contents of a changeset. 1421 1451 */ 1422 1452 int sqlite3changeset_start( 1423 1453 sqlite3_changeset_iter **pp, /* OUT: Changeset iterator handle */ 1424 1454 int nChangeset, /* Size of buffer pChangeset in bytes */ ................................................................................ 1544 1574 c = *(aChange++); 1545 1575 if( c=='T' ){ 1546 1576 int nByte; /* Bytes to allocate for apValue */ 1547 1577 aChange += sessionVarintGet(aChange, &p->nCol); 1548 1578 p->zTab = (char *)aChange; 1549 1579 aChange += (strlen((char *)aChange) + 1); 1550 1580 p->op = *(aChange++); 1581 + p->bIndirect = *(aChange++); 1551 1582 sqlite3_free(p->apValue); 1552 1583 nByte = sizeof(sqlite3_value *) * p->nCol * 2; 1553 1584 p->apValue = (sqlite3_value **)sqlite3_malloc(nByte); 1554 1585 if( !p->apValue ){ 1555 1586 return (p->rc = SQLITE_NOMEM); 1556 1587 } 1557 1588 memset(p->apValue, 0, sizeof(sqlite3_value*)*p->nCol*2); 1558 1589 }else{ 1559 1590 p->op = c; 1591 + p->bIndirect = *(aChange++); 1560 1592 } 1561 1593 if( p->op!=SQLITE_UPDATE && p->op!=SQLITE_DELETE && p->op!=SQLITE_INSERT ){ 1562 1594 return (p->rc = SQLITE_CORRUPT); 1563 1595 } 1564 1596 1565 1597 /* If this is an UPDATE or DELETE, read the old.* record. */ 1566 1598 if( p->op!=SQLITE_INSERT ){ ................................................................................ 1583 1615 ** from a changeset iterator. They may only be called after changeset_next() 1584 1616 ** has returned SQLITE_ROW. 1585 1617 */ 1586 1618 int sqlite3changeset_op( 1587 1619 sqlite3_changeset_iter *pIter, /* Iterator handle */ 1588 1620 const char **pzTab, /* OUT: Pointer to table name */ 1589 1621 int *pnCol, /* OUT: Number of columns in table */ 1590 - int *pOp /* OUT: SQLITE_INSERT, DELETE or UPDATE */ 1622 + int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */ 1623 + int *pbIndirect /* OUT: True if change is indirect */ 1591 1624 ){ 1592 1625 *pOp = pIter->op; 1593 1626 *pnCol = pIter->nCol; 1594 1627 *pzTab = pIter->zTab; 1628 + if( pbIndirect ) *pbIndirect = pIter->bIndirect; 1595 1629 return SQLITE_OK; 1596 1630 } 1597 1631 1598 1632 /* 1599 1633 ** This function may only be called while the iterator is pointing to an 1600 1634 ** SQLITE_UPDATE or SQLITE_DELETE change (see sqlite3changeset_op()). 1601 1635 ** Otherwise, SQLITE_MISUSE is returned. ................................................................................ 1736 1770 i += nByte; 1737 1771 break; 1738 1772 } 1739 1773 1740 1774 case SQLITE_INSERT: 1741 1775 case SQLITE_DELETE: { 1742 1776 int nByte; 1743 - u8 *aEnd = &aIn[i+1]; 1777 + u8 *aEnd = &aIn[i+2]; 1744 1778 1745 1779 sessionReadRecord(&aEnd, nCol, 0); 1746 1780 aOut[i] = (eType==SQLITE_DELETE ? SQLITE_INSERT : SQLITE_DELETE); 1747 - nByte = aEnd - &aIn[i+1]; 1748 - memcpy(&aOut[i+1], &aIn[i+1], nByte); 1749 - i += 1 + nByte; 1781 + aOut[i+1] = aIn[i+1]; 1782 + nByte = aEnd - &aIn[i+2]; 1783 + memcpy(&aOut[i+2], &aIn[i+2], nByte); 1784 + i += 2 + nByte; 1750 1785 break; 1751 1786 } 1752 1787 1753 1788 case SQLITE_UPDATE: { 1754 1789 int nByte1; /* Size of old.* record in bytes */ 1755 1790 int nByte2; /* Size of new.* record in bytes */ 1756 - u8 *aEnd = &aIn[i+1]; 1791 + u8 *aEnd = &aIn[i+2]; 1757 1792 1758 1793 sessionReadRecord(&aEnd, nCol, 0); 1759 - nByte1 = aEnd - &aIn[i+1]; 1794 + nByte1 = aEnd - &aIn[i+2]; 1760 1795 sessionReadRecord(&aEnd, nCol, 0); 1761 - nByte2 = aEnd - &aIn[i+1] - nByte1; 1796 + nByte2 = aEnd - &aIn[i+2] - nByte1; 1762 1797 1763 1798 aOut[i] = SQLITE_UPDATE; 1764 - memcpy(&aOut[i+1], &aIn[i+1+nByte1], nByte2); 1765 - memcpy(&aOut[i+1+nByte2], &aIn[i+1], nByte1); 1799 + aOut[i+1] = aIn[i+1]; 1800 + memcpy(&aOut[i+2], &aIn[i+2+nByte1], nByte2); 1801 + memcpy(&aOut[i+2+nByte2], &aIn[i+2], nByte1); 1766 1802 1767 - i += 1 + nByte1 + nByte2; 1803 + i += 2 + nByte1 + nByte2; 1768 1804 break; 1769 1805 } 1770 1806 1771 1807 default: 1772 1808 sqlite3_free(aOut); 1773 1809 return SQLITE_CORRUPT; 1774 1810 } ................................................................................ 2093 2129 sqlite3_stmt *pSelect /* SELECT statement from sessionSelectRow() */ 2094 2130 ){ 2095 2131 int rc; /* Return code */ 2096 2132 int nCol; /* Number of columns in table */ 2097 2133 int op; /* Changset operation (SQLITE_UPDATE etc.) */ 2098 2134 const char *zDummy; /* Unused */ 2099 2135 2100 - sqlite3changeset_op(pIter, &zDummy, &nCol, &op); 2136 + sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); 2101 2137 rc = sessionBindRow(pIter, 2102 2138 op==SQLITE_INSERT ? sqlite3changeset_new : sqlite3changeset_old, 2103 2139 nCol, abPK, pSelect 2104 2140 ); 2105 2141 2106 2142 if( rc==SQLITE_OK ){ 2107 2143 rc = sqlite3_step(pSelect); ................................................................................ 2156 2192 ){ 2157 2193 int res; /* Value returned by conflict handler */ 2158 2194 int rc; 2159 2195 int nCol; 2160 2196 int op; 2161 2197 const char *zDummy; 2162 2198 2163 - sqlite3changeset_op(pIter, &zDummy, &nCol, &op); 2199 + sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); 2164 2200 2165 2201 assert( eType==SQLITE_CHANGESET_CONFLICT || eType==SQLITE_CHANGESET_DATA ); 2166 2202 assert( SQLITE_CHANGESET_CONFLICT+1==SQLITE_CHANGESET_CONSTRAINT ); 2167 2203 assert( SQLITE_CHANGESET_DATA+1==SQLITE_CHANGESET_NOTFOUND ); 2168 2204 2169 2205 /* Bind the new.* PRIMARY KEY values to the SELECT statement. */ 2170 2206 if( pbReplace ){ ................................................................................ 2244 2280 int nCol; 2245 2281 int rc = SQLITE_OK; 2246 2282 2247 2283 assert( p->pDelete && p->pUpdate && p->pInsert && p->pSelect ); 2248 2284 assert( p->azCol && p->abPK ); 2249 2285 assert( !pbReplace || *pbReplace==0 ); 2250 2286 2251 - sqlite3changeset_op(pIter, &zDummy, &nCol, &op); 2287 + sqlite3changeset_op(pIter, &zDummy, &nCol, &op, 0); 2252 2288 2253 2289 if( op==SQLITE_DELETE ){ 2254 2290 2255 2291 /* Bind values to the DELETE statement. */ 2256 2292 rc = sessionBindRow(pIter, sqlite3changeset_old, nCol, 0, p->pDelete); 2257 2293 if( rc==SQLITE_OK && sqlite3_bind_parameter_count(p->pDelete)>nCol ){ 2258 2294 rc = sqlite3_bind_int(p->pDelete, nCol+1, pbRetry==0); ................................................................................ 2358 2394 rc = sqlite3_exec(db, "SAVEPOINT changeset_apply", 0, 0, 0); 2359 2395 while( rc==SQLITE_OK && SQLITE_ROW==sqlite3changeset_next(pIter) ){ 2360 2396 int nCol; 2361 2397 int op; 2362 2398 int bReplace = 0; 2363 2399 int bRetry = 0; 2364 2400 const char *zNew; 2365 - sqlite3changeset_op(pIter, &zNew, &nCol, &op); 2401 + sqlite3changeset_op(pIter, &zNew, &nCol, &op, 0); 2366 2402 2367 2403 if( zTab==0 || sqlite3_strnicmp(zNew, zTab, nTab+1) ){ 2368 2404 sqlite3_free(sApply.azCol); 2369 2405 sqlite3_finalize(sApply.pDelete); 2370 2406 sqlite3_finalize(sApply.pUpdate); 2371 2407 sqlite3_finalize(sApply.pInsert); 2372 2408 sqlite3_finalize(sApply.pSelect);
Changes to ext/session/sqlite3session.h.
86 86 ** no-op, and may be used to query the current state of the session. 87 87 ** 88 88 ** The return value indicates the final state of the session object: 0 if 89 89 ** the session is disabled, or 1 if it is enabled. 90 90 */ 91 91 int sqlite3session_enable(sqlite3_session *pSession, int bEnable); 92 92 93 +/* 94 +** CAPI3REF: Set Or Clear the Indirect Change Flag 95 +** 96 +** Each change recorded by a session object is marked as either direct or 97 +** indirect. A change is marked as indirect if either: 98 +** 99 +** <ul> 100 +** <li> The session object "indirect" flag is set when the change is 101 +** made, or 102 +** <li> The change is made by an SQL trigger or foreign key action 103 +** instead of directly as a result of a users SQL statement. 104 +** </ul> 105 +** 106 +** If a single row is affected by more than one operation within a session, 107 +** then the change is considered indirect if all operations meet the criteria 108 +** for an indirect change above, or direct otherwise. 109 +** 110 +** This function is used to set, clear or query the session object indirect 111 +** flag. If the second argument passed to this function is zero, then the 112 +** indirect flag is cleared. If it is greater than zero, the indirect flag 113 +** is set. Passing a value less than zero does not modify the current value 114 +** of the indirect flag, and may be used to query the current state of the 115 +** indirect flag for the specified session object. 116 +** 117 +** The return value indicates the final state of the indirect flag: 0 if 118 +** it is clear, or 1 if it is set. 119 +*/ 120 +int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); 121 + 93 122 /* 94 123 ** CAPI3REF: Attach A Table To A Session Object 95 124 ** 96 125 ** If argument zTab is not NULL, then it is the name of a table to attach 97 126 ** to the session object passed as the first argument. All subsequent changes 98 127 ** made to the table while the session object is enabled will be recorded. See 99 128 ** documentation for [sqlite3session_changeset()] for further details. ................................................................................ 288 317 ** is not the case, this function returns [SQLITE_MISUSE]. 289 318 ** 290 319 ** If argument pzTab is not NULL, then *pzTab is set to point to a 291 320 ** nul-terminated utf-8 encoded string containing the name of the table 292 321 ** affected by the current change. The buffer remains valid until either 293 322 ** sqlite3changeset_next() is called on the iterator or until the 294 323 ** conflict-handler function returns. If pnCol is not NULL, then *pnCol is 295 -** set to the number of columns in the table affected by the change. Finally, 296 -** if pOp is not NULL, then *pOp is set to one of [SQLITE_INSERT], 297 -** [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the type of change that 298 -** the iterator currently points to. 324 +** set to the number of columns in the table affected by the change. If 325 +** pbIncorrect is not NULL, then *pbIndirect is set to true (1) if the change 326 +** is an indirect change, or false (0) otherwise. See the documentation for 327 +** [sqlite3session_indirect()] for a description of direct and indirect 328 +** changes. Finally, if pOp is not NULL, then *pOp is set to one of 329 +** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the 330 +** type of change that the iterator currently points to. 299 331 ** 300 332 ** If no error occurs, SQLITE_OK is returned. If an error does occur, an 301 333 ** SQLite error code is returned. The values of the output variables may not 302 334 ** be trusted in this case. 303 335 */ 304 336 int sqlite3changeset_op( 305 337 sqlite3_changeset_iter *pIter, /* Iterator object */ 306 338 const char **pzTab, /* OUT: Pointer to table name */ 307 339 int *pnCol, /* OUT: Number of columns in table */ 308 - int *pOp /* OUT: SQLITE_INSERT, DELETE or UPDATE */ 340 + int *pOp, /* OUT: SQLITE_INSERT, DELETE or UPDATE */ 341 + int *pbIndirect /* OUT: True for an 'indirect' change */ 309 342 ); 310 343 311 344 /* 312 345 ** CAPI3REF: Obtain old.* Values From A Changeset Iterator 313 346 ** 314 347 ** The pIter argument passed to this function may either be an iterator 315 348 ** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator
Changes to ext/session/test_session.c.
13 13 } 14 14 15 15 /* 16 16 ** Tclcmd: $session attach TABLE 17 17 ** $session changeset 18 18 ** $session delete 19 19 ** $session enable BOOL 20 +** $session indirect BOOL 20 21 */ 21 22 static int test_session_cmd( 22 23 void *clientData, 23 24 Tcl_Interp *interp, 24 25 int objc, 25 26 Tcl_Obj *CONST objv[] 26 27 ){ ................................................................................ 30 31 int nArg; 31 32 const char *zMsg; 32 33 int iSub; 33 34 } aSub[] = { 34 35 { "attach", 1, "TABLE", }, /* 0 */ 35 36 { "changeset", 0, "", }, /* 1 */ 36 37 { "delete", 0, "", }, /* 2 */ 37 - { "enable", 1, "", }, /* 3 */ 38 + { "enable", 1, "BOOL", }, /* 3 */ 39 + { "indirect", 1, "BOOL", }, /* 4 */ 38 40 { 0 } 39 41 }; 40 42 int iSub; 41 43 int rc; 42 44 43 45 if( objc<2 ){ 44 46 Tcl_WrongNumArgs(interp, 1, objv, "SUBCOMMAND ..."); ................................................................................ 84 86 case 3: { /* enable */ 85 87 int val; 86 88 if( Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR; 87 89 val = sqlite3session_enable(pSession, val); 88 90 Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); 89 91 break; 90 92 } 93 + 94 + case 4: { /* indirect */ 95 + int val; 96 + if( Tcl_GetBooleanFromObj(interp, objv[2], &val) ) return TCL_ERROR; 97 + val = sqlite3session_indirect(pSession, val); 98 + Tcl_SetObjResult(interp, Tcl_NewBooleanObj(val)); 99 + break; 100 + } 91 101 } 92 102 93 103 return TCL_OK; 94 104 } 95 105 96 106 static void test_session_del(void *clientData){ 97 107 sqlite3_session *pSession = (sqlite3_session *)clientData; ................................................................................ 201 211 int op; /* SQLITE_UPDATE, DELETE or INSERT */ 202 212 const char *zTab; /* Name of table conflict is on */ 203 213 int nCol; /* Number of columns in table zTab */ 204 214 205 215 pEval = Tcl_DuplicateObj(p->pScript); 206 216 Tcl_IncrRefCount(pEval); 207 217 208 - sqlite3changeset_op(pIter, &zTab, &nCol, &op); 218 + sqlite3changeset_op(pIter, &zTab, &nCol, &op, 0); 209 219 210 220 /* Append the operation type. */ 211 221 Tcl_ListObjAppendElement(0, pEval, Tcl_NewStringObj( 212 222 op==SQLITE_INSERT ? "INSERT" : 213 223 op==SQLITE_UPDATE ? "UPDATE" : 214 224 "DELETE", -1 215 225 )); ................................................................................ 392 402 while( SQLITE_ROW==sqlite3changeset_next(pIter) ){ 393 403 int nCol; /* Number of columns in table */ 394 404 int op; /* SQLITE_INSERT, UPDATE or DELETE */ 395 405 const char *zTab; /* Name of table change applies to */ 396 406 Tcl_Obj *pVar; /* Tcl value to set $VARNAME to */ 397 407 Tcl_Obj *pOld; /* Vector of old.* values */ 398 408 Tcl_Obj *pNew; /* Vector of new.* values */ 409 + int bIndirect; 399 410 400 - sqlite3changeset_op(pIter, &zTab, &nCol, &op); 411 + sqlite3changeset_op(pIter, &zTab, &nCol, &op, &bIndirect); 401 412 pVar = Tcl_NewObj(); 402 413 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj( 403 414 op==SQLITE_INSERT ? "INSERT" : 404 415 op==SQLITE_UPDATE ? "UPDATE" : 405 416 "DELETE", -1 406 417 )); 407 418 Tcl_ListObjAppendElement(0, pVar, Tcl_NewStringObj(zTab, -1)); 419 + Tcl_ListObjAppendElement(0, pVar, Tcl_NewBooleanObj(bIndirect)); 408 420 409 421 pOld = Tcl_NewObj(); 410 422 if( op!=SQLITE_INSERT ){ 411 423 int i; 412 424 for(i=0; i<nCol; i++){ 413 425 sqlite3_value *pVal; 414 426 sqlite3changeset_old(pIter, i, &pVal);