mirror of
https://github.com/sqlite/sqlite.git
synced 2025-07-29 08:01:23 +03:00
Add support for the RETURNING clause following PostgreSQL syntax.
FossilOrigin-Name: 416c898bfb8ff9639ffbaefcfb47fce3782763af1fc67969fa91c5f01a336676
This commit is contained in:
42
manifest
42
manifest
@ -1,5 +1,5 @@
|
||||
C Fix\san\sassert()\sthat\smight\sbe\soff-by-one\sin\sthe\scase\sof\sa\sprior\nerrors\sin\sthe\sparse.
|
||||
D 2021-02-03T12:35:51.366
|
||||
C Add\ssupport\sfor\sthe\sRETURNING\sclause\sfollowing\sPostgreSQL\ssyntax.
|
||||
D 2021-02-03T13:08:09.152
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
@ -485,24 +485,24 @@ F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6
|
||||
F src/btree.c 4da25694985ac8f5f714bfa58a6cd453f9161d7da9394a95605aaa4db2752757
|
||||
F src/btree.h 285f8377aa1353185a32bf455faafa9ff9a0d40d074d60509534d14990c7829e
|
||||
F src/btreeInt.h 7614cae30f95b6aed0c7cac7718276a55cfe2c77058cbfd8bef5b75329757331
|
||||
F src/build.c d4c06261b0e532523ede58dc511381a7a9c155132e4b65a6bb2ff76fe657793a
|
||||
F src/build.c 118e1076282415229420d04f9cc25bb148a2c412d82ea3c319136d2122c842e5
|
||||
F src/callback.c d0b853dd413255d2e337b34545e54d888ea02f20da5ad0e63585b389624c4a6c
|
||||
F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
|
||||
F src/ctime.c 2a322b9a3d75771fb4d99e0702851f4f68dda982507a0f798eefb0712969a410
|
||||
F src/date.c dace306a10d9b02ee553d454c8e1cf8d3c9b932e137738a6b15b90253a9bfc10
|
||||
F src/dbpage.c 8a01e865bf8bc6d7b1844b4314443a6436c07c3efe1d488ed89e81719047833a
|
||||
F src/dbstat.c 3aa79fc3aed7ce906e4ea6c10e85d657299e304f6049861fe300053ac57de36c
|
||||
F src/delete.c 927cf8f900583e79aca8f1a321979e0a8f053babd9a690b44b38f79de2cc09fe
|
||||
F src/delete.c 352ea931218c45a3daf17472d4141b9c7fc026d85da3f1ade404ea5bb6d67f77
|
||||
F src/expr.c 47c85263e6d179424e6b09e2c79db5704ab5b8cbc2fae2ee3285faa2566f2e74
|
||||
F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
|
||||
F src/fkey.c 83372403298e6a7dd989a47aaacdbaa5b4307b5199dbd56e07d4896066b3de72
|
||||
F src/fkey.c 02e4a3311885cd2b31eb17fd58dc2fc738cd2c823d0d39e4dd5595169c6f8bc3
|
||||
F src/func.c 2ea99e9e0531b7f020d5e8e167d25344d618afc718ddc94dd91fa8fef1c85a91
|
||||
F src/global.c ed55af196a9b66e198aaeda3f5454c3aa7d7d050c6c938181fd044b70d180a81
|
||||
F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19
|
||||
F src/hash.h 9d56a9079d523b648774c1784b74b89bd93fac7b365210157482e4319a468f38
|
||||
F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144
|
||||
F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
|
||||
F src/insert.c 9b970eff058a858fbd9f2db71425ef195942c2610855daa66ae23024432d52f5
|
||||
F src/insert.c 97be36c52c667a64aacbba76398544d224268d62444b67d011a077c486e375bb
|
||||
F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
|
||||
F src/loadext.c 8c9c8cd2bd8eecdb06d9b6e89de7e9e65bae45cc8fc33609cc74023a5c296067
|
||||
F src/main.c 1c5de7b3fabcdf05f4fe563aab5d81d175b89c67a8678a12ba86629356afa356
|
||||
@ -530,7 +530,7 @@ F src/os_win.c 77d39873836f1831a9b0b91894fec45ab0e9ca8e067dc8c549e1d1eca1566fe9
|
||||
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
|
||||
F src/pager.c c49952ac5e9cc536778eff528091d79d38b3e45cbeeed4695dc05e207dc6547d
|
||||
F src/pager.h 4bf9b3213a4b2bebbced5eaa8b219cf25d4a82f385d093cd64b7e93e5285f66f
|
||||
F src/parse.y 6c8aa09a7fa6e0867c3a3d67ef61b911aa392c9b084a61dc632cd93732aef8ad
|
||||
F src/parse.y 67ba503780de64b967ae195b7e14c33531329228e1bc0b83d63324beb733680b
|
||||
F src/pcache.c 385ff064bca69789d199a98e2169445dc16e4291fa807babd61d4890c3b34177
|
||||
F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586
|
||||
F src/pcache1.c 6596e10baf3d8f84cc1585d226cf1ab26564a5f5caf85a15757a281ff977d51a
|
||||
@ -539,14 +539,14 @@ F src/pragma.h 8dc78ab7e9ec6ce3ded8332810a2066f1ef6267e2e03cd7356ee00276125c6cf
|
||||
F src/prepare.c f288cbc35f79eb32e162de7e80a63ebe00d80e639dcfac071bee11570cbdb16f
|
||||
F src/printf.c 30e92b638fac71dcd85cdea1d12ecfae354c9adee2c71e8e1ae4727cde7c91ed
|
||||
F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
|
||||
F src/resolve.c 1948a92ca9eab776632816b97e57c61d933474a78aad4f4ef835c916a83dbb1c
|
||||
F src/resolve.c f6761473ea4b51190fc52f8f2121498b78717266e106e7bff12849ea2d52165f
|
||||
F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
|
||||
F src/select.c 738cb746189f721f59972993c13085fa2975c4cbfd04ba26445f3b42c81237dc
|
||||
F src/shell.c.in 9ebc74e4f05cfbd0f4a36060fdaeff1da4e9af4458358722bc08c5a1ab9a0879
|
||||
F src/sqlite.h.in 0af968a1fa3c717261e1df0ed105fa7bddb4d82de7e0adb3eab49e6aa81b4de7
|
||||
F src/sqlite.h.in 8855a19f37ade8dad189a9e48233a2ebe1b46faf469c7eb0906a654e252dcc57
|
||||
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
|
||||
F src/sqlite3ext.h 61b38c073d5e1e96a3d45271b257aef27d0d13da2bea5347692ae579475cd95e
|
||||
F src/sqliteInt.h 3e5bc0446611e272b93754265e3265f36249d0458da25e32991fce241d69dbcf
|
||||
F src/sqliteInt.h 0fda3b2c05b1559135aa2c4ecb8e75bd2085ba4433310bbb5427d97c2d81315d
|
||||
F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
|
||||
F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
|
||||
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
|
||||
@ -607,17 +607,17 @@ F src/test_wsd.c 41cadfd9d97fe8e3e4e44f61a4a8ccd6f7ca8fe9
|
||||
F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c
|
||||
F src/tokenize.c c64c49d7c2ec4490c2fef1f24350167ba16b03b0c6cee58ad1a1d70a4325d4e9
|
||||
F src/treeview.c 4b92992176fb2caefbe06ba5bd06e0e0ebcde3d5564758da672631f17aa51cda
|
||||
F src/trigger.c 731ea5ed6b308574b7dc2a5d2a9187ef5510a3692cc1ea06a34608a084b8f376
|
||||
F src/update.c 9f126204a6acb96bbe47391ae48e0fc579105d8e76a6d9c4fab3271367476580
|
||||
F src/trigger.c 0a242d65dd9b9822d4e990653eb4ece3557dcda01374934aa3cc1f9718d8dee3
|
||||
F src/update.c 0f5a61f0787199983530a33f6fffe4f52742f35fcdf6ccfad1078b1a8bc17723
|
||||
F src/upsert.c df8f1727d62b5987c4fd302cd4d7c0c84ae57cd65683c5a34a740dfe24039235
|
||||
F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0
|
||||
F src/util.c 41c7a72da1df47864faa378a1c720b38adb288c6838cb6be5594511b6287a048
|
||||
F src/vacuum.c 492422c1463c076473bae1858799c7a0a5fe87a133d1223239447c422cd26286
|
||||
F src/vdbe.c 102d21260bddbb43c845603c3a2d6b4f3762e72f836ccda12991f291485d2539
|
||||
F src/vdbe.h 83603854bfa5851af601fc0947671eb260f4363e62e960e8a994fb9bbcd2aaa1
|
||||
F src/vdbe.c 9b9a714318e49b59a282b4e175080dc53a07b8099895fa21613e6b80fbad3727
|
||||
F src/vdbe.h a71bf43572d3de57923d1928ac01ae8d355cd67e94462ba4f7462265cedbef9a
|
||||
F src/vdbeInt.h 3ca5e9fd6e095a8b6cf6bc3587a46fc93499503b2fe48951e1034ba9e2ce2f6e
|
||||
F src/vdbeapi.c c5e7cb2ab89a24d7f723e87b508f21bfb1359a04db5277d8a99fd1e015c12eb9
|
||||
F src/vdbeaux.c e91d74e24babcf61969279b193e228cf4f8bc724a9cc59ed287db064326876f8
|
||||
F src/vdbeaux.c 2be30e4918126122fa358ef8303206cad0feffe17d320077c77ff5c2a34f3626
|
||||
F src/vdbeblob.c 253ed82894924c362a7fa3079551d3554cd1cdace39aa833da77d3bc67e7c1b1
|
||||
F src/vdbemem.c 947f2a65910edb4014dc981d33e414a68c51f169f9df8c4c493a0ba840b6eb1f
|
||||
F src/vdbesort.c f5b5e473a7cee44e47a94817b042fd7172cf3aa2c0a7928a8339d612bcfdec5a
|
||||
@ -1286,6 +1286,7 @@ F test/releasetest.tcl fb76d8fcc95ac29d6356cd9e52b726ab9e43a24082897618dfbcb7c2b
|
||||
F test/releasetest_data.tcl b9cb30360759b80d92d4ea86b84ebfd8035b97f9078a482deb3cf9d0b2442655
|
||||
F test/resetdb.test 8062cf10a09d8c048f8de7711e94571c38b38168db0e5877ba7561789e5eeb2b
|
||||
F test/resolver01.test f4022acafda7f4d40eca94dbf16bc5fc4ac30ceb
|
||||
F test/returning1.test 684e1c73d961422a7376c932fcdd6dacf02bad21d12f749cfe8c19991ef379f6
|
||||
F test/rollback.test 06680159bc6746d0f26276e339e3ae2f951c64812468308838e0a3362d911eaa
|
||||
F test/rollback2.test bc868d57899dc6972e2b4483faae0e03365a0556941474eec487ae21d8d38bb6
|
||||
F test/rollbackfault.test 0e646aeab8840c399cfbfa43daab46fd609cf04a
|
||||
@ -1751,7 +1752,7 @@ F test/whereK.test f8e3cf26a8513ecc7f514f54df9f0572c046c42b
|
||||
F test/whereL.test 1afe47227f093dc0547236491fb37529b7be9724b8575925a321001b80e6a23a
|
||||
F test/wherefault.test 1374c3aa198388925246475f84ad4cd5f9528864
|
||||
F test/wherelfault.test 9012e4ef5259058b771606616bd007af5d154e64cc25fa9fd4170f6411db44e3
|
||||
F test/wherelimit.test 592081800806d297dd7449b1030c863d2883d6d42901837ccd2e5a9bd962edb0
|
||||
F test/wherelimit.test daa0fd9122c5745cc459ec40b8d3c16ce13ce8382b5b847e7cfff4b871260cbf
|
||||
F test/wherelimit2.test 657a3f24aadee62d058c5091ea682dc4af4b95ffe32f137155be49799a58e721
|
||||
F test/win32heap.test 10fd891266bd00af68671e702317726375e5407561d859be1aa04696f2aeee74
|
||||
F test/win32lock.test fbf107c91d8f5512be5a5b87c4c42ab9fdd54972
|
||||
@ -1826,7 +1827,7 @@ F tool/max-limits.c cbb635fbb37ae4d05f240bfb5b5270bb63c54439
|
||||
F tool/mkautoconfamal.sh f62353eb6c06ab264da027fd4507d09914433dbdcab9cb011cdc18016f1ab3b8
|
||||
F tool/mkccode.tcl 86463e68ce9c15d3041610fedd285ce32a5cf7a58fc88b3202b8b76837650dbe x
|
||||
F tool/mkctimec.tcl dd183b73ae1c28249669741c250525f0407e579a70482371668fd5f130d9feb3
|
||||
F tool/mkkeywordhash.c 24e4396ae665d985fed9e040e8b748129c1a12d77eeeae7ad4609821c41ba7bf
|
||||
F tool/mkkeywordhash.c 750f25aef0e23f8e3367af6d824fbf5ed7d3e285f27cea91aa2dd72c367630eb
|
||||
F tool/mkmsvcmin.tcl 6ecab9fe22c2c8de4d82d4c46797bda3d2deac8e763885f5a38d0c44a895ab33
|
||||
F tool/mkopcodec.tcl d1b6362bd3aa80d5520d4d6f3765badf01f6c43c
|
||||
F tool/mkopcodeh.tcl 352a4319c0ad869eb26442bf7c3b015aa15594c21f1cce5a6420dbe999367c21
|
||||
@ -1898,7 +1899,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P e4ccfac09b6fe8cc3aec29d10f4e4c83097964f29882343db52ed91f6f0dde1c
|
||||
R 30096bbeccf9e03a69ea838444df44cb
|
||||
P 06b15b17be38c804dd2641d8616a2a7bd396d2eb9901a0fbf94edd8bd508cf9c a10c5a2503ff2998f6ee40f721aab8c9579052e535dc141bd57d10551eaea387
|
||||
R 10c9b96c07db17433ca188cab65447b4
|
||||
T +closed a10c5a2503ff2998f6ee40f721aab8c9579052e535dc141bd57d10551eaea387
|
||||
U drh
|
||||
Z 17a66c8e0166569653feae3b32e8dbae
|
||||
Z 40f463993ad19f6f3483715fa2a6abfe
|
||||
|
@ -1 +1 @@
|
||||
06b15b17be38c804dd2641d8616a2a7bd396d2eb9901a0fbf94edd8bd508cf9c
|
||||
416c898bfb8ff9639ffbaefcfb47fce3782763af1fc67969fa91c5f01a336676
|
69
src/build.c
69
src/build.c
@ -1243,6 +1243,75 @@ void sqlite3ColumnPropertiesFromName(Table *pTab, Column *pCol){
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
** Name of the special TEMP trigger used to implement RETURNING. The
|
||||
** name begins with "sqlite_" so that it is guaranteed not to collide
|
||||
** with any application-generated triggers.
|
||||
*/
|
||||
#define RETURNING_TRIGGER_NAME "sqlite_returning"
|
||||
|
||||
/*
|
||||
** Clean up the data structures associated with the RETURNING clause.
|
||||
*/
|
||||
static void sqlite3DeleteReturning(sqlite3 *db, Returning *pRet){
|
||||
Hash *pHash;
|
||||
pHash = &(db->aDb[1].pSchema->trigHash);
|
||||
sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, 0);
|
||||
sqlite3ExprListDelete(db, pRet->pReturnEL);
|
||||
sqlite3DbFree(db, pRet);
|
||||
}
|
||||
|
||||
/*
|
||||
** Add the RETURNING clause to the parse currently underway.
|
||||
**
|
||||
** This routine creates a special TEMP trigger that will fire for each row
|
||||
** of the DML statement. That TEMP trigger contains a single SELECT
|
||||
** statement with a result set that is the argument of the RETURNING clause.
|
||||
** The trigger has the Trigger.bReturning flag and an opcode of
|
||||
** TK_RETURNING instead of TK_SELECT, so that the trigger code generator
|
||||
** knows to handle it specially. The TEMP trigger is automatically
|
||||
** removed at the end of the parse.
|
||||
**
|
||||
** When this routine is called, we do not yet know if the RETURNING clause
|
||||
** is attached to a DELETE, INSERT, or UPDATE, so construct it as a
|
||||
** RETURNING trigger instead. It will then be converted into the appropriate
|
||||
** type on the first call to sqlite3TriggersExist().
|
||||
*/
|
||||
void sqlite3AddReturning(Parse *pParse, ExprList *pList){
|
||||
Returning *pRet;
|
||||
Hash *pHash;
|
||||
sqlite3 *db = pParse->db;
|
||||
assert( !pParse->bReturning );
|
||||
pParse->bReturning = 1;
|
||||
pRet = sqlite3DbMallocZero(db, sizeof(*pRet));
|
||||
if( pRet==0 ){
|
||||
sqlite3ExprListDelete(db, pList);
|
||||
return;
|
||||
}
|
||||
pRet->pParse = pParse;
|
||||
pRet->pReturnEL = pList;
|
||||
sqlite3ParserAddCleanup(pParse,
|
||||
(void(*)(sqlite3*,void*))sqlite3DeleteReturning, pRet);
|
||||
if( db->mallocFailed ) return;
|
||||
pRet->retTrig.zName = RETURNING_TRIGGER_NAME;
|
||||
pRet->retTrig.op = TK_RETURNING;
|
||||
pRet->retTrig.tr_tm = TRIGGER_AFTER;
|
||||
pRet->retTrig.bReturning = 1;
|
||||
pRet->retTrig.pSchema = db->aDb[1].pSchema;
|
||||
pRet->retTrig.step_list = &pRet->retTStep;
|
||||
pRet->retTStep.op = TK_RETURNING;
|
||||
pRet->retTStep.pTrig = &pRet->retTrig;
|
||||
pRet->retTStep.pSelect = &pRet->retSel;
|
||||
pRet->retSel.op = TK_ALL;
|
||||
pRet->retSel.pEList = pList;
|
||||
pRet->retSel.pSrc = (SrcList*)&pRet->retSrcList;
|
||||
pHash = &(db->aDb[1].pSchema->trigHash);
|
||||
assert( sqlite3HashFind(pHash, RETURNING_TRIGGER_NAME)==0 );
|
||||
if( sqlite3HashInsert(pHash, RETURNING_TRIGGER_NAME, &pRet->retTrig)
|
||||
==&pRet->retTrig ){
|
||||
sqlite3OomFault(db);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
** Add a new column to the table currently being constructed.
|
||||
|
@ -387,6 +387,7 @@ void sqlite3DeleteFrom(
|
||||
if( (db->flags & SQLITE_CountRows)!=0
|
||||
&& !pParse->nested
|
||||
&& !pParse->pTriggerTab
|
||||
&& !pParse->bReturning
|
||||
){
|
||||
memCnt = ++pParse->nMem;
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 0, memCnt);
|
||||
@ -608,7 +609,7 @@ void sqlite3DeleteFrom(
|
||||
** invoke the callback function.
|
||||
*/
|
||||
if( memCnt ){
|
||||
sqlite3VdbeAddOp2(v, OP_ResultRow, memCnt, 1);
|
||||
sqlite3VdbeAddOp2(v, OP_ChngCntRow, memCnt, 1);
|
||||
sqlite3VdbeSetNumCols(v, 1);
|
||||
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows deleted", SQLITE_STATIC);
|
||||
}
|
||||
|
@ -954,6 +954,7 @@ void sqlite3Insert(
|
||||
if( (db->flags & SQLITE_CountRows)!=0
|
||||
&& !pParse->nested
|
||||
&& !pParse->pTriggerTab
|
||||
&& !pParse->bReturning
|
||||
){
|
||||
regRowCount = ++pParse->nMem;
|
||||
sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
|
||||
@ -1318,7 +1319,7 @@ insert_end:
|
||||
** invoke the callback function.
|
||||
*/
|
||||
if( regRowCount ){
|
||||
sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
|
||||
sqlite3VdbeAddOp2(v, OP_ChngCntRow, regRowCount, 1);
|
||||
sqlite3VdbeSetNumCols(v, 1);
|
||||
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows inserted", SQLITE_STATIC);
|
||||
}
|
||||
|
26
src/parse.y
26
src/parse.y
@ -868,7 +868,7 @@ limit_opt(A) ::= LIMIT expr(X) COMMA expr(Y).
|
||||
/////////////////////////// The DELETE statement /////////////////////////////
|
||||
//
|
||||
%if SQLITE_ENABLE_UPDATE_DELETE_LIMIT || SQLITE_UDL_CAPABLE_PARSER
|
||||
cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt(W)
|
||||
cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt_ret(W)
|
||||
orderby_opt(O) limit_opt(L). {
|
||||
sqlite3SrcListIndexedBy(pParse, X, &I);
|
||||
#ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
|
||||
@ -881,7 +881,7 @@ cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt(W)
|
||||
sqlite3DeleteFrom(pParse,X,W,O,L);
|
||||
}
|
||||
%else
|
||||
cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt(W). {
|
||||
cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt_ret(W). {
|
||||
sqlite3SrcListIndexedBy(pParse, X, &I);
|
||||
sqlite3DeleteFrom(pParse,X,W,0,0);
|
||||
}
|
||||
@ -889,15 +889,23 @@ cmd ::= with DELETE FROM xfullname(X) indexed_opt(I) where_opt(W). {
|
||||
|
||||
%type where_opt {Expr*}
|
||||
%destructor where_opt {sqlite3ExprDelete(pParse->db, $$);}
|
||||
%type where_opt_ret {Expr*}
|
||||
%destructor where_opt_ret {sqlite3ExprDelete(pParse->db, $$);}
|
||||
|
||||
where_opt(A) ::= . {A = 0;}
|
||||
where_opt(A) ::= WHERE expr(X). {A = X;}
|
||||
where_opt_ret(A) ::= . {A = 0;}
|
||||
where_opt_ret(A) ::= WHERE expr(X). {A = X;}
|
||||
where_opt_ret(A) ::= RETURNING selcollist(X).
|
||||
{sqlite3AddReturning(pParse,X); A = 0;}
|
||||
where_opt_ret(A) ::= WHERE expr(X) RETURNING selcollist(Y).
|
||||
{sqlite3AddReturning(pParse,Y); A = X;}
|
||||
|
||||
////////////////////////// The UPDATE command ////////////////////////////////
|
||||
//
|
||||
%if SQLITE_ENABLE_UPDATE_DELETE_LIMIT || SQLITE_UDL_CAPABLE_PARSER
|
||||
cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from(F)
|
||||
where_opt(W) orderby_opt(O) limit_opt(L). {
|
||||
where_opt_ret(W) orderby_opt(O) limit_opt(L). {
|
||||
sqlite3SrcListIndexedBy(pParse, X, &I);
|
||||
X = sqlite3SrcListAppendList(pParse, X, F);
|
||||
sqlite3ExprListCheckLength(pParse,Y,"set list");
|
||||
@ -912,7 +920,7 @@ cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from(F)
|
||||
}
|
||||
%else
|
||||
cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from(F)
|
||||
where_opt(W). {
|
||||
where_opt_ret(W). {
|
||||
sqlite3SrcListIndexedBy(pParse, X, &I);
|
||||
sqlite3ExprListCheckLength(pParse,Y,"set list");
|
||||
X = sqlite3SrcListAppendList(pParse, X, F);
|
||||
@ -946,7 +954,7 @@ cmd ::= with insert_cmd(R) INTO xfullname(X) idlist_opt(F) select(S)
|
||||
upsert(U). {
|
||||
sqlite3Insert(pParse, X, S, F, R, U);
|
||||
}
|
||||
cmd ::= with insert_cmd(R) INTO xfullname(X) idlist_opt(F) DEFAULT VALUES.
|
||||
cmd ::= with insert_cmd(R) INTO xfullname(X) idlist_opt(F) DEFAULT VALUES returning.
|
||||
{
|
||||
sqlite3Insert(pParse, X, 0, F, R, 0);
|
||||
}
|
||||
@ -959,16 +967,20 @@ cmd ::= with insert_cmd(R) INTO xfullname(X) idlist_opt(F) DEFAULT VALUES.
|
||||
// avoid unreachable code.
|
||||
//%destructor upsert {sqlite3UpsertDelete(pParse->db,$$);}
|
||||
upsert(A) ::= . { A = 0; }
|
||||
upsert(A) ::= RETURNING selcollist(X). { A = 0; sqlite3AddReturning(pParse,X); }
|
||||
upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW)
|
||||
DO UPDATE SET setlist(Z) where_opt(W) upsert(N).
|
||||
{ A = sqlite3UpsertNew(pParse->db,T,TW,Z,W,N);}
|
||||
upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW) DO NOTHING upsert(N).
|
||||
{ A = sqlite3UpsertNew(pParse->db,T,TW,0,0,N); }
|
||||
upsert(A) ::= ON CONFLICT DO NOTHING.
|
||||
upsert(A) ::= ON CONFLICT DO NOTHING returning.
|
||||
{ A = sqlite3UpsertNew(pParse->db,0,0,0,0,0); }
|
||||
upsert(A) ::= ON CONFLICT DO UPDATE SET setlist(Z) where_opt(W).
|
||||
upsert(A) ::= ON CONFLICT DO UPDATE SET setlist(Z) where_opt(W) returning.
|
||||
{ A = sqlite3UpsertNew(pParse->db,0,0,Z,W,0);}
|
||||
|
||||
returning ::= RETURNING selcollist(X). {sqlite3AddReturning(pParse,X);}
|
||||
returning ::= .
|
||||
|
||||
%type insert_cmd {int}
|
||||
insert_cmd(A) ::= INSERT orconf(R). {A = R;}
|
||||
insert_cmd(A) ::= REPLACE. {A = OE_Replace;}
|
||||
|
@ -371,23 +371,26 @@ static int lookupName(
|
||||
** it is a new.* or old.* trigger argument reference. Or
|
||||
** maybe it is an excluded.* from an upsert.
|
||||
*/
|
||||
if( zDb==0 && zTab!=0 && cntTab==0 ){
|
||||
if( zDb==0 && cntTab==0 ){
|
||||
pTab = 0;
|
||||
#ifndef SQLITE_OMIT_TRIGGER
|
||||
if( pParse->pTriggerTab!=0 ){
|
||||
int op = pParse->eTriggerOp;
|
||||
assert( op==TK_DELETE || op==TK_UPDATE || op==TK_INSERT );
|
||||
if( op!=TK_DELETE && sqlite3StrICmp("new",zTab) == 0 ){
|
||||
if( op!=TK_DELETE && zTab && sqlite3StrICmp("new",zTab) == 0 ){
|
||||
pExpr->iTable = 1;
|
||||
pTab = pParse->pTriggerTab;
|
||||
}else if( op!=TK_INSERT && sqlite3StrICmp("old",zTab)==0 ){
|
||||
}else if( op!=TK_INSERT && zTab && sqlite3StrICmp("old",zTab)==0 ){
|
||||
pExpr->iTable = 0;
|
||||
pTab = pParse->pTriggerTab;
|
||||
}else if( pParse->bReturning ){
|
||||
pExpr->iTable = op!=TK_DELETE;
|
||||
pTab = pParse->pTriggerTab;
|
||||
}
|
||||
}
|
||||
#endif /* SQLITE_OMIT_TRIGGER */
|
||||
#ifndef SQLITE_OMIT_UPSERT
|
||||
if( (pNC->ncFlags & NC_UUpsert)!=0 ){
|
||||
if( (pNC->ncFlags & NC_UUpsert)!=0 && ALWAYS(zTab) ){
|
||||
Upsert *pUpsert = pNC->uNC.pUpsert;
|
||||
if( pUpsert && sqlite3StrICmp("excluded",zTab)==0 ){
|
||||
pTab = pUpsert->pUpsertSrc->a[0].pTab;
|
||||
|
@ -2115,7 +2115,13 @@ struct sqlite3_mem_methods {
|
||||
** The second parameter is a pointer to an integer into which
|
||||
** is written 0 or 1 to indicate whether triggers are disabled or enabled
|
||||
** following this call. The second parameter may be a NULL pointer, in
|
||||
** which case the trigger setting is not reported back. </dd>
|
||||
** which case the trigger setting is not reported back.
|
||||
**
|
||||
** <p>Originally this option disabled all triggers. ^(However, since
|
||||
** SQLite version 3.35.0, TEMP triggers are still allowed even if
|
||||
** this option is off. So, in other words, this option now only disables
|
||||
** triggers in the main database schema or in the schemas of ATTACH-ed
|
||||
** databases.)^ </dd>
|
||||
**
|
||||
** [[SQLITE_DBCONFIG_ENABLE_VIEW]]
|
||||
** <dt>SQLITE_DBCONFIG_ENABLE_VIEW</dt>
|
||||
|
@ -1159,6 +1159,7 @@ typedef struct ParseCleanup ParseCleanup;
|
||||
typedef struct PreUpdate PreUpdate;
|
||||
typedef struct PrintfArguments PrintfArguments;
|
||||
typedef struct RenameToken RenameToken;
|
||||
typedef struct Returning Returning;
|
||||
typedef struct RowSet RowSet;
|
||||
typedef struct Savepoint Savepoint;
|
||||
typedef struct Select Select;
|
||||
@ -3429,6 +3430,7 @@ struct Parse {
|
||||
u32 oldmask; /* Mask of old.* columns referenced */
|
||||
u32 newmask; /* Mask of new.* columns referenced */
|
||||
u8 eTriggerOp; /* TK_UPDATE, TK_INSERT or TK_DELETE */
|
||||
u8 bReturning; /* Coding a RETURNING trigger */
|
||||
u8 eOrconf; /* Default ON CONFLICT policy for trigger steps */
|
||||
u8 disableTriggers; /* True to disable triggers */
|
||||
|
||||
@ -3578,6 +3580,7 @@ struct 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 */
|
||||
u8 bReturning; /* This trigger implements a RETURNING clause */
|
||||
Expr *pWhen; /* The WHEN clause of the expression (may be NULL) */
|
||||
IdList *pColumns; /* If this is an UPDATE OF <column-list> trigger,
|
||||
the <column-list> is stored here */
|
||||
@ -3636,7 +3639,8 @@ struct Trigger {
|
||||
*
|
||||
*/
|
||||
struct TriggerStep {
|
||||
u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */
|
||||
u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT,
|
||||
** or TK_RETURNING */
|
||||
u8 orconf; /* OE_Rollback etc. */
|
||||
Trigger *pTrig; /* The trigger that this step is a part of */
|
||||
Select *pSelect; /* SELECT statement or RHS of INSERT INTO SELECT ... */
|
||||
@ -3651,6 +3655,18 @@ struct TriggerStep {
|
||||
TriggerStep *pLast; /* Last element in link-list. Valid for 1st elem only */
|
||||
};
|
||||
|
||||
/*
|
||||
** Information about a RETURNING clause
|
||||
*/
|
||||
struct Returning {
|
||||
Parse *pParse; /* The parse that includes the RETURNING clause */
|
||||
ExprList *pReturnEL; /* List of expressions to return */
|
||||
Trigger retTrig; /* The transient trigger that implements RETURNING */
|
||||
TriggerStep retTStep; /* The trigger step */
|
||||
Select retSel; /* The SELECT statement that implements RETURNING */
|
||||
u64 retSrcList; /* The empty FROM clause of the SELECT */
|
||||
};
|
||||
|
||||
/*
|
||||
** An objected used to accumulate the text of a string where we
|
||||
** do not necessarily know how big the string will be in the end.
|
||||
@ -4252,6 +4268,7 @@ void sqlite3AddDefaultValue(Parse*,Expr*,const char*,const char*);
|
||||
void sqlite3AddCollateType(Parse*, Token*);
|
||||
void sqlite3AddGenerated(Parse*,Expr*,Token*);
|
||||
void sqlite3EndTable(Parse*,Token*,Token*,u8,Select*);
|
||||
void sqlite3AddReturning(Parse*,ExprList*);
|
||||
int sqlite3ParseUri(const char*,const char*,unsigned int*,
|
||||
sqlite3_vfs**,char**,char **);
|
||||
#define sqlite3CodecQueryParameters(A,B,C) 0
|
||||
|
129
src/trigger.c
129
src/trigger.c
@ -69,6 +69,11 @@ Trigger *sqlite3TriggerList(Parse *pParse, Table *pTab){
|
||||
){
|
||||
pTrig->pNext = pList;
|
||||
pList = pTrig;
|
||||
}else if( pTrig->op==TK_RETURNING ){
|
||||
pTrig->table = pTab->zName;
|
||||
pTrig->pTabSchema = pTab->pSchema;
|
||||
pTrig->pNext = pList;
|
||||
pList = pTrig;
|
||||
}
|
||||
p = sqliteHashNext(p);
|
||||
}
|
||||
@ -562,7 +567,7 @@ TriggerStep *sqlite3TriggerDeleteStep(
|
||||
** Recursively delete a Trigger structure
|
||||
*/
|
||||
void sqlite3DeleteTrigger(sqlite3 *db, Trigger *pTrigger){
|
||||
if( pTrigger==0 ) return;
|
||||
if( pTrigger==0 || pTrigger->bReturning ) return;
|
||||
sqlite3DeleteTriggerStep(db, pTrigger->step_list);
|
||||
sqlite3DbFree(db, pTrigger->zName);
|
||||
sqlite3DbFree(db, pTrigger->table);
|
||||
@ -727,15 +732,48 @@ Trigger *sqlite3TriggersExist(
|
||||
Trigger *pList = 0;
|
||||
Trigger *p;
|
||||
|
||||
if( (pParse->db->flags & SQLITE_EnableTrigger)!=0 ){
|
||||
pList = sqlite3TriggerList(pParse, pTab);
|
||||
assert( pList==0 || IsVirtual(pTab)==0
|
||||
|| (pList->bReturning && pList->pNext==0) );
|
||||
if( pList!=0 ){
|
||||
p = pList;
|
||||
if( (pParse->db->flags & SQLITE_EnableTrigger)==0
|
||||
&& pTab->pTrigger!=0
|
||||
){
|
||||
/* The SQLITE_DBCONFIG_ENABLE_TRIGGER setting is off. That means that
|
||||
** only TEMP triggers are allowed. Truncate the pList so that it
|
||||
** includes only TEMP triggers */
|
||||
if( pList==pTab->pTrigger ){
|
||||
pList = 0;
|
||||
goto exit_triggers_exist;
|
||||
}
|
||||
assert( pList==0 || IsVirtual(pTab)==0 );
|
||||
for(p=pList; p; p=p->pNext){
|
||||
while( ALWAYS(p->pNext) && p->pNext!=pTab->pTrigger ) p = p->pNext;
|
||||
p->pNext = 0;
|
||||
p = pList;
|
||||
}
|
||||
do{
|
||||
if( p->op==op && checkColumnOverlap(p->pColumns, pChanges) ){
|
||||
mask |= p->tr_tm;
|
||||
}else if( p->op==TK_RETURNING ){
|
||||
/* The first time a RETURNING trigger is seen, the "op" value tells
|
||||
** us what time of trigger it should be. */
|
||||
assert( sqlite3IsToplevel(pParse) );
|
||||
p->op = op;
|
||||
mask |= TRIGGER_AFTER;
|
||||
if( IsVirtual(pTab) && op!=TK_INSERT ){
|
||||
sqlite3ErrorMsg(pParse,
|
||||
"%s RETURNING is not available on virtual tables",
|
||||
op==TK_DELETE ? "DELETE" : "UPDATE");
|
||||
}
|
||||
}else if( p->bReturning && p->op==TK_INSERT && op==TK_UPDATE
|
||||
&& sqlite3IsToplevel(pParse) ){
|
||||
/* Also fire a RETURNING trigger for an UPSERT */
|
||||
mask |= TRIGGER_AFTER;
|
||||
}
|
||||
p = p->pNext;
|
||||
}while( p );
|
||||
}
|
||||
exit_triggers_exist:
|
||||
if( pMask ){
|
||||
*pMask = mask;
|
||||
}
|
||||
@ -778,6 +816,47 @@ SrcList *sqlite3TriggerStepSrc(
|
||||
return pSrc;
|
||||
}
|
||||
|
||||
/* The input list pList is the list of result set terms from a RETURNING
|
||||
** clause. The table that we are returning from is pTab.
|
||||
**
|
||||
** This routine makes a copy of the pList, and at the same time expands
|
||||
** any "*" wildcards to be the complete set of columns from pTab.
|
||||
*/
|
||||
static ExprList *sqlite3ExpandReturning(
|
||||
Parse *pParse, /* Parsing context */
|
||||
ExprList *pList, /* The arguments to RETURNING */
|
||||
Table *pTab /* The table being updated */
|
||||
){
|
||||
ExprList *pNew = 0;
|
||||
sqlite3 *db = pParse->db;
|
||||
int i;
|
||||
for(i=0; i<pList->nExpr; i++){
|
||||
Expr *pOldExpr = pList->a[i].pExpr;
|
||||
if( ALWAYS(pOldExpr!=0) && pOldExpr->op==TK_ASTERISK ){
|
||||
int jj;
|
||||
for(jj=0; jj<pTab->nCol; jj++){
|
||||
if( IsHiddenColumn(pTab->aCol+jj) ) continue;
|
||||
Expr *pNewExpr = sqlite3Expr(db, TK_ID, pTab->aCol[jj].zName);
|
||||
pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr);
|
||||
if( !db->mallocFailed ){
|
||||
struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1];
|
||||
pItem->zEName = sqlite3DbStrDup(db, pTab->aCol[jj].zName);
|
||||
pItem->eEName = ENAME_NAME;
|
||||
}
|
||||
}
|
||||
}else{
|
||||
Expr *pNewExpr = sqlite3ExprDup(db, pOldExpr, 0);
|
||||
pNew = sqlite3ExprListAppend(pParse, pNew, pNewExpr);
|
||||
if( !db->mallocFailed && ALWAYS(pList->a[i].zEName!=0) ){
|
||||
struct ExprList_item *pItem = &pNew->a[pNew->nExpr-1];
|
||||
pItem->zEName = sqlite3DbStrDup(db, pList->a[i].zEName);
|
||||
pItem->eEName = pList->a[i].eEName;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pNew;
|
||||
}
|
||||
|
||||
/*
|
||||
** Generate VDBE code for the statements inside the body of a single
|
||||
** trigger.
|
||||
@ -827,6 +906,7 @@ static int codeTriggerProgram(
|
||||
sqlite3ExprDup(db, pStep->pWhere, 0),
|
||||
pParse->eOrconf, 0, 0, 0
|
||||
);
|
||||
sqlite3VdbeAddOp0(v, OP_ResetCount);
|
||||
break;
|
||||
}
|
||||
case TK_INSERT: {
|
||||
@ -837,6 +917,7 @@ static int codeTriggerProgram(
|
||||
pParse->eOrconf,
|
||||
sqlite3UpsertDup(db, pStep->pUpsert)
|
||||
);
|
||||
sqlite3VdbeAddOp0(v, OP_ResetCount);
|
||||
break;
|
||||
}
|
||||
case TK_DELETE: {
|
||||
@ -844,9 +925,10 @@ static int codeTriggerProgram(
|
||||
sqlite3TriggerStepSrc(pParse, pStep),
|
||||
sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0
|
||||
);
|
||||
sqlite3VdbeAddOp0(v, OP_ResetCount);
|
||||
break;
|
||||
}
|
||||
default: assert( pStep->op==TK_SELECT ); {
|
||||
case TK_SELECT: {
|
||||
SelectDest sDest;
|
||||
Select *pSelect = sqlite3SelectDup(db, pStep->pSelect, 0);
|
||||
sqlite3SelectDestInit(&sDest, SRT_Discard, 0);
|
||||
@ -854,9 +936,26 @@ static int codeTriggerProgram(
|
||||
sqlite3SelectDelete(db, pSelect);
|
||||
break;
|
||||
}
|
||||
default: assert( pStep->op==TK_RETURNING ); {
|
||||
Select *pSelect = pStep->pSelect;
|
||||
ExprList *pList = pSelect->pEList;
|
||||
SelectDest sDest;
|
||||
Select *pNew;
|
||||
pSelect->pEList =
|
||||
sqlite3ExpandReturning(pParse, pList, pParse->pTriggerTab);
|
||||
sqlite3SelectDestInit(&sDest, SRT_Output, 0);
|
||||
pNew = sqlite3SelectDup(db, pSelect, 0);
|
||||
if( pNew ){
|
||||
sqlite3Select(pParse, pNew, &sDest);
|
||||
if( pNew->selFlags & (SF_Aggregate|SF_HasAgg|SF_WinRewrite) ){
|
||||
sqlite3ErrorMsg(pParse, "aggregates not allowed in RETURNING");
|
||||
}
|
||||
sqlite3SelectDelete(db, pNew);
|
||||
}
|
||||
sqlite3ExprListDelete(db, pSelect->pEList);
|
||||
pStep->pSelect->pEList = pList;
|
||||
break;
|
||||
}
|
||||
if( pStep->op!=TK_SELECT ){
|
||||
sqlite3VdbeAddOp0(v, OP_ResetCount);
|
||||
}
|
||||
}
|
||||
|
||||
@ -947,6 +1046,7 @@ static TriggerPrg *codeRowTrigger(
|
||||
pSubParse->pToplevel = pTop;
|
||||
pSubParse->zAuthContext = pTrigger->zName;
|
||||
pSubParse->eTriggerOp = pTrigger->op;
|
||||
pSubParse->bReturning = pTrigger->bReturning;
|
||||
pSubParse->nQueryLoop = pParse->nQueryLoop;
|
||||
pSubParse->disableVtab = pParse->disableVtab;
|
||||
|
||||
@ -996,6 +1096,9 @@ static TriggerPrg *codeRowTrigger(
|
||||
if( db->mallocFailed==0 && pParse->nErr==0 ){
|
||||
pProgram->aOp = sqlite3VdbeTakeOpArray(v, &pProgram->nOp, &pTop->nMaxArg);
|
||||
}
|
||||
if( pTrigger->bReturning ){
|
||||
sqlite3VdbeColumnInfoXfer(sqlite3ParseToplevel(pParse)->pVdbe, v);
|
||||
}
|
||||
pProgram->nMem = pSubParse->nMem;
|
||||
pProgram->nCsr = pSubParse->nTab;
|
||||
pProgram->token = (void *)pTrigger;
|
||||
@ -1150,12 +1253,20 @@ void sqlite3CodeRowTrigger(
|
||||
assert( p->pSchema==p->pTabSchema
|
||||
|| p->pSchema==pParse->db->aDb[1].pSchema );
|
||||
|
||||
/* Determine whether we should code this trigger */
|
||||
if( p->op==op
|
||||
/* Determine whether we should code this trigger. One of two choices:
|
||||
** 1. The trigger is an exact match to the current DML statement
|
||||
** 2. This is a RETURNING trigger for INSERT but we are currently
|
||||
** doing the UPDATE part of an UPSERT.
|
||||
*/
|
||||
if( (p->op==op || (p->bReturning && p->op==TK_INSERT && op==TK_UPDATE))
|
||||
&& p->tr_tm==tr_tm
|
||||
&& checkColumnOverlap(p->pColumns, pChanges)
|
||||
&& (sqlite3IsToplevel(pParse) || !p->bReturning)
|
||||
){
|
||||
u8 origOp = p->op;
|
||||
p->op = op;
|
||||
sqlite3CodeRowTriggerDirect(pParse, p, pTab, reg, orconf, ignoreJump);
|
||||
p->op = origOp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -643,6 +643,7 @@ void sqlite3Update(
|
||||
if( (db->flags&SQLITE_CountRows)!=0
|
||||
&& !pParse->pTriggerTab
|
||||
&& !pParse->nested
|
||||
&& !pParse->bReturning
|
||||
&& pUpsert==0
|
||||
){
|
||||
regRowCount = ++pParse->nMem;
|
||||
@ -1106,7 +1107,7 @@ void sqlite3Update(
|
||||
** that information.
|
||||
*/
|
||||
if( regRowCount ){
|
||||
sqlite3VdbeAddOp2(v, OP_ResultRow, regRowCount, 1);
|
||||
sqlite3VdbeAddOp2(v, OP_ChngCntRow, regRowCount, 1);
|
||||
sqlite3VdbeSetNumCols(v, 1);
|
||||
sqlite3VdbeSetColName(v, 0, COLNAME_NAME, "rows updated", SQLITE_STATIC);
|
||||
}
|
||||
|
48
src/vdbe.c
48
src/vdbe.c
@ -1445,6 +1445,26 @@ case OP_IntCopy: { /* out2 */
|
||||
break;
|
||||
}
|
||||
|
||||
/* Opcode: ChngCntRow P1 P2 * * *
|
||||
** Synopsis: output=r[P1]
|
||||
**
|
||||
** Output value in register P1 as the chance count for a DML statement,
|
||||
** due to the "PRAGMA count_changes=ON" setting. Or, if there was a
|
||||
** foreign key error in the statement, trigger the error now.
|
||||
**
|
||||
** This opcode is a variant of OP_ResultRow that checks the foreign key
|
||||
** immediate constraint count and throws an error if the count is
|
||||
** non-zero. The P2 opcode must be 1.
|
||||
*/
|
||||
case OP_ChngCntRow: {
|
||||
assert( pOp->p2==1 );
|
||||
if( (rc = sqlite3VdbeCheckFk(p,0))!=SQLITE_OK ){
|
||||
goto abort_due_to_error;
|
||||
}
|
||||
/* Fall through to the next case, OP_String */
|
||||
/* no break */ deliberate_fall_through
|
||||
}
|
||||
|
||||
/* Opcode: ResultRow P1 P2 * * *
|
||||
** Synopsis: output=r[P1@P2]
|
||||
**
|
||||
@ -1461,34 +1481,6 @@ case OP_ResultRow: {
|
||||
assert( pOp->p1>0 );
|
||||
assert( pOp->p1+pOp->p2<=(p->nMem+1 - p->nCursor)+1 );
|
||||
|
||||
/* If this statement has violated immediate foreign key constraints, do
|
||||
** not return the number of rows modified. And do not RELEASE the statement
|
||||
** transaction. It needs to be rolled back. */
|
||||
if( SQLITE_OK!=(rc = sqlite3VdbeCheckFk(p, 0)) ){
|
||||
assert( db->flags&SQLITE_CountRows );
|
||||
assert( p->usesStmtJournal );
|
||||
goto abort_due_to_error;
|
||||
}
|
||||
|
||||
/* If the SQLITE_CountRows flag is set in sqlite3.flags mask, then
|
||||
** DML statements invoke this opcode to return the number of rows
|
||||
** modified to the user. This is the only way that a VM that
|
||||
** opens a statement transaction may invoke this opcode.
|
||||
**
|
||||
** In case this is such a statement, close any statement transaction
|
||||
** opened by this VM before returning control to the user. This is to
|
||||
** ensure that statement-transactions are always nested, not overlapping.
|
||||
** If the open statement-transaction is not closed here, then the user
|
||||
** may step another VM that opens its own statement transaction. This
|
||||
** may lead to overlapping statement transactions.
|
||||
**
|
||||
** The statement transaction is never a top-level transaction. Hence
|
||||
** the RELEASE call below can never fail.
|
||||
*/
|
||||
assert( p->iStatement==0 || db->flags&SQLITE_CountRows );
|
||||
rc = sqlite3VdbeCloseStatement(p, SAVEPOINT_RELEASE);
|
||||
assert( rc==SQLITE_OK );
|
||||
|
||||
/* Invalidate all ephemeral cursor row caches */
|
||||
p->cacheCtr = (p->cacheCtr + 2)|1;
|
||||
|
||||
|
@ -259,6 +259,7 @@ void sqlite3VdbeResetStepResult(Vdbe*);
|
||||
void sqlite3VdbeRewind(Vdbe*);
|
||||
int sqlite3VdbeReset(Vdbe*);
|
||||
void sqlite3VdbeSetNumCols(Vdbe*,int);
|
||||
void sqlite3VdbeColumnInfoXfer(Vdbe*,Vdbe*);
|
||||
int sqlite3VdbeSetColName(Vdbe*, int, int, const char *, void(*)(void*));
|
||||
void sqlite3VdbeCountChanges(Vdbe*);
|
||||
sqlite3 *sqlite3VdbeDb(Vdbe*);
|
||||
|
@ -2595,6 +2595,23 @@ void sqlite3VdbeSetNumCols(Vdbe *p, int nResColumn){
|
||||
initMemArray(p->aColName, n, db, MEM_Null);
|
||||
}
|
||||
|
||||
/*
|
||||
** Transfer the column count and name information from one Vdbe to
|
||||
** another.
|
||||
*/
|
||||
void sqlite3VdbeColumnInfoXfer(Vdbe *pTo, Vdbe *pFrom){
|
||||
sqlite3 *db = pTo->db;
|
||||
assert( db==pFrom->db );
|
||||
if( pTo->nResColumn ){
|
||||
releaseMemArray(pTo->aColName, pTo->nResColumn*COLNAME_N);
|
||||
sqlite3DbFree(db, pTo->aColName);
|
||||
}
|
||||
pTo->aColName = pFrom->aColName;
|
||||
pFrom->aColName = 0;
|
||||
pTo->nResColumn = pFrom->nResColumn;
|
||||
pFrom->nResColumn = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
** Set the name of the idx'th column to be returned by the SQL statement.
|
||||
** zName must be a pointer to a nul terminated string.
|
||||
|
87
test/returning1.test
Normal file
87
test/returning1.test
Normal file
@ -0,0 +1,87 @@
|
||||
# 2021-01-28
|
||||
#
|
||||
# The author disclaims copyright to this source code. In place of
|
||||
# a legal notice, here is a blessing:
|
||||
#
|
||||
# May you do good and not evil.
|
||||
# May you find forgiveness for yourself and forgive others.
|
||||
# May you share freely, never taking more than you give.
|
||||
#
|
||||
#***********************************************************************
|
||||
# This file implements regression tests for SQLite library. The
|
||||
# focus of this file is the new RETURNING clause
|
||||
#
|
||||
|
||||
set testdir [file dirname $argv0]
|
||||
source $testdir/tester.tcl
|
||||
set testprefix returning1
|
||||
|
||||
do_execsql_test 1.0 {
|
||||
CREATE TABLE t1(a INTEGER PRIMARY KEY,b,c DEFAULT 'pax');
|
||||
INSERT INTO t1(b) VALUES(10),('happy'),(NULL) RETURNING a,b,c;
|
||||
} {1 10 pax 2 happy pax 3 {} pax}
|
||||
do_execsql_test 1.1 {
|
||||
SELECT * FROM t1;
|
||||
} {1 10 pax 2 happy pax 3 {} pax}
|
||||
do_execsql_test 1.2 {
|
||||
INSERT INTO t1(b,c) VALUES(5,99) RETURNING b,c,a,rowid;
|
||||
} {5 99 4 4}
|
||||
do_execsql_test 1.3 {
|
||||
SELECT * FROM t1;
|
||||
} {1 10 pax 2 happy pax 3 {} pax 4 5 99}
|
||||
do_execsql_test 1.4 {
|
||||
INSERT INTO t1 DEFAULT VALUES RETURNING *;
|
||||
} {5 {} pax}
|
||||
do_execsql_test 1.5 {
|
||||
SELECT * FROM t1;
|
||||
} {1 10 pax 2 happy pax 3 {} pax 4 5 99 5 {} pax}
|
||||
do_execsql_test 1.6 {
|
||||
CREATE TABLE t2(x,y,z);
|
||||
INSERT INTO t2 VALUES(11,12,13),(21,'b','c'),(31,'b-value',4.75);
|
||||
}
|
||||
do_execsql_test 1.7 {
|
||||
INSERT INTO t1 SELECT * FROM t2 RETURNING *;
|
||||
} {11 12 13 21 b c 31 b-value 4.75}
|
||||
do_execsql_test 1.8 {
|
||||
SELECT *, '|' FROM t1;
|
||||
} {1 10 pax | 2 happy pax | 3 {} pax | 4 5 99 | 5 {} pax | 11 12 13 | 21 b c | 31 b-value 4.75 |}
|
||||
|
||||
do_execsql_test 2.1 {
|
||||
UPDATE t1 SET c='bellum' WHERE c='pax' RETURNING rowid, b, '|';
|
||||
} {1 10 | 2 happy | 3 {} | 5 {} |}
|
||||
do_execsql_test 2.2 {
|
||||
SELECT *, '|' FROM t1;
|
||||
} {1 10 bellum | 2 happy bellum | 3 {} bellum | 4 5 99 | 5 {} bellum | 11 12 13 | 21 b c | 31 b-value 4.75 |}
|
||||
|
||||
do_execsql_test 3.1 {
|
||||
DELETE FROM t1 WHERE c='bellum' RETURNING rowid, *, '|';
|
||||
} {1 1 10 bellum | 2 2 happy bellum | 3 3 {} bellum | 5 5 {} bellum |}
|
||||
do_execsql_test 3.2 {
|
||||
SELECT *, '|' FROM t1;
|
||||
} {4 5 99 | 11 12 13 | 21 b c | 31 b-value 4.75 |}
|
||||
|
||||
do_execsql_test 4.1 {
|
||||
CREATE TABLE t4(a INT, b INT DEFAULT 1234, c INT DEFAULT -16);
|
||||
CREATE UNIQUE INDEX t4a ON t4(a);
|
||||
INSERT INTO t4(a,b,c) VALUES(1,2,3);
|
||||
} {}
|
||||
do_execsql_test 4.2 {
|
||||
INSERT INTO t4(a,b,c) VALUES(1,22,33)
|
||||
ON CONFLICT(a) DO UPDATE SET b=44
|
||||
RETURNING *;
|
||||
} {1 44 3}
|
||||
do_execsql_test 4.3 {
|
||||
SELECT * FROM t4;
|
||||
} {1 44 3}
|
||||
do_execsql_test 4.4 {
|
||||
DELETE FROM t4;
|
||||
INSERT INTO t4 VALUES(1,2,3),(4,5,6),(7,8,9);
|
||||
} {}
|
||||
do_execsql_test 4.5 {
|
||||
INSERT INTO t4(a,b,c) VALUES(2,3,4),(4,5,6),(5,6,7)
|
||||
ON CONFLICT(a) DO UPDATE SET b=100
|
||||
RETURNING *, '|';
|
||||
} {2 3 4 | 4 100 6 | 5 6 7 |}
|
||||
|
||||
|
||||
finish_test
|
@ -94,21 +94,31 @@ ifcapable {update_delete_limit} {
|
||||
execsql {DELETE FROM t1 ORDER BY x LIMIT 5}
|
||||
execsql {SELECT count(*) FROM t1}
|
||||
} {15}
|
||||
create_test_data 4
|
||||
do_test wherelimit-1.3b {
|
||||
# limit 5
|
||||
execsql {DELETE FROM t1 RETURNING x, y, '|' ORDER BY x, y LIMIT 5}
|
||||
} {1 1 | 1 2 | 1 3 | 1 4 | 2 1 |}
|
||||
do_test wherelimit-1.3c {
|
||||
execsql {SELECT count(*) FROM t1}
|
||||
} {11}
|
||||
do_test wherelimit-1.4 {
|
||||
# limit 5, offset 2
|
||||
execsql {DELETE FROM t1 ORDER BY x LIMIT 5 OFFSET 2}
|
||||
execsql {DELETE FROM t1 RETURNING x, y, '|' ORDER BY x LIMIT 5 OFFSET 2}
|
||||
} {2 4 | 3 1 | 3 2 | 3 3 | 3 4 |}
|
||||
do_test wherelimit-1.4cnt {
|
||||
execsql {SELECT count(*) FROM t1}
|
||||
} {10}
|
||||
} {6}
|
||||
do_test wherelimit-1.5 {
|
||||
# limit 5, offset -2
|
||||
execsql {DELETE FROM t1 ORDER BY x LIMIT 5 OFFSET -2}
|
||||
execsql {SELECT count(*) FROM t1}
|
||||
} {5}
|
||||
} {1}
|
||||
do_test wherelimit-1.6 {
|
||||
# limit -5 (no limit), offset 2
|
||||
execsql {DELETE FROM t1 ORDER BY x LIMIT 2, -5}
|
||||
execsql {SELECT count(*) FROM t1}
|
||||
} {2}
|
||||
} {1}
|
||||
do_test wherelimit-1.7 {
|
||||
# limit 5, offset -2 (no offset)
|
||||
execsql {DELETE FROM t1 ORDER BY x LIMIT -2, 5}
|
||||
@ -227,7 +237,9 @@ ifcapable {update_delete_limit} {
|
||||
} {11}
|
||||
create_test_data 6
|
||||
do_test wherelimit-3.2 {
|
||||
execsql {UPDATE t1 SET y=1 WHERE x=1 LIMIT 5}
|
||||
execsql {UPDATE t1 SET y=1 WHERE x=1 RETURNING x, old.y, '|' LIMIT 5}
|
||||
} {1 1 | 1 2 | 1 3 | 1 4 | 1 5 |}
|
||||
do_test wherelimit-3.2cnt {
|
||||
execsql {SELECT count(*) FROM t1 WHERE y=1}
|
||||
} {10}
|
||||
do_test wherelimit-3.3 {
|
||||
|
@ -159,6 +159,12 @@ struct Keyword {
|
||||
#else
|
||||
# define GENCOL 0x00200000
|
||||
#endif
|
||||
#ifdef SQLITE_OMIT_RETURNING
|
||||
# define RETURNING 0
|
||||
#else
|
||||
# define RETURNING 0x00400000
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
** These are the keywords
|
||||
@ -280,6 +286,7 @@ static Keyword aKeywordTable[] = {
|
||||
{ "RENAME", "TK_RENAME", ALTER, 1 },
|
||||
{ "REPLACE", "TK_REPLACE", CONFLICT, 10 },
|
||||
{ "RESTRICT", "TK_RESTRICT", FKEY, 1 },
|
||||
{ "RETURNING", "TK_RETURNING", RETURNING, 10 },
|
||||
{ "RIGHT", "TK_JOIN_KW", ALWAYS, 0 },
|
||||
{ "ROLLBACK", "TK_ROLLBACK", ALWAYS, 1 },
|
||||
{ "ROW", "TK_ROW", TRIGGER, 1 },
|
||||
|
Reference in New Issue
Block a user