From: dan Date: Wed, 29 Apr 2020 20:11:01 +0000 (+0000) Subject: Fix problems with using LIMIT and FROM clauses as part of single UPDATE statement. X-Git-Tag: version-3.33.0~53^2~9^2~11 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=f2972b6083aef8bd57c2e1ff60d357c5b1ff7f88;p=thirdparty%2Fsqlite.git Fix problems with using LIMIT and FROM clauses as part of single UPDATE statement. FossilOrigin-Name: b717dc3c5fafb9b86a141e7ecffc030fd9b36aa57a0b3e5200d64ad23a0aa13d --- diff --git a/manifest b/manifest index f4a1a2e9dc..e0eba4c3f8 100644 --- a/manifest +++ b/manifest @@ -1,5 +1,5 @@ -C Fix\svarious\sbugs\sin\snew\sfeature\son\sthis\sbranch. -D 2020-04-29T17:41:29.116 +C Fix\sproblems\swith\susing\sLIMIT\sand\sFROM\sclauses\sas\spart\sof\ssingle\sUPDATE\sstatement. +D 2020-04-29T20:11:01.860 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 @@ -530,14 +530,14 @@ F src/pragma.h 9473160d220416456b40f27323bb4b316d4e4e08ffbf8bf88c5f7045d49c38e5 F src/prepare.c 8d4d6c8aa6afefc48027c54b41cdf134b4d6bc2fc4badbe483ad7fd9e1728a28 F src/printf.c 9be6945837c839ba57837b4bc3af349eba630920fa5532aa518816defe42a7d4 F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384 -F src/resolve.c d36a2b1639e1c33d7b508abfd3452a63e7fd81737f6f3940bfef085fca6f21f4 +F src/resolve.c 05471a183504f72aedf249851d94ad6feeac4d02be044fefdb4b42a8a6e13e42 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92 -F src/select.c 88ffd4e1a2b6bf221e3aaad026885485c7f2fe9291201f5e7d0656a3603b6bbe +F src/select.c 51882f7b24503163414ddcdc33705ea4f74707da70d7d0545e52aa8dff9f8983 F src/shell.c.in 1fc834b80c72dd37587ea87a4f4167cf5e6d98d12d143184ed2e732f529c0950 F src/sqlite.h.in fd6fcfe173accab8d9cb9a843856d9e9fb475f893b60a455e01d8739b5076f0e F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3ext.h 2d1af80082edffd71c6f96f70ad1ce6a4fb46615ad10291fc77fe0dea9ff0197 -F src/sqliteInt.h 3caabf4700e4d98bf6d4d3d60fab71c808d43ca9a98ee9674342f99d1a96d382 +F src/sqliteInt.h cea0d39df23798779db3cd0a2aa9cecc769ba9b13f58d5958e4a9e9325fa1b58 F src/sqliteLimit.h 95cb8479ca459496d9c1c6a9f76b38aee12203a56ce1092fe13e50ae2454c032 F src/status.c 9ff2210207c6c3b4d9631a8241a7d45ab1b26a0e9c84cb07a9b5ce2de9a3b278 F src/table.c b46ad567748f24a326d9de40e5b9659f96ffff34 @@ -599,7 +599,7 @@ F src/threads.c 4ae07fa022a3dc7c5beb373cf744a85d3c5c6c3c F src/tokenize.c eee7bae3ec0bc4abee951554bf46a8ba567c0f7752ac90c820ed8afff4c612dc F src/treeview.c 82c6391a3ba76215d4185fd4719a56ec4caf186a40c8a7b6e6ba4ae4467c2742 F src/trigger.c 4ada1037cc99777f647a882cdacbd1a4deb6567b69daf02946286401b88cdc04 -F src/update.c 3dda0590ff4a87f29828cd21840f11e6ddc853061a94f4aebc2af8f37d0ba92c +F src/update.c 15c1e9b6191be56d0752235f38e8c4e0eddf7b8a26e31992631a848cf3a87517 F src/upsert.c 2920de71b20f04fe25eb00b655d086f0ba60ea133c59d7fa3325c49838818e78 F src/utf.c 95fb6e03a5ca679045c5adccd05380f0addccabef5911abddcb06af069500ab7 F src/util.c 3b6cedf7a0c69bd6e1acce832873952d416212d6293b18d03064e07d7a9b5118 @@ -1618,7 +1618,7 @@ F test/update.test e906ca7cb1dc6f52af1ea243e08f727edfa79f924c2691f2f9e72481f8473 F test/update2.test 67455bc61fcbcf96923c45b3bc4f87bc72be7d67575ad35f134906148c7b06d3 F test/upfrom1.tcl 62efddee869b3a6f3e076b2816793fec9422e38d10ea42b63da733cdd2b1ad8e F test/upfrom1.test 543389b4eef43c7a21079df018cf95e29d7c2a4efd09b2597e54a03bbdbd30b9 -F test/upfrom2.test aba21e9988809b05de336bc882ad7318a0ca275c735c9a9c6688ebc01053ce29 +F test/upfrom2.test 1c0f8d3b963304f0feb04ca172484942a15ffca975cd25463fdfd4614d1245d0 F test/upsert1.test 88f9e258c6a0eeeb85937b08831e8daad440ba41f125af48439e9d33f266fb18 F test/upsert2.test 9c3cdbb1a890227f6504ce4b0e3de68f4cdfa16bb21d8641208a9239896c5a09 F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c @@ -1865,7 +1865,7 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 -P f353a1a613bb7ad8cedcda377a7fe6fd05ee03b1f50665b00b84a868a71c5bec -R ff3249230b7ba7b12347209dea6f6d16 +P 823ba94e29dece1687e28711e503a1f56d392c306b0cbc0a20548180834530d1 +R c7060e5765cacf17cc72ccb6e64f7f11 U dan -Z 37eb2f9d556f45297467e2642dd5f97e +Z 2cbbaf692ee6084a41961733ac9445a3 diff --git a/manifest.uuid b/manifest.uuid index 8f74ee97ef..d8889b7ff8 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -823ba94e29dece1687e28711e503a1f56d392c306b0cbc0a20548180834530d1 \ No newline at end of file +b717dc3c5fafb9b86a141e7ecffc030fd9b36aa57a0b3e5200d64ad23a0aa13d \ No newline at end of file diff --git a/src/resolve.c b/src/resolve.c index 60fed0b109..76cdc8ccd7 100644 --- a/src/resolve.c +++ b/src/resolve.c @@ -756,7 +756,7 @@ static int resolveExprStep(Walker *pWalker, Expr *pExpr){ case TK_ROW: { SrcList *pSrcList = pNC->pSrcList; struct SrcList_item *pItem; - assert( pSrcList && pSrcList->nSrc==1 ); + assert( pSrcList && pSrcList->nSrc>=1 ); pItem = pSrcList->a; assert( HasRowid(pItem->pTab) && pItem->pTab->pSelect==0 ); pExpr->op = TK_COLUMN; diff --git a/src/select.c b/src/select.c index fdffe91634..757fa3265a 100644 --- a/src/select.c +++ b/src/select.c @@ -1006,7 +1006,8 @@ static void selectInnerLoop( testcase( eDest==SRT_Coroutine ); testcase( eDest==SRT_Output ); assert( eDest==SRT_Set || eDest==SRT_Mem - || eDest==SRT_Coroutine || eDest==SRT_Output ); + || eDest==SRT_Coroutine || eDest==SRT_Output + || eDest==SRT_Upfrom ); } sRowLoadInfo.regResult = regResult; sRowLoadInfo.ecelFlags = ecelFlags; @@ -1156,14 +1157,18 @@ static void selectInnerLoop( } case SRT_Upfrom: { - assert( pSort==0 ); - int i2 = pDest->iSDParm2; - int r1 = sqlite3GetTempReg(pParse); - sqlite3VdbeAddOp3(v, OP_MakeRecord,regResult+(i2<0),nResultCol-(i2<0),r1); - if( i2<0 ){ - sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, regResult); + if( pSort ){ + pushOntoSorter( + pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg); }else{ - sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, i2); + int i2 = pDest->iSDParm2; + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_MakeRecord,regResult+(i2<0),nResultCol-(i2<0),r1); + if( i2<0 ){ + sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, regResult); + }else{ + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, i2); + } } break; } @@ -1600,6 +1605,17 @@ static void generateSortTail( break; } #endif + case SRT_Upfrom: { + int i2 = pDest->iSDParm2; + int r1 = sqlite3GetTempReg(pParse); + sqlite3VdbeAddOp3(v, OP_MakeRecord,regRow+(i2<0),nColumn-(i2<0),r1); + if( i2<0 ){ + sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, regRow); + }else{ + sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regRow, i2); + } + break; + } default: { assert( eDest==SRT_Output || eDest==SRT_Coroutine ); testcase( eDest==SRT_Output ); diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 34d6fea7ec..c668e6bee3 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -3187,8 +3187,7 @@ struct Select { #define SRT_EphemTab 12 /* Create transient tab and store like SRT_Table */ #define SRT_Coroutine 13 /* Generate a single row of result */ #define SRT_Table 14 /* Store result as data with an automatic rowid */ -#define SRT_ISet 15 /* Store result as data with rowid */ -#define SRT_Upfrom 16 /* Store result as data with rowid */ +#define SRT_Upfrom 15 /* Store result as data with rowid */ /* ** An instance of this object describes where to put of the results of diff --git a/src/update.c b/src/update.c index 80f07d3de4..5d4e5ae7db 100644 --- a/src/update.c +++ b/src/update.c @@ -164,7 +164,9 @@ static void updatePopulateEphTable( Index *pPk, /* PK if table 0 is WITHOUT ROWID */ ExprList *pChanges, /* List of expressions to return */ SrcList *pTabList, /* List of tables to select from */ - Expr *pWhere /* WHERE clause for query */ + Expr *pWhere, /* WHERE clause for query */ + ExprList *pOrderBy, + Expr *pLimit ){ int i; sqlite3 *db = pParse->db; @@ -174,6 +176,9 @@ static void updatePopulateEphTable( Table *pTab = pTabList->a[0].pTab; SrcList *pSrc = sqlite3SrcListDup(db, pTabList, 0); Expr *pWhere2 = sqlite3ExprDup(db, pWhere, 0); + Expr *pLimit2 = sqlite3ExprDup(db, pLimit, 0); + ExprList *pOrderBy2 = sqlite3ExprListDup(db, pOrderBy, 0); + ExprList *pGroupBy = 0; int eDest; assert( pTabList->nSrc>1 ); @@ -184,11 +189,16 @@ static void updatePopulateEphTable( } if( pPk ){ for(i=0; inKeyCol; i++){ - pList = sqlite3ExprListAppend(pParse, pList, - sqlite3PExpr(pParse, TK_DOT, - sqlite3Expr(db, TK_ID, pTab->zName), - sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zName) - )); + Expr *pNew = sqlite3PExpr(pParse, TK_DOT, + sqlite3Expr(db, TK_ID, pTab->zName), + sqlite3Expr(db, TK_ID, pTab->aCol[pPk->aiColumn[i]].zName) + ); + if( pLimit ){ + pGroupBy = sqlite3ExprListAppend(pParse, pGroupBy, + sqlite3ExprDup(db, pNew, 0) + ); + } + pList = sqlite3ExprListAppend(pParse, pList, pNew); } eDest = SRT_Upfrom; }else if( pTab->pSelect ){ @@ -199,19 +209,24 @@ static void updatePopulateEphTable( )); eDest = SRT_Table; }else{ - pList = sqlite3ExprListAppend(pParse, pList, - sqlite3PExpr(pParse, TK_DOT, - sqlite3Expr(db, TK_ID, pTab->zName), - sqlite3Expr(db, TK_ID, "_rowid_") - )); eDest = IsVirtual(pTab) ? SRT_Table : SRT_Upfrom; + pList = sqlite3ExprListAppend(pParse, pList, + sqlite3PExpr(pParse, TK_ROW, 0, 0) + ); + if( pLimit ){ + pGroupBy = sqlite3ExprListAppend(pParse, pGroupBy, + sqlite3PExpr(pParse, TK_ROW, 0, 0) + ); + } } for(i=0; inExpr; i++){ pList = sqlite3ExprListAppend(pParse, pList, sqlite3ExprDup(db, pChanges->a[i].pExpr, 0) ); } - pSelect = sqlite3SelectNew(pParse, pList, pSrc, pWhere2, 0, 0, 0, 0, 0); + pSelect = sqlite3SelectNew( + pParse, pList, pSrc, pWhere2, pGroupBy, 0, pOrderBy2, 0, pLimit2 + ); sqlite3SelectDestInit(&dest, eDest, iEph); dest.iSDParm2 = (pPk ? pPk->nKeyCol : -1); sqlite3Select(pParse, pSelect, &dest); @@ -323,7 +338,7 @@ void sqlite3Update( assert( nChangeFrom==0 || pUpsert==0 ); #ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT - if( !isView ){ + if( !isView && nChangeFrom==0 ){ pWhere = sqlite3LimitWhere( pParse, pTabList, pWhere, pOrderBy, pLimit, "UPDATE" ); @@ -608,7 +623,9 @@ void sqlite3Update( addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nEphCol); if( pPk ) sqlite3VdbeSetP4KeyInfo(pParse, pPk); if( nChangeFrom ){ - updatePopulateEphTable(pParse, iEph, pPk, pChanges, pTabList, pWhere); + updatePopulateEphTable( + pParse, iEph, pPk, pChanges, pTabList, pWhere, pOrderBy, pLimit + ); #ifndef SQLITE_OMIT_SUBQUERY if( isView ) iDataCur = iEph; #endif @@ -1148,7 +1165,7 @@ static void updateVirtualTable( } } - updatePopulateEphTable(pParse, ephemTab, 0, pList, pSrc, pWhere); + updatePopulateEphTable(pParse, ephemTab, 0, pList, pSrc, pWhere, 0, 0); sqlite3ExprListDelete(db, pList); eOnePass = ONEPASS_OFF; }else{ diff --git a/test/upfrom2.test b/test/upfrom2.test index 0c3cf31efb..2d8d51b82d 100644 --- a/test/upfrom2.test +++ b/test/upfrom2.test @@ -14,26 +14,14 @@ set testdir [file dirname $argv0] source $testdir/tester.tcl set testprefix upfrom2 -if 0 { -do_execsql_test 0.0 { - CREATE TABLE t1 (a PRIMARY KEY, b, c) WITHOUT ROWID; -} -explain_i { REPLACE INTO t1 VALUES('one', 'two', 'three'); } -breakpoint -execsql { - REPLACE INTO t1 VALUES('one', 'two', 'three'); - REPLACE INTO t1 VALUES('one', 'two', 'four'); -} -do_execsql_test x { - SELECT * FROM t1 -} {one two four} -exit -} - # Test cases: # # 1.*: Test that triggers are fired correctly for UPDATE FROM statements, -# and only once for each row. +# and only once for each row. Except for INSTEAD OF triggers on +# views - these are fired once for each row returned by the join, +# including duplicates. +# +# 2.*: Test adding ORDER BY and LIMIT clauses with UPDATE FROM statements. # foreach {tn wo} { @@ -110,9 +98,75 @@ foreach {tn wo} { ten->eight i->i two->twelve xii->xii } + + do_test 1.%TN%.4 { db changes } {2} + + do_execsql_test 1.%TN%.5 { + CREATE VIEW v1 AS SELECT * FROM t1; + CREATE TRIGGER v1tr INSTEAD OF UPDATE ON v1 BEGIN + UPDATE t1 SET y=new.y, z=new.z WHERE x=new.x; + END; + + DELETE FROM log; + WITH data(k, v) AS ( + VALUES(3, 'thirteen'), (3, 'fourteen'), (4, 'fifteen'), (4, 'sixteen') + ) + UPDATE v1 SET z=v FROM data WHERE x=k; + } + + do_execsql_test 1.%TN%.6 { + SELECT * FROM v1; + SELECT * FROM log; + } { + 1 i eight 2 xii twelve 3 v fourteen 4 iv sixteen + thirty->thirteen v->v + thirteen->fourteen v->v + four->fifteen iv->iv + fifteen->sixteen iv->iv + } + }] } +ifcapable update_delete_limit { +foreach {tn wo} { + 1 "" + 2 "WITHOUT ROWID" +} { + reset_db + +eval [string map [list %WO% $wo %TN% $tn] { + do_execsql_test 2.%TN%.1 { + CREATE TABLE x1(a INTEGER PRIMARY KEY, b) %WO%; + INSERT INTO x1 VALUES + (1, 'one'), (2, 'two'), (3, 'three'), (4, 'four'), + (5, 'five'), (6, 'six'), (7, 'seven'), (8, 'eight'); + } + + do_execsql_test 2.%TN%.2 { + CREATE TABLE data1(x, y); + INSERT INTO data1 VALUES + (1, 'eleven'), (1, 'twenty-one'), (2, 'twelve'), (2, 'twenty-two'), + (3, 'thirteen'), (3, 'twenty-three'), (4, 'fourteen'), (4, 'twenty-four'); + } + + do_execsql_test 2.%TN%.3 { + UPDATE x1 SET b=y FROM data1 WHERE a=x ORDER BY a LIMIT 3; + SELECT * FROM x1; + } { + 1 eleven 2 twelve 3 thirteen 4 four 5 five 6 six 7 seven 8 eight + } + + do_execsql_test 2.%TN%.4 { + UPDATE x1 SET b=b||y FROM data1 WHERE a=x ORDER BY b LIMIT 3; + SELECT * FROM x1; + } { + 1 eleveneleven 2 twelve 3 thirteenthirteen 4 fourfourteen + 5 five 6 six 7 seven 8 eight + } + +}] +}} finish_test