1
0
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:
drh
2021-02-03 13:08:09 +00:00
18 changed files with 428 additions and 89 deletions

View File

@ -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

View File

@ -1 +1 @@
06b15b17be38c804dd2641d8616a2a7bd396d2eb9901a0fbf94edd8bd508cf9c
416c898bfb8ff9639ffbaefcfb47fce3782763af1fc67969fa91c5f01a336676

View File

@ -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.

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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;}

View File

@ -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;

View File

@ -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>

View File

@ -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

View File

@ -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 );
for(p=pList; p; p=p->pNext){
if( p->op==op && checkColumnOverlap(p->pColumns, pChanges) ){
mask |= p->tr_tm;
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;
}
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;
}
}
if( pStep->op!=TK_SELECT ){
sqlite3VdbeAddOp0(v, OP_ResetCount);
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;
}
}
}
@ -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;
}
}
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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*);

View File

@ -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
View 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

View File

@ -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 {

View File

@ -155,10 +155,16 @@ struct Keyword {
# define WINDOWFUNC 0x00100000
#endif
#ifdef SQLITE_OMIT_GENERATED_COLUMNS
# define GENCOL 0
# define GENCOL 0
#else
# define GENCOL 0x00200000
# 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 },