Merge in changes to support recursive invocation of triggers (disabled by default).
FossilOrigin-Name: c1b388c30de1b660c56afbcac9c69700500808a9
This commit is contained in:
@@ -1,8 +1,5 @@
|
||||
-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA1
|
||||
|
||||
C More\sthorough\sinitialization\sof\sa\sbuffer\swhen\swriting\sthe\srollback\sjournal\nheader,\sin\sorder\sto\sprevent\sa\sharmless\svalgrind\swarning.\nCVSTrac\sticket\s#4039
|
||||
D 2009-08-31T16:09:59
|
||||
C Merge\sin\schanges\sto\ssupport\srecursive\sinvocation\sof\striggers\s(disabled\sby\sdefault).
|
||||
D 2009-09-01T16:39:06
|
||||
F Makefile.arm-wince-mingw32ce-gcc fcd5e9cd67fe88836360bb4f9ef4cb7f8e2fb5a0
|
||||
F Makefile.in 73ddeec9dd10b85876c5c2ce1fdce627e1dcc7f8
|
||||
F Makefile.linux-gcc d53183f4aa6a9192d249731c90dbdffbd2c68654
|
||||
@@ -102,34 +99,34 @@ F spec.template 86a4a43b99ebb3e75e6b9a735d5fd293a24e90ca
|
||||
F sqlite.pc.in 42b7bf0d02e08b9e77734a47798d1a55a9e0716b
|
||||
F sqlite3.1 6be1ad09113570e1fc8dcaff84c9b0b337db5ffc
|
||||
F sqlite3.pc.in ae6f59a76e862f5c561eb32a380228a02afc3cad
|
||||
F src/alter.c 8b42cace4f8e312de596807ba2685179da64fec4
|
||||
F src/alter.c 55b601857d924e07c91cfa69e9b2cb5093498c93
|
||||
F src/analyze.c 4584556d374fe3ab791be9be32ada7713fdced52
|
||||
F src/attach.c 13995348fc5a26cdd136a50806faf292aabc173f
|
||||
F src/auth.c 802a9439dfa0b8c208b10055cba400e82ef18025
|
||||
F src/auth.c 4cfb8fae12656cca2739176abebab84ed79dd8f6
|
||||
F src/backup.c 6f1c2d9862c8a3feb7739dfcca02c1f5352e37f3
|
||||
F src/bitvec.c e08f6c1a9551b88081fc737916c6c3fd5029a6cf
|
||||
F src/btmutex.c 0f43a75bb5b8147b386e8e1c3e71ba734e3863b7
|
||||
F src/btree.c 6b60ece56141bbe23aa6efca10f1612d34271c2f
|
||||
F src/btree.h 577448a890c2ab9b21e6ab74f073526184bceebe
|
||||
F src/btreeInt.h 1c86297e69380f6577e7ae67452597dd8d5c2705
|
||||
F src/build.c a43c959c5953c25989207c929f99ef808d5336a6
|
||||
F src/build.c df8dfff696329c192240e3d532f9dad0ef5abace
|
||||
F src/callback.c 9bc0ae998f15207d3115224979331c0aafc9bcc3
|
||||
F src/complete.c 5ad5c6cd4548211867c204c41a126d73a9fbcea0
|
||||
F src/date.c ab5f7137656652a48434d64f96bdcdc823bb23b3
|
||||
F src/delete.c dcf07632d8ca3d4086df8b65ea907a47278e6382
|
||||
F src/expr.c adb61b7de6bf9f51080195ed7dab9282a697ba24
|
||||
F src/delete.c 6b95963dabd558d45385e9b5be1fb4aa7ba7fa62
|
||||
F src/expr.c cadf65591f130e052aaa9718e19ec019b7cb598e
|
||||
F src/fault.c dc88c821842157460750d2d61a8a8b4197d047ff
|
||||
F src/func.c e536218d193b8d326aab91120bc4c6f28aa2b606
|
||||
F src/global.c 271952d199a8cc59d4ce840b3bbbfd2f30c8ba32
|
||||
F src/hash.c ebcaa921ffd9d86f7ea5ae16a0a29d1c871130a7
|
||||
F src/hash.h 35b216c13343d0b4f87d9f21969ac55ad72174e1
|
||||
F src/hwtime.h 4a1d45f4cae1f402ea19686acf24acf4f0cb53cb
|
||||
F src/insert.c 95625f99f377a9ef264c289407173b722c7af6e8
|
||||
F src/insert.c c90b07a1c8c8c31f55ad906cc3fc2de10ba182dd
|
||||
F src/journal.c e00df0c0da8413ab6e1bb7d7cab5665d4a9000d0
|
||||
F src/legacy.c 303b4ffcf1ae652fcf5ef635846c563c254564f6
|
||||
F src/lempar.c 0c4d1ab0a5ef2b0381eb81a732c54f68f27a574d
|
||||
F src/loadext.c 0e88a335665db0b2fb4cece3e49dcb65d832635a
|
||||
F src/main.c c2fc25f25edf7bfb4b70622e21dc6c8476b6d66d
|
||||
F src/main.c 09506443147ada14d98973a917667f4b2275aac7
|
||||
F src/malloc.c b1725183bcc4ce2e569f1b65da844dc3e4c7a643
|
||||
F src/mem0.c f2f84062d1f35814d6535c9f9e33de3bfb3b132c
|
||||
F src/mem1.c e6d5c23941288df8191b8a98c28e3f57771e2270
|
||||
@@ -156,17 +153,17 @@ F src/parse.y 6c42631e72a3d14cde2bee85e79409066066d3df
|
||||
F src/pcache.c c92ffd4f3e1279b3766854c6d18b5bf4aac0d1fa
|
||||
F src/pcache.h 435ef324197f79391f9c92b71d7f92b548ad7a36
|
||||
F src/pcache1.c 211295a9ff6a5b30f1ca50516731a5cf3e9bf82c
|
||||
F src/pragma.c 9eb44ac1d3dc1ac3ea4f444abe1a10ae8acaa16c
|
||||
F src/prepare.c 0b966d20979237121ec5fec449c9db45f6b9789a
|
||||
F src/pragma.c 6b1fa9f180d88b3f905cebd593ef6aef9334be43
|
||||
F src/prepare.c 9803fc01f0db29ac4a17fa662902af285f37c06b
|
||||
F src/printf.c 508a1c59433353552b6553cba175eaa7331f8fc1
|
||||
F src/random.c 676b9d7ac820fe81e6fb2394ac8c10cff7f38628
|
||||
F src/resolve.c 4a61d03e49b15440878096e6030863fc628828f0
|
||||
F src/resolve.c e78029b04dd811e4e0b7be6c51e7c31accc024b6
|
||||
F src/rowset.c c64dafba1f9fd876836c8db8682966b9d197eb1f
|
||||
F src/select.c 67b0778c9585905c8aa75aaa469e76ef3c1d315a
|
||||
F src/select.c 8bc7347fe88cf16ac2cb1a37fb918d1417ab4142
|
||||
F src/shell.c db2643650b9268df89a4bedca3f1c6d9e786f1bb
|
||||
F src/sqlite.h.in 9d03ceaad971882482c158c0e3d39d361c2c18a1
|
||||
F src/sqlite3ext.h 1db7d63ab5de4b3e6b83dd03d1a4e64fef6d2a17
|
||||
F src/sqliteInt.h 20ab1da1a9a652ea673e5bc586382143914381c0
|
||||
F src/sqliteInt.h f25f4fcefe6f0c3b5fcbddc0e4dee759adde0f37
|
||||
F src/sqliteLimit.h ffe93f5a0c4e7bd13e70cd7bf84cfb5c3465f45d
|
||||
F src/status.c 237b193efae0cf6ac3f0817a208de6c6c6ef6d76
|
||||
F src/table.c cc86ad3d6ad54df7c63a3e807b5783c90411a08d
|
||||
@@ -203,19 +200,19 @@ F src/test_tclvar.c 9e42fa59d3d2f064b7ab8628e7ab2dc8a9fe93d4
|
||||
F src/test_thread.c b8a1ab7ca1a632f18e8a361880d5d65eeea08eac
|
||||
F src/test_wsd.c 3ae5101de6cbfda2720152ab659ea84079719241
|
||||
F src/tokenize.c af8a56e6a50c5042fc305bfa796275e9bf26ff2b
|
||||
F src/trigger.c 545f3b8354001577a82c44fc6c4beac598b45b86
|
||||
F src/update.c 4da327f706c0d0dfedf4d92154b1b5688bdea0ac
|
||||
F src/trigger.c 8158237fc5fa4b51a126a294791729c64d27a0a9
|
||||
F src/update.c 12ffd68c2f241c730d5e051eb29b23e90682ff8a
|
||||
F src/utf.c 3ca2c9461b8e942c68da28bfccd448663f536a6f
|
||||
F src/util.c efb5f8e533d4beef545cf765cab5f7920b4c75f9
|
||||
F src/vacuum.c 3fe0eebea6d2311c1c2ab2962887d11f7a4dcfb0
|
||||
F src/vdbe.c 464e2e30b1287554a23cdaa0b6b010a9dcb5eb29
|
||||
F src/vdbe.h 457b6c70f02885cec1f5225b5e6441d067b55d3f
|
||||
F src/vdbeInt.h 831c254a6eef237ef4664c8381a0137586567007
|
||||
F src/vdbeapi.c 0ab8ada7260b32031ca97f338caecf0812460624
|
||||
F src/vdbeaux.c 014a60435469fed0b1c2fdf5e4db6273d7632901
|
||||
F src/vdbeblob.c a3f3e0e877fc64ea50165eec2855f5ada4477611
|
||||
F src/vdbemem.c c4a5188ff43692f2ca78d3539ad4877e14b70712
|
||||
F src/vtab.c aedd76e8670d5a5379f93804398d3ba960125547
|
||||
F src/vdbe.c fcc15fb2cfb84e261a423e6f71e0b963b8739280
|
||||
F src/vdbe.h 080fe6bc1264438becb8bf9b9f3c84074c336b78
|
||||
F src/vdbeInt.h 6094e60f64d7a9d4352d4e6b90d1219dce947ad9
|
||||
F src/vdbeapi.c 8d5013ab6104be757c208a70ffb191cc27d2b688
|
||||
F src/vdbeaux.c a88ef864f13ed698a267bcec29af22fbcd727f82
|
||||
F src/vdbeblob.c f93cb60ac388633ed3bde8a94ef161ad2dbfb689
|
||||
F src/vdbemem.c dc551981833756ea34a3e0b238f759479e7cf526
|
||||
F src/vtab.c 3e54fe39374e5feb8b174de32a90e7a21966025d
|
||||
F src/walker.c 1edca756275f158b80f20eb6f104c8d3fcc96a04
|
||||
F src/where.c a3218dfcf32e3d933270b76a72b97065f24b3f2c
|
||||
F test/aggerror.test a867e273ef9e3d7919f03ef4f0e8c0d2767944f2
|
||||
@@ -235,12 +232,12 @@ F test/async4.test aafa6328c559d3e4bb587de770cbdecfca06f0da
|
||||
F test/async5.test f3592d79c84d6e83a5f50d3fd500445f7d97dfdf
|
||||
F test/attach.test 1d1be27b9e4c654f9bb14d011a4a87753c0b197a
|
||||
F test/attach2.test a295d2d7061adcee5884ef4a93c7c96a82765437
|
||||
F test/attach3.test 7b92dc8e40c1ebca9732ca6f2d3fefbd46f196df
|
||||
F test/attach3.test ab28cc309888e2ac25795b52b2eee2214920b360
|
||||
F test/attachmalloc.test cf8cf17d183de357b1147a9baacbdfc85b940b61
|
||||
F test/auth.test 8e9a21d7321c9ad20d26f630acc02e15f2f2a3b6
|
||||
F test/auth.test 0f1237e10ab94108a0a4b3d463ddae3bddf31ed5
|
||||
F test/auth2.test ee3ba272e2b975e913afc9b041ee75706e190005
|
||||
F test/auth3.test a4755e6a2a2fea547ffe63c874eb569e60a28eb5
|
||||
F test/autoinc.test 71bc5183c93ed5e2b8b3a71c218d777b55e4fffc
|
||||
F test/autoinc.test 8242b84e5af8af6e00db4b5c5d48afdf304a12cb
|
||||
F test/autovacuum.test 25f891bc343a8bf5d9229e2e9ddab9f31a9ab5ec
|
||||
F test/autovacuum_ioerr2.test 598b0663074d3673a9c1bc9a16e80971313bafe6
|
||||
F test/avtrans.test 1e901d8102706b63534dbd2bdd4d8f16c4082650
|
||||
@@ -484,7 +481,7 @@ F test/minmax.test 722d80816f7e096bf2c04f4111f1a6c1ba65453d
|
||||
F test/minmax2.test 33504c01a03bd99226144e4b03f7631a274d66e0
|
||||
F test/minmax3.test 94742aa922153953ce3562702815e4f1f079bdb8
|
||||
F test/misc1.test 1b89c02c4a33b49dee4cd1d20d161aaaba719075
|
||||
F test/misc2.test 1ee89298de9c16b61454658b24999c403e86afe4
|
||||
F test/misc2.test 0bd5a44926124b9194b03499b37b8377f6d0b23b
|
||||
F test/misc3.test 72c5dc87a78e7865c5ec7a969fc572913dbe96b6
|
||||
F test/misc4.test 91e8ed25c092c2bb4e0bb01864631e2930f8d7de
|
||||
F test/misc5.test 6a5c1e3217a95b0db05ff9a0f1ecb5ce9043ffef
|
||||
@@ -646,7 +643,7 @@ F test/tkt3581.test 1966b7193f1e3f14951cce8c66907ae69454e9a3
|
||||
F test/tkt35xx.test ed9721bd9eb1693b3b4d3cf2a093fa7f92af0c93
|
||||
F test/tkt3630.test 929f64852103054125200bc825c316d5f75d42f7
|
||||
F test/tkt3718.test 3b59dcb5c4e7754dacd91e7fd353a61492cc402a
|
||||
F test/tkt3731.test 8a6e3732f5a8a24eb875a6faf287ef77bb8c0579
|
||||
F test/tkt3731.test d3ae761d8e31ddb2919e55776000e645e6b5b9f1
|
||||
F test/tkt3757.test 8f2208930655bbd4f92c14e19e72303a43e098ef
|
||||
F test/tkt3761.test b95ea9c98f21cf91325f18a984887e62caceab33
|
||||
F test/tkt3762.test 2a9f3b03df44ec49ec0cfa8d5da6574c2a7853df
|
||||
@@ -665,7 +662,7 @@ F test/tkt3918.test e6cdf6bfcfe9ba939d86a4238a9dc55d6eec5d42
|
||||
F test/tkt3922.test 022ace32c049e3964f68492c12eb803e8e4856d8
|
||||
F test/tkt3929.test 6a4c3baefb4e75127356b7d675b5df42c35c00d1
|
||||
F test/tkt3935.test e15261fedb9e30a4305a311da614a5d8e693c767
|
||||
F test/tkt3992.test c193b9643b1c25d020c503a986d5e4089e65c530
|
||||
F test/tkt3992.test 88bd3d596b4b5ce4cbdf5b2f564d1321276ee097
|
||||
F test/tkt3997.test a335fa41ca3985660a139df7b734a26ef53284bd
|
||||
F test/tkt4018.test 7c2c9ba4df489c676a0a7a0e809a1fb9b2185bd1
|
||||
F test/tokenize.test ce430a7aed48fc98301611429595883fdfcab5d7
|
||||
@@ -673,17 +670,18 @@ F test/trace.test 19ffbc09885c3321d56358a5738feae8587fb377
|
||||
F test/trans.test d887cb07630dc39879a322d958ad8b006137485c
|
||||
F test/trans2.test d5337e61de45e66b1fcbf9db833fa8c82e624b22
|
||||
F test/trans3.test d728abaa318ca364dc370e06576aa7e5fbed7e97
|
||||
F test/trigger1.test 4ecf469e6f46610e0864f52b29d0ea162a2767c1
|
||||
F test/trigger2.test 53cf2209f68eb5bbb0ea0a07d694007fa702f1ed
|
||||
F test/trigger3.test 501b8489eb6b9cb5b005f60b071583c01a3c3041
|
||||
F test/trigger1.test 2e18561f85e448bb633c9c9de792e9bbf7b2dd3e
|
||||
F test/trigger2.test 10b5fa93a8646b4a446ad10e9e36af896b83c408
|
||||
F test/trigger3.test 841602f2659b17bc4a2fcbbf7fcbea4577750aad
|
||||
F test/trigger4.test 8e90ee98cba940cd5f96493f82e55083806ab8a0
|
||||
F test/trigger5.test 619391a3e9fc194081d22cefd830d811e7badf83
|
||||
F test/trigger6.test 0e411654f122552da6590f0b4e6f781048a4a9b9
|
||||
F test/trigger7.test 72feaf8dbc52cea84de0c3e6ce7559ff19c479af
|
||||
F test/trigger8.test 83d92c212f36442d26527d6f7701575905a52ae1
|
||||
F test/trigger9.test e6e8dbab673666b3c0a63f0fefcff2329fe6bba8
|
||||
F test/trigger8.test 30cb0530bd7c4728055420e3f739aa00412eafa4
|
||||
F test/trigger9.test 5b0789f1c5c4600961f8e68511b825b87be53e31
|
||||
F test/triggerA.test 0718ad2d9bfef27c7af00e636df79bee6b988da7
|
||||
F test/triggerB.test 56780c031b454abac2340dbb3b71ac5c56c3d7fe
|
||||
F test/triggerC.test 3806d257b32e95ada0b230161ce9d36e4a7f335a
|
||||
F test/types.test 9a825ec8eea4e965d7113b74c76a78bb5240f2ac
|
||||
F test/types2.test 3555aacf8ed8dc883356e59efc314707e6247a84
|
||||
F test/types3.test a0f66bf12f80fad89493535474f7a6d16fa58150
|
||||
@@ -750,15 +748,8 @@ F tool/speedtest16.c c8a9c793df96db7e4933f0852abb7a03d48f2e81
|
||||
F tool/speedtest2.tcl ee2149167303ba8e95af97873c575c3e0fab58ff
|
||||
F tool/speedtest8.c 2902c46588c40b55661e471d7a86e4dd71a18224
|
||||
F tool/speedtest8inst1.c 293327bc76823f473684d589a8160bde1f52c14e
|
||||
F tool/vdbe-compress.tcl 672f81d693a03f80f5ae60bfefacd8a349e76746
|
||||
P 6abcba1021b237452f542f1fbb69eb75d9f50f53
|
||||
R 3214f074a14462fb9e96d75b13564e70
|
||||
U drh
|
||||
Z 1d55cf71a62a55978d3163a870a9fc02
|
||||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GnuPG v1.4.6 (GNU/Linux)
|
||||
|
||||
iD8DBQFKm/XboxKgR168RlERArBMAJ9T7O5vuB1uCdEihrXB/n4t3eJDtQCePxqX
|
||||
HHms1P8yqpZacj8LVysqzOY=
|
||||
=3H49
|
||||
-----END PGP SIGNATURE-----
|
||||
F tool/vdbe-compress.tcl d70ea6d8a19e3571d7ab8c9b75cba86d1173ff0f
|
||||
P 5e2f5496834bffb79236f803cdfedb18fbd533b7 a2a062a4b52f45af85c195582a36d1b76d2675db
|
||||
R bdae7ca38a0ff6f2a23becb7bc516ab2
|
||||
U dan
|
||||
Z f119bed83386ec346d66a132d507d343
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
5e2f5496834bffb79236f803cdfedb18fbd533b7
|
||||
c1b388c30de1b660c56afbcac9c69700500808a9
|
||||
+3
-3
@@ -196,10 +196,10 @@ static char *whereTempTriggers(Parse *pParse, Table *pTab){
|
||||
for(pTrig=sqlite3TriggerList(pParse, pTab); pTrig; pTrig=pTrig->pNext){
|
||||
if( pTrig->pSchema==pTempSchema ){
|
||||
if( !zWhere ){
|
||||
zWhere = sqlite3MPrintf(db, "name=%Q", pTrig->name);
|
||||
zWhere = sqlite3MPrintf(db, "name=%Q", pTrig->zName);
|
||||
}else{
|
||||
tmp = zWhere;
|
||||
zWhere = sqlite3MPrintf(db, "%s OR name=%Q", zWhere, pTrig->name);
|
||||
zWhere = sqlite3MPrintf(db, "%s OR name=%Q", zWhere, pTrig->zName);
|
||||
sqlite3DbFree(db, tmp);
|
||||
}
|
||||
}
|
||||
@@ -235,7 +235,7 @@ static void reloadTableSchema(Parse *pParse, Table *pTab, const char *zName){
|
||||
for(pTrig=sqlite3TriggerList(pParse, pTab); pTrig; pTrig=pTrig->pNext){
|
||||
int iTrigDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema);
|
||||
assert( iTrigDb==iDb || iTrigDb==1 );
|
||||
sqlite3VdbeAddOp4(v, OP_DropTrigger, iTrigDb, 0, 0, pTrig->name, 0);
|
||||
sqlite3VdbeAddOp4(v, OP_DropTrigger, iTrigDb, 0, 0, pTrig->zName, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
+13
-15
@@ -113,36 +113,34 @@ void sqlite3AuthRead(
|
||||
int iSrc; /* Index in pTabList->a[] of table being read */
|
||||
const char *zDBase; /* Name of database being accessed */
|
||||
int iDb; /* The index of the database the expression refers to */
|
||||
int iCol; /* Index of column in table */
|
||||
|
||||
if( db->xAuth==0 ) return;
|
||||
assert( pExpr->op==TK_COLUMN );
|
||||
iDb = sqlite3SchemaToIndex(pParse->db, pSchema);
|
||||
if( iDb<0 ){
|
||||
/* An attempt to read a column out of a subquery or other
|
||||
** temporary table. */
|
||||
return;
|
||||
}
|
||||
if( pTabList ){
|
||||
|
||||
assert( pExpr->op==TK_COLUMN || pExpr->op==TK_TRIGGER );
|
||||
if( pExpr->op==TK_TRIGGER ){
|
||||
pTab = pParse->pTriggerTab;
|
||||
}else{
|
||||
assert( pTabList );
|
||||
for(iSrc=0; iSrc<pTabList->nSrc; iSrc++){
|
||||
if( pExpr->iTable==pTabList->a[iSrc].iCursor ){
|
||||
pTab = pTabList->a[iSrc].pTab;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( !pTab ){
|
||||
TriggerStack *pStack = pParse->trigStack;
|
||||
if( ALWAYS(pStack) ){
|
||||
/* This must be an attempt to read the NEW or OLD pseudo-tables
|
||||
** of a trigger. */
|
||||
assert( pExpr->iTable==pStack->newIdx || pExpr->iTable==pStack->oldIdx );
|
||||
pTab = pStack->pTab;
|
||||
}
|
||||
}
|
||||
iCol = pExpr->iColumn;
|
||||
if( NEVER(pTab==0) ) return;
|
||||
if( pExpr->iColumn>=0 ){
|
||||
assert( pExpr->iColumn<pTab->nCol );
|
||||
zCol = pTab->aCol[pExpr->iColumn].zName;
|
||||
|
||||
if( iCol>=0 ){
|
||||
assert( iCol<pTab->nCol );
|
||||
zCol = pTab->aCol[iCol].zName;
|
||||
}else if( pTab->iPKey>=0 ){
|
||||
assert( pTab->iPKey<pTab->nCol );
|
||||
zCol = pTab->aCol[pTab->iPKey].zName;
|
||||
|
||||
+27
-25
@@ -64,31 +64,32 @@ void sqlite3TableLock(
|
||||
u8 isWriteLock, /* True for a write lock */
|
||||
const char *zName /* Name of the table to be locked */
|
||||
){
|
||||
Parse *pToplevel = sqlite3ParseToplevel(pParse);
|
||||
int i;
|
||||
int nBytes;
|
||||
TableLock *p;
|
||||
|
||||
assert( iDb>=0 );
|
||||
for(i=0; i<pParse->nTableLock; i++){
|
||||
p = &pParse->aTableLock[i];
|
||||
|
||||
for(i=0; i<pToplevel->nTableLock; i++){
|
||||
p = &pToplevel->aTableLock[i];
|
||||
if( p->iDb==iDb && p->iTab==iTab ){
|
||||
p->isWriteLock = (p->isWriteLock || isWriteLock);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
nBytes = sizeof(TableLock) * (pParse->nTableLock+1);
|
||||
pParse->aTableLock =
|
||||
sqlite3DbReallocOrFree(pParse->db, pParse->aTableLock, nBytes);
|
||||
if( pParse->aTableLock ){
|
||||
p = &pParse->aTableLock[pParse->nTableLock++];
|
||||
nBytes = sizeof(TableLock) * (pToplevel->nTableLock+1);
|
||||
pToplevel->aTableLock =
|
||||
sqlite3DbReallocOrFree(pToplevel->db, pToplevel->aTableLock, nBytes);
|
||||
if( pToplevel->aTableLock ){
|
||||
p = &pToplevel->aTableLock[pToplevel->nTableLock++];
|
||||
p->iDb = iDb;
|
||||
p->iTab = iTab;
|
||||
p->isWriteLock = isWriteLock;
|
||||
p->zName = zName;
|
||||
}else{
|
||||
pParse->nTableLock = 0;
|
||||
pParse->db->mallocFailed = 1;
|
||||
pToplevel->nTableLock = 0;
|
||||
pToplevel->db->mallocFailed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +195,7 @@ void sqlite3FinishCoding(Parse *pParse){
|
||||
#endif
|
||||
assert( pParse->iCacheLevel==0 ); /* Disables and re-enables match */
|
||||
sqlite3VdbeMakeReady(v, pParse->nVar, pParse->nMem,
|
||||
pParse->nTab, pParse->explain);
|
||||
pParse->nTab, pParse->nMaxArg, pParse->explain);
|
||||
pParse->rc = SQLITE_DONE;
|
||||
pParse->colNamesSet = 0;
|
||||
}else if( pParse->rc==SQLITE_OK ){
|
||||
@@ -3422,26 +3423,26 @@ int sqlite3OpenTempDatabase(Parse *pParse){
|
||||
** early in the code, before we know if any database tables will be used.
|
||||
*/
|
||||
void sqlite3CodeVerifySchema(Parse *pParse, int iDb){
|
||||
sqlite3 *db;
|
||||
Vdbe *v;
|
||||
int mask;
|
||||
Parse *pToplevel = sqlite3ParseToplevel(pParse);
|
||||
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
if( v==0 ) return; /* This only happens if there was a prior error */
|
||||
db = pParse->db;
|
||||
if( pParse->cookieGoto==0 ){
|
||||
pParse->cookieGoto = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0)+1;
|
||||
if( pToplevel->cookieGoto==0 ){
|
||||
Vdbe *v = sqlite3GetVdbe(pToplevel);
|
||||
if( v==0 ) return; /* This only happens if there was a prior error */
|
||||
pToplevel->cookieGoto = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0)+1;
|
||||
}
|
||||
if( iDb>=0 ){
|
||||
sqlite3 *db = pToplevel->db;
|
||||
int mask;
|
||||
|
||||
assert( iDb<db->nDb );
|
||||
assert( db->aDb[iDb].pBt!=0 || iDb==1 );
|
||||
assert( iDb<SQLITE_MAX_ATTACHED+2 );
|
||||
mask = 1<<iDb;
|
||||
if( (pParse->cookieMask & mask)==0 ){
|
||||
pParse->cookieMask |= mask;
|
||||
pParse->cookieValue[iDb] = db->aDb[iDb].pSchema->schema_cookie;
|
||||
if( (pToplevel->cookieMask & mask)==0 ){
|
||||
pToplevel->cookieMask |= mask;
|
||||
pToplevel->cookieValue[iDb] = db->aDb[iDb].pSchema->schema_cookie;
|
||||
if( !OMIT_TEMPDB && iDb==1 ){
|
||||
sqlite3OpenTempDatabase(pParse);
|
||||
sqlite3OpenTempDatabase(pToplevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3461,9 +3462,10 @@ void sqlite3CodeVerifySchema(Parse *pParse, int iDb){
|
||||
** necessary to undo a write and the checkpoint should not be set.
|
||||
*/
|
||||
void sqlite3BeginWriteOperation(Parse *pParse, int setStatement, int iDb){
|
||||
Parse *pToplevel = sqlite3ParseToplevel(pParse);
|
||||
sqlite3CodeVerifySchema(pParse, iDb);
|
||||
pParse->writeMask |= 1<<iDb;
|
||||
if( setStatement && pParse->nested==0 ){
|
||||
pToplevel->writeMask |= 1<<iDb;
|
||||
if( setStatement && pParse->nested==0 && pParse==pToplevel ){
|
||||
/* Every place where this routine is called with setStatement!=0 has
|
||||
** already successfully created a VDBE. */
|
||||
assert( pParse->pVdbe );
|
||||
|
||||
+34
-69
@@ -228,7 +228,6 @@ void sqlite3DeleteFrom(
|
||||
int iCur; /* VDBE Cursor number for pTab */
|
||||
sqlite3 *db; /* Main database structure */
|
||||
AuthContext sContext; /* Authorization context */
|
||||
int oldIdx = -1; /* Cursor for the OLD table of AFTER triggers */
|
||||
NameContext sNC; /* Name context to resolve expressions in */
|
||||
int iDb; /* Database number */
|
||||
int memCnt = -1; /* Memory cell used for change counting */
|
||||
@@ -238,11 +237,6 @@ void sqlite3DeleteFrom(
|
||||
int isView; /* True if attempting to delete from a view */
|
||||
Trigger *pTrigger; /* List of table triggers, if required */
|
||||
#endif
|
||||
int iBeginAfterTrigger = 0; /* Address of after trigger program */
|
||||
int iEndAfterTrigger = 0; /* Exit of after trigger program */
|
||||
int iBeginBeforeTrigger = 0; /* Address of before trigger program */
|
||||
int iEndBeforeTrigger = 0; /* Exit of before trigger program */
|
||||
u32 old_col_mask = 0; /* Mask of OLD.* columns in use */
|
||||
|
||||
memset(&sContext, 0, sizeof(sContext));
|
||||
db = pParse->db;
|
||||
@@ -293,12 +287,6 @@ void sqlite3DeleteFrom(
|
||||
}
|
||||
assert(!isView || pTrigger);
|
||||
|
||||
/* Allocate a cursor used to store the old.* data for a trigger.
|
||||
*/
|
||||
if( pTrigger ){
|
||||
oldIdx = pParse->nTab++;
|
||||
}
|
||||
|
||||
/* Assign cursor number to the table and all its indices.
|
||||
*/
|
||||
assert( pTabList->nSrc==1 );
|
||||
@@ -322,24 +310,6 @@ void sqlite3DeleteFrom(
|
||||
if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
|
||||
sqlite3BeginWriteOperation(pParse, (pTrigger?1:0), iDb);
|
||||
|
||||
if( pTrigger ){
|
||||
int orconf = ((pParse->trigStack)?pParse->trigStack->orconf:OE_Default);
|
||||
int iGoto = sqlite3VdbeAddOp0(v, OP_Goto);
|
||||
addr = sqlite3VdbeMakeLabel(v);
|
||||
|
||||
iBeginBeforeTrigger = sqlite3VdbeCurrentAddr(v);
|
||||
(void)sqlite3CodeRowTrigger(pParse, pTrigger, TK_DELETE, 0,
|
||||
TRIGGER_BEFORE, pTab, -1, oldIdx, orconf, addr, &old_col_mask, 0);
|
||||
iEndBeforeTrigger = sqlite3VdbeAddOp0(v, OP_Goto);
|
||||
|
||||
iBeginAfterTrigger = sqlite3VdbeCurrentAddr(v);
|
||||
(void)sqlite3CodeRowTrigger(pParse, pTrigger, TK_DELETE, 0,
|
||||
TRIGGER_AFTER, pTab, -1, oldIdx, orconf, addr, &old_col_mask, 0);
|
||||
iEndAfterTrigger = sqlite3VdbeAddOp0(v, OP_Goto);
|
||||
|
||||
sqlite3VdbeJumpHere(v, iGoto);
|
||||
}
|
||||
|
||||
/* If we are trying to delete from a view, realize that view into
|
||||
** a ephemeral table.
|
||||
*/
|
||||
@@ -385,8 +355,9 @@ void sqlite3DeleteFrom(
|
||||
** the table and pick which records to delete.
|
||||
*/
|
||||
{
|
||||
int iRowid = ++pParse->nMem; /* Used for storing rowid values. */
|
||||
int iRowSet = ++pParse->nMem; /* Register for rowset of rows to delete */
|
||||
int iRowid = ++pParse->nMem; /* Used for storing rowid values. */
|
||||
int regOld = pParse->nMem + 1; /* Start of array for old.* (if triggers) */
|
||||
int regRowid; /* Actual register containing rowids */
|
||||
|
||||
/* Collect rowids of every row to be deleted.
|
||||
@@ -401,35 +372,30 @@ void sqlite3DeleteFrom(
|
||||
}
|
||||
sqlite3WhereEnd(pWInfo);
|
||||
|
||||
/* Open the pseudo-table used to store OLD if there are triggers.
|
||||
*/
|
||||
if( pTrigger ){
|
||||
sqlite3VdbeAddOp3(v, OP_OpenPseudo, oldIdx, 0, pTab->nCol);
|
||||
}
|
||||
|
||||
/* Delete every item whose key was written to the list during the
|
||||
** database scan. We have to delete items after the scan is complete
|
||||
** because deleting an item can change the scan order.
|
||||
*/
|
||||
** because deleting an item can change the scan order. */
|
||||
end = sqlite3VdbeMakeLabel(v);
|
||||
|
||||
/* Unless this is a view, open cursors for the table we are
|
||||
** deleting from and all its indices. If this is a view, then the
|
||||
** only effect this statement has is to fire the INSTEAD OF
|
||||
** triggers. */
|
||||
if( !isView ){
|
||||
/* Open cursors for the table we are deleting from and
|
||||
** all its indices.
|
||||
*/
|
||||
sqlite3OpenTableAndIndices(pParse, pTab, iCur, OP_OpenWrite);
|
||||
}
|
||||
|
||||
/* This is the beginning of the delete loop. If a trigger encounters
|
||||
** an IGNORE constraint, it jumps back to here.
|
||||
*/
|
||||
if( pTrigger ){
|
||||
sqlite3VdbeResolveLabel(v, addr);
|
||||
}
|
||||
addr = sqlite3VdbeAddOp3(v, OP_RowSetRead, iRowSet, end, iRowid);
|
||||
|
||||
/* If there are triggers, populate an array of registers with the
|
||||
** data required by the old.* references in the trigger bodies. */
|
||||
if( pTrigger ){
|
||||
int iData = ++pParse->nMem; /* For storing row data of OLD table */
|
||||
u32 mask = 0; /* Mask of OLD.* columns in use */
|
||||
pParse->nMem += pTab->nCol;
|
||||
|
||||
/* Open the pseudo-table used to store OLD if there are triggers. */
|
||||
mask = sqlite3TriggerOldmask(
|
||||
pParse, pTrigger, TK_DELETE, 0, pTab, OE_Default);
|
||||
|
||||
/* If the record is no longer present in the table, jump to the
|
||||
** next iteration of the loop through the contents of the fifo.
|
||||
@@ -437,16 +403,19 @@ void sqlite3DeleteFrom(
|
||||
sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, iRowid);
|
||||
|
||||
/* Populate the OLD.* pseudo-table */
|
||||
if( old_col_mask ){
|
||||
sqlite3VdbeAddOp2(v, OP_RowData, iCur, iData);
|
||||
}else{
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, iData);
|
||||
assert( regOld==iRowid+1 );
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( mask==0xffffffff || mask&(1<<i) ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regOld+i);
|
||||
sqlite3ColumnDefault(v, pTab, i, regOld+i);
|
||||
}
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, oldIdx, iData, iRowid);
|
||||
sqlite3VdbeAddOp2(v, OP_Affinity, regOld, pTab->nCol);
|
||||
sqlite3TableAffinityStr(v, pTab);
|
||||
|
||||
/* Jump back and run the BEFORE triggers */
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginBeforeTrigger);
|
||||
sqlite3VdbeJumpHere(v, iEndBeforeTrigger);
|
||||
sqlite3CodeRowTrigger(pParse, pTrigger,
|
||||
TK_DELETE, 0, TRIGGER_BEFORE, pTab, -1, iRowid, OE_Default, addr
|
||||
);
|
||||
}
|
||||
|
||||
if( !isView ){
|
||||
@@ -463,21 +432,17 @@ void sqlite3DeleteFrom(
|
||||
}
|
||||
}
|
||||
|
||||
/* If there are row triggers, close all cursors then invoke
|
||||
** the AFTER triggers
|
||||
*/
|
||||
if( pTrigger ){
|
||||
/* Jump back and run the AFTER triggers */
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginAfterTrigger);
|
||||
sqlite3VdbeJumpHere(v, iEndAfterTrigger);
|
||||
}
|
||||
/* Code the AFTER triggers. This is a no-op if there are no triggers. */
|
||||
sqlite3CodeRowTrigger(pParse,
|
||||
pTrigger, TK_DELETE, 0, TRIGGER_AFTER, pTab, -1, iRowid, OE_Default, addr
|
||||
);
|
||||
|
||||
/* End of the delete loop */
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, addr);
|
||||
sqlite3VdbeResolveLabel(v, end);
|
||||
|
||||
/* Close the cursors after the loop if there are no row triggers */
|
||||
if( !isView && !IsVirtual(pTab) ){
|
||||
/* Close the cursors open on the table and its indexes. */
|
||||
if( !isView && !IsVirtual(pTab) ){
|
||||
for(i=1, pIdx=pTab->pIndex; pIdx; i++, pIdx=pIdx->pNext){
|
||||
sqlite3VdbeAddOp2(v, OP_Close, iCur + i, pIdx->tnum);
|
||||
}
|
||||
@@ -489,7 +454,7 @@ void sqlite3DeleteFrom(
|
||||
** maximum rowid counter values recorded while inserting into
|
||||
** autoincrement tables.
|
||||
*/
|
||||
if( pParse->nested==0 && pParse->trigStack==0 ){
|
||||
if( pParse->nested==0 && pParse->pTriggerTab==0 ){
|
||||
sqlite3AutoincrementEnd(pParse);
|
||||
}
|
||||
|
||||
@@ -498,7 +463,7 @@ void sqlite3DeleteFrom(
|
||||
** generating code because of a call to sqlite3NestedParse(), do not
|
||||
** invoke the callback function.
|
||||
*/
|
||||
if( db->flags & SQLITE_CountRows && pParse->nested==0 && !pParse->trigStack ){
|
||||
if( (db->flags&SQLITE_CountRows) && !pParse->nested && !pParse->pTriggerTab ){
|
||||
sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
|
||||
sqlite3VdbeSetNumCols(v, 1);
|
||||
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC);
|
||||
|
||||
+66
-20
@@ -90,7 +90,9 @@ CollSeq *sqlite3ExprCollSeq(Parse *pParse, Expr *pExpr){
|
||||
pColl = p->pColl;
|
||||
if( pColl ) break;
|
||||
op = p->op;
|
||||
if( (op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_REGISTER) && p->pTab!=0 ){
|
||||
if( p->pTab!=0 && (
|
||||
op==TK_AGG_COLUMN || op==TK_COLUMN || op==TK_REGISTER || op==TK_TRIGGER
|
||||
)){
|
||||
/* op==TK_REGISTER && p->pTab!=0 happens when pExpr was originally
|
||||
** a TK_COLUMN but was previously evaluated and cached in a register */
|
||||
const char *zColl;
|
||||
@@ -1396,7 +1398,6 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, int *prNotFound){
|
||||
if( iCol<0 ){
|
||||
int iMem = ++pParse->nMem;
|
||||
int iAddr;
|
||||
sqlite3VdbeUsesBtree(v, iDb);
|
||||
|
||||
iAddr = sqlite3VdbeAddOp1(v, OP_If, iMem);
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 1, iMem);
|
||||
@@ -1430,9 +1431,6 @@ int sqlite3FindInIndex(Parse *pParse, Expr *pX, int *prNotFound){
|
||||
char *pKey;
|
||||
|
||||
pKey = (char *)sqlite3IndexKeyinfo(pParse, pIdx);
|
||||
iDb = sqlite3SchemaToIndex(db, pIdx->pSchema);
|
||||
sqlite3VdbeUsesBtree(v, iDb);
|
||||
|
||||
iAddr = sqlite3VdbeAddOp1(v, OP_If, iMem);
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 1, iMem);
|
||||
|
||||
@@ -1521,7 +1519,7 @@ void sqlite3CodeSubselect(
|
||||
** If all of the above are false, then we can run this code just once
|
||||
** save the results, and reuse the same result on subsequent invocations.
|
||||
*/
|
||||
if( !ExprHasAnyProperty(pExpr, EP_VarSelect) && !pParse->trigStack ){
|
||||
if( !ExprHasAnyProperty(pExpr, EP_VarSelect) && !pParse->pTriggerTab ){
|
||||
int mem = ++pParse->nMem;
|
||||
sqlite3VdbeAddOp1(v, OP_If, mem);
|
||||
testAddr = sqlite3VdbeAddOp2(v, OP_Integer, 1, mem);
|
||||
@@ -2556,6 +2554,58 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
|
||||
break;
|
||||
}
|
||||
|
||||
case TK_TRIGGER: {
|
||||
/* If the opcode is TK_TRIGGER, then the expression is a reference
|
||||
** to a column in the new.* or old.* pseudo-tables available to
|
||||
** trigger programs. In this case Expr.iTable is set to 1 for the
|
||||
** new.* pseudo-table, or 0 for the old.* pseudo-table. Expr.iColumn
|
||||
** is set to the column of the pseudo-table to read, or to -1 to
|
||||
** read the rowid field.
|
||||
**
|
||||
** The expression is implemented using an OP_Param opcode. The p1
|
||||
** parameter is set to 0 for an old.rowid reference, or to (i+1)
|
||||
** to reference another column of the old.* pseudo-table, where
|
||||
** i is the index of the column. For a new.rowid reference, p1 is
|
||||
** set to (n+1), where n is the number of columns in each pseudo-table.
|
||||
** For a reference to any other column in the new.* pseudo-table, p1
|
||||
** is set to (n+2+i), where n and i are as defined previously. For
|
||||
** example, if the table on which triggers are being fired is
|
||||
** declared as:
|
||||
**
|
||||
** CREATE TABLE t1(a, b);
|
||||
**
|
||||
** Then p1 is interpreted as follows:
|
||||
**
|
||||
** p1==0 -> old.rowid p1==3 -> new.rowid
|
||||
** p1==1 -> old.a p1==4 -> new.a
|
||||
** p1==2 -> old.b p1==5 -> new.b
|
||||
*/
|
||||
Table *pTab = pExpr->pTab;
|
||||
int p1 = pExpr->iTable * (pTab->nCol+1) + 1 + pExpr->iColumn;
|
||||
|
||||
assert( pExpr->iTable==0 || pExpr->iTable==1 );
|
||||
assert( pExpr->iColumn>=-1 && pExpr->iColumn<pTab->nCol );
|
||||
assert( pTab->iPKey<0 || pExpr->iColumn!=pTab->iPKey );
|
||||
assert( p1>=0 && p1<(pTab->nCol*2+2) );
|
||||
|
||||
sqlite3VdbeAddOp2(v, OP_Param, p1, target);
|
||||
VdbeComment((v, "%s.%s -> $%d",
|
||||
(pExpr->iTable ? "new" : "old"),
|
||||
(pExpr->iColumn<0 ? "rowid" : pExpr->pTab->aCol[pExpr->iColumn].zName),
|
||||
target
|
||||
));
|
||||
|
||||
/* If the column has REAL affinity, it may currently be stored as an
|
||||
** integer. Use OP_RealAffinity to make sure it is really real. */
|
||||
if( pExpr->iColumn>=0
|
||||
&& pTab->aCol[pExpr->iColumn].affinity==SQLITE_AFF_REAL
|
||||
){
|
||||
sqlite3VdbeAddOp1(v, OP_RealAffinity, target);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Form A:
|
||||
** CASE x WHEN e1 THEN r1 WHEN e2 THEN r2 ... WHEN eN THEN rN ELSE y END
|
||||
@@ -2640,24 +2690,20 @@ int sqlite3ExprCodeTarget(Parse *pParse, Expr *pExpr, int target){
|
||||
}
|
||||
#ifndef SQLITE_OMIT_TRIGGER
|
||||
case TK_RAISE: {
|
||||
if( !pParse->trigStack ){
|
||||
int vrc;
|
||||
if( !pParse->pTriggerTab ){
|
||||
sqlite3ErrorMsg(pParse,
|
||||
"RAISE() may only be used within a trigger-program");
|
||||
return 0;
|
||||
}
|
||||
if( pExpr->affinity!=OE_Ignore ){
|
||||
assert( pExpr->affinity==OE_Rollback ||
|
||||
pExpr->affinity == OE_Abort ||
|
||||
pExpr->affinity == OE_Fail );
|
||||
assert( !ExprHasProperty(pExpr, EP_IntValue) );
|
||||
sqlite3VdbeAddOp4(v, OP_Halt, SQLITE_CONSTRAINT, pExpr->affinity, 0,
|
||||
pExpr->u.zToken, 0);
|
||||
} else {
|
||||
assert( pExpr->affinity == OE_Ignore );
|
||||
sqlite3VdbeAddOp2(v, OP_ContextPop, 0, 0);
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, pParse->trigStack->ignoreJump);
|
||||
VdbeComment((v, "raise(IGNORE)"));
|
||||
}
|
||||
assert( pExpr->affinity==OE_Rollback
|
||||
|| pExpr->affinity==OE_Abort
|
||||
|| pExpr->affinity==OE_Fail
|
||||
|| pExpr->affinity==OE_Ignore
|
||||
);
|
||||
assert( !ExprHasProperty(pExpr, EP_IntValue) );
|
||||
vrc = (pExpr->affinity==OE_Ignore ? SQLITE_OK : SQLITE_CONSTRAINT);
|
||||
sqlite3VdbeAddOp4(v, OP_Halt, vrc, pExpr->affinity, 0, pExpr->u.zToken,0);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
+45
-75
@@ -197,20 +197,21 @@ static int autoIncBegin(
|
||||
){
|
||||
int memId = 0; /* Register holding maximum rowid */
|
||||
if( pTab->tabFlags & TF_Autoincrement ){
|
||||
Parse *pToplevel = sqlite3ParseToplevel(pParse);
|
||||
AutoincInfo *pInfo;
|
||||
|
||||
pInfo = pParse->pAinc;
|
||||
pInfo = pToplevel->pAinc;
|
||||
while( pInfo && pInfo->pTab!=pTab ){ pInfo = pInfo->pNext; }
|
||||
if( pInfo==0 ){
|
||||
pInfo = sqlite3DbMallocRaw(pParse->db, sizeof(*pInfo));
|
||||
if( pInfo==0 ) return 0;
|
||||
pInfo->pNext = pParse->pAinc;
|
||||
pParse->pAinc = pInfo;
|
||||
pInfo->pNext = pToplevel->pAinc;
|
||||
pToplevel->pAinc = pInfo;
|
||||
pInfo->pTab = pTab;
|
||||
pInfo->iDb = iDb;
|
||||
pParse->nMem++; /* Register to hold name of table */
|
||||
pInfo->regCtr = ++pParse->nMem; /* Max rowid register */
|
||||
pParse->nMem++; /* Rowid in sqlite_sequence */
|
||||
pToplevel->nMem++; /* Register to hold name of table */
|
||||
pInfo->regCtr = ++pToplevel->nMem; /* Max rowid register */
|
||||
pToplevel->nMem++; /* Rowid in sqlite_sequence */
|
||||
}
|
||||
memId = pInfo->regCtr;
|
||||
}
|
||||
@@ -229,6 +230,9 @@ void sqlite3AutoincrementBegin(Parse *pParse){
|
||||
int addr; /* A VDBE address */
|
||||
Vdbe *v = pParse->pVdbe; /* VDBE under construction */
|
||||
|
||||
/* If currently generating a trigger program, this call is a no-op */
|
||||
if( pParse->pTriggerTab ) return;
|
||||
|
||||
assert( v ); /* We failed long ago if this is not so */
|
||||
for(p = pParse->pAinc; p; p = p->pNext){
|
||||
pDb = &db->aDb[p->iDb];
|
||||
@@ -450,7 +454,6 @@ void sqlite3Insert(
|
||||
int addrCont = 0; /* Top of insert loop. Label "C" in templates 3 and 4 */
|
||||
int addrSelect = 0; /* Address of coroutine that implements the SELECT */
|
||||
SelectDest dest; /* Destination for SELECT on rhs of INSERT */
|
||||
int newIdx = -1; /* Cursor for the NEW pseudo-table */
|
||||
int iDb; /* Index of database holding TABLE */
|
||||
Db *pDb; /* The database containing table being inserted into */
|
||||
int appendFlag = 0; /* True if the insert is likely to be an append */
|
||||
@@ -466,7 +469,6 @@ void sqlite3Insert(
|
||||
int regEof = 0; /* Register recording end of SELECT data */
|
||||
int *aRegIdx = 0; /* One register allocated to each index */
|
||||
|
||||
|
||||
#ifndef SQLITE_OMIT_TRIGGER
|
||||
int isView; /* True if attempting to insert into a view */
|
||||
Trigger *pTrigger; /* List of triggers on pTab, if required */
|
||||
@@ -536,11 +538,6 @@ void sqlite3Insert(
|
||||
if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
|
||||
sqlite3BeginWriteOperation(pParse, pSelect || pTrigger, iDb);
|
||||
|
||||
/* if there are row triggers, allocate a temp table for new.* references. */
|
||||
if( pTrigger ){
|
||||
newIdx = pParse->nTab++;
|
||||
}
|
||||
|
||||
#ifndef SQLITE_OMIT_XFER_OPT
|
||||
/* If the statement is of the form
|
||||
**
|
||||
@@ -744,12 +741,6 @@ void sqlite3Insert(
|
||||
if( pColumn==0 && nColumn>0 ){
|
||||
keyColumn = pTab->iPKey;
|
||||
}
|
||||
|
||||
/* Open the temp table for FOR EACH ROW triggers
|
||||
*/
|
||||
if( pTrigger ){
|
||||
sqlite3VdbeAddOp3(v, OP_OpenPseudo, newIdx, 0, pTab->nCol);
|
||||
}
|
||||
|
||||
/* Initialize the count of rows to be inserted
|
||||
*/
|
||||
@@ -816,9 +807,7 @@ void sqlite3Insert(
|
||||
*/
|
||||
endOfLoop = sqlite3VdbeMakeLabel(v);
|
||||
if( tmask & TRIGGER_BEFORE ){
|
||||
int regTrigRowid;
|
||||
int regCols;
|
||||
int regRec;
|
||||
int regCols = sqlite3GetTempRange(pParse, pTab->nCol+1);
|
||||
|
||||
/* build the NEW.* reference row. Note that if there is an INTEGER
|
||||
** PRIMARY KEY into which a NULL is being inserted, that NULL will be
|
||||
@@ -826,31 +815,29 @@ void sqlite3Insert(
|
||||
** we do not know what the unique ID will be (because the insert has
|
||||
** not happened yet) so we substitute a rowid of -1
|
||||
*/
|
||||
regTrigRowid = sqlite3GetTempReg(pParse);
|
||||
if( keyColumn<0 ){
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, -1, regTrigRowid);
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols);
|
||||
}else{
|
||||
int j1;
|
||||
if( useTempTable ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, srcTab, keyColumn, regTrigRowid);
|
||||
sqlite3VdbeAddOp3(v, OP_Column, srcTab, keyColumn, regCols);
|
||||
}else{
|
||||
assert( pSelect==0 ); /* Otherwise useTempTable is true */
|
||||
sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr, regTrigRowid);
|
||||
sqlite3ExprCode(pParse, pList->a[keyColumn].pExpr, regCols);
|
||||
}
|
||||
j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regTrigRowid);
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, -1, regTrigRowid);
|
||||
j1 = sqlite3VdbeAddOp1(v, OP_NotNull, regCols);
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, -1, regCols);
|
||||
sqlite3VdbeJumpHere(v, j1);
|
||||
sqlite3VdbeAddOp1(v, OP_MustBeInt, regTrigRowid);
|
||||
sqlite3VdbeAddOp1(v, OP_MustBeInt, regCols);
|
||||
}
|
||||
|
||||
/* Cannot have triggers on a virtual table. If it were possible,
|
||||
** this block would have to account for hidden column.
|
||||
*/
|
||||
assert(!IsVirtual(pTab));
|
||||
assert( !IsVirtual(pTab) );
|
||||
|
||||
/* Create the new column data
|
||||
*/
|
||||
regCols = sqlite3GetTempRange(pParse, pTab->nCol);
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( pColumn==0 ){
|
||||
j = i;
|
||||
@@ -860,16 +847,14 @@ void sqlite3Insert(
|
||||
}
|
||||
}
|
||||
if( pColumn && j>=pColumn->nId ){
|
||||
sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regCols+i);
|
||||
sqlite3ExprCode(pParse, pTab->aCol[i].pDflt, regCols+i+1);
|
||||
}else if( useTempTable ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, regCols+i);
|
||||
sqlite3VdbeAddOp3(v, OP_Column, srcTab, j, regCols+i+1);
|
||||
}else{
|
||||
assert( pSelect==0 ); /* Otherwise useTempTable is true */
|
||||
sqlite3ExprCodeAndCache(pParse, pList->a[j].pExpr, regCols+i);
|
||||
sqlite3ExprCodeAndCache(pParse, pList->a[j].pExpr, regCols+i+1);
|
||||
}
|
||||
}
|
||||
regRec = sqlite3GetTempReg(pParse);
|
||||
sqlite3VdbeAddOp3(v, OP_MakeRecord, regCols, pTab->nCol, regRec);
|
||||
|
||||
/* If this is an INSERT on a view with an INSTEAD OF INSERT trigger,
|
||||
** do not attempt any conversions before assembling the record.
|
||||
@@ -877,18 +862,15 @@ void sqlite3Insert(
|
||||
** table column affinities.
|
||||
*/
|
||||
if( !isView ){
|
||||
sqlite3VdbeAddOp2(v, OP_Affinity, regCols+1, pTab->nCol);
|
||||
sqlite3TableAffinityStr(v, pTab);
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, newIdx, regRec, regTrigRowid);
|
||||
sqlite3ReleaseTempReg(pParse, regRec);
|
||||
sqlite3ReleaseTempReg(pParse, regTrigRowid);
|
||||
sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol);
|
||||
|
||||
/* Fire BEFORE or INSTEAD OF triggers */
|
||||
if( sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE,
|
||||
pTab, newIdx, -1, onError, endOfLoop, 0, 0) ){
|
||||
goto insert_cleanup;
|
||||
}
|
||||
sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_BEFORE,
|
||||
pTab, -1, regCols-pTab->nCol-1, onError, endOfLoop);
|
||||
|
||||
sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol+1);
|
||||
}
|
||||
|
||||
/* Push the record number for the new entry onto the stack. The
|
||||
@@ -995,8 +977,7 @@ void sqlite3Insert(
|
||||
keyColumn>=0, 0, onError, endOfLoop, &isReplace
|
||||
);
|
||||
sqlite3CompleteInsertion(
|
||||
pParse, pTab, baseCur, regIns, aRegIdx, 0,
|
||||
(tmask&TRIGGER_AFTER) ? newIdx : -1, appendFlag, isReplace==0
|
||||
pParse, pTab, baseCur, regIns, aRegIdx, 0, appendFlag, isReplace==0
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1009,10 +990,8 @@ void sqlite3Insert(
|
||||
|
||||
if( pTrigger ){
|
||||
/* Code AFTER triggers */
|
||||
if( sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_AFTER,
|
||||
pTab, newIdx, -1, onError, endOfLoop, 0, 0) ){
|
||||
goto insert_cleanup;
|
||||
}
|
||||
sqlite3CodeRowTrigger(pParse, pTrigger, TK_INSERT, 0, TRIGGER_AFTER,
|
||||
pTab, -1, regData-2-pTab->nCol, onError, endOfLoop);
|
||||
}
|
||||
|
||||
/* The bottom of the main insertion loop, if the data source
|
||||
@@ -1041,7 +1020,7 @@ insert_end:
|
||||
** maximum rowid counter values recorded while inserting into
|
||||
** autoincrement tables.
|
||||
*/
|
||||
if( pParse->nested==0 && pParse->trigStack==0 ){
|
||||
if( pParse->nested==0 && pParse->pTriggerTab==0 ){
|
||||
sqlite3AutoincrementEnd(pParse);
|
||||
}
|
||||
|
||||
@@ -1050,7 +1029,7 @@ insert_end:
|
||||
** generating code because of a call to sqlite3NestedParse(), do not
|
||||
** invoke the callback function.
|
||||
*/
|
||||
if( db->flags & SQLITE_CountRows && pParse->nested==0 && !pParse->trigStack ){
|
||||
if( (db->flags&SQLITE_CountRows) && !pParse->nested && !pParse->pTriggerTab ){
|
||||
sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
|
||||
sqlite3VdbeSetNumCols(v, 1);
|
||||
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows inserted", SQLITE_STATIC);
|
||||
@@ -1069,26 +1048,24 @@ insert_cleanup:
|
||||
**
|
||||
** The input is a range of consecutive registers as follows:
|
||||
**
|
||||
** 1. The rowid of the row to be updated before the update. This
|
||||
** value is omitted unless we are doing an UPDATE that involves a
|
||||
** change to the record number or writing to a virtual table.
|
||||
** 1. The rowid of the row after the update.
|
||||
**
|
||||
** 2. The rowid of the row after the update.
|
||||
**
|
||||
** 3. The data in the first column of the entry after the update.
|
||||
** 2. The data in the first column of the entry after the update.
|
||||
**
|
||||
** i. Data from middle columns...
|
||||
**
|
||||
** N. The data in the last column of the entry after the update.
|
||||
**
|
||||
** The regRowid parameter is the index of the register containing (2).
|
||||
** The regRowid parameter is the index of the register containing (1).
|
||||
**
|
||||
** The old rowid shown as entry (1) above is omitted unless both isUpdate
|
||||
** and rowidChng are 1. isUpdate is true for UPDATEs and false for
|
||||
** INSERTs. RowidChng means that the new rowid is explicitly specified by
|
||||
** the update or insert statement. If rowidChng is false, it means that
|
||||
** the rowid is computed automatically in an insert or that the rowid value
|
||||
** is not modified by the update.
|
||||
** If isUpdate is true and rowidChng is non-zero, then rowidChng contains
|
||||
** the address of a register containing the rowid before the update takes
|
||||
** place. isUpdate is true for UPDATEs and false for INSERTs. If isUpdate
|
||||
** is false, indicating an INSERT statement, then a non-zero rowidChng
|
||||
** indicates that the rowid was explicitly specified as part of the
|
||||
** INSERT statement. If rowidChng is false, it means that the rowid is
|
||||
** computed automatically in an insert or that the rowid value is not
|
||||
** modified by an update.
|
||||
**
|
||||
** The code generated by this routine store new index entries into
|
||||
** registers identified by aRegIdx[]. No index entry is created for
|
||||
@@ -1163,7 +1140,7 @@ void sqlite3GenerateConstraintChecks(
|
||||
int iCur; /* Table cursor number */
|
||||
Index *pIdx; /* Pointer to one of the indices */
|
||||
int seenReplace = 0; /* True if REPLACE is used to resolve INT PK conflict */
|
||||
int hasTwoRowids = (isUpdate && rowidChng);
|
||||
int regOldRowid = (rowidChng && isUpdate) ? rowidChng : regRowid;
|
||||
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
assert( v!=0 );
|
||||
@@ -1171,7 +1148,6 @@ void sqlite3GenerateConstraintChecks(
|
||||
nCol = pTab->nCol;
|
||||
regData = regRowid + 1;
|
||||
|
||||
|
||||
/* Test all NOT NULL constraints.
|
||||
*/
|
||||
for(i=0; i<nCol; i++){
|
||||
@@ -1247,7 +1223,7 @@ void sqlite3GenerateConstraintChecks(
|
||||
|
||||
if( onError!=OE_Replace || pTab->pIndex ){
|
||||
if( isUpdate ){
|
||||
j2 = sqlite3VdbeAddOp3(v, OP_Eq, regRowid, 0, regRowid-1);
|
||||
j2 = sqlite3VdbeAddOp3(v, OP_Eq, regRowid, 0, rowidChng);
|
||||
}
|
||||
j3 = sqlite3VdbeAddOp3(v, OP_NotExists, baseCur, 0, regRowid);
|
||||
switch( onError ){
|
||||
@@ -1324,7 +1300,7 @@ void sqlite3GenerateConstraintChecks(
|
||||
|
||||
/* Check to see if the new index entry will be unique */
|
||||
regR = sqlite3GetTempReg(pParse);
|
||||
sqlite3VdbeAddOp2(v, OP_SCopy, regRowid-hasTwoRowids, regR);
|
||||
sqlite3VdbeAddOp2(v, OP_SCopy, regOldRowid, regR);
|
||||
j3 = sqlite3VdbeAddOp4(v, OP_IsUnique, baseCur+iCur+1, 0,
|
||||
regR, SQLITE_INT_TO_PTR(regIdx),
|
||||
P4_INT32);
|
||||
@@ -1395,7 +1371,6 @@ void sqlite3CompleteInsertion(
|
||||
int regRowid, /* Range of content */
|
||||
int *aRegIdx, /* Register used by each index. 0 for unused indices */
|
||||
int isUpdate, /* True for UPDATE, False for INSERT */
|
||||
int newIdx, /* Index of NEW table for triggers. -1 if none */
|
||||
int appendBias, /* True if this is likely to be an append */
|
||||
int useSeekResult /* True to set the USESEEKRESULT flag on OP_[Idx]Insert */
|
||||
){
|
||||
@@ -1423,11 +1398,6 @@ void sqlite3CompleteInsertion(
|
||||
sqlite3VdbeAddOp3(v, OP_MakeRecord, regData, pTab->nCol, regRec);
|
||||
sqlite3TableAffinityStr(v, pTab);
|
||||
sqlite3ExprCacheAffinityChange(pParse, regData, pTab->nCol);
|
||||
#ifndef SQLITE_OMIT_TRIGGER
|
||||
if( newIdx>=0 ){
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, newIdx, regRec, regRowid);
|
||||
}
|
||||
#endif
|
||||
if( pParse->nested ){
|
||||
pik_flags = 0;
|
||||
}else{
|
||||
|
||||
@@ -1590,6 +1590,9 @@ static int openDatabase(
|
||||
#endif
|
||||
#ifdef SQLITE_ENABLE_LOAD_EXTENSION
|
||||
| SQLITE_LoadExtension
|
||||
#endif
|
||||
#if 1 || defined(SQLITE_DISABLE_RECURSIVE_TRIGGERS)
|
||||
| SQLITE_NoRecTriggers
|
||||
#endif
|
||||
;
|
||||
sqlite3HashInit(&db->aCollSeq);
|
||||
|
||||
@@ -190,6 +190,7 @@ static int flagPragma(Parse *pParse, const char *zLeft, const char *zRight){
|
||||
/* TODO: Maybe it shouldn't be possible to change the ReadUncommitted
|
||||
** flag if there are any active statements. */
|
||||
{ "read_uncommitted", SQLITE_ReadUncommitted },
|
||||
{ "disable_recursive_triggers", SQLITE_NoRecTriggers },
|
||||
};
|
||||
int i;
|
||||
const struct sPragmaType *p;
|
||||
|
||||
@@ -676,6 +676,14 @@ static int sqlite3Prepare(
|
||||
sqlite3Error(db, rc, 0);
|
||||
}
|
||||
|
||||
/* Delete any TriggerPrg structures allocated while parsing this statement. */
|
||||
while( pParse->pTriggerPrg ){
|
||||
TriggerPrg *pT = pParse->pTriggerPrg;
|
||||
pParse->pTriggerPrg = pT->pNext;
|
||||
sqlite3VdbeProgramDelete(db, pT->pProgram, 0);
|
||||
sqlite3DbFree(db, pT);
|
||||
}
|
||||
|
||||
end_prepare:
|
||||
|
||||
sqlite3StackFree(db, pParse);
|
||||
|
||||
+33
-27
@@ -137,6 +137,7 @@ static int lookupName(
|
||||
struct SrcList_item *pMatch = 0; /* The matching pSrcList item */
|
||||
NameContext *pTopNC = pNC; /* First namecontext in the list */
|
||||
Schema *pSchema = 0; /* Schema of the expression */
|
||||
int isTrigger = 0;
|
||||
|
||||
assert( pNC ); /* the name context cannot be NULL. */
|
||||
assert( zCol ); /* The Z in X.Y.Z cannot be NULL */
|
||||
@@ -222,42 +223,47 @@ static int lookupName(
|
||||
/* If we have not already resolved the name, then maybe
|
||||
** it is a new.* or old.* trigger argument reference
|
||||
*/
|
||||
if( zDb==0 && zTab!=0 && cnt==0 && pParse->trigStack!=0 ){
|
||||
TriggerStack *pTriggerStack = pParse->trigStack;
|
||||
if( zDb==0 && zTab!=0 && cnt==0 && pParse->pTriggerTab!=0 ){
|
||||
int op = pParse->eTriggerOp;
|
||||
Table *pTab = 0;
|
||||
u32 *piColMask = 0;
|
||||
if( pTriggerStack->newIdx != -1 && sqlite3StrICmp("new", zTab) == 0 ){
|
||||
pExpr->iTable = pTriggerStack->newIdx;
|
||||
assert( pTriggerStack->pTab );
|
||||
pTab = pTriggerStack->pTab;
|
||||
piColMask = &(pTriggerStack->newColMask);
|
||||
}else if( pTriggerStack->oldIdx != -1 && sqlite3StrICmp("old", zTab)==0 ){
|
||||
pExpr->iTable = pTriggerStack->oldIdx;
|
||||
assert( pTriggerStack->pTab );
|
||||
pTab = pTriggerStack->pTab;
|
||||
piColMask = &(pTriggerStack->oldColMask);
|
||||
assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT );
|
||||
if( op!=TK_DELETE && sqlite3StrICmp("new",zTab) == 0 ){
|
||||
pExpr->iTable = 1;
|
||||
pTab = pParse->pTriggerTab;
|
||||
}else if( op!=TK_INSERT && sqlite3StrICmp("old",zTab)==0 ){
|
||||
pExpr->iTable = 0;
|
||||
pTab = pParse->pTriggerTab;
|
||||
}
|
||||
|
||||
if( pTab ){
|
||||
int iCol;
|
||||
Column *pCol = pTab->aCol;
|
||||
|
||||
pSchema = pTab->pSchema;
|
||||
cntTab++;
|
||||
for(iCol=0; iCol < pTab->nCol; iCol++, pCol++) {
|
||||
if( sqlite3StrICmp(pCol->zName, zCol)==0 ){
|
||||
cnt++;
|
||||
pExpr->iColumn = iCol==pTab->iPKey ? -1 : (i16)iCol;
|
||||
pExpr->pTab = pTab;
|
||||
if( sqlite3IsRowid(zCol) ){
|
||||
iCol = -1;
|
||||
}else{
|
||||
for(iCol=0; iCol<pTab->nCol; iCol++){
|
||||
Column *pCol = &pTab->aCol[iCol];
|
||||
if( sqlite3StrICmp(pCol->zName, zCol)==0 ){
|
||||
if( iCol==pTab->iPKey ){
|
||||
iCol = -1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( iCol<pTab->nCol ){
|
||||
cnt++;
|
||||
if( iCol<0 ){
|
||||
pExpr->affinity = SQLITE_AFF_INTEGER;
|
||||
}else if( pExpr->iTable==0 ){
|
||||
testcase( iCol==31 );
|
||||
testcase( iCol==32 );
|
||||
if( iCol>=32 ){
|
||||
*piColMask = 0xffffffff;
|
||||
}else{
|
||||
*piColMask |= ((u32)1)<<iCol;
|
||||
}
|
||||
break;
|
||||
pParse->oldmask |= (iCol>=32 ? 0xffffffff : (((u32)1)<<iCol));
|
||||
}
|
||||
pExpr->iColumn = iCol;
|
||||
pExpr->pTab = pTab;
|
||||
isTrigger = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -369,7 +375,7 @@ static int lookupName(
|
||||
pExpr->pLeft = 0;
|
||||
sqlite3ExprDelete(db, pExpr->pRight);
|
||||
pExpr->pRight = 0;
|
||||
pExpr->op = TK_COLUMN;
|
||||
pExpr->op = (isTrigger ? TK_TRIGGER : TK_COLUMN);
|
||||
lookupname_end:
|
||||
if( cnt==1 ){
|
||||
assert( pNC!=0 );
|
||||
|
||||
+3
-2
@@ -2734,8 +2734,9 @@ static int flattenSubquery(
|
||||
if( ALWAYS(pSubitem->pTab!=0) ){
|
||||
Table *pTabToDel = pSubitem->pTab;
|
||||
if( pTabToDel->nRef==1 ){
|
||||
pTabToDel->pNextZombie = pParse->pZombieTab;
|
||||
pParse->pZombieTab = pTabToDel;
|
||||
Parse *pToplevel = sqlite3ParseToplevel(pParse);
|
||||
pTabToDel->pNextZombie = pToplevel->pZombieTab;
|
||||
pToplevel->pZombieTab = pTabToDel;
|
||||
}else{
|
||||
pTabToDel->nRef--;
|
||||
}
|
||||
|
||||
+46
-47
@@ -611,7 +611,7 @@ typedef struct StrAccum StrAccum;
|
||||
typedef struct Table Table;
|
||||
typedef struct TableLock TableLock;
|
||||
typedef struct Token Token;
|
||||
typedef struct TriggerStack TriggerStack;
|
||||
typedef struct TriggerPrg TriggerPrg;
|
||||
typedef struct TriggerStep TriggerStep;
|
||||
typedef struct Trigger Trigger;
|
||||
typedef struct UnpackedRecord UnpackedRecord;
|
||||
@@ -911,6 +911,7 @@ struct sqlite3 {
|
||||
|
||||
#define SQLITE_RecoveryMode 0x00040000 /* Ignore schema errors */
|
||||
#define SQLITE_ReverseOrder 0x00100000 /* Reverse unordered SELECTs */
|
||||
#define SQLITE_NoRecTriggers 0x00200000 /* Disable recursive triggers */
|
||||
|
||||
/*
|
||||
** Possible values for the sqlite.magic field.
|
||||
@@ -1576,7 +1577,8 @@ struct Expr {
|
||||
*********************************************************************/
|
||||
|
||||
int iTable; /* TK_COLUMN: cursor number of table holding column
|
||||
** TK_REGISTER: register number */
|
||||
** TK_REGISTER: register number
|
||||
** TK_TRIGGER: 1 -> new, 0 -> old */
|
||||
i16 iColumn; /* TK_COLUMN: column index. -1 for rowid */
|
||||
i16 iAgg; /* Which entry in pAggInfo->aCol[] or ->aFunc[] */
|
||||
i16 iRightJoinTable; /* If EP_FromJoin, the right table of the join */
|
||||
@@ -2015,6 +2017,31 @@ struct AutoincInfo {
|
||||
# define SQLITE_N_COLCACHE 10
|
||||
#endif
|
||||
|
||||
/*
|
||||
** At least one instance of the following structure is created for each
|
||||
** trigger that may be fired while parsing an INSERT, UPDATE or DELETE
|
||||
** statement. All such objects are stored in the linked list headed at
|
||||
** Parse.pTriggerPrg and deleted once statement compilation has been
|
||||
** completed.
|
||||
**
|
||||
** A Vdbe sub-program that implements the body and WHEN clause of trigger
|
||||
** TriggerPrg.pTrigger, assuming a default ON CONFLICT clause of
|
||||
** TriggerPrg.orconf, is stored in the TriggerPrg.pProgram variable.
|
||||
** The Parse.pTriggerPrg list never contains two entries with the same
|
||||
** values for both pTrigger and orconf.
|
||||
**
|
||||
** The TriggerPrg.oldmask variable is set to a mask of old.* columns
|
||||
** accessed (or set to 0 for triggers fired as a result of INSERT
|
||||
** statements).
|
||||
*/
|
||||
struct TriggerPrg {
|
||||
Trigger *pTrigger; /* Trigger this program was coded from */
|
||||
int orconf; /* Default ON CONFLICT policy */
|
||||
SubProgram *pProgram; /* Program implementing pTrigger/orconf */
|
||||
u32 oldmask; /* Mask of old.* columns accessed */
|
||||
TriggerPrg *pNext; /* Next entry in Parse.pTriggerPrg list */
|
||||
};
|
||||
|
||||
/*
|
||||
** An SQL parser context. A copy of this structure is passed through
|
||||
** the parser and down into all the parser action routine in order to
|
||||
@@ -2075,6 +2102,14 @@ struct Parse {
|
||||
int regRowid; /* Register holding rowid of CREATE TABLE entry */
|
||||
int regRoot; /* Register holding root page number for new objects */
|
||||
AutoincInfo *pAinc; /* Information about AUTOINCREMENT counters */
|
||||
int nMaxArg; /* Max args passed to user function by sub-program */
|
||||
|
||||
/* Information used while coding trigger programs. */
|
||||
Parse *pToplevel; /* Parse structure for main program (or NULL) */
|
||||
Table *pTriggerTab; /* Table triggers are being coded for */
|
||||
u32 oldmask; /* Mask of old.* columns referenced */
|
||||
u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */
|
||||
u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */
|
||||
|
||||
/* Above is constant between recursions. Below is reset before and after
|
||||
** each recursion */
|
||||
@@ -2092,7 +2127,6 @@ struct Parse {
|
||||
const char *zTail; /* All SQL text past the last semicolon parsed */
|
||||
Table *pNewTable; /* A table being constructed by CREATE TABLE */
|
||||
Trigger *pNewTrigger; /* Trigger under construct by a CREATE TRIGGER */
|
||||
TriggerStack *trigStack; /* Trigger actions being coded */
|
||||
const char *zAuthContext; /* The 6th parameter to db->xAuth callbacks */
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
Token sArg; /* Complete text of a module argument */
|
||||
@@ -2102,6 +2136,7 @@ struct Parse {
|
||||
#endif
|
||||
int nHeight; /* Expression tree height of current sub-select */
|
||||
Table *pZombieTab; /* List of Table objects to delete after code gen */
|
||||
TriggerPrg *pTriggerPrg; /* Linked list of coded triggers */
|
||||
};
|
||||
|
||||
#ifdef SQLITE_OMIT_VIRTUALTABLE
|
||||
@@ -2144,7 +2179,7 @@ struct AuthContext {
|
||||
* containing the SQL statements specified as the trigger program.
|
||||
*/
|
||||
struct Trigger {
|
||||
char *name; /* The name of the trigger */
|
||||
char *zName; /* The name of the trigger */
|
||||
char *table; /* The table or view to which the trigger applies */
|
||||
u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT */
|
||||
u8 tr_tm; /* One of TRIGGER_BEFORE, TRIGGER_AFTER */
|
||||
@@ -2218,45 +2253,6 @@ struct TriggerStep {
|
||||
TriggerStep *pLast; /* Last element in link-list. Valid for 1st elem only */
|
||||
};
|
||||
|
||||
/*
|
||||
* An instance of struct TriggerStack stores information required during code
|
||||
* generation of a single trigger program. While the trigger program is being
|
||||
* coded, its associated TriggerStack instance is pointed to by the
|
||||
* "pTriggerStack" member of the Parse structure.
|
||||
*
|
||||
* The pTab member points to the table that triggers are being coded on. The
|
||||
* newIdx member contains the index of the vdbe cursor that points at the temp
|
||||
* table that stores the new.* references. If new.* references are not valid
|
||||
* for the trigger being coded (for example an ON DELETE trigger), then newIdx
|
||||
* is set to -1. The oldIdx member is analogous to newIdx, for old.* references.
|
||||
*
|
||||
* The ON CONFLICT policy to be used for the trigger program steps is stored
|
||||
* as the orconf member. If this is OE_Default, then the ON CONFLICT clause
|
||||
* specified for individual triggers steps is used.
|
||||
*
|
||||
* struct TriggerStack has a "pNext" member, to allow linked lists to be
|
||||
* constructed. When coding nested triggers (triggers fired by other triggers)
|
||||
* each nested trigger stores its parent trigger's TriggerStack as the "pNext"
|
||||
* pointer. Once the nested trigger has been coded, the pNext value is restored
|
||||
* to the pTriggerStack member of the Parse stucture and coding of the parent
|
||||
* trigger continues.
|
||||
*
|
||||
* Before a nested trigger is coded, the linked list pointed to by the
|
||||
* pTriggerStack is scanned to ensure that the trigger is not about to be coded
|
||||
* recursively. If this condition is detected, the nested trigger is not coded.
|
||||
*/
|
||||
struct TriggerStack {
|
||||
Table *pTab; /* Table that triggers are currently being coded on */
|
||||
int newIdx; /* Index of vdbe cursor to "new" temp table */
|
||||
int oldIdx; /* Index of vdbe cursor to "old" temp table */
|
||||
u32 newColMask;
|
||||
u32 oldColMask;
|
||||
int orconf; /* Current orconf policy */
|
||||
int ignoreJump; /* where to jump to for a RAISE(IGNORE) */
|
||||
Trigger *pTrigger; /* The trigger currently being coded */
|
||||
TriggerStack *pNext; /* Next trigger down on the trigger stack */
|
||||
};
|
||||
|
||||
/*
|
||||
** The following structure contains information used by the sqliteFix...
|
||||
** routines as they walk the parse tree to make database references
|
||||
@@ -2651,7 +2647,7 @@ void sqlite3GenerateRowIndexDelete(Parse*, Table*, int, int*);
|
||||
int sqlite3GenerateIndexKey(Parse*, Index*, int, int, int);
|
||||
void sqlite3GenerateConstraintChecks(Parse*,Table*,int,int,
|
||||
int*,int,int,int,int,int*);
|
||||
void sqlite3CompleteInsertion(Parse*, Table*, int, int, int*, int, int,int,int);
|
||||
void sqlite3CompleteInsertion(Parse*, Table*, int, int, int*, int, int, int);
|
||||
int sqlite3OpenTableAndIndices(Parse*, Table*, int, int);
|
||||
void sqlite3BeginWriteOperation(Parse*, int, int);
|
||||
Expr *sqlite3ExprDup(sqlite3*,Expr*,int);
|
||||
@@ -2687,8 +2683,8 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, int);
|
||||
void sqlite3DropTriggerPtr(Parse*, Trigger*);
|
||||
Trigger *sqlite3TriggersExist(Parse *, Table*, int, ExprList*, int *pMask);
|
||||
Trigger *sqlite3TriggerList(Parse *, Table *);
|
||||
int sqlite3CodeRowTrigger(Parse*, Trigger *, int, ExprList*, int, Table *,
|
||||
int, int, int, int, u32*, u32*);
|
||||
void sqlite3CodeRowTrigger(Parse*, Trigger *, int, ExprList*, int, Table *,
|
||||
int, int, int, int);
|
||||
void sqliteViewTriggers(Parse*, Table*, Expr*, int, ExprList*);
|
||||
void sqlite3DeleteTriggerStep(sqlite3*, TriggerStep*);
|
||||
TriggerStep *sqlite3TriggerSelectStep(sqlite3*,Select*);
|
||||
@@ -2698,13 +2694,16 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, int);
|
||||
TriggerStep *sqlite3TriggerDeleteStep(sqlite3*,Token*, Expr*);
|
||||
void sqlite3DeleteTrigger(sqlite3*, Trigger*);
|
||||
void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*);
|
||||
u32 sqlite3TriggerOldmask(Parse*,Trigger*,int,ExprList*,Table*,int);
|
||||
# define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p))
|
||||
#else
|
||||
# define sqlite3TriggersExist(B,C,D,E,F) 0
|
||||
# define sqlite3DeleteTrigger(A,B)
|
||||
# define sqlite3DropTriggerPtr(A,B)
|
||||
# define sqlite3UnlinkAndDeleteTrigger(A,B,C)
|
||||
# define sqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I,J,K,L) 0
|
||||
# define sqlite3CodeRowTrigger(A,B,C,D,E,F,G,H,I,J)
|
||||
# define sqlite3TriggerList(X, Y) 0
|
||||
# define sqlite3ParseToplevel(p) p
|
||||
#endif
|
||||
|
||||
int sqlite3JoinType(Parse*, Token*, Token*, Token*);
|
||||
|
||||
+293
-130
@@ -219,7 +219,7 @@ void sqlite3BeginTrigger(
|
||||
/* Build the Trigger object */
|
||||
pTrigger = (Trigger*)sqlite3DbMallocZero(db, sizeof(Trigger));
|
||||
if( pTrigger==0 ) goto trigger_cleanup;
|
||||
pTrigger->name = zName;
|
||||
pTrigger->zName = zName;
|
||||
zName = 0;
|
||||
pTrigger->table = sqlite3DbStrDup(db, pTableName->a[0].zName);
|
||||
pTrigger->pSchema = db->aDb[iDb].pSchema;
|
||||
@@ -262,14 +262,14 @@ void sqlite3FinishTrigger(
|
||||
pTrig = pParse->pNewTrigger;
|
||||
pParse->pNewTrigger = 0;
|
||||
if( NEVER(pParse->nErr) || !pTrig ) goto triggerfinish_cleanup;
|
||||
zName = pTrig->name;
|
||||
zName = pTrig->zName;
|
||||
iDb = sqlite3SchemaToIndex(pParse->db, pTrig->pSchema);
|
||||
pTrig->step_list = pStepList;
|
||||
while( pStepList ){
|
||||
pStepList->pTrig = pTrig;
|
||||
pStepList = pStepList->pNext;
|
||||
}
|
||||
nameToken.z = pTrig->name;
|
||||
nameToken.z = pTrig->zName;
|
||||
nameToken.n = sqlite3Strlen30(nameToken.z);
|
||||
if( sqlite3FixInit(&sFix, pParse, iDb, "trigger", &nameToken)
|
||||
&& sqlite3FixTriggerStep(&sFix, pTrig->step_list) ){
|
||||
@@ -451,7 +451,7 @@ TriggerStep *sqlite3TriggerDeleteStep(
|
||||
void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){
|
||||
if( pTrigger==0 ) return;
|
||||
sqlite3DeleteTriggerStep(db, pTrigger->step_list);
|
||||
sqlite3DbFree(db, pTrigger->name);
|
||||
sqlite3DbFree(db, pTrigger->zName);
|
||||
sqlite3DbFree(db, pTrigger->table);
|
||||
sqlite3ExprDelete(db, pTrigger->pWhen);
|
||||
sqlite3IdListDelete(db, pTrigger->pColumns);
|
||||
@@ -531,7 +531,7 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){
|
||||
const char *zDb = db->aDb[iDb].zName;
|
||||
const char *zTab = SCHEMA_TABLE(iDb);
|
||||
if( iDb==1 ) code = SQLITE_DROP_TEMP_TRIGGER;
|
||||
if( sqlite3AuthCheck(pParse, code, pTrigger->name, pTable->zName, zDb) ||
|
||||
if( sqlite3AuthCheck(pParse, code, pTrigger->zName, pTable->zName, zDb) ||
|
||||
sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
|
||||
return;
|
||||
}
|
||||
@@ -558,11 +558,11 @@ void sqlite3DropTriggerPtr(Parse *pParse, Trigger *pTrigger){
|
||||
sqlite3BeginWriteOperation(pParse, 0, iDb);
|
||||
sqlite3OpenMasterTable(pParse, iDb);
|
||||
base = sqlite3VdbeAddOpList(v, ArraySize(dropTrigger), dropTrigger);
|
||||
sqlite3VdbeChangeP4(v, base+1, pTrigger->name, 0);
|
||||
sqlite3VdbeChangeP4(v, base+1, pTrigger->zName, 0);
|
||||
sqlite3VdbeChangeP4(v, base+4, "trigger", P4_STATIC);
|
||||
sqlite3ChangeCookie(pParse, iDb);
|
||||
sqlite3VdbeAddOp2(v, OP_Close, 0, 0);
|
||||
sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->name, 0);
|
||||
sqlite3VdbeAddOp4(v, OP_DropTrigger, iDb, 0, 0, pTrigger->zName, 0);
|
||||
if( pParse->nMem<3 ){
|
||||
pParse->nMem = 3;
|
||||
}
|
||||
@@ -666,78 +666,254 @@ static SrcList *targetSrcList(
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate VDBE code for zero or more statements inside the body of a
|
||||
** trigger.
|
||||
** Generate VDBE code for the statements inside the body of a single
|
||||
** trigger.
|
||||
*/
|
||||
static int codeTriggerProgram(
|
||||
Parse *pParse, /* The parser context */
|
||||
TriggerStep *pStepList, /* List of statements inside the trigger body */
|
||||
int orconfin /* Conflict algorithm. (OE_Abort, etc) */
|
||||
int orconf /* Conflict algorithm. (OE_Abort, etc) */
|
||||
){
|
||||
TriggerStep * pTriggerStep = pStepList;
|
||||
int orconf;
|
||||
TriggerStep *pStep;
|
||||
Vdbe *v = pParse->pVdbe;
|
||||
sqlite3 *db = pParse->db;
|
||||
|
||||
assert( pTriggerStep!=0 );
|
||||
assert( pParse->pTriggerTab && pParse->pToplevel );
|
||||
assert( pStepList );
|
||||
assert( v!=0 );
|
||||
sqlite3VdbeAddOp2(v, OP_ContextPush, 0, 0);
|
||||
VdbeComment((v, "begin trigger %s", pStepList->pTrig->name));
|
||||
while( pTriggerStep ){
|
||||
sqlite3ExprCacheClear(pParse);
|
||||
orconf = (orconfin == OE_Default)?pTriggerStep->orconf:orconfin;
|
||||
pParse->trigStack->orconf = orconf;
|
||||
switch( pTriggerStep->op ){
|
||||
for(pStep=pStepList; pStep; pStep=pStep->pNext){
|
||||
/* Figure out the ON CONFLICT policy that will be used for this step
|
||||
** of the trigger program. If the statement that caused this trigger
|
||||
** to fire had an explicit ON CONFLICT, then use it. Otherwise, use
|
||||
** the ON CONFLICT policy that was specified as part of the trigger
|
||||
** step statement. Example:
|
||||
**
|
||||
** CREATE TRIGGER AFTER INSERT ON t1 BEGIN;
|
||||
** INSERT OR REPLACE INTO t2 VALUES(new.a, new.b);
|
||||
** END;
|
||||
**
|
||||
** INSERT INTO t1 ... ; -- insert into t2 uses REPLACE policy
|
||||
** INSERT OR IGNORE INTO t1 ... ; -- insert into t2 uses IGNORE policy
|
||||
*/
|
||||
pParse->eOrconf = (orconf==OE_Default)?pStep->orconf:orconf;
|
||||
|
||||
switch( pStep->op ){
|
||||
case TK_UPDATE: {
|
||||
SrcList *pSrc;
|
||||
pSrc = targetSrcList(pParse, pTriggerStep);
|
||||
sqlite3VdbeAddOp2(v, OP_ResetCount, 0, 0);
|
||||
sqlite3Update(pParse, pSrc,
|
||||
sqlite3ExprListDup(db, pTriggerStep->pExprList, 0),
|
||||
sqlite3ExprDup(db, pTriggerStep->pWhere, 0), orconf);
|
||||
sqlite3VdbeAddOp2(v, OP_ResetCount, 1, 0);
|
||||
sqlite3Update(pParse,
|
||||
targetSrcList(pParse, pStep),
|
||||
sqlite3ExprListDup(db, pStep->pExprList, 0),
|
||||
sqlite3ExprDup(db, pStep->pWhere, 0),
|
||||
pParse->eOrconf
|
||||
);
|
||||
break;
|
||||
}
|
||||
case TK_INSERT: {
|
||||
SrcList *pSrc;
|
||||
pSrc = targetSrcList(pParse, pTriggerStep);
|
||||
sqlite3VdbeAddOp2(v, OP_ResetCount, 0, 0);
|
||||
sqlite3Insert(pParse, pSrc,
|
||||
sqlite3ExprListDup(db, pTriggerStep->pExprList, 0),
|
||||
sqlite3SelectDup(db, pTriggerStep->pSelect, 0),
|
||||
sqlite3IdListDup(db, pTriggerStep->pIdList), orconf);
|
||||
sqlite3VdbeAddOp2(v, OP_ResetCount, 1, 0);
|
||||
sqlite3Insert(pParse,
|
||||
targetSrcList(pParse, pStep),
|
||||
sqlite3ExprListDup(db, pStep->pExprList, 0),
|
||||
sqlite3SelectDup(db, pStep->pSelect, 0),
|
||||
sqlite3IdListDup(db, pStep->pIdList),
|
||||
pParse->eOrconf
|
||||
);
|
||||
break;
|
||||
}
|
||||
case TK_DELETE: {
|
||||
SrcList *pSrc;
|
||||
sqlite3VdbeAddOp2(v, OP_ResetCount, 0, 0);
|
||||
pSrc = targetSrcList(pParse, pTriggerStep);
|
||||
sqlite3DeleteFrom(pParse, pSrc,
|
||||
sqlite3ExprDup(db, pTriggerStep->pWhere, 0));
|
||||
sqlite3VdbeAddOp2(v, OP_ResetCount, 1, 0);
|
||||
sqlite3DeleteFrom(pParse,
|
||||
targetSrcList(pParse, pStep),
|
||||
sqlite3ExprDup(db, pStep->pWhere, 0)
|
||||
);
|
||||
break;
|
||||
}
|
||||
default: assert( pTriggerStep->op==TK_SELECT ); {
|
||||
Select *ss = sqlite3SelectDup(db, pTriggerStep->pSelect, 0);
|
||||
if( ss ){
|
||||
SelectDest dest;
|
||||
|
||||
sqlite3SelectDestInit(&dest, SRT_Discard, 0);
|
||||
sqlite3Select(pParse, ss, &dest);
|
||||
sqlite3SelectDelete(db, ss);
|
||||
}
|
||||
default: assert( pStep->op==TK_SELECT ); {
|
||||
SelectDest sDest;
|
||||
Select *pSelect = sqlite3SelectDup(db, pStep->pSelect, 0);
|
||||
sqlite3SelectDestInit(&sDest, SRT_Discard, 0);
|
||||
sqlite3Select(pParse, pSelect, &sDest);
|
||||
sqlite3SelectDelete(db, pSelect);
|
||||
break;
|
||||
}
|
||||
}
|
||||
pTriggerStep = pTriggerStep->pNext;
|
||||
if( pStep->op!=TK_SELECT ){
|
||||
sqlite3VdbeAddOp1(v, OP_ResetCount, 1);
|
||||
}
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_ContextPop, 0, 0);
|
||||
VdbeComment((v, "end trigger %s", pStepList->pTrig->name));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
/*
|
||||
** This function is used to add VdbeComment() annotations to a VDBE
|
||||
** program. It is not used in production code, only for debugging.
|
||||
*/
|
||||
static const char *onErrorText(int onError){
|
||||
switch( onError ){
|
||||
case OE_Abort: return "abort";
|
||||
case OE_Rollback: return "rollback";
|
||||
case OE_Fail: return "fail";
|
||||
case OE_Replace: return "replace";
|
||||
case OE_Ignore: return "ignore";
|
||||
case OE_Default: return "default";
|
||||
}
|
||||
return "n/a";
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Parse context structure pFrom has just been used to create a sub-vdbe
|
||||
** (trigger program). If an error has occurred, transfer error information
|
||||
** from pFrom to pTo.
|
||||
*/
|
||||
static void transferParseError(Parse *pTo, Parse *pFrom){
|
||||
assert( pFrom->zErrMsg==0 || pFrom->nErr );
|
||||
assert( pTo->zErrMsg==0 || pTo->nErr );
|
||||
if( pTo->nErr==0 ){
|
||||
pTo->zErrMsg = pFrom->zErrMsg;
|
||||
pTo->nErr = pFrom->nErr;
|
||||
}else{
|
||||
sqlite3DbFree(pFrom->db, pFrom->zErrMsg);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Create and populate a new TriggerPrg object with a sub-program
|
||||
** implementing trigger pTrigger with ON CONFLICT policy orconf.
|
||||
*/
|
||||
static TriggerPrg *codeRowTrigger(
|
||||
Parse *pParse, /* Current parse context */
|
||||
Trigger *pTrigger, /* Trigger to code */
|
||||
Table *pTab, /* The table pTrigger is attached to */
|
||||
int orconf /* ON CONFLICT policy to code trigger program with */
|
||||
){
|
||||
Parse *pTop = sqlite3ParseToplevel(pParse);
|
||||
sqlite3 *db = pParse->db; /* Database handle */
|
||||
TriggerPrg *pPrg; /* Value to return */
|
||||
Expr *pWhen = 0; /* Duplicate of trigger WHEN expression */
|
||||
Vdbe *v; /* Temporary VM */
|
||||
NameContext sNC; /* Name context for sub-vdbe */
|
||||
SubProgram *pProgram = 0; /* Sub-vdbe for trigger program */
|
||||
Parse *pSubParse; /* Parse context for sub-vdbe */
|
||||
int iEndTrigger = 0; /* Label to jump to if WHEN is false */
|
||||
|
||||
assert( pTab==tableOfTrigger(pTrigger) );
|
||||
|
||||
/* Allocate the TriggerPrg and SubProgram objects. To ensure that they
|
||||
** are freed if an error occurs, link them into the Parse.pTriggerPrg
|
||||
** list of the top-level Parse object sooner rather than later. */
|
||||
pPrg = sqlite3DbMallocZero(db, sizeof(TriggerPrg));
|
||||
if( !pPrg ) return 0;
|
||||
pPrg->pNext = pTop->pTriggerPrg;
|
||||
pTop->pTriggerPrg = pPrg;
|
||||
pPrg->pProgram = pProgram = sqlite3DbMallocZero(db, sizeof(SubProgram));
|
||||
if( !pProgram ) return 0;
|
||||
pProgram->nRef = 1;
|
||||
pPrg->pTrigger = pTrigger;
|
||||
pPrg->orconf = orconf;
|
||||
|
||||
/* Allocate and populate a new Parse context to use for coding the
|
||||
** trigger sub-program. */
|
||||
pSubParse = sqlite3StackAllocZero(db, sizeof(Parse));
|
||||
if( !pSubParse ) return 0;
|
||||
memset(&sNC, 0, sizeof(sNC));
|
||||
sNC.pParse = pSubParse;
|
||||
pSubParse->db = db;
|
||||
pSubParse->pTriggerTab = pTab;
|
||||
pSubParse->pToplevel = pTop;
|
||||
pSubParse->zAuthContext = pTrigger->zName;
|
||||
pSubParse->eTriggerOp = pTrigger->op;
|
||||
|
||||
v = sqlite3GetVdbe(pSubParse);
|
||||
if( v ){
|
||||
VdbeComment((v, "Start: %s.%s (%s %s%s%s ON %s)",
|
||||
pTrigger->zName, onErrorText(orconf),
|
||||
(pTrigger->tr_tm==TRIGGER_BEFORE ? "BEFORE" : "AFTER"),
|
||||
(pTrigger->op==TK_UPDATE ? "UPDATE" : ""),
|
||||
(pTrigger->op==TK_INSERT ? "INSERT" : ""),
|
||||
(pTrigger->op==TK_DELETE ? "DELETE" : ""),
|
||||
pTab->zName
|
||||
));
|
||||
#ifndef SQLITE_OMIT_TRACE
|
||||
sqlite3VdbeChangeP4(v, -1,
|
||||
sqlite3MPrintf(db, "-- TRIGGER %s", pTrigger->zName), P4_DYNAMIC
|
||||
);
|
||||
#endif
|
||||
|
||||
/* If one was specified, code the WHEN clause. If it evaluates to false
|
||||
** (or NULL) the sub-vdbe is immediately halted by jumping to the
|
||||
** OP_Halt inserted at the end of the program. */
|
||||
if( pTrigger->pWhen ){
|
||||
pWhen = sqlite3ExprDup(db, pTrigger->pWhen, 0);
|
||||
if( SQLITE_OK==sqlite3ResolveExprNames(&sNC, pWhen)
|
||||
&& db->mallocFailed==0
|
||||
){
|
||||
iEndTrigger = sqlite3VdbeMakeLabel(v);
|
||||
sqlite3ExprIfFalse(pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL);
|
||||
}
|
||||
sqlite3ExprDelete(db, pWhen);
|
||||
}
|
||||
|
||||
/* Code the trigger program into the sub-vdbe. */
|
||||
codeTriggerProgram(pSubParse, pTrigger->step_list, orconf);
|
||||
|
||||
/* Insert an OP_Halt at the end of the sub-program. */
|
||||
if( iEndTrigger ){
|
||||
sqlite3VdbeResolveLabel(v, iEndTrigger);
|
||||
}
|
||||
sqlite3VdbeAddOp0(v, OP_Halt);
|
||||
VdbeComment((v, "End: %s.%s", pTrigger->zName, onErrorText(orconf)));
|
||||
|
||||
transferParseError(pParse, pSubParse);
|
||||
if( db->mallocFailed==0 ){
|
||||
pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg);
|
||||
}
|
||||
pProgram->nMem = pSubParse->nMem;
|
||||
pProgram->nCsr = pSubParse->nTab;
|
||||
pProgram->token = (void *)pTrigger;
|
||||
pPrg->oldmask = pSubParse->oldmask;
|
||||
sqlite3VdbeDelete(v);
|
||||
}
|
||||
|
||||
assert( !pSubParse->pAinc && !pSubParse->pZombieTab );
|
||||
assert( !pSubParse->pTriggerPrg && !pSubParse->nMaxArg );
|
||||
sqlite3StackFree(db, pSubParse);
|
||||
|
||||
return pPrg;
|
||||
}
|
||||
|
||||
/*
|
||||
** Return a pointer to a TriggerPrg object containing the sub-program for
|
||||
** trigger pTrigger with default ON CONFLICT algorithm orconf. If no such
|
||||
** TriggerPrg object exists, a new object is allocated and populated before
|
||||
** being returned.
|
||||
*/
|
||||
static TriggerPrg *getRowTrigger(
|
||||
Parse *pParse, /* Current parse context */
|
||||
Trigger *pTrigger, /* Trigger to code */
|
||||
Table *pTab, /* The table trigger pTrigger is attached to */
|
||||
int orconf /* ON CONFLICT algorithm. */
|
||||
){
|
||||
Parse *pRoot = sqlite3ParseToplevel(pParse);
|
||||
TriggerPrg *pPrg;
|
||||
|
||||
assert( pTab==tableOfTrigger(pTrigger) );
|
||||
|
||||
/* It may be that this trigger has already been coded (or is in the
|
||||
** process of being coded). If this is the case, then an entry with
|
||||
** a matching TriggerPrg.pTrigger field will be present somewhere
|
||||
** in the Parse.pTriggerPrg list. Search for such an entry. */
|
||||
for(pPrg=pRoot->pTriggerPrg;
|
||||
pPrg && (pPrg->pTrigger!=pTrigger || pPrg->orconf!=orconf);
|
||||
pPrg=pPrg->pNext
|
||||
);
|
||||
|
||||
/* If an existing TriggerPrg could not be located, create a new one. */
|
||||
if( !pPrg ){
|
||||
pPrg = codeRowTrigger(pParse, pTrigger, pTab, orconf);
|
||||
}
|
||||
|
||||
return pPrg;
|
||||
}
|
||||
|
||||
/*
|
||||
** This is called to code FOR EACH ROW triggers.
|
||||
**
|
||||
@@ -765,7 +941,7 @@ static int codeTriggerProgram(
|
||||
** output mask is set to the special value 0xffffffff.
|
||||
**
|
||||
*/
|
||||
int sqlite3CodeRowTrigger(
|
||||
void sqlite3CodeRowTrigger(
|
||||
Parse *pParse, /* Parse context */
|
||||
Trigger *pTrigger, /* List of triggers on table pTab */
|
||||
int op, /* One of TK_UPDATE, TK_INSERT, TK_DELETE */
|
||||
@@ -775,100 +951,87 @@ int sqlite3CodeRowTrigger(
|
||||
int newIdx, /* The indice of the "new" row to access */
|
||||
int oldIdx, /* The indice of the "old" row to access */
|
||||
int orconf, /* ON CONFLICT policy */
|
||||
int ignoreJump, /* Instruction to jump to for RAISE(IGNORE) */
|
||||
u32 *piOldColMask, /* OUT: Mask of columns used from the OLD.* table */
|
||||
u32 *piNewColMask /* OUT: Mask of columns used from the NEW.* table */
|
||||
int ignoreJump /* Instruction to jump to for RAISE(IGNORE) */
|
||||
){
|
||||
Trigger *p;
|
||||
sqlite3 *db = pParse->db;
|
||||
TriggerStack trigStackEntry;
|
||||
|
||||
trigStackEntry.oldColMask = 0;
|
||||
trigStackEntry.newColMask = 0;
|
||||
|
||||
assert(op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE);
|
||||
assert(tr_tm == TRIGGER_BEFORE || tr_tm == TRIGGER_AFTER );
|
||||
|
||||
assert(newIdx != -1 || oldIdx != -1);
|
||||
|
||||
for(p=pTrigger; p; p=p->pNext){
|
||||
int fire_this = 0;
|
||||
|
||||
/* Sanity checking: The schema for the trigger and for the table are
|
||||
** always defined. The trigger must be in the same schema as the table
|
||||
** or else it must be a TEMP trigger. */
|
||||
assert( p->pSchema!=0 );
|
||||
assert( p->pTabSchema!=0 );
|
||||
assert( p->pSchema==p->pTabSchema || p->pSchema==db->aDb[1].pSchema );
|
||||
assert( p->pSchema==p->pTabSchema
|
||||
|| p->pSchema==pParse->db->aDb[1].pSchema );
|
||||
|
||||
/* Determine whether we should code this trigger */
|
||||
if(
|
||||
p->op==op &&
|
||||
p->tr_tm==tr_tm &&
|
||||
checkColumnOverlap(p->pColumns,pChanges)
|
||||
if( p->op==op
|
||||
&& p->tr_tm==tr_tm
|
||||
&& checkColumnOverlap(p->pColumns,pChanges)
|
||||
){
|
||||
TriggerStack *pS; /* Pointer to trigger-stack entry */
|
||||
for(pS=pParse->trigStack; pS && p!=pS->pTrigger; pS=pS->pNext){}
|
||||
if( !pS ){
|
||||
fire_this = 1;
|
||||
Vdbe *v = sqlite3GetVdbe(pParse); /* Main VM */
|
||||
TriggerPrg *pPrg;
|
||||
pPrg = getRowTrigger(pParse, p, pTab, orconf);
|
||||
assert( pPrg || pParse->nErr || pParse->db->mallocFailed );
|
||||
|
||||
/* Code the OP_Program opcode in the parent VDBE. P4 of the OP_Program
|
||||
** is a pointer to the sub-vdbe containing the trigger program. */
|
||||
if( pPrg ){
|
||||
sqlite3VdbeAddOp3(v, OP_Program, oldIdx, ignoreJump, ++pParse->nMem);
|
||||
pPrg->pProgram->nRef++;
|
||||
sqlite3VdbeChangeP4(v, -1, (const char *)pPrg->pProgram, P4_SUBPROGRAM);
|
||||
VdbeComment((v, "Call: %s.%s", p->zName, onErrorText(orconf)));
|
||||
}
|
||||
#if 0 /* Give no warning for recursive triggers. Just do not do them */
|
||||
else{
|
||||
sqlite3ErrorMsg(pParse, "recursive triggers not supported (%s)",
|
||||
p->name);
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if( fire_this ){
|
||||
int endTrigger;
|
||||
Expr * whenExpr;
|
||||
AuthContext sContext;
|
||||
NameContext sNC;
|
||||
|
||||
#ifndef SQLITE_OMIT_TRACE
|
||||
sqlite3VdbeAddOp4(pParse->pVdbe, OP_Trace, 0, 0, 0,
|
||||
sqlite3MPrintf(db, "-- TRIGGER %s", p->name),
|
||||
P4_DYNAMIC);
|
||||
#endif
|
||||
memset(&sNC, 0, sizeof(sNC));
|
||||
sNC.pParse = pParse;
|
||||
|
||||
/* Push an entry on to the trigger stack */
|
||||
trigStackEntry.pTrigger = p;
|
||||
trigStackEntry.newIdx = newIdx;
|
||||
trigStackEntry.oldIdx = oldIdx;
|
||||
trigStackEntry.pTab = pTab;
|
||||
trigStackEntry.pNext = pParse->trigStack;
|
||||
trigStackEntry.ignoreJump = ignoreJump;
|
||||
pParse->trigStack = &trigStackEntry;
|
||||
sqlite3AuthContextPush(pParse, &sContext, p->name);
|
||||
|
||||
/* code the WHEN clause */
|
||||
endTrigger = sqlite3VdbeMakeLabel(pParse->pVdbe);
|
||||
whenExpr = sqlite3ExprDup(db, p->pWhen, 0);
|
||||
if( db->mallocFailed || sqlite3ResolveExprNames(&sNC, whenExpr) ){
|
||||
pParse->trigStack = trigStackEntry.pNext;
|
||||
sqlite3ExprDelete(db, whenExpr);
|
||||
return 1;
|
||||
}
|
||||
sqlite3ExprIfFalse(pParse, whenExpr, endTrigger, SQLITE_JUMPIFNULL);
|
||||
sqlite3ExprDelete(db, whenExpr);
|
||||
|
||||
sqlite3ExprCachePush(pParse);
|
||||
codeTriggerProgram(pParse, p->step_list, orconf);
|
||||
sqlite3ExprCachePop(pParse, 1);
|
||||
|
||||
/* Pop the entry off the trigger stack */
|
||||
pParse->trigStack = trigStackEntry.pNext;
|
||||
sqlite3AuthContextPop(&sContext);
|
||||
|
||||
sqlite3VdbeResolveLabel(pParse->pVdbe, endTrigger);
|
||||
}
|
||||
}
|
||||
if( piOldColMask ) *piOldColMask |= trigStackEntry.oldColMask;
|
||||
if( piNewColMask ) *piNewColMask |= trigStackEntry.newColMask;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Triggers fired by UPDATE or DELETE statements may access values stored
|
||||
** in the old.* pseudo-table. This function returns a 32-bit bitmask
|
||||
** indicating which columns of the old.* table actually are used by
|
||||
** triggers. This information may be used by the caller to avoid having
|
||||
** to load the entire old.* record into memory when executing an UPDATE
|
||||
** or DELETE command.
|
||||
**
|
||||
** Bit 0 of the returned mask is set if the left-most column of the
|
||||
** table may be accessed using an old.<col> reference. Bit 1 is set if
|
||||
** the second leftmost column value is required, and so on. If there
|
||||
** are more than 32 columns in the table, and at least one of the columns
|
||||
** with an index greater than 32 may be accessed, 0xffffffff is returned.
|
||||
**
|
||||
** It is not possible to determine if the old.rowid column is accessed
|
||||
** by triggers. The caller must always assume that it is.
|
||||
**
|
||||
** There is no equivalent function for new.* references.
|
||||
*/
|
||||
u32 sqlite3TriggerOldmask(
|
||||
Parse *pParse, /* Parse context */
|
||||
Trigger *pTrigger, /* List of triggers on table pTab */
|
||||
int op, /* Either TK_UPDATE or TK_DELETE */
|
||||
ExprList *pChanges, /* Changes list for any UPDATE OF triggers */
|
||||
Table *pTab, /* The table to code triggers from */
|
||||
int orconf /* Default ON CONFLICT policy for trigger steps */
|
||||
){
|
||||
u32 mask = 0;
|
||||
Trigger *p;
|
||||
|
||||
assert(op==TK_UPDATE || op==TK_DELETE);
|
||||
for(p=pTrigger; p; p=p->pNext){
|
||||
if( p->op==op && checkColumnOverlap(p->pColumns,pChanges) ){
|
||||
TriggerPrg *pPrg;
|
||||
pPrg = getRowTrigger(pParse, p, pTab, orconf);
|
||||
if( pPrg ){
|
||||
mask |= pPrg->oldmask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
#endif /* !defined(SQLITE_OMIT_TRIGGER) */
|
||||
|
||||
+86
-187
@@ -120,22 +120,16 @@ void sqlite3Update(
|
||||
int isView; /* Trying to update a view */
|
||||
Trigger *pTrigger; /* List of triggers on pTab, if required */
|
||||
#endif
|
||||
int iBeginAfterTrigger = 0; /* Address of after trigger program */
|
||||
int iEndAfterTrigger = 0; /* Exit of after trigger program */
|
||||
int iBeginBeforeTrigger = 0; /* Address of before trigger program */
|
||||
int iEndBeforeTrigger = 0; /* Exit of before trigger program */
|
||||
u32 old_col_mask = 0; /* Mask of OLD.* columns in use */
|
||||
u32 new_col_mask = 0; /* Mask of NEW.* columns in use */
|
||||
|
||||
int newIdx = -1; /* index of trigger "new" temp table */
|
||||
int oldIdx = -1; /* index of trigger "old" temp table */
|
||||
u32 oldmask = 0; /* Mask of OLD.* columns in use */
|
||||
|
||||
/* Register Allocations */
|
||||
int regRowCount = 0; /* A count of rows changed */
|
||||
int regOldRowid; /* The old rowid */
|
||||
int regNewRowid; /* The new rowid */
|
||||
int regData; /* New data for the row */
|
||||
int regNew;
|
||||
int regOld;
|
||||
int regRowSet = 0; /* Rowset of rows to be updated */
|
||||
int regRec; /* Register used for new table record to insert */
|
||||
|
||||
memset(&sContext, 0, sizeof(sContext));
|
||||
db = pParse->db;
|
||||
@@ -151,7 +145,7 @@ void sqlite3Update(
|
||||
iDb = sqlite3SchemaToIndex(pParse->db, pTab->pSchema);
|
||||
|
||||
/* Figure out if we have any triggers and if the table being
|
||||
** updated is a view
|
||||
** updated is a view.
|
||||
*/
|
||||
#ifndef SQLITE_OMIT_TRIGGER
|
||||
pTrigger = sqlite3TriggersExist(pParse, pTab, TK_UPDATE, pChanges, 0);
|
||||
@@ -175,14 +169,6 @@ void sqlite3Update(
|
||||
if( aXRef==0 ) goto update_cleanup;
|
||||
for(i=0; i<pTab->nCol; i++) aXRef[i] = -1;
|
||||
|
||||
/* If there are FOR EACH ROW triggers, allocate cursors for the
|
||||
** special OLD and NEW tables
|
||||
*/
|
||||
if( pTrigger ){
|
||||
newIdx = pParse->nTab++;
|
||||
oldIdx = pParse->nTab++;
|
||||
}
|
||||
|
||||
/* Allocate a cursors for the main database table and for all indices.
|
||||
** The index cursors might not be used, but if they are used they
|
||||
** need to occur right after the database cursor. So go ahead and
|
||||
@@ -268,24 +254,7 @@ void sqlite3Update(
|
||||
aRegIdx[j] = reg;
|
||||
}
|
||||
|
||||
/* Allocate a block of register used to store the change record
|
||||
** sent to sqlite3GenerateConstraintChecks(). There are either
|
||||
** one or two registers for holding the rowid. One rowid register
|
||||
** is used if chngRowid is false and two are used if chngRowid is
|
||||
** true. Following these are pTab->nCol register holding column
|
||||
** data.
|
||||
*/
|
||||
regOldRowid = regNewRowid = pParse->nMem + 1;
|
||||
pParse->nMem += pTab->nCol + 1;
|
||||
if( chngRowid ){
|
||||
regNewRowid++;
|
||||
pParse->nMem++;
|
||||
}
|
||||
regData = regNewRowid+1;
|
||||
|
||||
|
||||
/* Begin generating code.
|
||||
*/
|
||||
/* Begin generating code. */
|
||||
v = sqlite3GetVdbe(pParse);
|
||||
if( v==0 ) goto update_cleanup;
|
||||
if( pParse->nested==0 ) sqlite3VdbeCountChanges(v);
|
||||
@@ -302,40 +271,27 @@ void sqlite3Update(
|
||||
}
|
||||
#endif
|
||||
|
||||
/* Start the view context
|
||||
*/
|
||||
/* Allocate required registers. */
|
||||
regOldRowid = regNewRowid = ++pParse->nMem;
|
||||
if( pTrigger ){
|
||||
regOld = pParse->nMem + 1;
|
||||
pParse->nMem += pTab->nCol;
|
||||
}
|
||||
if( chngRowid || pTrigger ){
|
||||
regNewRowid = ++pParse->nMem;
|
||||
}
|
||||
regNew = pParse->nMem + 1;
|
||||
pParse->nMem += pTab->nCol;
|
||||
regRec = ++pParse->nMem;
|
||||
|
||||
/* Start the view context. */
|
||||
if( isView ){
|
||||
sqlite3AuthContextPush(pParse, &sContext, pTab->zName);
|
||||
}
|
||||
|
||||
/* Generate the code for triggers.
|
||||
*/
|
||||
if( pTrigger ){
|
||||
int iGoto;
|
||||
|
||||
/* Create pseudo-tables for NEW and OLD
|
||||
*/
|
||||
sqlite3VdbeAddOp3(v, OP_OpenPseudo, oldIdx, 0, pTab->nCol);
|
||||
sqlite3VdbeAddOp3(v, OP_OpenPseudo, newIdx, 0, pTab->nCol);
|
||||
|
||||
iGoto = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0);
|
||||
addr = sqlite3VdbeMakeLabel(v);
|
||||
iBeginBeforeTrigger = sqlite3VdbeCurrentAddr(v);
|
||||
if( sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
|
||||
TRIGGER_BEFORE, pTab, newIdx, oldIdx, onError, addr,
|
||||
&old_col_mask, &new_col_mask) ){
|
||||
goto update_cleanup;
|
||||
}
|
||||
iEndBeforeTrigger = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0);
|
||||
iBeginAfterTrigger = sqlite3VdbeCurrentAddr(v);
|
||||
if( sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
|
||||
TRIGGER_AFTER, pTab, newIdx, oldIdx, onError, addr,
|
||||
&old_col_mask, &new_col_mask) ){
|
||||
goto update_cleanup;
|
||||
}
|
||||
iEndAfterTrigger = sqlite3VdbeAddOp2(v, OP_Goto, 0, 0);
|
||||
sqlite3VdbeJumpHere(v, iGoto);
|
||||
}
|
||||
/* If there are any triggers, set oldmask and new_col_mask. */
|
||||
oldmask = sqlite3TriggerOldmask(
|
||||
pParse, pTrigger, TK_UPDATE, pChanges, pTab, onError);
|
||||
|
||||
/* If we are trying to update a view, realize that view into
|
||||
** a ephemeral table.
|
||||
@@ -374,7 +330,7 @@ void sqlite3Update(
|
||||
|
||||
/* Initialize the count of updated rows
|
||||
*/
|
||||
if( db->flags & SQLITE_CountRows && !pParse->trigStack ){
|
||||
if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab ){
|
||||
regRowCount = ++pParse->nMem;
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
|
||||
}
|
||||
@@ -407,11 +363,6 @@ void sqlite3Update(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Jump back to this point if a trigger encounters an IGNORE constraint. */
|
||||
if( pTrigger ){
|
||||
sqlite3VdbeResolveLabel(v, addr);
|
||||
}
|
||||
|
||||
/* Top of the update loop */
|
||||
if( okOnePass ){
|
||||
@@ -422,139 +373,91 @@ void sqlite3Update(
|
||||
addr = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet, 0, regOldRowid);
|
||||
}
|
||||
|
||||
/* Make cursor iCur point to the record that is being updated. If
|
||||
** this record does not exist for some reason (deleted by a trigger,
|
||||
** for example, then jump to the next iteration of the RowSet loop. */
|
||||
sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid);
|
||||
|
||||
/* If there are triggers on this table, populate an array of registers
|
||||
** with the required old.* column data. */
|
||||
if( pTrigger ){
|
||||
int regRowid;
|
||||
int regRow;
|
||||
int regCols;
|
||||
|
||||
/* Make cursor iCur point to the record that is being updated.
|
||||
*/
|
||||
sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid);
|
||||
|
||||
/* Generate the OLD table
|
||||
*/
|
||||
regRowid = sqlite3GetTempReg(pParse);
|
||||
regRow = sqlite3GetTempReg(pParse);
|
||||
sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regRowid);
|
||||
if( !old_col_mask ){
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regRow);
|
||||
}else{
|
||||
sqlite3VdbeAddOp2(v, OP_RowData, iCur, regRow);
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, oldIdx, regRow, regRowid);
|
||||
|
||||
/* Generate the NEW table
|
||||
*/
|
||||
if( chngRowid ){
|
||||
sqlite3ExprCodeAndCache(pParse, pRowidExpr, regRowid);
|
||||
sqlite3VdbeAddOp1(v, OP_MustBeInt, regRowid);
|
||||
}else{
|
||||
sqlite3VdbeAddOp2(v, OP_Rowid, iCur, regRowid);
|
||||
}
|
||||
regCols = sqlite3GetTempRange(pParse, pTab->nCol);
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( i==pTab->iPKey ){
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regCols+i);
|
||||
continue;
|
||||
}
|
||||
j = aXRef[i];
|
||||
if( (i<32 && (new_col_mask&((u32)1<<i))!=0) || new_col_mask==0xffffffff ){
|
||||
if( j<0 ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regCols+i);
|
||||
sqlite3ColumnDefault(v, pTab, i, -1);
|
||||
}else{
|
||||
sqlite3ExprCodeAndCache(pParse, pChanges->a[j].pExpr, regCols+i);
|
||||
}
|
||||
if( aXRef[i]<0 || oldmask==0xffffffff || (oldmask & (1<<i)) ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regOld+i);
|
||||
sqlite3ColumnDefault(v, pTab, i, regOld+i);
|
||||
}else{
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regCols+i);
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regOld+i);
|
||||
}
|
||||
}
|
||||
sqlite3VdbeAddOp3(v, OP_MakeRecord, regCols, pTab->nCol, regRow);
|
||||
if( !isView ){
|
||||
sqlite3TableAffinityStr(v, pTab);
|
||||
sqlite3ExprCacheAffinityChange(pParse, regCols, pTab->nCol);
|
||||
}
|
||||
sqlite3ReleaseTempRange(pParse, regCols, pTab->nCol);
|
||||
/* if( pParse->nErr ) goto update_cleanup; */
|
||||
sqlite3VdbeAddOp3(v, OP_Insert, newIdx, regRow, regRowid);
|
||||
sqlite3ReleaseTempReg(pParse, regRowid);
|
||||
sqlite3ReleaseTempReg(pParse, regRow);
|
||||
}
|
||||
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginBeforeTrigger);
|
||||
sqlite3VdbeJumpHere(v, iEndBeforeTrigger);
|
||||
/* If the record number will change, set register regNewRowid to
|
||||
** contain the new value. If the record number is not being modified,
|
||||
** then regNewRowid is the same register as regOldRowid, which is
|
||||
** already populated. */
|
||||
assert( chngRowid || pTrigger || regOldRowid==regNewRowid );
|
||||
if( chngRowid ){
|
||||
sqlite3ExprCode(pParse, pRowidExpr, regNewRowid);
|
||||
sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid);
|
||||
}else if( pTrigger ){
|
||||
sqlite3VdbeAddOp2(v, OP_Copy, regOldRowid, regNewRowid);
|
||||
}
|
||||
|
||||
/* Populate the array of registers beginning at regNew with the new
|
||||
** row data. This array is used to check constaints, create the new
|
||||
** table and index records, and as the values for any new.* references
|
||||
** made by triggers. */
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( i==pTab->iPKey ){
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regNew+i);
|
||||
}else{
|
||||
j = aXRef[i];
|
||||
if( j<0 ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regNew+i);
|
||||
sqlite3ColumnDefault(v, pTab, i, regNew+i);
|
||||
}else{
|
||||
sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regNew+i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Fire any BEFORE UPDATE triggers. This happens before constraints are
|
||||
** verified. One could argue that this is wrong. */
|
||||
if( pTrigger ){
|
||||
sqlite3VdbeAddOp2(v, OP_Affinity, regNew, pTab->nCol);
|
||||
sqlite3TableAffinityStr(v, pTab);
|
||||
sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
|
||||
TRIGGER_BEFORE, pTab, -1, regOldRowid, onError, addr);
|
||||
}
|
||||
|
||||
if( !isView ){
|
||||
/* Loop over every record that needs updating. We have to load
|
||||
** the old data for each record to be updated because some columns
|
||||
** might not change and we will need to copy the old value.
|
||||
** Also, the old data is needed to delete the old index entries.
|
||||
** So make the cursor point at the old record.
|
||||
*/
|
||||
sqlite3VdbeAddOp3(v, OP_NotExists, iCur, addr, regOldRowid);
|
||||
|
||||
/* If the record number will change, push the record number as it
|
||||
** will be after the update. (The old record number is currently
|
||||
** on top of the stack.)
|
||||
*/
|
||||
if( chngRowid ){
|
||||
sqlite3ExprCode(pParse, pRowidExpr, regNewRowid);
|
||||
sqlite3VdbeAddOp1(v, OP_MustBeInt, regNewRowid);
|
||||
}
|
||||
|
||||
/* Compute new data for this record.
|
||||
*/
|
||||
for(i=0; i<pTab->nCol; i++){
|
||||
if( i==pTab->iPKey ){
|
||||
sqlite3VdbeAddOp2(v, OP_Null, 0, regData+i);
|
||||
continue;
|
||||
}
|
||||
j = aXRef[i];
|
||||
if( j<0 ){
|
||||
sqlite3VdbeAddOp3(v, OP_Column, iCur, i, regData+i);
|
||||
sqlite3ColumnDefault(v, pTab, i, regData+i);
|
||||
}else{
|
||||
sqlite3ExprCode(pParse, pChanges->a[j].pExpr, regData+i);
|
||||
}
|
||||
}
|
||||
|
||||
/* Do constraint checks
|
||||
*/
|
||||
/* Do constraint checks. */
|
||||
sqlite3GenerateConstraintChecks(pParse, pTab, iCur, regNewRowid,
|
||||
aRegIdx, chngRowid, 1,
|
||||
onError, addr, 0);
|
||||
aRegIdx, (chngRowid?regOldRowid:0), 1, onError, addr, 0);
|
||||
|
||||
/* Delete the old indices for the current record.
|
||||
*/
|
||||
/* Delete the index entries associated with the current record. */
|
||||
j1 = sqlite3VdbeAddOp3(v, OP_NotExists, iCur, 0, regOldRowid);
|
||||
sqlite3GenerateRowIndexDelete(pParse, pTab, iCur, aRegIdx);
|
||||
|
||||
/* If changing the record number, delete the old record.
|
||||
*/
|
||||
|
||||
/* If changing the record number, delete the old record. */
|
||||
if( chngRowid ){
|
||||
sqlite3VdbeAddOp2(v, OP_Delete, iCur, 0);
|
||||
}
|
||||
sqlite3VdbeJumpHere(v, j1);
|
||||
|
||||
/* Create the new index entries and the new record.
|
||||
*/
|
||||
sqlite3CompleteInsertion(pParse, pTab, iCur, regNewRowid,
|
||||
aRegIdx, 1, -1, 0, 0);
|
||||
|
||||
/* Insert the new index entries and the new record. */
|
||||
sqlite3CompleteInsertion(pParse, pTab, iCur, regNewRowid, aRegIdx, 1, 0, 0);
|
||||
}
|
||||
|
||||
/* Increment the row counter
|
||||
*/
|
||||
if( db->flags & SQLITE_CountRows && !pParse->trigStack){
|
||||
if( (db->flags & SQLITE_CountRows) && !pParse->pTriggerTab){
|
||||
sqlite3VdbeAddOp2(v, OP_AddImm, regRowCount, 1);
|
||||
}
|
||||
|
||||
/* If there are triggers, close all the cursors after each iteration
|
||||
** through the loop. The fire the after triggers.
|
||||
*/
|
||||
if( pTrigger ){
|
||||
sqlite3VdbeAddOp2(v, OP_Goto, 0, iBeginAfterTrigger);
|
||||
sqlite3VdbeJumpHere(v, iEndAfterTrigger);
|
||||
}
|
||||
sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
|
||||
TRIGGER_AFTER, pTab, -1, regOldRowid, onError, addr);
|
||||
|
||||
/* Repeat the above with the next record to be updated, until
|
||||
** all record selected by the WHERE clause have been updated.
|
||||
@@ -569,16 +472,12 @@ void sqlite3Update(
|
||||
}
|
||||
}
|
||||
sqlite3VdbeAddOp2(v, OP_Close, iCur, 0);
|
||||
if( pTrigger ){
|
||||
sqlite3VdbeAddOp2(v, OP_Close, newIdx, 0);
|
||||
sqlite3VdbeAddOp2(v, OP_Close, oldIdx, 0);
|
||||
}
|
||||
|
||||
/* Update the sqlite_sequence table by storing the content of the
|
||||
** maximum rowid counter values recorded while inserting into
|
||||
** autoincrement tables.
|
||||
*/
|
||||
if( pParse->nested==0 && pParse->trigStack==0 ){
|
||||
if( pParse->nested==0 && pParse->pTriggerTab==0 ){
|
||||
sqlite3AutoincrementEnd(pParse);
|
||||
}
|
||||
|
||||
@@ -587,7 +486,7 @@ void sqlite3Update(
|
||||
** generating code because of a call to sqlite3NestedParse(), do not
|
||||
** invoke the callback function.
|
||||
*/
|
||||
if( db->flags & SQLITE_CountRows && !pParse->trigStack && pParse->nested==0 ){
|
||||
if( (db->flags&SQLITE_CountRows) && !pParse->pTriggerTab && !pParse->nested ){
|
||||
sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
|
||||
sqlite3VdbeSetNumCols(v, 1);
|
||||
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC);
|
||||
|
||||
+175
-42
@@ -846,9 +846,27 @@ case OP_HaltIfNull: { /* in3 */
|
||||
** is the same as executing Halt.
|
||||
*/
|
||||
case OP_Halt: {
|
||||
if( pOp->p1==SQLITE_OK && p->pFrame ){
|
||||
/* Halt the sub-program. Return control to the parent frame. */
|
||||
VdbeFrame *pFrame = p->pFrame;
|
||||
p->pFrame = pFrame->pParent;
|
||||
p->nFrame--;
|
||||
sqlite3VdbeSetChanges(db, p->nChange);
|
||||
pc = sqlite3VdbeFrameRestore(pFrame);
|
||||
if( pOp->p2==OE_Ignore ){
|
||||
/* Instruction pc is the OP_Program that invoked the sub-program
|
||||
** currently being halted. If the p2 instruction of this OP_Halt
|
||||
** instruction is set to OE_Ignore, then the sub-program is throwing
|
||||
** an IGNORE exception. In this case jump to the address specified
|
||||
** as the p2 of the calling OP_Program. */
|
||||
pc = p->aOp[pc].p2-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
p->rc = pOp->p1;
|
||||
p->pc = pc;
|
||||
p->errorAction = (u8)pOp->p2;
|
||||
p->pc = pc;
|
||||
if( pOp->p4.z ){
|
||||
sqlite3SetString(&p->zErrMsg, db, "%s", pOp->p4.z);
|
||||
}
|
||||
@@ -3537,11 +3555,11 @@ case OP_Sequence: { /* out2-prerelease */
|
||||
** table that cursor P1 points to. The new record number is written
|
||||
** written to register P2.
|
||||
**
|
||||
** If P3>0 then P3 is a register that holds the largest previously
|
||||
** generated record number. No new record numbers are allowed to be less
|
||||
** than this value. When this value reaches its maximum, a SQLITE_FULL
|
||||
** error is generated. The P3 register is updated with the generated
|
||||
** record number. This P3 mechanism is used to help implement the
|
||||
** If P3>0 then P3 is a register in the root frame of this VDBE that holds
|
||||
** the largest previously generated record number. No new record numbers are
|
||||
** allowed to be less than this value. When this value reaches its maximum,
|
||||
** a SQLITE_FULL error is generated. The P3 register is updated with the '
|
||||
** generated record number. This P3 mechanism is used to help implement the
|
||||
** AUTOINCREMENT feature.
|
||||
*/
|
||||
case OP_NewRowid: { /* out2-prerelease */
|
||||
@@ -3550,6 +3568,7 @@ case OP_NewRowid: { /* out2-prerelease */
|
||||
int res; /* Result of an sqlite3BtreeLast() */
|
||||
int cnt; /* Counter to limit the number of searches */
|
||||
Mem *pMem; /* Register holding largest rowid for AUTOINCREMENT */
|
||||
VdbeFrame *pFrame; /* Root frame of VDBE */
|
||||
|
||||
v = 0;
|
||||
res = 0;
|
||||
@@ -3608,9 +3627,16 @@ case OP_NewRowid: { /* out2-prerelease */
|
||||
|
||||
#ifndef SQLITE_OMIT_AUTOINCREMENT
|
||||
if( pOp->p3 ){
|
||||
assert( pOp->p3>0 && pOp->p3<=p->nMem ); /* P3 is a valid memory cell */
|
||||
pMem = &p->aMem[pOp->p3];
|
||||
REGISTER_TRACE(pOp->p3, pMem);
|
||||
if( p->pFrame ){
|
||||
for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent);
|
||||
pMem = &pFrame->aMem[pOp->p3];
|
||||
}else{
|
||||
pMem = &p->aMem[pOp->p3];
|
||||
}
|
||||
/* Assert that P3 is a valid memory cell. */
|
||||
assert( pOp->p3>0 && pOp->p3<=(p->pFrame ? pFrame->nMem : p->nMem) );
|
||||
|
||||
REGISTER_TRACE(pOp->p3, pMem);
|
||||
sqlite3VdbeMemIntegerify(pMem);
|
||||
assert( (pMem->flags & MEM_Int)!=0 ); /* mem(P3) holds an integer */
|
||||
if( pMem->u.i==MAX_ROWID || pC->useRandomRowid ){
|
||||
@@ -4731,57 +4757,164 @@ case OP_RowSetTest: { /* jump, in1, in3 */
|
||||
|
||||
|
||||
#ifndef SQLITE_OMIT_TRIGGER
|
||||
/* Opcode: ContextPush * * *
|
||||
**
|
||||
** Save the current Vdbe context such that it can be restored by a ContextPop
|
||||
** opcode. The context stores the last insert row id, the last statement change
|
||||
** count, and the current statement change count.
|
||||
*/
|
||||
case OP_ContextPush: {
|
||||
int i;
|
||||
Context *pContext;
|
||||
|
||||
i = p->contextStackTop++;
|
||||
assert( i>=0 );
|
||||
/* FIX ME: This should be allocated as part of the vdbe at compile-time */
|
||||
if( i>=p->contextStackDepth ){
|
||||
p->contextStackDepth = i+1;
|
||||
p->contextStack = sqlite3DbReallocOrFree(db, p->contextStack,
|
||||
sizeof(Context)*(i+1));
|
||||
if( p->contextStack==0 ) goto no_mem;
|
||||
/* Opcode: Program P1 P2 P3 P4 *
|
||||
**
|
||||
** Execute the trigger program passed as P4 (type P4_SUBPROGRAM).
|
||||
**
|
||||
** P1 contains the address of the memory cell that contains the first memory
|
||||
** cell in an array of values used as arguments to the sub-program. P2
|
||||
** contains the address to jump to if the sub-program throws an IGNORE
|
||||
** exception using the RAISE() function. Register P3 contains the address
|
||||
** of a memory cell in this (the parent) VM that is used to allocate the
|
||||
** memory required by the sub-vdbe at runtime.
|
||||
**
|
||||
** P4 is a pointer to the VM containing the trigger program.
|
||||
*/
|
||||
case OP_Program: { /* jump */
|
||||
int nMem; /* Number of memory registers for sub-program */
|
||||
int nByte; /* Bytes of runtime space required for sub-program */
|
||||
Mem *pRt; /* Register to allocate runtime space */
|
||||
Mem *pMem; /* Used to iterate through memory cells */
|
||||
Mem *pEnd; /* Last memory cell in new array */
|
||||
VdbeFrame *pFrame; /* New vdbe frame to execute in */
|
||||
SubProgram *pProgram; /* Sub-program to execute */
|
||||
void *t; /* Token identifying trigger */
|
||||
|
||||
pProgram = pOp->p4.pProgram;
|
||||
pRt = &p->aMem[pOp->p3];
|
||||
assert( pProgram->nOp>0 );
|
||||
|
||||
/* If the SQLITE_NoRecTriggers flag it set, then recursive invocation of
|
||||
** triggers is disabled for backwards compatibility (flag set/cleared by
|
||||
** the "PRAGMA disable_recursive_triggers" command).
|
||||
**
|
||||
** It is recursive invocation of triggers, at the SQL level, that is
|
||||
** disabled. In some cases a single trigger may generate more than one
|
||||
** SubProgram (if the trigger may be executed with more than one different
|
||||
** ON CONFLICT algorithm). SubProgram structures associated with a
|
||||
** single trigger all have the same value for the SubProgram.token
|
||||
** variable.
|
||||
*/
|
||||
if( db->flags&SQLITE_NoRecTriggers ){
|
||||
t = pProgram->token;
|
||||
for(pFrame=p->pFrame; pFrame && pFrame->token!=t; pFrame=pFrame->pParent);
|
||||
if( pFrame ) break;
|
||||
}
|
||||
pContext = &p->contextStack[i];
|
||||
pContext->lastRowid = db->lastRowid;
|
||||
pContext->nChange = p->nChange;
|
||||
|
||||
/* TODO: This constant should be configurable. */
|
||||
if( p->nFrame>1000 ){
|
||||
rc = SQLITE_ERROR;
|
||||
sqlite3SetString(&p->zErrMsg, db, "too many levels of trigger recursion");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Register pRt is used to store the memory required to save the state
|
||||
** of the current program, and the memory required at runtime to execute
|
||||
** the trigger program. If this trigger has been fired before, then pRt
|
||||
** is already allocated. Otherwise, it must be initialized. */
|
||||
if( (pRt->flags&MEM_Frame)==0 ){
|
||||
/* SubProgram.nMem is set to the number of memory cells used by the
|
||||
** program stored in SubProgram.aOp. As well as these, one memory
|
||||
** cell is required for each cursor used by the program. Set local
|
||||
** variable nMem (and later, VdbeFrame.nChildMem) to this value.
|
||||
*/
|
||||
nMem = pProgram->nMem + pProgram->nCsr;
|
||||
nByte = ROUND8(sizeof(VdbeFrame))
|
||||
+ nMem * sizeof(Mem)
|
||||
+ pProgram->nCsr * sizeof(VdbeCursor *);
|
||||
pFrame = sqlite3DbMallocZero(db, nByte);
|
||||
if( !pFrame ){
|
||||
goto no_mem;
|
||||
}
|
||||
sqlite3VdbeMemRelease(pRt);
|
||||
pRt->flags = MEM_Frame;
|
||||
pRt->u.pFrame = pFrame;
|
||||
|
||||
pFrame->v = p;
|
||||
pFrame->nChildMem = nMem;
|
||||
pFrame->nChildCsr = pProgram->nCsr;
|
||||
pFrame->pc = pc;
|
||||
pFrame->aMem = p->aMem;
|
||||
pFrame->nMem = p->nMem;
|
||||
pFrame->apCsr = p->apCsr;
|
||||
pFrame->nCursor = p->nCursor;
|
||||
pFrame->aOp = p->aOp;
|
||||
pFrame->nOp = p->nOp;
|
||||
pFrame->token = pProgram->token;
|
||||
|
||||
pEnd = &VdbeFrameMem(pFrame)[pFrame->nChildMem];
|
||||
for(pMem=VdbeFrameMem(pFrame); pMem!=pEnd; pMem++){
|
||||
pMem->flags = MEM_Null;
|
||||
pMem->db = db;
|
||||
}
|
||||
}else{
|
||||
pFrame = pRt->u.pFrame;
|
||||
assert( pProgram->nMem+pProgram->nCsr==pFrame->nChildMem );
|
||||
assert( pProgram->nCsr==pFrame->nChildCsr );
|
||||
assert( pc==pFrame->pc );
|
||||
}
|
||||
|
||||
p->nFrame++;
|
||||
pFrame->pParent = p->pFrame;
|
||||
pFrame->lastRowid = db->lastRowid;
|
||||
pFrame->nChange = p->nChange;
|
||||
p->nChange = 0;
|
||||
p->pFrame = pFrame;
|
||||
p->aMem = &VdbeFrameMem(pFrame)[-1];
|
||||
p->nMem = pFrame->nChildMem;
|
||||
p->nCursor = pFrame->nChildCsr;
|
||||
p->apCsr = (VdbeCursor **)&p->aMem[p->nMem+1];
|
||||
p->aOp = pProgram->aOp;
|
||||
p->nOp = pProgram->nOp;
|
||||
pc = -1;
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: ContextPop * * *
|
||||
/* Opcode: Param P1 P2 * * *
|
||||
**
|
||||
** Restore the Vdbe context to the state it was in when contextPush was last
|
||||
** executed. The context stores the last insert row id, the last statement
|
||||
** change count, and the current statement change count.
|
||||
** This opcode is only ever present in sub-programs called via the
|
||||
** OP_Program instruction. Copy a value currently stored in a memory
|
||||
** cell of the calling (parent) frame to cell P2 in the current frames
|
||||
** address space. This is used by trigger programs to access the new.*
|
||||
** and old.* values.
|
||||
**
|
||||
** The address of the cell in the parent frame is determined by adding
|
||||
** the value of the P1 argument to the value of the P1 argument to the
|
||||
** calling OP_Program instruction.
|
||||
*/
|
||||
case OP_ContextPop: {
|
||||
Context *pContext;
|
||||
pContext = &p->contextStack[--p->contextStackTop];
|
||||
assert( p->contextStackTop>=0 );
|
||||
db->lastRowid = pContext->lastRowid;
|
||||
p->nChange = pContext->nChange;
|
||||
case OP_Param: { /* out2-prerelease */
|
||||
VdbeFrame *pFrame;
|
||||
Mem *pIn;
|
||||
pFrame = p->pFrame;
|
||||
pIn = &pFrame->aMem[pOp->p1 + pFrame->aOp[pFrame->pc].p1];
|
||||
sqlite3VdbeMemShallowCopy(pOut, pIn, MEM_Ephem);
|
||||
break;
|
||||
}
|
||||
|
||||
#endif /* #ifndef SQLITE_OMIT_TRIGGER */
|
||||
|
||||
#ifndef SQLITE_OMIT_AUTOINCREMENT
|
||||
/* Opcode: MemMax P1 P2 * * *
|
||||
**
|
||||
** Set the value of register P1 to the maximum of its current value
|
||||
** and the value in register P2.
|
||||
** P1 is a register in the root frame of this VM (the root frame is
|
||||
** different from the current frame if this instruction is being executed
|
||||
** within a sub-program). Set the value of register P1 to the maximum of
|
||||
** its current value and the value in register P2.
|
||||
**
|
||||
** This instruction throws an error if the memory cell is not initially
|
||||
** an integer.
|
||||
*/
|
||||
case OP_MemMax: { /* in1, in2 */
|
||||
case OP_MemMax: { /* in2 */
|
||||
Mem *pIn1;
|
||||
VdbeFrame *pFrame;
|
||||
if( p->pFrame ){
|
||||
for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent);
|
||||
pIn1 = &pFrame->aMem[pOp->p1];
|
||||
}else{
|
||||
pIn1 = &p->aMem[pOp->p1];
|
||||
}
|
||||
sqlite3VdbeMemIntegerify(pIn1);
|
||||
sqlite3VdbeMemIntegerify(pIn2);
|
||||
if( pIn1->u.i<pIn2->u.i){
|
||||
|
||||
+21
-3
@@ -34,6 +34,7 @@ typedef struct Vdbe Vdbe;
|
||||
*/
|
||||
typedef struct VdbeFunc VdbeFunc;
|
||||
typedef struct Mem Mem;
|
||||
typedef struct SubProgram SubProgram;
|
||||
|
||||
/*
|
||||
** A single instruction of the virtual machine has an opcode
|
||||
@@ -48,7 +49,7 @@ struct VdbeOp {
|
||||
int p1; /* First operand */
|
||||
int p2; /* Second parameter (often the jump destination) */
|
||||
int p3; /* The third parameter */
|
||||
union { /* forth parameter */
|
||||
union { /* fourth parameter */
|
||||
int i; /* Integer value if p4type==P4_INT32 */
|
||||
void *p; /* Generic pointer */
|
||||
char *z; /* Pointer to data for string (char array) types */
|
||||
@@ -61,6 +62,7 @@ struct VdbeOp {
|
||||
VTable *pVtab; /* Used when p4type is P4_VTAB */
|
||||
KeyInfo *pKeyInfo; /* Used when p4type is P4_KEYINFO */
|
||||
int *ai; /* Used when p4type is P4_INTARRAY */
|
||||
SubProgram *pProgram; /* Used when p4type is P4_SUBPROGRAM */
|
||||
} p4;
|
||||
#ifdef SQLITE_DEBUG
|
||||
char *zComment; /* Comment to improve readability */
|
||||
@@ -72,6 +74,19 @@ struct VdbeOp {
|
||||
};
|
||||
typedef struct VdbeOp VdbeOp;
|
||||
|
||||
|
||||
/*
|
||||
** A sub-routine used to implement a trigger program.
|
||||
*/
|
||||
struct SubProgram {
|
||||
VdbeOp *aOp; /* Array of opcodes for sub-program */
|
||||
int nOp; /* Elements in aOp[] */
|
||||
int nMem; /* Number of memory cells required */
|
||||
int nCsr; /* Number of cursors required */
|
||||
int nRef; /* Number of pointers to this structure */
|
||||
void *token; /* id that may be used to recursive triggers */
|
||||
};
|
||||
|
||||
/*
|
||||
** A smaller version of VdbeOp used for the VdbeAddOpList() function because
|
||||
** it takes up less space.
|
||||
@@ -85,7 +100,7 @@ struct VdbeOpList {
|
||||
typedef struct VdbeOpList VdbeOpList;
|
||||
|
||||
/*
|
||||
** Allowed values of VdbeOp.p3type
|
||||
** Allowed values of VdbeOp.p4type
|
||||
*/
|
||||
#define P4_NOTUSED 0 /* The P4 parameter is not used */
|
||||
#define P4_DYNAMIC (-1) /* Pointer to a string obtained from sqliteMalloc() */
|
||||
@@ -102,6 +117,7 @@ typedef struct VdbeOpList VdbeOpList;
|
||||
#define P4_INT64 (-13) /* P4 is a 64-bit signed integer */
|
||||
#define P4_INT32 (-14) /* P4 is a 32-bit signed integer */
|
||||
#define P4_INTARRAY (-15) /* P4 is a vector of 32-bit integers */
|
||||
#define P4_SUBPROGRAM (-18) /* P4 is a pointer to a SubProgram structure */
|
||||
|
||||
/* When adding a P4 argument using P4_KEYINFO, a copy of the KeyInfo structure
|
||||
** is made. That copy is freed when the Vdbe is finalized. But if the
|
||||
@@ -168,7 +184,7 @@ void sqlite3VdbeUsesBtree(Vdbe*, int);
|
||||
VdbeOp *sqlite3VdbeGetOp(Vdbe*, int);
|
||||
int sqlite3VdbeMakeLabel(Vdbe*);
|
||||
void sqlite3VdbeDelete(Vdbe*);
|
||||
void sqlite3VdbeMakeReady(Vdbe*,int,int,int,int);
|
||||
void sqlite3VdbeMakeReady(Vdbe*,int,int,int,int,int);
|
||||
int sqlite3VdbeFinalize(Vdbe*);
|
||||
void sqlite3VdbeResolveLabel(Vdbe*, int);
|
||||
int sqlite3VdbeCurrentAddr(Vdbe*);
|
||||
@@ -183,6 +199,8 @@ void sqlite3VdbeCountChanges(Vdbe*);
|
||||
sqlite3 *sqlite3VdbeDb(Vdbe*);
|
||||
void sqlite3VdbeSetSql(Vdbe*, const char *z, int n, int);
|
||||
void sqlite3VdbeSwap(Vdbe*,Vdbe*);
|
||||
VdbeOp *sqlite3VdbeTakeOpArray(Vdbe*, int*, int*);
|
||||
void sqlite3VdbeProgramDelete(sqlite3 *, SubProgram *, int);
|
||||
|
||||
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
|
||||
int sqlite3VdbeReleaseMemory(int);
|
||||
|
||||
+39
-18
@@ -89,6 +89,39 @@ struct VdbeCursor {
|
||||
};
|
||||
typedef struct VdbeCursor VdbeCursor;
|
||||
|
||||
/*
|
||||
** When a sub-program is executed (OP_Program), a structure of this type
|
||||
** is allocated to store the current value of the program counter, as
|
||||
** well as the current memory cell array and various other frame specific
|
||||
** values stored in the Vdbe struct. When the sub-program is finished,
|
||||
** these values are copied back to the Vdbe from the VdbeFrame structure,
|
||||
** restoring the state of the VM to as it was before the sub-program
|
||||
** began executing.
|
||||
**
|
||||
** Frames are stored in a linked list headed at Vdbe.pParent. Vdbe.pParent
|
||||
** is the parent of the current frame, or zero if the current frame
|
||||
** is the main Vdbe program.
|
||||
*/
|
||||
typedef struct VdbeFrame VdbeFrame;
|
||||
struct VdbeFrame {
|
||||
Vdbe *v; /* VM this frame belongs to */
|
||||
int pc; /* Program Counter */
|
||||
Op *aOp; /* Program instructions */
|
||||
int nOp; /* Size of aOp array */
|
||||
Mem *aMem; /* Array of memory cells */
|
||||
int nMem; /* Number of entries in aMem */
|
||||
VdbeCursor **apCsr; /* Element of Vdbe cursors */
|
||||
u16 nCursor; /* Number of entries in apCsr */
|
||||
void *token; /* Copy of SubProgram.token */
|
||||
int nChildMem; /* Number of memory cells for child frame */
|
||||
int nChildCsr; /* Number of cursors for child frame */
|
||||
i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */
|
||||
int nChange; /* Statement changes (Vdbe.nChanges) */
|
||||
VdbeFrame *pParent; /* Parent of this frame */
|
||||
};
|
||||
|
||||
#define VdbeFrameMem(p) ((Mem *)&((u8 *)p)[ROUND8(sizeof(VdbeFrame))])
|
||||
|
||||
/*
|
||||
** A value for VdbeCursor.cacheValid that means the cache is always invalid.
|
||||
*/
|
||||
@@ -111,6 +144,7 @@ struct Mem {
|
||||
int nZero; /* Used when bit MEM_Zero is set in flags */
|
||||
FuncDef *pDef; /* Used only when flags==MEM_Agg */
|
||||
RowSet *pRowSet; /* Used only when flags==MEM_RowSet */
|
||||
VdbeFrame *pFrame; /* Used when flags==MEM_Frame */
|
||||
} u;
|
||||
double r; /* Real value */
|
||||
sqlite3 *db; /* The associated database connection */
|
||||
@@ -144,6 +178,7 @@ struct Mem {
|
||||
#define MEM_Real 0x0008 /* Value is a real number */
|
||||
#define MEM_Blob 0x0010 /* Value is a BLOB */
|
||||
#define MEM_RowSet 0x0020 /* Value is a RowSet object */
|
||||
#define MEM_Frame 0x0040 /* Value is a VdbeFrame object */
|
||||
#define MEM_TypeMask 0x00ff /* Mask of type bits */
|
||||
|
||||
/* Whenever Mem contains a valid string or blob representation, one of
|
||||
@@ -223,21 +258,6 @@ struct Set {
|
||||
HashElem *prev; /* Previously accessed hash elemen */
|
||||
};
|
||||
|
||||
/*
|
||||
** A Context stores the last insert rowid, the last statement change count,
|
||||
** and the current statement change count (i.e. changes since last statement).
|
||||
** The current keylist is also stored in the context.
|
||||
** Elements of Context structure type make up the ContextStack, which is
|
||||
** updated by the ContextPush and ContextPop opcodes (used by triggers).
|
||||
** The context is pushed before executing a trigger a popped when the
|
||||
** trigger finishes.
|
||||
*/
|
||||
typedef struct Context Context;
|
||||
struct Context {
|
||||
i64 lastRowid; /* Last insert rowid (sqlite3.lastRowid) */
|
||||
int nChange; /* Statement changes (Vdbe.nChanges) */
|
||||
};
|
||||
|
||||
/*
|
||||
** An instance of the virtual machine. This structure contains the complete
|
||||
** state of the virtual machine.
|
||||
@@ -277,9 +297,6 @@ struct Vdbe {
|
||||
int nMem; /* Number of memory locations currently allocated */
|
||||
Mem *aMem; /* The memory locations */
|
||||
int cacheCtr; /* VdbeCursor row cache generation counter */
|
||||
int contextStackTop; /* Index of top element in the context stack */
|
||||
int contextStackDepth; /* The size of the "context" stack */
|
||||
Context *contextStack; /* Stack used by opcodes ContextPush & ContextPop*/
|
||||
int pc; /* The program counter */
|
||||
int rc; /* Value to return */
|
||||
char *zErrMsg; /* Error message written here */
|
||||
@@ -302,6 +319,8 @@ struct Vdbe {
|
||||
#ifdef SQLITE_DEBUG
|
||||
FILE *trace; /* Write an execution trace here, if not NULL */
|
||||
#endif
|
||||
VdbeFrame *pFrame; /* Parent frame */
|
||||
int nFrame; /* Number of frames in pFrame list */
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -362,6 +381,8 @@ const char *sqlite3OpcodeName(int);
|
||||
int sqlite3VdbeOpcodeHasProperty(int, int);
|
||||
int sqlite3VdbeMemGrow(Mem *pMem, int n, int preserve);
|
||||
int sqlite3VdbeCloseStatement(Vdbe *, int);
|
||||
void sqlite3VdbeFrameDelete(VdbeFrame*);
|
||||
int sqlite3VdbeFrameRestore(VdbeFrame *);
|
||||
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
|
||||
int sqlite3VdbeReleaseBuffers(Vdbe *p);
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -76,7 +76,7 @@ int sqlite3_reset(sqlite3_stmt *pStmt){
|
||||
Vdbe *v = (Vdbe*)pStmt;
|
||||
sqlite3_mutex_enter(v->db->mutex);
|
||||
rc = sqlite3VdbeReset(v);
|
||||
sqlite3VdbeMakeReady(v, -1, 0, 0, 0);
|
||||
sqlite3VdbeMakeReady(v, -1, 0, 0, 0, 0);
|
||||
assert( (rc & (v->db->errMask))==rc );
|
||||
rc = sqlite3ApiExit(v->db, rc);
|
||||
sqlite3_mutex_leave(v->db->mutex);
|
||||
|
||||
+200
-44
@@ -266,7 +266,7 @@ void sqlite3VdbeResolveLabel(Vdbe *p, int x){
|
||||
*/
|
||||
static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
|
||||
int i;
|
||||
int nMaxArgs = 0;
|
||||
int nMaxArgs = *pMaxFuncArgs;
|
||||
Op *pOp;
|
||||
int *aLabel = p->aLabel;
|
||||
int doesStatementRollback = 0;
|
||||
@@ -295,7 +295,7 @@ static void resolveP2Values(Vdbe *p, int *pMaxFuncArgs){
|
||||
}else if( opcode==OP_Transaction && pOp->p2!=0 ){
|
||||
p->readOnly = 0;
|
||||
#ifndef SQLITE_OMIT_VIRTUALTABLE
|
||||
}else if( opcode==OP_VUpdate || opcode==OP_VRename ){
|
||||
}else if( opcode==OP_VUpdate || opcode==OP_VRename || opcode==OP_Program ){
|
||||
doesStatementRollback = 1;
|
||||
}else if( opcode==OP_VFilter ){
|
||||
int n;
|
||||
@@ -339,6 +339,30 @@ int sqlite3VdbeCurrentAddr(Vdbe *p){
|
||||
return p->nOp;
|
||||
}
|
||||
|
||||
/*
|
||||
** This function returns a pointer to the array of opcodes associated with
|
||||
** the Vdbe passed as the first argument. It is the callers responsibility
|
||||
** to arrange for the returned array to be eventually freed using the
|
||||
** vdbeFreeOpArray() function.
|
||||
**
|
||||
** Before returning, *pnOp is set to the number of entries in the returned
|
||||
** array. Also, *pnMaxArg is set to the larger of its current value and
|
||||
** the number of entries in the Vdbe.apArg[] array required to execute the
|
||||
** returned program.
|
||||
*/
|
||||
VdbeOp *sqlite3VdbeTakeOpArray(Vdbe *p, int *pnOp, int *pnMaxArg){
|
||||
VdbeOp *aOp = p->aOp;
|
||||
assert( aOp && !p->db->mallocFailed );
|
||||
|
||||
/* Check that sqlite3VdbeUsesBtree() was not called on this VM */
|
||||
assert( p->aMutex.nMutex==0 );
|
||||
|
||||
resolveP2Values(p, pnMaxArg);
|
||||
*pnOp = p->nOp;
|
||||
p->aOp = 0;
|
||||
return aOp;
|
||||
}
|
||||
|
||||
/*
|
||||
** Add a whole list of operations to the operation stack. Return the
|
||||
** address of the first operation added.
|
||||
@@ -482,6 +506,57 @@ static void freeP4(sqlite3 *db, int p4type, void *p4){
|
||||
sqlite3VtabUnlock((VTable *)p4);
|
||||
break;
|
||||
}
|
||||
case P4_SUBPROGRAM : {
|
||||
sqlite3VdbeProgramDelete(db, (SubProgram *)p4, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Free the space allocated for aOp and any p4 values allocated for the
|
||||
** opcodes contained within. If aOp is not NULL it is assumed to contain
|
||||
** nOp entries.
|
||||
*/
|
||||
static void vdbeFreeOpArray(sqlite3 *db, Op *aOp, int nOp){
|
||||
if( aOp ){
|
||||
Op *pOp;
|
||||
for(pOp=aOp; pOp<&aOp[nOp]; pOp++){
|
||||
freeP4(db, pOp->p4type, pOp->p4.p);
|
||||
#ifdef SQLITE_DEBUG
|
||||
sqlite3DbFree(db, pOp->zComment);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
sqlite3DbFree(db, aOp);
|
||||
}
|
||||
|
||||
/*
|
||||
** Decrement the ref-count on the SubProgram structure passed as the
|
||||
** second argument. If the ref-count reaches zero, free the structure.
|
||||
**
|
||||
** The array of VDBE opcodes stored as SubProgram.aOp is freed if
|
||||
** either the ref-count reaches zero or parameter freeop is non-zero.
|
||||
**
|
||||
** Since the array of opcodes pointed to by SubProgram.aOp may directly
|
||||
** or indirectly contain a reference to the SubProgram structure itself.
|
||||
** By passing a non-zero freeop parameter, the caller may ensure that all
|
||||
** SubProgram structures and their aOp arrays are freed, even when there
|
||||
** are such circular references.
|
||||
*/
|
||||
void sqlite3VdbeProgramDelete(sqlite3 *db, SubProgram *p, int freeop){
|
||||
if( p ){
|
||||
assert( p->nRef>0 );
|
||||
if( freeop || p->nRef==1 ){
|
||||
Op *aOp = p->aOp;
|
||||
p->aOp = 0;
|
||||
vdbeFreeOpArray(db, aOp, p->nOp);
|
||||
p->nOp = 0;
|
||||
}
|
||||
p->nRef--;
|
||||
if( p->nRef==0 ){
|
||||
sqlite3DbFree(db, p);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -604,6 +679,7 @@ void sqlite3VdbeChangeP4(Vdbe *p, int addr, const char *zP4, int n){
|
||||
*/
|
||||
void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){
|
||||
va_list ap;
|
||||
if( !p ) return;
|
||||
assert( p->nOp>0 || p->aOp==0 );
|
||||
assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed );
|
||||
if( p->nOp ){
|
||||
@@ -616,6 +692,7 @@ void sqlite3VdbeComment(Vdbe *p, const char *zFormat, ...){
|
||||
}
|
||||
void sqlite3VdbeNoopComment(Vdbe *p, const char *zFormat, ...){
|
||||
va_list ap;
|
||||
if( !p ) return;
|
||||
sqlite3VdbeAddOp0(p, OP_Noop);
|
||||
assert( p->nOp>0 || p->aOp==0 );
|
||||
assert( p->aOp==0 || p->aOp[p->nOp-1].zComment==0 || p->db->mallocFailed );
|
||||
@@ -752,6 +829,10 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){
|
||||
sqlite3_snprintf(nTemp, zTemp, "intarray");
|
||||
break;
|
||||
}
|
||||
case P4_SUBPROGRAM: {
|
||||
sqlite3_snprintf(nTemp, zTemp, "program");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
zP4 = pOp->p4.z;
|
||||
if( zP4==0 ){
|
||||
@@ -767,7 +848,6 @@ static char *displayP4(Op *pOp, char *zTemp, int nTemp){
|
||||
|
||||
/*
|
||||
** Declare to the Vdbe that the BTree object at db->aDb[i] is used.
|
||||
**
|
||||
*/
|
||||
void sqlite3VdbeUsesBtree(Vdbe *p, int i){
|
||||
int mask;
|
||||
@@ -826,7 +906,7 @@ static void releaseMemArray(Mem *p, int N){
|
||||
** with no indexes using a single prepared INSERT statement, bind()
|
||||
** and reset(). Inserts are grouped into a transaction.
|
||||
*/
|
||||
if( p->flags&(MEM_Agg|MEM_Dyn) ){
|
||||
if( p->flags&(MEM_Agg|MEM_Dyn|MEM_Frame|MEM_RowSet) ){
|
||||
sqlite3VdbeMemRelease(p);
|
||||
}else if( p->zMalloc ){
|
||||
sqlite3DbFree(db, p->zMalloc);
|
||||
@@ -839,6 +919,22 @@ static void releaseMemArray(Mem *p, int N){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Delete a VdbeFrame object and its contents. VdbeFrame objects are
|
||||
** allocated by the OP_Program opcode in sqlite3VdbeExec().
|
||||
*/
|
||||
void sqlite3VdbeFrameDelete(VdbeFrame *p){
|
||||
int i;
|
||||
Mem *aMem = VdbeFrameMem(p);
|
||||
VdbeCursor **apCsr = (VdbeCursor **)&aMem[p->nChildMem];
|
||||
for(i=0; i<p->nChildCsr; i++){
|
||||
sqlite3VdbeFreeCursor(p->v, apCsr[i]);
|
||||
}
|
||||
releaseMemArray(aMem, p->nChildMem);
|
||||
sqlite3DbFree(p->v->db, p);
|
||||
}
|
||||
|
||||
|
||||
#ifdef SQLITE_ENABLE_MEMORY_MANAGEMENT
|
||||
int sqlite3VdbeReleaseBuffers(Vdbe *p){
|
||||
int ii;
|
||||
@@ -875,6 +971,10 @@ int sqlite3VdbeReleaseBuffers(Vdbe *p){
|
||||
int sqlite3VdbeList(
|
||||
Vdbe *p /* The VDBE */
|
||||
){
|
||||
int nRow; /* Total number of rows to return */
|
||||
int nSub = 0; /* Number of sub-vdbes seen so far */
|
||||
SubProgram **apSub = 0; /* Array of sub-vdbes */
|
||||
Mem *pSub = 0;
|
||||
sqlite3 *db = p->db;
|
||||
int i;
|
||||
int rc = SQLITE_OK;
|
||||
@@ -889,7 +989,7 @@ int sqlite3VdbeList(
|
||||
** the result, result columns may become dynamic if the user calls
|
||||
** sqlite3_column_text16(), causing a translation to UTF-16 encoding.
|
||||
*/
|
||||
releaseMemArray(pMem, p->nMem);
|
||||
releaseMemArray(pMem, 8);
|
||||
|
||||
if( p->rc==SQLITE_NOMEM ){
|
||||
/* This happens if a malloc() inside a call to sqlite3_column_text() or
|
||||
@@ -898,10 +998,24 @@ int sqlite3VdbeList(
|
||||
return SQLITE_ERROR;
|
||||
}
|
||||
|
||||
/* Figure out total number of rows that will be returned by this
|
||||
** EXPLAIN program. */
|
||||
nRow = p->nOp;
|
||||
if( p->explain==1 ){
|
||||
pSub = &p->aMem[9];
|
||||
if( pSub->flags&MEM_Blob ){
|
||||
nSub = pSub->n/sizeof(Vdbe*);
|
||||
apSub = (SubProgram **)pSub->z;
|
||||
}
|
||||
for(i=0; i<nSub; i++){
|
||||
nRow += apSub[i]->nOp;
|
||||
}
|
||||
}
|
||||
|
||||
do{
|
||||
i = p->pc++;
|
||||
}while( i<p->nOp && p->explain==2 && p->aOp[i].opcode!=OP_Explain );
|
||||
if( i>=p->nOp ){
|
||||
}while( i<nRow && p->explain==2 && p->aOp[i].opcode!=OP_Explain );
|
||||
if( i>=nRow ){
|
||||
p->rc = SQLITE_OK;
|
||||
rc = SQLITE_DONE;
|
||||
}else if( db->u1.isInterrupted ){
|
||||
@@ -910,7 +1024,17 @@ int sqlite3VdbeList(
|
||||
sqlite3SetString(&p->zErrMsg, db, "%s", sqlite3ErrStr(p->rc));
|
||||
}else{
|
||||
char *z;
|
||||
Op *pOp = &p->aOp[i];
|
||||
Op *pOp;
|
||||
if( i<p->nOp ){
|
||||
pOp = &p->aOp[i];
|
||||
}else{
|
||||
int j;
|
||||
i -= p->nOp;
|
||||
for(j=0; i>=apSub[j]->nOp; j++){
|
||||
i -= apSub[j]->nOp;
|
||||
}
|
||||
pOp = &apSub[j]->aOp[i];
|
||||
}
|
||||
if( p->explain==1 ){
|
||||
pMem->flags = MEM_Int;
|
||||
pMem->type = SQLITE_INTEGER;
|
||||
@@ -924,6 +1048,20 @@ int sqlite3VdbeList(
|
||||
pMem->type = SQLITE_TEXT;
|
||||
pMem->enc = SQLITE_UTF8;
|
||||
pMem++;
|
||||
|
||||
if( pOp->p4type==P4_SUBPROGRAM ){
|
||||
int nByte = (nSub+1)*sizeof(SubProgram*);
|
||||
int j;
|
||||
for(j=0; j<nSub; j++){
|
||||
if( apSub[j]==pOp->p4.pProgram ) break;
|
||||
}
|
||||
if( j==nSub && SQLITE_OK==sqlite3VdbeMemGrow(pSub, nByte, 1) ){
|
||||
apSub = (SubProgram **)pSub->z;
|
||||
apSub[nSub++] = pOp->p4.pProgram;
|
||||
pSub->flags |= MEM_Blob;
|
||||
pSub->n = nSub*sizeof(SubProgram*);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pMem->flags = MEM_Int;
|
||||
@@ -1098,6 +1236,7 @@ void sqlite3VdbeMakeReady(
|
||||
int nVar, /* Number of '?' see in the SQL statement */
|
||||
int nMem, /* Number of memory cells to allocate */
|
||||
int nCursor, /* Number of cursors to allocate */
|
||||
int nArg, /* Maximum number of args in SubPrograms */
|
||||
int isExplain /* True if the EXPLAIN keywords is present */
|
||||
){
|
||||
int n;
|
||||
@@ -1133,7 +1272,6 @@ void sqlite3VdbeMakeReady(
|
||||
u8 *zCsr = (u8 *)&p->aOp[p->nOp];
|
||||
u8 *zEnd = (u8 *)&p->aOp[p->nOpAlloc];
|
||||
int nByte;
|
||||
int nArg; /* Maximum number of args passed to a user function. */
|
||||
resolveP2Values(p, &nArg);
|
||||
if( isExplain && nMem<10 ){
|
||||
nMem = 10;
|
||||
@@ -1232,19 +1370,54 @@ void sqlite3VdbeFreeCursor(Vdbe *p, VdbeCursor *pCx){
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Copy the values stored in the VdbeFrame structure to its Vdbe. This
|
||||
** is used, for example, when a trigger sub-program is halted to restore
|
||||
** control to the main program.
|
||||
*/
|
||||
int sqlite3VdbeFrameRestore(VdbeFrame *pFrame){
|
||||
Vdbe *v = pFrame->v;
|
||||
v->aOp = pFrame->aOp;
|
||||
v->nOp = pFrame->nOp;
|
||||
v->aMem = pFrame->aMem;
|
||||
v->nMem = pFrame->nMem;
|
||||
v->apCsr = pFrame->apCsr;
|
||||
v->nCursor = pFrame->nCursor;
|
||||
v->db->lastRowid = pFrame->lastRowid;
|
||||
v->nChange = pFrame->nChange;
|
||||
return pFrame->pc;
|
||||
}
|
||||
|
||||
/*
|
||||
** Close all cursors.
|
||||
**
|
||||
** Also release any dynamic memory held by the VM in the Vdbe.aMem memory
|
||||
** cell array. This is necessary as the memory cell array may contain
|
||||
** pointers to VdbeFrame objects, which may in turn contain pointers to
|
||||
** open cursors.
|
||||
*/
|
||||
static void closeAllCursors(Vdbe *p){
|
||||
int i;
|
||||
if( p->apCsr==0 ) return;
|
||||
for(i=0; i<p->nCursor; i++){
|
||||
VdbeCursor *pC = p->apCsr[i];
|
||||
if( pC ){
|
||||
sqlite3VdbeFreeCursor(p, pC);
|
||||
p->apCsr[i] = 0;
|
||||
if( p->pFrame ){
|
||||
VdbeFrame *pFrame = p->pFrame;
|
||||
for(pFrame=p->pFrame; pFrame->pParent; pFrame=pFrame->pParent);
|
||||
sqlite3VdbeFrameRestore(pFrame);
|
||||
}
|
||||
p->pFrame = 0;
|
||||
p->nFrame = 0;
|
||||
|
||||
if( p->apCsr ){
|
||||
int i;
|
||||
for(i=0; i<p->nCursor; i++){
|
||||
VdbeCursor *pC = p->apCsr[i];
|
||||
if( pC ){
|
||||
sqlite3VdbeFreeCursor(p, pC);
|
||||
p->apCsr[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( p->aMem ){
|
||||
releaseMemArray(&p->aMem[1], p->nMem);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -1255,23 +1428,16 @@ static void closeAllCursors(Vdbe *p){
|
||||
** variables in the aVar[] array.
|
||||
*/
|
||||
static void Cleanup(Vdbe *p){
|
||||
int i;
|
||||
sqlite3 *db = p->db;
|
||||
Mem *pMem;
|
||||
closeAllCursors(p);
|
||||
for(pMem=&p->aMem[1], i=1; i<=p->nMem; i++, pMem++){
|
||||
if( pMem->flags & MEM_RowSet ){
|
||||
sqlite3RowSetClear(pMem->u.pRowSet);
|
||||
}
|
||||
MemSetTypeFlag(pMem, MEM_Null);
|
||||
}
|
||||
releaseMemArray(&p->aMem[1], p->nMem);
|
||||
if( p->contextStack ){
|
||||
sqlite3DbFree(db, p->contextStack);
|
||||
}
|
||||
p->contextStack = 0;
|
||||
p->contextStackDepth = 0;
|
||||
p->contextStackTop = 0;
|
||||
|
||||
#ifdef SQLITE_DEBUG
|
||||
/* Execute assert() statements to ensure that the Vdbe.apCsr[] and
|
||||
** Vdbe.aMem[] arrays have already been cleaned up. */
|
||||
int i;
|
||||
for(i=0; i<p->nCursor; i++) assert( p->apCsr==0 || p->apCsr[i]==0 );
|
||||
for(i=1; i<=p->nMem; i++) assert( p->aMem==0 || p->aMem[i].flags==MEM_Null );
|
||||
#endif
|
||||
|
||||
sqlite3DbFree(db, p->zErrMsg);
|
||||
p->zErrMsg = 0;
|
||||
p->pResultSet = 0;
|
||||
@@ -1996,7 +2162,6 @@ void sqlite3VdbeDeleteAuxData(VdbeFunc *pVdbeFunc, int mask){
|
||||
** Delete an entire VDBE.
|
||||
*/
|
||||
void sqlite3VdbeDelete(Vdbe *p){
|
||||
int i;
|
||||
sqlite3 *db;
|
||||
|
||||
if( NEVER(p==0) ) return;
|
||||
@@ -2010,22 +2175,13 @@ void sqlite3VdbeDelete(Vdbe *p){
|
||||
if( p->pNext ){
|
||||
p->pNext->pPrev = p->pPrev;
|
||||
}
|
||||
if( p->aOp ){
|
||||
Op *pOp = p->aOp;
|
||||
for(i=0; i<p->nOp; i++, pOp++){
|
||||
freeP4(db, pOp->p4type, pOp->p4.p);
|
||||
#ifdef SQLITE_DEBUG
|
||||
sqlite3DbFree(db, pOp->zComment);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
releaseMemArray(p->aVar, p->nVar);
|
||||
sqlite3DbFree(db, p->aLabel);
|
||||
releaseMemArray(p->aColName, p->nResColumn*COLNAME_N);
|
||||
vdbeFreeOpArray(db, p->aOp, p->nOp);
|
||||
sqlite3DbFree(db, p->aLabel);
|
||||
sqlite3DbFree(db, p->aColName);
|
||||
sqlite3DbFree(db, p->zSql);
|
||||
p->magic = VDBE_MAGIC_DEAD;
|
||||
sqlite3DbFree(db, p->aOp);
|
||||
sqlite3DbFree(db, p->pFree);
|
||||
sqlite3DbFree(db, p);
|
||||
}
|
||||
|
||||
+1
-1
@@ -204,7 +204,7 @@ int sqlite3_blob_open(
|
||||
sqlite3VdbeChangeP4(v, 3+flags, SQLITE_INT_TO_PTR(pTab->nCol+1),P4_INT32);
|
||||
sqlite3VdbeChangeP2(v, 7, pTab->nCol);
|
||||
if( !db->mallocFailed ){
|
||||
sqlite3VdbeMakeReady(v, 1, 1, 1, 0);
|
||||
sqlite3VdbeMakeReady(v, 1, 1, 1, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-1
@@ -270,7 +270,7 @@ int sqlite3VdbeMemFinalize(Mem *pMem, FuncDef *pFunc){
|
||||
*/
|
||||
void sqlite3VdbeMemReleaseExternal(Mem *p){
|
||||
assert( p->db==0 || sqlite3_mutex_held(p->db->mutex) );
|
||||
if( p->flags&(MEM_Agg|MEM_Dyn|MEM_RowSet) ){
|
||||
if( p->flags&(MEM_Agg|MEM_Dyn|MEM_RowSet|MEM_Frame) ){
|
||||
if( p->flags&MEM_Agg ){
|
||||
sqlite3VdbeMemFinalize(p, p->u.pDef);
|
||||
assert( (p->flags & MEM_Agg)==0 );
|
||||
@@ -281,6 +281,9 @@ void sqlite3VdbeMemReleaseExternal(Mem *p){
|
||||
p->xDel = 0;
|
||||
}else if( p->flags&MEM_RowSet ){
|
||||
sqlite3RowSetClear(p->u.pRowSet);
|
||||
}else if( p->flags&MEM_Frame ){
|
||||
sqlite3VdbeFrameDelete(p->u.pFrame);
|
||||
p->flags &= ~MEM_Frame;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -482,6 +485,9 @@ int sqlite3VdbeMemNumerify(Mem *pMem){
|
||||
** Delete any previous value and set the value stored in *pMem to NULL.
|
||||
*/
|
||||
void sqlite3VdbeMemSetNull(Mem *pMem){
|
||||
if( pMem->flags & MEM_Frame ){
|
||||
sqlite3VdbeFrameDelete(pMem->u.pFrame);
|
||||
}
|
||||
if( pMem->flags & MEM_RowSet ){
|
||||
sqlite3RowSetClear(pMem->u.pRowSet);
|
||||
}
|
||||
|
||||
+8
-7
@@ -941,20 +941,21 @@ FuncDef *sqlite3VtabOverloadFunction(
|
||||
** is a no-op.
|
||||
*/
|
||||
void sqlite3VtabMakeWritable(Parse *pParse, Table *pTab){
|
||||
Parse *pToplevel = sqlite3ParseToplevel(pParse);
|
||||
int i, n;
|
||||
Table **apVtabLock;
|
||||
|
||||
assert( IsVirtual(pTab) );
|
||||
for(i=0; i<pParse->nVtabLock; i++){
|
||||
if( pTab==pParse->apVtabLock[i] ) return;
|
||||
for(i=0; i<pToplevel->nVtabLock; i++){
|
||||
if( pTab==pToplevel->apVtabLock[i] ) return;
|
||||
}
|
||||
n = (pParse->nVtabLock+1)*sizeof(pParse->apVtabLock[0]);
|
||||
apVtabLock = sqlite3_realloc(pParse->apVtabLock, n);
|
||||
n = (pToplevel->nVtabLock+1)*sizeof(pToplevel->apVtabLock[0]);
|
||||
apVtabLock = sqlite3_realloc(pToplevel->apVtabLock, n);
|
||||
if( apVtabLock ){
|
||||
pParse->apVtabLock = apVtabLock;
|
||||
pParse->apVtabLock[pParse->nVtabLock++] = pTab;
|
||||
pToplevel->apVtabLock = apVtabLock;
|
||||
pToplevel->apVtabLock[pToplevel->nVtabLock++] = pTab;
|
||||
}else{
|
||||
pParse->db->mallocFailed = 1;
|
||||
pToplevel->db->mallocFailed = 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,11 @@ ifcapable !attach {
|
||||
return
|
||||
}
|
||||
|
||||
# The tests in this file were written before SQLite supported recursive
|
||||
# trigger invocation, and some tests depend on that to pass. So disable
|
||||
# recursive triggers for this file.
|
||||
catchsql { pragma disable_recursive_triggers = 1 }
|
||||
|
||||
# Create tables t1 and t2 in the main database
|
||||
execsql {
|
||||
CREATE TABLE t1(a, b);
|
||||
@@ -317,6 +322,7 @@ do_test attach3-12.9 {
|
||||
db_list
|
||||
} {main temp {}}
|
||||
do_test attach3-12.10 {
|
||||
breakpoint
|
||||
execsql {
|
||||
DETACH ?
|
||||
}
|
||||
|
||||
+2
-2
@@ -2273,13 +2273,13 @@ do_test auth-4.5 {
|
||||
set authargs
|
||||
} [list \
|
||||
SQLITE_DELETE v1 {} main {} \
|
||||
SQLITE_INSERT v1chng {} main r3 \
|
||||
SQLITE_READ v1 x main r3 \
|
||||
SQLITE_SELECT {} {} {} v1 \
|
||||
SQLITE_READ t2 a main v1 \
|
||||
SQLITE_READ t2 b main v1 \
|
||||
SQLITE_SELECT {} {} {} {} \
|
||||
SQLITE_READ v1 x main v1 \
|
||||
SQLITE_INSERT v1chng {} main r3 \
|
||||
SQLITE_READ v1 x main r3 \
|
||||
]
|
||||
|
||||
} ;# ifcapable view && trigger
|
||||
|
||||
@@ -25,6 +25,8 @@ ifcapable {!autoinc} {
|
||||
return
|
||||
}
|
||||
|
||||
sqlite3_db_config_lookaside db 0 0 0
|
||||
|
||||
# The database is initially empty.
|
||||
#
|
||||
do_test autoinc-1.1 {
|
||||
@@ -556,6 +558,8 @@ do_test autoinc-9.1 {
|
||||
}
|
||||
} {t3 0}
|
||||
|
||||
catchsql { pragma disable_recursive_triggers = 1 }
|
||||
|
||||
# Ticket #3928. Make sure that triggers to not make extra slots in
|
||||
# the SQLITE_SEQUENCE table.
|
||||
#
|
||||
|
||||
@@ -18,6 +18,11 @@
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
|
||||
# The tests in this file were written before SQLite supported recursive
|
||||
# trigger invocation, and some tests depend on that to pass. So disable
|
||||
# recursive triggers for this file.
|
||||
catchsql { pragma disable_recursive_triggers = 1 }
|
||||
|
||||
ifcapable {trigger} {
|
||||
# Test for ticket #360
|
||||
#
|
||||
@@ -358,6 +363,7 @@ ifcapable tclvar {
|
||||
db close
|
||||
file delete -force test.db
|
||||
sqlite3 db test.db
|
||||
catchsql { pragma disable_recursive_triggers = 1 }
|
||||
|
||||
# Ticket #453. If the SQL ended with "-", the tokenizer was calling that
|
||||
# an incomplete token, which caused problem. The solution was to just call
|
||||
|
||||
@@ -18,6 +18,11 @@ ifcapable {!trigger} {
|
||||
return
|
||||
}
|
||||
|
||||
# The tests in this file were written before SQLite supported recursive
|
||||
# trigger invocation, and some tests depend on that to pass. So disable
|
||||
# recursive triggers for this file.
|
||||
catchsql { pragma disable_recursive_triggers = 1 }
|
||||
|
||||
do_test tkt3731-1.1 {
|
||||
execsql {
|
||||
CREATE TABLE t1(a PRIMARY KEY, b);
|
||||
|
||||
@@ -72,9 +72,6 @@ do_test tkt3992-2.3 {
|
||||
}
|
||||
set res
|
||||
} {real}
|
||||
explain {
|
||||
UPDATE t2 SET a = 'I';
|
||||
}
|
||||
|
||||
|
||||
finish_test
|
||||
|
||||
+38
-38
@@ -63,84 +63,84 @@ do_test trigger1-1.1.3 {
|
||||
}
|
||||
} {1 {near "STATEMENT": syntax error}}
|
||||
execsql {
|
||||
CREATE TRIGGER tr1 INSERT ON t1 BEGIN
|
||||
INSERT INTO t1 values(1);
|
||||
END;
|
||||
CREATE TRIGGER tr1 INSERT ON t1 BEGIN
|
||||
INSERT INTO t1 values(1);
|
||||
END;
|
||||
}
|
||||
do_test trigger1-1.2.0 {
|
||||
catchsql {
|
||||
CREATE TRIGGER IF NOT EXISTS tr1 DELETE ON t1 BEGIN
|
||||
SELECT * FROM sqlite_master;
|
||||
END
|
||||
CREATE TRIGGER IF NOT EXISTS tr1 DELETE ON t1 BEGIN
|
||||
SELECT * FROM sqlite_master;
|
||||
END
|
||||
}
|
||||
} {0 {}}
|
||||
do_test trigger1-1.2.1 {
|
||||
catchsql {
|
||||
CREATE TRIGGER tr1 DELETE ON t1 BEGIN
|
||||
SELECT * FROM sqlite_master;
|
||||
END
|
||||
CREATE TRIGGER tr1 DELETE ON t1 BEGIN
|
||||
SELECT * FROM sqlite_master;
|
||||
END
|
||||
}
|
||||
} {1 {trigger tr1 already exists}}
|
||||
do_test trigger1-1.2.2 {
|
||||
catchsql {
|
||||
CREATE TRIGGER "tr1" DELETE ON t1 BEGIN
|
||||
SELECT * FROM sqlite_master;
|
||||
END
|
||||
CREATE TRIGGER "tr1" DELETE ON t1 BEGIN
|
||||
SELECT * FROM sqlite_master;
|
||||
END
|
||||
}
|
||||
} {1 {trigger "tr1" already exists}}
|
||||
do_test trigger1-1.2.3 {
|
||||
catchsql {
|
||||
CREATE TRIGGER [tr1] DELETE ON t1 BEGIN
|
||||
SELECT * FROM sqlite_master;
|
||||
END
|
||||
CREATE TRIGGER [tr1] DELETE ON t1 BEGIN
|
||||
SELECT * FROM sqlite_master;
|
||||
END
|
||||
}
|
||||
} {1 {trigger [tr1] already exists}}
|
||||
|
||||
do_test trigger1-1.3 {
|
||||
catchsql {
|
||||
BEGIN;
|
||||
CREATE TRIGGER tr2 INSERT ON t1 BEGIN
|
||||
SELECT * from sqlite_master; END;
|
||||
BEGIN;
|
||||
CREATE TRIGGER tr2 INSERT ON t1 BEGIN
|
||||
SELECT * from sqlite_master; END;
|
||||
ROLLBACK;
|
||||
CREATE TRIGGER tr2 INSERT ON t1 BEGIN
|
||||
SELECT * from sqlite_master; END;
|
||||
CREATE TRIGGER tr2 INSERT ON t1 BEGIN
|
||||
SELECT * from sqlite_master; END;
|
||||
}
|
||||
} {0 {}}
|
||||
|
||||
do_test trigger1-1.4 {
|
||||
catchsql {
|
||||
DROP TRIGGER IF EXISTS tr1;
|
||||
CREATE TRIGGER tr1 DELETE ON t1 BEGIN
|
||||
SELECT * FROM sqlite_master;
|
||||
END
|
||||
DROP TRIGGER IF EXISTS tr1;
|
||||
CREATE TRIGGER tr1 DELETE ON t1 BEGIN
|
||||
SELECT * FROM sqlite_master;
|
||||
END
|
||||
}
|
||||
} {0 {}}
|
||||
|
||||
do_test trigger1-1.5 {
|
||||
execsql {
|
||||
BEGIN;
|
||||
DROP TRIGGER tr2;
|
||||
ROLLBACK;
|
||||
DROP TRIGGER tr2;
|
||||
BEGIN;
|
||||
DROP TRIGGER tr2;
|
||||
ROLLBACK;
|
||||
DROP TRIGGER tr2;
|
||||
}
|
||||
} {}
|
||||
|
||||
do_test trigger1-1.6.1 {
|
||||
catchsql {
|
||||
DROP TRIGGER IF EXISTS biggles;
|
||||
DROP TRIGGER IF EXISTS biggles;
|
||||
}
|
||||
} {0 {}}
|
||||
|
||||
do_test trigger1-1.6.2 {
|
||||
catchsql {
|
||||
DROP TRIGGER biggles;
|
||||
DROP TRIGGER biggles;
|
||||
}
|
||||
} {1 {no such trigger: biggles}}
|
||||
|
||||
do_test trigger1-1.7 {
|
||||
catchsql {
|
||||
DROP TABLE t1;
|
||||
DROP TRIGGER tr1;
|
||||
DROP TABLE t1;
|
||||
DROP TRIGGER tr1;
|
||||
}
|
||||
} {1 {no such trigger: tr1}}
|
||||
|
||||
@@ -150,10 +150,10 @@ ifcapable tempdb {
|
||||
}
|
||||
do_test trigger1-1.8 {
|
||||
execsql {
|
||||
CREATE TRIGGER temp_trig UPDATE ON temp_table BEGIN
|
||||
SELECT * from sqlite_master;
|
||||
END;
|
||||
SELECT count(*) FROM sqlite_master WHERE name = 'temp_trig';
|
||||
CREATE TRIGGER temp_trig UPDATE ON temp_table BEGIN
|
||||
SELECT * from sqlite_master;
|
||||
END;
|
||||
SELECT count(*) FROM sqlite_master WHERE name = 'temp_trig';
|
||||
}
|
||||
} {0}
|
||||
}
|
||||
@@ -403,14 +403,14 @@ do_test trigger1-6.1 {
|
||||
do_test trigger1-6.2 {
|
||||
execsql {
|
||||
CREATE TRIGGER t2 BEFORE DELETE ON t2 BEGIN
|
||||
SELECT RAISE(ABORT,'deletes are not allows');
|
||||
SELECT RAISE(ABORT,'deletes are not permitted');
|
||||
END;
|
||||
SELECT type, name FROM sqlite_master;
|
||||
}
|
||||
} [concat $view_v1 {table t2 trigger t2}]
|
||||
do_test trigger1-6.3 {
|
||||
catchsql {DELETE FROM t2}
|
||||
} {1 {deletes are not allows}}
|
||||
} {1 {deletes are not permitted}}
|
||||
do_test trigger1-6.4 {
|
||||
execsql {SELECT * FROM t2}
|
||||
} {3 4 7 8}
|
||||
|
||||
@@ -54,6 +54,11 @@ ifcapable {!trigger} {
|
||||
return
|
||||
}
|
||||
|
||||
# The tests in this file were written before SQLite supported recursive
|
||||
# trigger invocation, and some tests depend on that to pass. So disable
|
||||
# recursive triggers for this file.
|
||||
catchsql { pragma disable_recursive_triggers = 1 }
|
||||
|
||||
# 1.
|
||||
ifcapable subquery {
|
||||
set ii 0
|
||||
|
||||
+8
-4
@@ -18,10 +18,14 @@ ifcapable {!trigger} {
|
||||
return
|
||||
}
|
||||
|
||||
# Test that we can cause ROLLBACK, FAIL and ABORT correctly
|
||||
# catchsql { DROP TABLE tbl; }
|
||||
catchsql { CREATE TABLE tbl (a, b, c) }
|
||||
# The tests in this file were written before SQLite supported recursive }
|
||||
# trigger invocation, and some tests depend on that to pass. So disable
|
||||
# recursive triggers for this file.
|
||||
catchsql { pragma disable_recursive_triggers = 1 }
|
||||
|
||||
# Test that we can cause ROLLBACK, FAIL and ABORT correctly
|
||||
#
|
||||
catchsql { CREATE TABLE tbl(a, b ,c) }
|
||||
execsql {
|
||||
CREATE TRIGGER before_tbl_insert BEFORE INSERT ON tbl BEGIN SELECT CASE
|
||||
WHEN (new.a = 4) THEN RAISE(IGNORE) END;
|
||||
@@ -54,7 +58,7 @@ do_test trigger3-1.3 {
|
||||
# FAIL
|
||||
do_test trigger3-2.1 {
|
||||
catchsql {
|
||||
BEGIN;
|
||||
BEGIN;
|
||||
INSERT INTO tbl VALUES (5, 5, 6);
|
||||
INSERT INTO tbl VALUES (2, 5, 6);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ if {$tcl_platform(platform) == "symbian"} {
|
||||
set nStatement 1000
|
||||
}
|
||||
|
||||
set nStatement 5
|
||||
do_test trigger8-1.1 {
|
||||
execsql {
|
||||
CREATE TABLE t1(x);
|
||||
|
||||
+4
-4
@@ -75,7 +75,7 @@ do_test trigger9-1.3.1 {
|
||||
} {1 2 3}
|
||||
do_test trigger9-1.3.2 {
|
||||
has_rowdata {DELETE FROM t1}
|
||||
} 1
|
||||
} 0
|
||||
do_test trigger9-1.3.3 { execsql { ROLLBACK } } {}
|
||||
|
||||
do_test trigger9-1.4.1 {
|
||||
@@ -90,7 +90,7 @@ do_test trigger9-1.4.1 {
|
||||
} {1}
|
||||
do_test trigger9-1.4.2 {
|
||||
has_rowdata {DELETE FROM t1}
|
||||
} 1
|
||||
} 0
|
||||
do_test trigger9-1.4.3 { execsql { ROLLBACK } } {}
|
||||
|
||||
do_test trigger9-1.5.1 {
|
||||
@@ -120,7 +120,7 @@ do_test trigger9-1.6.1 {
|
||||
} {1 2 3}
|
||||
do_test trigger9-1.6.2 {
|
||||
has_rowdata {UPDATE t1 SET y = ''}
|
||||
} 1
|
||||
} 0
|
||||
do_test trigger9-1.6.3 { execsql { ROLLBACK } } {}
|
||||
|
||||
do_test trigger9-1.7.1 {
|
||||
@@ -135,7 +135,7 @@ do_test trigger9-1.7.1 {
|
||||
} {2 3}
|
||||
do_test trigger9-1.7.2 {
|
||||
has_rowdata {UPDATE t1 SET y = ''}
|
||||
} 1
|
||||
} 0
|
||||
do_test trigger9-1.7.3 { execsql { ROLLBACK } } {}
|
||||
|
||||
do_test trigger9-3.1 {
|
||||
|
||||
@@ -0,0 +1,460 @@
|
||||
# 2009 August 24
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
#***********************************************************************
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
ifcapable {!trigger} {
|
||||
finish_test
|
||||
return
|
||||
}
|
||||
|
||||
# Enable recursive triggers for this file.
|
||||
#
|
||||
execsql { PRAGMA disable_recursive_triggers = 0 }
|
||||
|
||||
#sqlite3_db_config_lookaside db 0 0 0
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# This block of tests, triggerC-1.*, are not aimed at any specific
|
||||
# property of the triggers sub-system. They were created to debug
|
||||
# specific problems while modifying SQLite to support recursive
|
||||
# triggers. They are left here in case they can help debug the
|
||||
# same problems again.
|
||||
#
|
||||
do_test triggerC-1.1 {
|
||||
execsql {
|
||||
CREATE TABLE t1(a, b, c);
|
||||
CREATE TABLE log(t, a1, b1, c1, a2, b2, c2);
|
||||
CREATE TRIGGER trig1 BEFORE INSERT ON t1 BEGIN
|
||||
INSERT INTO log VALUES('before', NULL, NULL, NULL, new.a, new.b, new.c);
|
||||
END;
|
||||
CREATE TRIGGER trig2 AFTER INSERT ON t1 BEGIN
|
||||
INSERT INTO log VALUES('after', NULL, NULL, NULL, new.a, new.b, new.c);
|
||||
END;
|
||||
CREATE TRIGGER trig3 BEFORE UPDATE ON t1 BEGIN
|
||||
INSERT INTO log VALUES('before', old.a,old.b,old.c, new.a,new.b,new.c);
|
||||
END;
|
||||
CREATE TRIGGER trig4 AFTER UPDATE ON t1 BEGIN
|
||||
INSERT INTO log VALUES('after', old.a,old.b,old.c, new.a,new.b,new.c);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER trig5 BEFORE DELETE ON t1 BEGIN
|
||||
INSERT INTO log VALUES('before', old.a,old.b,old.c, NULL,NULL,NULL);
|
||||
END;
|
||||
CREATE TRIGGER trig6 AFTER DELETE ON t1 BEGIN
|
||||
INSERT INTO log VALUES('after', old.a,old.b,old.c, NULL,NULL,NULL);
|
||||
END;
|
||||
}
|
||||
} {}
|
||||
do_test triggerC-1.2 {
|
||||
execsql {
|
||||
INSERT INTO t1 VALUES('A', 'B', 'C');
|
||||
SELECT * FROM log;
|
||||
}
|
||||
} {before {} {} {} A B C after {} {} {} A B C}
|
||||
do_test triggerC-1.3 {
|
||||
execsql { SELECT * FROM t1 }
|
||||
} {A B C}
|
||||
do_test triggerC-1.4 {
|
||||
execsql {
|
||||
DELETE FROM log;
|
||||
UPDATE t1 SET a = 'a';
|
||||
SELECT * FROM log;
|
||||
}
|
||||
} {before A B C a B C after A B C a B C}
|
||||
do_test triggerC-1.5 {
|
||||
execsql { SELECT * FROM t1 }
|
||||
} {a B C}
|
||||
do_test triggerC-1.6 {
|
||||
execsql {
|
||||
DELETE FROM log;
|
||||
DELETE FROM t1;
|
||||
SELECT * FROM log;
|
||||
}
|
||||
} {before a B C {} {} {} after a B C {} {} {}}
|
||||
do_test triggerC-1.7 {
|
||||
execsql { SELECT * FROM t1 }
|
||||
} {}
|
||||
do_test triggerC-1.8 {
|
||||
execsql {
|
||||
CREATE TABLE t4(a, b);
|
||||
CREATE TRIGGER t4t AFTER DELETE ON t4 BEGIN
|
||||
SELECT RAISE(ABORT, 'delete is not supported');
|
||||
END;
|
||||
}
|
||||
} {}
|
||||
do_test triggerC-1.9 {
|
||||
execsql { INSERT INTO t4 VALUES(1, 2) }
|
||||
catchsql { DELETE FROM t4 }
|
||||
} {1 {delete is not supported}}
|
||||
do_test triggerC-1.10 {
|
||||
execsql { SELECT * FROM t4 }
|
||||
} {1 2}
|
||||
do_test triggerC-1.11 {
|
||||
execsql {
|
||||
CREATE TABLE t5 (a primary key, b, c);
|
||||
INSERT INTO t5 values (1, 2, 3);
|
||||
CREATE TRIGGER au_tbl AFTER UPDATE ON t5 BEGIN
|
||||
UPDATE OR IGNORE t5 SET a = new.a, c = 10;
|
||||
END;
|
||||
}
|
||||
} {}
|
||||
do_test triggerC-1.12 {
|
||||
catchsql { UPDATE OR REPLACE t5 SET a = 4 WHERE a = 1 }
|
||||
} {1 {too many levels of trigger recursion}}
|
||||
do_test triggerC-1.13 {
|
||||
execsql {
|
||||
CREATE TABLE t6(a INTEGER PRIMARY KEY, b);
|
||||
INSERT INTO t6 VALUES(1, 2);
|
||||
create trigger r1 after update on t6 for each row begin
|
||||
SELECT 1;
|
||||
end;
|
||||
UPDATE t6 SET a=a;
|
||||
}
|
||||
} {}
|
||||
do_test triggerC-1.14 {
|
||||
execsql {
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE cnt(n);
|
||||
INSERT INTO cnt VALUES(0);
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY, b UNIQUE, c, d, e);
|
||||
CREATE INDEX t1cd ON t1(c,d);
|
||||
CREATE TRIGGER t1r1 AFTER UPDATE ON t1 BEGIN UPDATE cnt SET n=n+1; END;
|
||||
INSERT INTO t1 VALUES(1,2,3,4,5);
|
||||
INSERT INTO t1 VALUES(6,7,8,9,10);
|
||||
INSERT INTO t1 VALUES(11,12,13,14,15);
|
||||
}
|
||||
} {}
|
||||
do_test triggerC-1.15 {
|
||||
catchsql { UPDATE OR ROLLBACK t1 SET a=100 }
|
||||
} {1 {PRIMARY KEY must be unique}}
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------
|
||||
# This block of tests, triggerC-2.*, tests that recursive trigger
|
||||
# programs (triggers that fire themselves) work. More specifically,
|
||||
# this block focuses on recursive INSERT triggers.
|
||||
#
|
||||
do_test triggerC-2.1.0 {
|
||||
execsql {
|
||||
CREATE TABLE t2(a PRIMARY KEY);
|
||||
}
|
||||
} {}
|
||||
|
||||
foreach {n tdefn rc} {
|
||||
1 {
|
||||
CREATE TRIGGER t2_trig AFTER INSERT ON t2 WHEN (new.a>0) BEGIN
|
||||
INSERT INTO t2 VALUES(new.a - 1);
|
||||
END;
|
||||
} {0 {10 9 8 7 6 5 4 3 2 1 0}}
|
||||
|
||||
2 {
|
||||
CREATE TRIGGER t2_trig AFTER INSERT ON t2 BEGIN
|
||||
SELECT CASE WHEN new.a==2 THEN RAISE(IGNORE) ELSE NULL END;
|
||||
INSERT INTO t2 VALUES(new.a - 1);
|
||||
END;
|
||||
} {0 {10 9 8 7 6 5 4 3 2}}
|
||||
|
||||
3 {
|
||||
CREATE TRIGGER t2_trig BEFORE INSERT ON t2 WHEN (new.a>0) BEGIN
|
||||
INSERT INTO t2 VALUES(new.a - 1);
|
||||
END;
|
||||
} {0 {0 1 2 3 4 5 6 7 8 9 10}}
|
||||
|
||||
4 {
|
||||
CREATE TRIGGER t2_trig BEFORE INSERT ON t2 BEGIN
|
||||
SELECT CASE WHEN new.a==2 THEN RAISE(IGNORE) ELSE NULL END;
|
||||
INSERT INTO t2 VALUES(new.a - 1);
|
||||
END;
|
||||
} {0 {3 4 5 6 7 8 9 10}}
|
||||
|
||||
5 {
|
||||
CREATE TRIGGER t2_trig BEFORE INSERT ON t2 BEGIN
|
||||
INSERT INTO t2 VALUES(new.a - 1);
|
||||
END;
|
||||
} {1 {too many levels of trigger recursion}}
|
||||
|
||||
6 {
|
||||
CREATE TRIGGER t2_trig AFTER INSERT ON t2 WHEN (new.a>0) BEGIN
|
||||
INSERT OR IGNORE INTO t2 VALUES(new.a);
|
||||
END;
|
||||
} {0 10}
|
||||
|
||||
7 {
|
||||
CREATE TRIGGER t2_trig BEFORE INSERT ON t2 WHEN (new.a>0) BEGIN
|
||||
INSERT OR IGNORE INTO t2 VALUES(new.a);
|
||||
END;
|
||||
} {1 {too many levels of trigger recursion}}
|
||||
} {
|
||||
do_test triggerC-2.1.$n {
|
||||
catchsql { DROP TRIGGER t2_trig }
|
||||
execsql { DELETE FROM t2 }
|
||||
execsql $tdefn
|
||||
catchsql {
|
||||
INSERT INTO t2 VALUES(10);
|
||||
SELECT * FROM t2;
|
||||
}
|
||||
} $rc
|
||||
}
|
||||
|
||||
do_test triggerC-2.2 {
|
||||
execsql {
|
||||
CREATE TABLE t22(x);
|
||||
|
||||
CREATE TRIGGER t22a AFTER INSERT ON t22 BEGIN
|
||||
INSERT INTO t22 SELECT x + (SELECT max(x) FROM t22) FROM t22;
|
||||
END;
|
||||
CREATE TRIGGER t22b BEFORE INSERT ON t22 BEGIN
|
||||
SELECT CASE WHEN (SELECT count(*) FROM t22) >= 100
|
||||
THEN RAISE(IGNORE)
|
||||
ELSE NULL END;
|
||||
END;
|
||||
|
||||
INSERT INTO t22 VALUES(1);
|
||||
SELECT count(*) FROM t22;
|
||||
}
|
||||
} {100}
|
||||
|
||||
do_test triggerC-2.3 {
|
||||
execsql {
|
||||
CREATE TABLE t23(x PRIMARY KEY);
|
||||
|
||||
CREATE TRIGGER t23a AFTER INSERT ON t23 BEGIN
|
||||
INSERT INTO t23 VALUES(new.x + 1);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER t23b BEFORE INSERT ON t23 BEGIN
|
||||
SELECT CASE WHEN new.x>500
|
||||
THEN RAISE(IGNORE)
|
||||
ELSE NULL END;
|
||||
END;
|
||||
|
||||
INSERT INTO t23 VALUES(1);
|
||||
SELECT count(*) FROM t23;
|
||||
}
|
||||
} {500}
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# This block of tests, triggerC-3.*, test that SQLite throws an exception
|
||||
# when it detects excessive recursion.
|
||||
#
|
||||
do_test triggerC-3.1.1 {
|
||||
execsql {
|
||||
CREATE TABLE t3(a, b);
|
||||
CREATE TRIGGER t3i AFTER INSERT ON t3 BEGIN
|
||||
DELETE FROM t3 WHERE rowid = new.rowid;
|
||||
END;
|
||||
CREATE TRIGGER t3d AFTER DELETE ON t3 BEGIN
|
||||
INSERT INTO t3 VALUES(old.a, old.b);
|
||||
END;
|
||||
}
|
||||
} {}
|
||||
do_test triggerC-3.1.2 {
|
||||
catchsql { INSERT INTO t3 VALUES(0,0) }
|
||||
} {1 {too many levels of trigger recursion}}
|
||||
do_test triggerC-3.1.3 {
|
||||
execsql { SELECT * FROM t3 }
|
||||
} {}
|
||||
|
||||
#-----------------------------------------------------------------------
|
||||
# This next block of tests, triggerC-4.*, checks that affinity
|
||||
# transformations and constraint processing is performed at the correct
|
||||
# times relative to BEFORE and AFTER triggers.
|
||||
#
|
||||
# For an INSERT statement, for each row to be inserted:
|
||||
#
|
||||
# 1. Apply affinities to non-rowid values to be inserted.
|
||||
# 2. Fire BEFORE triggers.
|
||||
# 3. Process constraints.
|
||||
# 4. Insert new record.
|
||||
# 5. Fire AFTER triggers.
|
||||
#
|
||||
# If the value of the rowid field is to be automatically assigned, it is
|
||||
# set to -1 in the new.* record. Even if it is explicitly set to NULL
|
||||
# by the INSERT statement.
|
||||
#
|
||||
# For an UPDATE statement, for each row to be deleted:
|
||||
#
|
||||
# 1. Apply affinities to non-rowid values to be inserted.
|
||||
# 2. Fire BEFORE triggers.
|
||||
# 3. Process constraints.
|
||||
# 4. Insert new record.
|
||||
# 5. Fire AFTER triggers.
|
||||
#
|
||||
# For a DELETE statement, for each row to be deleted:
|
||||
#
|
||||
# 1. Fire BEFORE triggers.
|
||||
# 2. Remove database record.
|
||||
# 3. Fire AFTER triggers.
|
||||
#
|
||||
# When a numeric value that as an exact integer representation is stored
|
||||
# in a column with REAL affinity, it is actually stored as an integer.
|
||||
# These tests check that the typeof() such values is always 'real',
|
||||
# not 'integer'.
|
||||
#
|
||||
# triggerC-4.1.*: Check that affinity transformations are made before
|
||||
# triggers are invoked.
|
||||
#
|
||||
do_test triggerC-4.1.1 {
|
||||
catchsql { DROP TABLE log }
|
||||
catchsql { DROP TABLE t4 }
|
||||
execsql {
|
||||
CREATE TABLE log(t);
|
||||
CREATE TABLE t4(a TEXT,b INTEGER,c REAL);
|
||||
CREATE TRIGGER t4bi BEFORE INSERT ON t4 BEGIN
|
||||
INSERT INTO log VALUES(new.rowid || ' ' || typeof(new.rowid) || ' ' ||
|
||||
new.a || ' ' || typeof(new.a) || ' ' ||
|
||||
new.b || ' ' || typeof(new.b) || ' ' ||
|
||||
new.c || ' ' || typeof(new.c)
|
||||
);
|
||||
END;
|
||||
CREATE TRIGGER t4ai AFTER INSERT ON t4 BEGIN
|
||||
INSERT INTO log VALUES(new.rowid || ' ' || typeof(new.rowid) || ' ' ||
|
||||
new.a || ' ' || typeof(new.a) || ' ' ||
|
||||
new.b || ' ' || typeof(new.b) || ' ' ||
|
||||
new.c || ' ' || typeof(new.c)
|
||||
);
|
||||
END;
|
||||
CREATE TRIGGER t4bd BEFORE DELETE ON t4 BEGIN
|
||||
INSERT INTO log VALUES(old.rowid || ' ' || typeof(old.rowid) || ' ' ||
|
||||
old.a || ' ' || typeof(old.a) || ' ' ||
|
||||
old.b || ' ' || typeof(old.b) || ' ' ||
|
||||
old.c || ' ' || typeof(old.c)
|
||||
);
|
||||
END;
|
||||
CREATE TRIGGER t4ad AFTER DELETE ON t4 BEGIN
|
||||
INSERT INTO log VALUES(old.rowid || ' ' || typeof(old.rowid) || ' ' ||
|
||||
old.a || ' ' || typeof(old.a) || ' ' ||
|
||||
old.b || ' ' || typeof(old.b) || ' ' ||
|
||||
old.c || ' ' || typeof(old.c)
|
||||
);
|
||||
END;
|
||||
CREATE TRIGGER t4bu BEFORE UPDATE ON t4 BEGIN
|
||||
INSERT INTO log VALUES(old.rowid || ' ' || typeof(old.rowid) || ' ' ||
|
||||
old.a || ' ' || typeof(old.a) || ' ' ||
|
||||
old.b || ' ' || typeof(old.b) || ' ' ||
|
||||
old.c || ' ' || typeof(old.c)
|
||||
);
|
||||
INSERT INTO log VALUES(new.rowid || ' ' || typeof(new.rowid) || ' ' ||
|
||||
new.a || ' ' || typeof(new.a) || ' ' ||
|
||||
new.b || ' ' || typeof(new.b) || ' ' ||
|
||||
new.c || ' ' || typeof(new.c)
|
||||
);
|
||||
END;
|
||||
CREATE TRIGGER t4au AFTER UPDATE ON t4 BEGIN
|
||||
INSERT INTO log VALUES(old.rowid || ' ' || typeof(old.rowid) || ' ' ||
|
||||
old.a || ' ' || typeof(old.a) || ' ' ||
|
||||
old.b || ' ' || typeof(old.b) || ' ' ||
|
||||
old.c || ' ' || typeof(old.c)
|
||||
);
|
||||
INSERT INTO log VALUES(new.rowid || ' ' || typeof(new.rowid) || ' ' ||
|
||||
new.a || ' ' || typeof(new.a) || ' ' ||
|
||||
new.b || ' ' || typeof(new.b) || ' ' ||
|
||||
new.c || ' ' || typeof(new.c)
|
||||
);
|
||||
END;
|
||||
}
|
||||
} {}
|
||||
foreach {n insert log} {
|
||||
|
||||
2 {
|
||||
INSERT INTO t4 VALUES('1', '1', '1');
|
||||
DELETE FROM t4;
|
||||
} {
|
||||
-1 integer 1 text 1 integer 1.0 real
|
||||
1 integer 1 text 1 integer 1.0 real
|
||||
1 integer 1 text 1 integer 1.0 real
|
||||
1 integer 1 text 1 integer 1.0 real
|
||||
}
|
||||
|
||||
3 {
|
||||
INSERT INTO t4(rowid,a,b,c) VALUES(45, 45, 45, 45);
|
||||
DELETE FROM t4;
|
||||
} {
|
||||
45 integer 45 text 45 integer 45.0 real
|
||||
45 integer 45 text 45 integer 45.0 real
|
||||
45 integer 45 text 45 integer 45.0 real
|
||||
45 integer 45 text 45 integer 45.0 real
|
||||
}
|
||||
|
||||
4 {
|
||||
INSERT INTO t4(rowid,a,b,c) VALUES(-42.0, -42.0, -42.0, -42.0);
|
||||
DELETE FROM t4;
|
||||
} {
|
||||
-42 integer -42.0 text -42 integer -42.0 real
|
||||
-42 integer -42.0 text -42 integer -42.0 real
|
||||
-42 integer -42.0 text -42 integer -42.0 real
|
||||
-42 integer -42.0 text -42 integer -42.0 real
|
||||
}
|
||||
|
||||
5 {
|
||||
INSERT INTO t4(rowid,a,b,c) VALUES(NULL, -42.4, -42.4, -42.4);
|
||||
DELETE FROM t4;
|
||||
} {
|
||||
-1 integer -42.4 text -42.4 real -42.4 real
|
||||
1 integer -42.4 text -42.4 real -42.4 real
|
||||
1 integer -42.4 text -42.4 real -42.4 real
|
||||
1 integer -42.4 text -42.4 real -42.4 real
|
||||
}
|
||||
|
||||
6 {
|
||||
INSERT INTO t4 VALUES(7, 7, 7);
|
||||
UPDATE t4 SET a=8, b=8, c=8;
|
||||
} {
|
||||
-1 integer 7 text 7 integer 7.0 real
|
||||
1 integer 7 text 7 integer 7.0 real
|
||||
1 integer 7 text 7 integer 7.0 real
|
||||
1 integer 8 text 8 integer 8.0 real
|
||||
1 integer 7 text 7 integer 7.0 real
|
||||
1 integer 8 text 8 integer 8.0 real
|
||||
}
|
||||
|
||||
7 {
|
||||
UPDATE t4 SET rowid=2;
|
||||
} {
|
||||
1 integer 8 text 8 integer 8.0 real
|
||||
2 integer 8 text 8 integer 8.0 real
|
||||
1 integer 8 text 8 integer 8.0 real
|
||||
2 integer 8 text 8 integer 8.0 real
|
||||
}
|
||||
|
||||
8 {
|
||||
UPDATE t4 SET a='9', b='9', c='9';
|
||||
} {
|
||||
2 integer 8 text 8 integer 8.0 real
|
||||
2 integer 9 text 9 integer 9.0 real
|
||||
2 integer 8 text 8 integer 8.0 real
|
||||
2 integer 9 text 9 integer 9.0 real
|
||||
}
|
||||
|
||||
9 {
|
||||
UPDATE t4 SET a='9.1', b='9.1', c='9.1';
|
||||
} {
|
||||
2 integer 9 text 9 integer 9.0 real
|
||||
2 integer 9.1 text 9.1 real 9.1 real
|
||||
2 integer 9 text 9 integer 9.0 real
|
||||
2 integer 9.1 text 9.1 real 9.1 real
|
||||
}
|
||||
} {
|
||||
do_test triggerC-4.1.$n {
|
||||
eval concat [execsql "
|
||||
DELETE FROM log;
|
||||
$insert ;
|
||||
SELECT * FROM log;
|
||||
"]
|
||||
} [join $log " "]
|
||||
}
|
||||
|
||||
finish_test
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ while {![eof stdin]} {
|
||||
append line " "
|
||||
foreach v $vlist {
|
||||
regsub -all "(\[^a-zA-Z0-9>.\])${v}(\\W)" $line "\\1u.$sname.$v\\2" line
|
||||
regsub -all "(\[^a-zA-Z0-9>.\])${v}(\\W)" $line "\\1u.$sname.$v\\2" line
|
||||
}
|
||||
append afterUnion [string trimright $line]\n
|
||||
} elseif {$line=="" && [eof stdin]} {
|
||||
|
||||
Reference in New Issue
Block a user