Merge in changes to support recursive invocation of triggers (disabled by default).

FossilOrigin-Name: c1b388c30de1b660c56afbcac9c69700500808a9
This commit is contained in:
dan
2009-09-01 16:39:06 +00:00
37 changed files with 1695 additions and 819 deletions
+41 -50
View File
@@ -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
View File
@@ -1 +1 @@
5e2f5496834bffb79236f803cdfedb18fbd533b7
c1b388c30de1b660c56afbcac9c69700500808a9
+3 -3
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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{
+3
View File
@@ -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);
+1
View File
@@ -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;
+8
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
}
+6
View File
@@ -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
View File
@@ -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
+4
View File
@@ -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.
#
+6
View File
@@ -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
+5
View File
@@ -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);
-3
View File
@@ -72,9 +72,6 @@ do_test tkt3992-2.3 {
}
set res
} {real}
explain {
UPDATE t2 SET a = 'I';
}
finish_test
+38 -38
View File
@@ -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}
+5
View File
@@ -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
View File
@@ -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);
}
+1
View File
@@ -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
View File
@@ -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 {
+460
View File
@@ -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
+1
View File
@@ -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]} {