]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Allow a FROM clause in UPDATE statements.
authordan <dan@noemail.net>
Mon, 27 Apr 2020 20:55:33 +0000 (20:55 +0000)
committerdan <dan@noemail.net>
Mon, 27 Apr 2020 20:55:33 +0000 (20:55 +0000)
FossilOrigin-Name: f353a1a613bb7ad8cedcda377a7fe6fd05ee03b1f50665b00b84a868a71c5bec

12 files changed:
manifest
manifest.uuid
src/build.c
src/delete.c
src/parse.y
src/select.c
src/sqliteInt.h
src/update.c
test/fts4upfrom.test [new file with mode: 0644]
test/pg_common.tcl
test/upfrom1.tcl [new file with mode: 0644]
test/upfrom1.test [new file with mode: 0644]

index e6fae833bcb4d95b349190661a2b2517d54ebe63..78387769ab3bd17977bf3f6e62585a32e1bd2dcb 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,5 +1,5 @@
-C Yet\sanother\sattempt\sto\senhance\ssqlite3_load_extension()\sso\sthat\sit\sworks\nwith\sWindow-style\spathnames\susing\sa\sbackslash\sseparator\scharacter.
-D 2020-04-26T22:04:48.458
+C Allow\sa\sFROM\sclause\sin\sUPDATE\sstatements.
+D 2020-04-27T20:55:33.061
 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
 F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -476,14 +476,14 @@ F src/btmutex.c 8acc2f464ee76324bf13310df5692a262b801808984c1b79defb2503bbafadb6
 F src/btree.c 02376eb7d49ccf31b53c2504f045ad74687c142a5c15ca837516e59e737867dc
 F src/btree.h 32672fa1aa74a7e9ab3aae822f94ffc8e732b1eb005988dc2283f91dc7573398
 F src/btreeInt.h 887cdd2ea7f4a65143074a8a7c8928b0546f8c18dda3c06a408ce7992cbab0c0
-F src/build.c ec6c0bda1e43ef55e5f5121a77ba19fac51fc6585f95ce2da795bcedcf6e8f36
+F src/build.c 8debc951e3f7e5152bbb7e6b2f26cad7b00a1db068c69af7db4aab136486e541
 F src/callback.c d0b853dd413255d2e337b34545e54d888ea02f20da5ad0e63585b389624c4a6c
 F src/complete.c a3634ab1e687055cd002e11b8f43eb75c17da23e
 F src/ctime.c 6a77ec9e0eb87aea929e002c816298907e337094a7b556898ae2d1e6be209f90
 F src/date.c b29b349d277e3d579dcc295b24c0a2caed83fd8f090a9f7cbe6070c0fd662384
 F src/dbpage.c 8a01e865bf8bc6d7b1844b4314443a6436c07c3efe1d488ed89e81719047833a
 F src/dbstat.c 793deaf88a0904f88285d93d6713c636d55ede0ffd9f08d10f4ea825531d367f
-F src/delete.c 11000121c4281c0bce4e41db29addfaea0038eaa127ece02557c9207bc3e541d
+F src/delete.c 6a4cbe008e8885eac5a0e0f9228ad716db92e3f9f2f0cb91a8ae276658d1f909
 F src/expr.c d1e1d42cbdec08bb867a1ab43a59b401d82ff2bc88bdcb4af20e479a5facb6d8
 F src/fault.c 460f3e55994363812d9d60844b2a6de88826e007
 F src/fkey.c 4b575423b0a5d4898b1a7868ce985cf1a8ad91c741c9abbb108ff02536d20f41
@@ -521,7 +521,7 @@ F src/os_win.c 035a813cbd17f355bdcad7ab894af214a9c13a1db8aeac902365350b98cd45a7
 F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
 F src/pager.c 52cee2f72710be47b5b13ff66b339ca3855e5bc48e92a94114d2affedc70041f
 F src/pager.h 3b33619a90180e0874c7eca31d6f6ceb464d9322c6fb4e9a7bbb318c8a17bdb3
-F src/parse.y c8eff38606f443d5ba245263fa7abc05e4116d95656e050c4b78e9bfbf931add
+F src/parse.y 5f2150bb4974e440924dfcc2e33cea7cf1895492b917464572efd258b0eab267
 F src/pcache.c 385ff064bca69789d199a98e2169445dc16e4291fa807babd61d4890c3b34177
 F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586
 F src/pcache1.c 6596e10baf3d8f84cc1585d226cf1ab26564a5f5caf85a15757a281ff977d51a
@@ -532,12 +532,12 @@ F src/printf.c 9be6945837c839ba57837b4bc3af349eba630920fa5532aa518816defe42a7d4
 F src/random.c 80f5d666f23feb3e6665a6ce04c7197212a88384
 F src/resolve.c d36a2b1639e1c33d7b508abfd3452a63e7fd81737f6f3940bfef085fca6f21f4
 F src/rowset.c ba9515a922af32abe1f7d39406b9d35730ed65efab9443dc5702693b60854c92
-F src/select.c c310de94bf67315054587c18a16e7a3e3dc3a98dc79168f0c2b776548d43f6cd
+F src/select.c 7e56a58673d027ab7951559adfda752192baff7c6083a88e4dd8db3c84e465e8
 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 0f3848c46310d197246003f052985b72d1cdbfc0b31e069db76cb5231062fa1d
+F src/sqliteInt.h bfed03b21bfa8fade8887a12d5bc0f5a349e98105aec675b9c1e027e9fd66f67
 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 3eb778c42155d944377a4ee5e440b04520f07094804ed6ce63d2528f619614d9
+F src/update.c 72aae4f6198aca8290c1368f26f6f8b7d29e23d0d2bfbd4f773eaa8d9a9380a4
 F src/upsert.c 2920de71b20f04fe25eb00b655d086f0ba60ea133c59d7fa3325c49838818e78
 F src/utf.c 95fb6e03a5ca679045c5adccd05380f0addccabef5911abddcb06af069500ab7
 F src/util.c 3b6cedf7a0c69bd6e1acce832873952d416212d6293b18d03064e07d7a9b5118
@@ -1003,6 +1003,7 @@ F test/fts4record.test a48508f69a84c9287c8019d3a1ae712f5730d8335ffaf8e2101e691d0
 F test/fts4rename.test 15fd9985c2bce6dea20da2245b22029ec89bd4710ed317c4c53abbe3cfd0c880
 F test/fts4umlaut.test fcaca4471de7e78c9d1f7e8976e3e8704d7d8ad979d57a739d00f3f757380429
 F test/fts4unicode.test ceca76422abc251818cb25dabe33d3c3970da5f7c90e1540f190824e6b3a7c95
+F test/fts4upfrom.test 04ef3b150370e0083cf7c721928b79163b70d930f74ebc4b91bc382b8123f659
 F test/full.test 6b3c8fb43c6beab6b95438c1675374b95fab245d
 F test/func.test f673822636fb8ed618dd2b80230d16e495d19c8f2e2e7d6c22e93e2b3de097ad
 F test/func2.test 772d66227e4e6684b86053302e2d74a2500e1e0f
@@ -1234,7 +1235,7 @@ F test/pcache.test c8acbedd3b6fd0f9a7ca887a83b11d24a007972b
 F test/pcache2.test af7f3deb1a819f77a6d0d81534e97d1cf62cd442
 F test/percentile.test 4243af26b8f3f4555abe166f723715a1f74c77ff
 F test/permutations.test c83339862d72b6272f957905205f874e6eefdbad2823380452c4f0128fd3d906
-F test/pg_common.tcl 222a1bad1c41c308fa366313cd7b51b3be7e9b21c8736a421b974ac941693b54
+F test/pg_common.tcl 3b27542224db1e713ae387459b5d117c836a5f6e328846922993b6d2b7640d9f
 F test/pragma.test 59becdfd720b80d463ab750f69f7118fde10dfd556aa5d554f3bf6b7e5ea7533
 F test/pragma2.test e5d5c176360c321344249354c0c16aec46214c9f
 F test/pragma3.test 92a46bbea12322dd94a404f49edcfbfc913a2c98115f0d030a7459bb4712ef31
@@ -1615,6 +1616,8 @@ F test/unixexcl.test d936ba2b06794018e136418addd59a2354eeae97
 F test/unordered.test ffeea7747d5ba962a8009a20b7e53d68cbae05b063604c68702c5998eb50c981
 F test/update.test e906ca7cb1dc6f52af1ea243e08f727edfa79f924c2691f2f9e72481f847310d
 F test/update2.test 67455bc61fcbcf96923c45b3bc4f87bc72be7d67575ad35f134906148c7b06d3
+F test/upfrom1.tcl 62efddee869b3a6f3e076b2816793fec9422e38d10ea42b63da733cdd2b1ad8e
+F test/upfrom1.test 543389b4eef43c7a21079df018cf95e29d7c2a4efd09b2597e54a03bbdbd30b9
 F test/upsert1.test 88f9e258c6a0eeeb85937b08831e8daad440ba41f125af48439e9d33f266fb18
 F test/upsert2.test 9c3cdbb1a890227f6504ce4b0e3de68f4cdfa16bb21d8641208a9239896c5a09
 F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c
@@ -1861,7 +1864,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
 F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
 F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
-P 57b16d8ca3d1ede3b411389256bec6686433aae716f47bca309ee7c8e5fe3128
-R cae58a18aebbf7b95fb40ba841d77f19
-U drh
-Z a013006b51e7e5f259533e9a3800742b
+P b73d9a7d6f7fec0ffc9640902a849289c305f8651e891388c01255c4da7a6c4b
+R bbc1e04364e396384c0537dbe66bc52f
+T *branch * update-from
+T *sym-update-from *
+T -sym-trunk *
+U dan
+Z 657e2dd1dadbea443cdb52f63d6f9883
index b3a75f3c82ae5e806253b84fb9f5556cb669657b..b06346868bcec35dc5cadcc4a2677cad163e7350 100644 (file)
@@ -1 +1 @@
-b73d9a7d6f7fec0ffc9640902a849289c305f8651e891388c01255c4da7a6c4b
\ No newline at end of file
+f353a1a613bb7ad8cedcda377a7fe6fd05ee03b1f50665b00b84a868a71c5bec
\ No newline at end of file
index cf36766aefa2d7b86a73d46435b82cb9f8c5c608..f0136cbe800d5b9e8021f976e734722d0fa463e5 100644 (file)
@@ -4495,6 +4495,26 @@ void sqlite3SrcListIndexedBy(Parse *pParse, SrcList *p, Token *pIndexedBy){
   }
 }
 
+/*
+** Append the contents of SrcList p2 to SrcList p1 and return the resulting
+** SrcList. Or, if an error occurs, return NULL. In all cases, p1 and p2
+** are deleted by this function.
+*/ 
+SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2){
+  if( p2 && p1 ){
+    assert( p1->nSrc==1 );
+    p1 = sqlite3SrcListEnlarge(pParse, p1, p2->nSrc, p1->nSrc);
+    if( p1 ){
+      assert( p1->nSrc==1+p2->nSrc );
+      memcpy(&p1->a[1], p2->a, p2->nSrc*sizeof(struct SrcList_item));
+      sqlite3_free(p2);
+    }else{
+      sqlite3SrcListDelete(pParse->db, p2);
+    }
+  }
+  return p1;
+}
+
 /*
 ** Add the list of function arguments to the SrcList entry for a
 ** table-valued-function.
index 60efc9d56939b53c2f8c1c94778bad2ecead3aab..5e73f76bb6d03a46c7a57b513f9ca44e30b1ac04 100644 (file)
@@ -31,7 +31,7 @@
 Table *sqlite3SrcListLookup(Parse *pParse, SrcList *pSrc){
   struct SrcList_item *pItem = pSrc->a;
   Table *pTab;
-  assert( pItem && pSrc->nSrc==1 );
+  assert( pItem && pSrc->nSrc>=1 );
   pTab = sqlite3LocateTableItem(pParse, 0, pItem);
   sqlite3DeleteTable(pParse->db, pItem->pTab);
   pItem->pTab = pTab;
index 09731eb99cc96bd2ab9daa6dc369a840df9e880b..3616e3de2470d6fdb1f157eca6e859882b6249e5 100644 (file)
@@ -637,7 +637,7 @@ as(X) ::= .            {X.n = 0; X.z = 0;}
 
 // A complete FROM clause.
 //
-from(A) ::= .                {A = sqlite3DbMallocZero(pParse->db, sizeof(*A));}
+from(A) ::= .                {A = 0;}
 from(A) ::= FROM seltablist(X). {
   A = X;
   sqlite3SrcListShiftJoinType(A);
@@ -867,18 +867,20 @@ where_opt(A) ::= WHERE expr(X).       {A = X;}
 ////////////////////////// The UPDATE command ////////////////////////////////
 //
 %ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
-cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y)
+cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from(F)
         where_opt(W) orderby_opt(O) limit_opt(L).  {
   sqlite3SrcListIndexedBy(pParse, X, &I);
+  X = sqlite3SrcListAppendList(pParse, X, F);
   sqlite3ExprListCheckLength(pParse,Y,"set list"); 
   sqlite3Update(pParse,X,Y,W,R,O,L,0);
 }
 %endif
 %ifndef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
-cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y)
+cmd ::= with UPDATE orconf(R) xfullname(X) indexed_opt(I) SET setlist(Y) from(F)
         where_opt(W).  {
   sqlite3SrcListIndexedBy(pParse, X, &I);
   sqlite3ExprListCheckLength(pParse,Y,"set list"); 
+  X = sqlite3SrcListAppendList(pParse, X, F);
   sqlite3Update(pParse,X,Y,W,R,0,0,0);
 }
 %endif
index 4b7ba37f9f21b8222d97bcbc4d92040d6be4e0a6..f6b48b5a0cf51bd48d6ff76ab88fa6ddc78f8b73 100644 (file)
@@ -1154,11 +1154,11 @@ static void selectInnerLoop(
       break;
     }
 
-#ifndef SQLITE_OMIT_SUBQUERY
     /* If we are creating a set for an "expr IN (SELECT ...)" construct,
     ** then there should be a single item on the stack.  Write this
     ** item into the set table with bogus data.
     */
+    case SRT_ISet:
     case SRT_Set: {
       if( pSort ){
         /* At first glance you would think we could optimize out the
@@ -1168,16 +1168,22 @@ static void selectInnerLoop(
         pushOntoSorter(
             pParse, pSort, p, regResult, regOrig, nResultCol, nPrefixReg);
       }else{
+        int bITab = (eDest==SRT_ISet);
         int r1 = sqlite3GetTempReg(pParse);
-        assert( sqlite3Strlen30(pDest->zAffSdst)==nResultCol );
-        sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult, nResultCol, 
-            r1, pDest->zAffSdst, nResultCol);
-        sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1, regResult, nResultCol);
+        sqlite3VdbeAddOp4(v, OP_MakeRecord, regResult+bITab, nResultCol-bITab,
+            r1, pDest->zAffSdst, 0
+        );
+        if( bITab ){
+          sqlite3VdbeAddOp3(v, OP_Insert, iParm, r1, regResult);
+        }else{
+          sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iParm, r1,regResult,nResultCol);
+        }
         sqlite3ReleaseTempReg(pParse, r1);
       }
       break;
     }
 
+#ifndef SQLITE_OMIT_SUBQUERY
     /* If any row exist in the result set, record that fact and abort.
     */
     case SRT_Exists: {
@@ -4981,8 +4987,8 @@ static int selectExpander(Walker *pWalker, Select *p){
   for(i=0, pFrom=pTabList->a; i<pTabList->nSrc; i++, pFrom++){
     Table *pTab;
     assert( pFrom->fg.isRecursive==0 || pFrom->pTab!=0 );
-    if( pFrom->fg.isRecursive ) continue;
-    assert( pFrom->pTab==0 );
+    if( pFrom->pTab ) continue;
+    assert( pFrom->fg.isRecursive==0 );
 #ifndef SQLITE_OMIT_CTE
     if( withExpand(pWalker, pFrom) ) return WRC_Abort;
     if( pFrom->pTab ) {} else
index aaa99a43a897ebac9c277f19617efd30293cc5f6..3cbc65cdd0a00cf2c8abc4b7a127c89e010b6b44 100644 (file)
@@ -3187,6 +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 */
 
 /*
 ** An instance of this object describes where to put of the results of
@@ -4204,6 +4205,7 @@ void *sqlite3ArrayAllocate(sqlite3*,void*,int,int*,int*);
 IdList *sqlite3IdListAppend(Parse*, IdList*, Token*);
 int sqlite3IdListIndex(IdList*,const char*);
 SrcList *sqlite3SrcListEnlarge(Parse*, SrcList*, int, int);
+SrcList *sqlite3SrcListAppendList(Parse *pParse, SrcList *p1, SrcList *p2);
 SrcList *sqlite3SrcListAppend(Parse*, SrcList*, Token*, Token*);
 SrcList *sqlite3SrcListAppendFromTerm(Parse*, SrcList*, Token*, Token*,
                                       Token*, Select*, Expr*, IdList*);
index d54a6cb9f073f198d48b961d7cd9e95041f56364..7257b2c6f2f1fc0576242f64e922054079769127 100644 (file)
@@ -130,6 +130,93 @@ static int indexWhereClauseMightChange(
                                             aXRef, chngRowid);
 }
 
+/*
+** This function generates VM code to run the query:
+**
+**   SELECT <other-columns>, pChanges FROM pTabList WHERE pWhere
+**
+** and write the results to the ephemeral table already opened as cursor 
+** iEph. None of pChanges, pTabList or pWhere are modified or consumed by 
+** this function, they must be deleted by the caller.
+**
+** Exactly how results are written to table iEph, and exactly what
+** the <other-columns> in the query above are is determined by the type
+** of table pTabList->a[0].pTab.
+**
+** If the table is a WITHOUT ROWID table, then argument pPk must be its
+** PRIMARY KEY. In this case <other-columns> are the primary key columns
+** of the table, in order. The results of the query are written to ephemeral
+** table iEph as index keys, using OP_IdxInsert.
+**
+** If the table is actually a view, then <other-columns> are all columns of
+** the view. The results are written to the ephemeral table iEph as records
+** with automatically assigned integer keys.
+**
+** If the table is a virtual or ordinary intkey table, then <other-columns> 
+** is its rowid. For a virtual table, the results are written to iEph as
+** records with automatically assigned integer keys For intkey tables, the
+** rowid value in <other-columns> is used as the integer key, and the 
+** remaining fields make up the table record. 
+*/
+static void updatePopulateEphTable(
+  Parse *pParse,                  /* Parse context */
+  int iEph,                       /* Cursor for open eph. table */
+  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 */
+){
+  int i;
+  sqlite3 *db = pParse->db;
+  SelectDest dest;
+  Select *pSelect = 0;
+  ExprList *pList = 0;
+  Table *pTab = pTabList->a[0].pTab;
+  SrcList *pSrc = sqlite3SrcListDup(db, pTabList, 0);
+  Expr *pWhere2 = sqlite3ExprDup(db, pWhere, 0);
+  int eDest;
+
+  assert( pTabList->nSrc>1 );
+  if( pSrc ){
+    pSrc->a[0].iCursor = -1;
+    pSrc->a[0].pTab->nTabRef--;
+    pSrc->a[0].pTab = 0;
+  }
+  if( pPk ){
+    for(i=0; i<pPk->nKeyCol; 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)
+      ));
+    }
+    eDest = SRT_Set;
+  }else if( pTab->pSelect ){
+    pList = sqlite3ExprListAppend(pParse, pList, 
+        sqlite3PExpr(pParse, TK_DOT,
+          sqlite3Expr(db, TK_ID, pTab->zName),
+          sqlite3PExpr(pParse, TK_ASTERISK, 0, 0)
+    ));
+    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_ISet;
+  }
+  for(i=0; i<pChanges->nExpr; i++){
+    pList = sqlite3ExprListAppend(pParse, pList, 
+        sqlite3ExprDup(db, pChanges->a[i].pExpr, 0)
+    );
+  }
+  pSelect = sqlite3SelectNew(pParse, pList, pSrc, pWhere2, 0, 0, 0, 0, 0);
+  sqlite3SelectDestInit(&dest, eDest, iEph);
+  sqlite3Select(pParse, pSelect, &dest);
+  sqlite3SelectDelete(db, pSelect);
+}
+
 /*
 ** Process an UPDATE statement.
 **
@@ -192,6 +279,7 @@ void sqlite3Update(
   i16 nPk = 0;           /* Number of components of the PRIMARY KEY */
   int bReplace = 0;      /* True if REPLACE conflict resolution might happen */
   int bFinishSeek = 1;   /* The OP_FinishSeek opcode is needed */
+  int nChangeFrom = 0;
 
   /* Register Allocations */
   int regRowCount = 0;   /* A count of rows changed */
@@ -207,7 +295,6 @@ void sqlite3Update(
   if( pParse->nErr || db->mallocFailed ){
     goto update_cleanup;
   }
-  assert( pTabList->nSrc==1 );
 
   /* Locate the table which we want to update. 
   */
@@ -231,6 +318,8 @@ void sqlite3Update(
 # undef isView
 # define isView 0
 #endif
+  nChangeFrom = (pTabList->nSrc>1) ? pChanges->nExpr : 0;
+  assert( nChangeFrom==0 || pUpsert==0 );
 
 #ifdef SQLITE_ENABLE_UPDATE_DELETE_LIMIT
   if( !isView ){
@@ -302,7 +391,9 @@ void sqlite3Update(
   */
   chngRowid = chngPk = 0;
   for(i=0; i<pChanges->nExpr; i++){
-    if( sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){
+    /* If this is an UPDATE with a FROM clause, do not resolve expressions
+    ** here. The call to sqlite3Select() below will do that. */
+    if( nChangeFrom==0 && sqlite3ResolveExprNames(&sNC, pChanges->a[i].pExpr) ){
       goto update_cleanup;
     }
     for(j=0; j<pTab->nCol; j++){
@@ -461,7 +552,7 @@ void sqlite3Update(
   ** an ephemeral table.
   */
 #if !defined(SQLITE_OMIT_VIEW) && !defined(SQLITE_OMIT_TRIGGER)
-  if( isView ){
+  if( nChangeFrom==0 && isView ){
     sqlite3MaterializeView(pParse, pTab, 
         pWhere, pOrderBy, pLimit, iDataCur
     );
@@ -473,7 +564,7 @@ void sqlite3Update(
   /* Resolve the column names in all the expressions in the
   ** WHERE clause.
   */
-  if( sqlite3ResolveExprNames(&sNC, pWhere) ){
+  if( nChangeFrom==0 && sqlite3ResolveExprNames(&sNC, pWhere) ){
     goto update_cleanup;
   }
 
@@ -500,105 +591,118 @@ void sqlite3Update(
     sqlite3VdbeAddOp2(v, OP_Integer, 0, regRowCount);
   }
 
-  if( HasRowid(pTab) ){
+  if( nChangeFrom==0 && HasRowid(pTab) ){
     sqlite3VdbeAddOp3(v, OP_Null, 0, regRowSet, regOldRowid);
   }else{
-    assert( pPk!=0 );
-    nPk = pPk->nKeyCol;
+    assert( pPk!=0 || HasRowid(pTab) );
+    nPk = pPk ? pPk->nKeyCol : 0;
     iPk = pParse->nMem+1;
     pParse->nMem += nPk;
+    pParse->nMem += nChangeFrom;
     regKey = ++pParse->nMem;
     if( pUpsert==0 ){
+      int nEphCol = nPk + nChangeFrom + (isView ? pTab->nCol : 0);
       iEph = pParse->nTab++;
-        sqlite3VdbeAddOp3(v, OP_Null, 0, iPk, iPk+nPk-1);
-      addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nPk);
-      sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+      if( pPk ) sqlite3VdbeAddOp3(v, OP_Null, 0, iPk, iPk+nPk-1);
+      addrOpen = sqlite3VdbeAddOp2(v, OP_OpenEphemeral, iEph, nEphCol);
+      if( pPk ) sqlite3VdbeSetP4KeyInfo(pParse, pPk);
+      if( nChangeFrom ){
+        updatePopulateEphTable(pParse, iEph, pPk, pChanges, pTabList, pWhere);
+#ifndef SQLITE_OMIT_SUBQUERY
+        if( isView ) iDataCur = iEph;
+#endif
+      }
     }
   }
   
-  if( pUpsert ){
-    /* If this is an UPSERT, then all cursors have already been opened by
-    ** the outer INSERT and the data cursor should be pointing at the row
-    ** that is to be updated.  So bypass the code that searches for the
-    ** row(s) to be updated.
-    */
-    pWInfo = 0;
-    eOnePass = ONEPASS_SINGLE;
-    sqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL);
-    bFinishSeek = 0;
+  if( nChangeFrom ){
+    sqlite3MultiWrite(pParse);
+    eOnePass = ONEPASS_OFF;
   }else{
-    /* Begin the database scan. 
-    **
-    ** Do not consider a single-pass strategy for a multi-row update if
-    ** there are any triggers or foreign keys to process, or rows may
-    ** be deleted as a result of REPLACE conflict handling. Any of these
-    ** things might disturb a cursor being used to scan through the table
-    ** or index, causing a single-pass approach to malfunction.  */
-    flags = WHERE_ONEPASS_DESIRED|WHERE_SEEK_UNIQ_TABLE;
-    if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){
-      flags |= WHERE_ONEPASS_MULTIROW;
-    }
-    pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags, iIdxCur);
-    if( pWInfo==0 ) goto update_cleanup;
-  
-    /* A one-pass strategy that might update more than one row may not
-    ** be used if any column of the index used for the scan is being
-    ** updated. Otherwise, if there is an index on "b", statements like
-    ** the following could create an infinite loop:
-    **
-    **   UPDATE t1 SET b=b+1 WHERE b>?
-    **
-    ** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI
-    ** strategy that uses an index for which one or more columns are being
-    ** updated.  */
-    eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
-    bFinishSeek = sqlite3WhereUsesDeferredSeek(pWInfo);
-    if( eOnePass!=ONEPASS_SINGLE ){
-      sqlite3MultiWrite(pParse);
-      if( eOnePass==ONEPASS_MULTI ){
-        int iCur = aiCurOnePass[1];
-        if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){
-          eOnePass = ONEPASS_OFF;
+    if( pUpsert ){
+      /* If this is an UPSERT, then all cursors have already been opened by
+      ** the outer INSERT and the data cursor should be pointing at the row
+      ** that is to be updated.  So bypass the code that searches for the
+      ** row(s) to be updated.
+      */
+      pWInfo = 0;
+      eOnePass = ONEPASS_SINGLE;
+      sqlite3ExprIfFalse(pParse, pWhere, labelBreak, SQLITE_JUMPIFNULL);
+      bFinishSeek = 0;
+    }else{
+      /* Begin the database scan. 
+      **
+      ** Do not consider a single-pass strategy for a multi-row update if
+      ** there are any triggers or foreign keys to process, or rows may
+      ** be deleted as a result of REPLACE conflict handling. Any of these
+      ** things might disturb a cursor being used to scan through the table
+      ** or index, causing a single-pass approach to malfunction.  */
+      flags = WHERE_ONEPASS_DESIRED|WHERE_SEEK_UNIQ_TABLE;
+      if( !pParse->nested && !pTrigger && !hasFK && !chngKey && !bReplace ){
+        flags |= WHERE_ONEPASS_MULTIROW;
+      }
+      pWInfo = sqlite3WhereBegin(pParse, pTabList, pWhere, 0, 0, flags,iIdxCur);
+      if( pWInfo==0 ) goto update_cleanup;
+
+      /* A one-pass strategy that might update more than one row may not
+      ** be used if any column of the index used for the scan is being
+      ** updated. Otherwise, if there is an index on "b", statements like
+      ** the following could create an infinite loop:
+      **
+      **   UPDATE t1 SET b=b+1 WHERE b>?
+      **
+      ** Fall back to ONEPASS_OFF if where.c has selected a ONEPASS_MULTI
+      ** strategy that uses an index for which one or more columns are being
+      ** updated.  */
+      eOnePass = sqlite3WhereOkOnePass(pWInfo, aiCurOnePass);
+      bFinishSeek = sqlite3WhereUsesDeferredSeek(pWInfo);
+      if( eOnePass!=ONEPASS_SINGLE ){
+        sqlite3MultiWrite(pParse);
+        if( eOnePass==ONEPASS_MULTI ){
+          int iCur = aiCurOnePass[1];
+          if( iCur>=0 && iCur!=iDataCur && aToOpen[iCur-iBaseCur] ){
+            eOnePass = ONEPASS_OFF;
+          }
+          assert( iCur!=iDataCur || !HasRowid(pTab) );
         }
-        assert( iCur!=iDataCur || !HasRowid(pTab) );
       }
     }
-  }
 
-  if( HasRowid(pTab) ){
-    /* Read the rowid of the current row of the WHERE scan. In ONEPASS_OFF
-    ** mode, write the rowid into the FIFO. In either of the one-pass modes,
-    ** leave it in register regOldRowid.  */
-    sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid);
-    if( eOnePass==ONEPASS_OFF ){
-      /* We need to use regRowSet, so reallocate aRegIdx[nAllIdx] */
-      aRegIdx[nAllIdx] = ++pParse->nMem;
-      sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid);
-    }
-  }else{
-    /* Read the PK of the current row into an array of registers. In
-    ** ONEPASS_OFF mode, serialize the array into a record and store it in
-    ** the ephemeral table. Or, in ONEPASS_SINGLE or MULTI mode, change
-    ** the OP_OpenEphemeral instruction to a Noop (the ephemeral table 
-    ** is not required) and leave the PK fields in the array of registers.  */
-    for(i=0; i<nPk; i++){
-      assert( pPk->aiColumn[i]>=0 );
-      sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur,
-                                      pPk->aiColumn[i], iPk+i);
-    }
-    if( eOnePass ){
-      if( addrOpen ) sqlite3VdbeChangeToNoop(v, addrOpen);
-      nKey = nPk;
-      regKey = iPk;
+    if( HasRowid(pTab) ){
+      /* Read the rowid of the current row of the WHERE scan. In ONEPASS_OFF
+      ** mode, write the rowid into the FIFO. In either of the one-pass modes,
+      ** leave it in register regOldRowid.  */
+      sqlite3VdbeAddOp2(v, OP_Rowid, iDataCur, regOldRowid);
+      if( eOnePass==ONEPASS_OFF ){
+        /* We need to use regRowSet, so reallocate aRegIdx[nAllIdx] */
+        aRegIdx[nAllIdx] = ++pParse->nMem;
+        sqlite3VdbeAddOp2(v, OP_RowSetAdd, regRowSet, regOldRowid);
+      }
     }else{
-      sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey,
-                        sqlite3IndexAffinityStr(db, pPk), nPk);
-      sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEph, regKey, iPk, nPk);
+      /* Read the PK of the current row into an array of registers. In
+      ** ONEPASS_OFF mode, serialize the array into a record and store it in
+      ** the ephemeral table. Or, in ONEPASS_SINGLE or MULTI mode, change
+      ** the OP_OpenEphemeral instruction to a Noop (the ephemeral table 
+      ** is not required) and leave the PK fields in the array of registers.  */
+      for(i=0; i<nPk; i++){
+        assert( pPk->aiColumn[i]>=0 );
+        sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur,
+                                        pPk->aiColumn[i], iPk+i);
+      }
+      if( eOnePass ){
+        if( addrOpen ) sqlite3VdbeChangeToNoop(v, addrOpen);
+        nKey = nPk;
+        regKey = iPk;
+      }else{
+        sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey,
+                          sqlite3IndexAffinityStr(db, pPk), nPk);
+        sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEph, regKey, iPk, nPk);
+      }
     }
   }
 
   if( pUpsert==0 ){
-    if( eOnePass!=ONEPASS_MULTI ){
+    if( nChangeFrom==0 && eOnePass!=ONEPASS_MULTI ){
       sqlite3WhereEnd(pWInfo);
     }
   
@@ -634,12 +738,32 @@ void sqlite3Update(
       sqlite3VdbeAddOp2(v, OP_IsNull, pPk ? regKey : regOldRowid, labelBreak);
       VdbeCoverageIf(v, pPk==0);
       VdbeCoverageIf(v, pPk!=0);
-    }else if( pPk ){
+    }else if( pPk || nChangeFrom ){
       labelContinue = sqlite3VdbeMakeLabel(pParse);
       sqlite3VdbeAddOp2(v, OP_Rewind, iEph, labelBreak); VdbeCoverage(v);
-      addrTop = sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey);
-      sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey, 0);
-      VdbeCoverage(v);
+      addrTop = sqlite3VdbeCurrentAddr(v);
+      if( nChangeFrom ){
+        if( !isView ){
+          if( pPk ){
+            for(i=0; i<nPk; i++){
+              sqlite3VdbeAddOp3(v, OP_Column, iEph, i, iPk+i);
+            }
+            sqlite3VdbeAddOp4Int(
+                v, OP_NotFound, iDataCur, labelContinue, iPk, nPk
+            );
+          }else{
+            sqlite3VdbeAddOp2(v, OP_Rowid, iEph, regOldRowid);
+            sqlite3VdbeAddOp3(
+                v, OP_NotExists, iDataCur, labelContinue, regOldRowid
+            );
+          }
+        }
+        VdbeCoverage(v);
+      }else{
+        sqlite3VdbeAddOp2(v, OP_RowData, iEph, regKey);
+        sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue, regKey,0);
+        VdbeCoverage(v);
+      }
     }else{
       labelContinue = sqlite3VdbeAddOp3(v, OP_RowSetRead, regRowSet,labelBreak,
                                regOldRowid);
@@ -708,7 +832,13 @@ void sqlite3Update(
     }else{
       j = aXRef[i];
       if( j>=0 ){
-        sqlite3ExprCode(pParse, pChanges->a[j].pExpr, k);
+        if( nChangeFrom ){
+          assert( eOnePass==ONEPASS_OFF );
+          int nOff = (isView ? pTab->nCol : nPk);
+          sqlite3VdbeAddOp3(v, OP_Column, iEph, nOff+j, k);
+        }else{
+          sqlite3ExprCode(pParse, pChanges->a[j].pExpr, k);
+        }
       }else if( 0==(tmask&TRIGGER_BEFORE) || i>31 || (newmask & MASKBIT32(i)) ){
         /* This branch loads the value of a column that will not be changed 
         ** into a register. This is done if there are no BEFORE triggers, or
@@ -740,43 +870,45 @@ void sqlite3Update(
     sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges, 
         TRIGGER_BEFORE, pTab, regOldRowid, onError, labelContinue);
 
-    /* The row-trigger may have deleted the row being updated. In this
-    ** case, jump to the next row. No updates or AFTER triggers are 
-    ** required. This behavior - what happens when the row being updated
-    ** is deleted or renamed by a BEFORE trigger - is left undefined in the
-    ** documentation.
-    */
-    if( pPk ){
-      sqlite3VdbeAddOp4Int(v, OP_NotFound, iDataCur, labelContinue,regKey,nKey);
-      VdbeCoverage(v);
-    }else{
-      sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue, regOldRowid);
-      VdbeCoverage(v);
-    }
+    if( !isView ){
+      /* The row-trigger may have deleted the row being updated. In this
+      ** case, jump to the next row. No updates or AFTER triggers are 
+      ** required. This behavior - what happens when the row being updated
+      ** is deleted or renamed by a BEFORE trigger - is left undefined in the
+      ** documentation.
+      */
+      if( pPk ){
+        sqlite3VdbeAddOp4Int(v, OP_NotFound,iDataCur,labelContinue,regKey,nKey);
+        VdbeCoverage(v);
+      }else{
+        sqlite3VdbeAddOp3(v, OP_NotExists, iDataCur, labelContinue,regOldRowid);
+        VdbeCoverage(v);
+      }
 
-    /* After-BEFORE-trigger-reload-loop:
-    ** If it did not delete it, the BEFORE trigger may still have modified 
-    ** some of the columns of the row being updated. Load the values for 
-    ** all columns not modified by the update statement into their registers
-    ** in case this has happened. Only unmodified columns are reloaded.
-    ** The values computed for modified columns use the values before the
-    ** BEFORE trigger runs.  See test case trigger1-18.0 (added 2018-04-26)
-    ** for an example.
-    */
-    for(i=0, k=regNew; i<pTab->nCol; i++, k++){
-      if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){
-        if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) k--;
-      }else if( aXRef[i]<0 && i!=pTab->iPKey ){
-        sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k);
+      /* After-BEFORE-trigger-reload-loop:
+      ** If it did not delete it, the BEFORE trigger may still have modified 
+      ** some of the columns of the row being updated. Load the values for 
+      ** all columns not modified by the update statement into their registers
+      ** in case this has happened. Only unmodified columns are reloaded.
+      ** The values computed for modified columns use the values before the
+      ** BEFORE trigger runs.  See test case trigger1-18.0 (added 2018-04-26)
+      ** for an example.
+      */
+      for(i=0, k=regNew; i<pTab->nCol; i++, k++){
+        if( pTab->aCol[i].colFlags & COLFLAG_GENERATED ){
+          if( pTab->aCol[i].colFlags & COLFLAG_VIRTUAL ) k--;
+        }else if( aXRef[i]<0 && i!=pTab->iPKey ){
+          sqlite3ExprCodeGetColumnOfTable(v, pTab, iDataCur, i, k);
+        }
       }
-    }
 #ifndef SQLITE_OMIT_GENERATED_COLUMNS
-    if( pTab->tabFlags & TF_HasGenerated ){
-      testcase( pTab->tabFlags & TF_HasVirtual );
-      testcase( pTab->tabFlags & TF_HasStored );
-      sqlite3ComputeGeneratedColumns(pParse, regNew, pTab);
-    }
+      if( pTab->tabFlags & TF_HasGenerated ){
+        testcase( pTab->tabFlags & TF_HasVirtual );
+        testcase( pTab->tabFlags & TF_HasStored );
+        sqlite3ComputeGeneratedColumns(pParse, regNew, pTab);
+      }
 #endif 
+    }
   }
 
   if( !isView ){
@@ -879,7 +1011,7 @@ void sqlite3Update(
   }else if( eOnePass==ONEPASS_MULTI ){
     sqlite3VdbeResolveLabel(v, labelContinue);
     sqlite3WhereEnd(pWInfo);
-  }else if( pPk ){
+  }else if( pPk || nChangeFrom ){
     sqlite3VdbeResolveLabel(v, labelContinue);
     sqlite3VdbeAddOp2(v, OP_Next, iEph, addrTop); VdbeCoverage(v);
   }else{
@@ -982,69 +1114,101 @@ static void updateVirtualTable(
   addr= sqlite3VdbeAddOp2(v, OP_OpenEphemeral, ephemTab, nArg);
   regArg = pParse->nMem + 1;
   pParse->nMem += nArg;
-  regRec = ++pParse->nMem;
-  regRowid = ++pParse->nMem;
-
-  /* Start scanning the virtual table */
-  pWInfo = sqlite3WhereBegin(pParse, pSrc, pWhere, 0,0,WHERE_ONEPASS_DESIRED,0);
-  if( pWInfo==0 ) return;
-
-  /* Populate the argument registers. */
-  for(i=0; i<pTab->nCol; i++){
-    assert( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)==0 );
-    if( aXRef[i]>=0 ){
-      sqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i);
-    }else{
-      sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+2+i);
-      sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG);/* Enable sqlite3_vtab_nochange() */
-    }
-  }
-  if( HasRowid(pTab) ){
-    sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg);
+  if( pSrc->nSrc>1 ){
+    ExprList *pList = 0;
     if( pRowid ){
-      sqlite3ExprCode(pParse, pRowid, regArg+1);
+      pList = sqlite3ExprListAppend(pParse, pList, sqlite3ExprDup(db,pRowid,0));
     }else{
-      sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg+1);
+      pList = sqlite3ExprListAppend(pParse, pList, 
+          sqlite3PExpr(pParse, TK_DOT,
+            sqlite3Expr(db, TK_ID, pTab->zName),
+            sqlite3Expr(db, TK_ID, "_rowid_")
+      ));
     }
+    for(i=0; i<pTab->nCol; i++){
+      if( aXRef[i]>=0 ){
+        pList = sqlite3ExprListAppend(pParse, pList,
+          sqlite3ExprDup(db, pChanges->a[aXRef[i]].pExpr, 0)
+        );
+      }else{
+        pList = sqlite3ExprListAppend(pParse, pList, 
+            sqlite3PExpr(pParse, TK_DOT,
+              sqlite3Expr(db, TK_ID, pTab->zName),
+              sqlite3Expr(db, TK_ID, pTab->aCol[i].zName)
+        ));
+      }
+    }
+
+    updatePopulateEphTable(pParse, ephemTab, 0, pList, pSrc, pWhere);
+    sqlite3ExprListDelete(db, pList);
+    eOnePass = ONEPASS_OFF;
   }else{
-    Index *pPk;   /* PRIMARY KEY index */
-    i16 iPk;      /* PRIMARY KEY column */
-    pPk = sqlite3PrimaryKeyIndex(pTab);
-    assert( pPk!=0 );
-    assert( pPk->nKeyCol==1 );
-    iPk = pPk->aiColumn[0];
-    sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, iPk, regArg);
-    sqlite3VdbeAddOp2(v, OP_SCopy, regArg+2+iPk, regArg+1);
-  }
+    regRec = ++pParse->nMem;
+    regRowid = ++pParse->nMem;
 
-  eOnePass = sqlite3WhereOkOnePass(pWInfo, aDummy);
+    /* Start scanning the virtual table */
+    pWInfo = sqlite3WhereBegin(pParse, pSrc,pWhere,0,0,WHERE_ONEPASS_DESIRED,0);
+    if( pWInfo==0 ) return;
 
-  /* There is no ONEPASS_MULTI on virtual tables */
-  assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE );
+    /* Populate the argument registers. */
+    for(i=0; i<pTab->nCol; i++){
+      assert( (pTab->aCol[i].colFlags & COLFLAG_GENERATED)==0 );
+      if( aXRef[i]>=0 ){
+        sqlite3ExprCode(pParse, pChanges->a[aXRef[i]].pExpr, regArg+2+i);
+      }else{
+        sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, i, regArg+2+i);
+        sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG);/* For sqlite3_vtab_nochange() */
+      }
+    }
+    if( HasRowid(pTab) ){
+      sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg);
+      if( pRowid ){
+        sqlite3ExprCode(pParse, pRowid, regArg+1);
+      }else{
+        sqlite3VdbeAddOp2(v, OP_Rowid, iCsr, regArg+1);
+      }
+    }else{
+      Index *pPk;   /* PRIMARY KEY index */
+      i16 iPk;      /* PRIMARY KEY column */
+      pPk = sqlite3PrimaryKeyIndex(pTab);
+      assert( pPk!=0 );
+      assert( pPk->nKeyCol==1 );
+      iPk = pPk->aiColumn[0];
+      sqlite3VdbeAddOp3(v, OP_VColumn, iCsr, iPk, regArg);
+      sqlite3VdbeAddOp2(v, OP_SCopy, regArg+2+iPk, regArg+1);
+    }
 
-  if( eOnePass ){
-    /* If using the onepass strategy, no-op out the OP_OpenEphemeral coded
-    ** above. */
-    sqlite3VdbeChangeToNoop(v, addr);
-    sqlite3VdbeAddOp1(v, OP_Close, iCsr);
-  }else{
-    /* Create a record from the argument register contents and insert it into
-    ** the ephemeral table. */
-    sqlite3MultiWrite(pParse);
-    sqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec);
+    eOnePass = sqlite3WhereOkOnePass(pWInfo, aDummy);
+
+    /* There is no ONEPASS_MULTI on virtual tables */
+    assert( eOnePass==ONEPASS_OFF || eOnePass==ONEPASS_SINGLE );
+
+    if( eOnePass ){
+      /* If using the onepass strategy, no-op out the OP_OpenEphemeral coded
+      ** above. */
+      sqlite3VdbeChangeToNoop(v, addr);
+      sqlite3VdbeAddOp1(v, OP_Close, iCsr);
+    }else{
+      /* Create a record from the argument register contents and insert it into
+      ** the ephemeral table. */
+      sqlite3MultiWrite(pParse);
+      sqlite3VdbeAddOp3(v, OP_MakeRecord, regArg, nArg, regRec);
 #ifdef SQLITE_DEBUG
-    /* Signal an assert() within OP_MakeRecord that it is allowed to
-    ** accept no-change records with serial_type 10 */
-    sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC);
+      /* Signal an assert() within OP_MakeRecord that it is allowed to
+      ** accept no-change records with serial_type 10 */
+      sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG_MAGIC);
 #endif
-    sqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid);
-    sqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid);
+      sqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid);
+      sqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid);
+    }
   }
 
 
   if( eOnePass==ONEPASS_OFF ){
     /* End the virtual table scan */
-    sqlite3WhereEnd(pWInfo);
+    if( pSrc->nSrc==1 ){
+      sqlite3WhereEnd(pWInfo);
+    }
 
     /* Begin scannning through the ephemeral table. */
     addr = sqlite3VdbeAddOp1(v, OP_Rewind, ephemTab); VdbeCoverage(v);
diff --git a/test/fts4upfrom.test b/test/fts4upfrom.test
new file mode 100644 (file)
index 0000000..4c72aff
--- /dev/null
@@ -0,0 +1,105 @@
+# 2020 February 24
+#
+# The author disclaims copyright to this source code.  In place of
+# a legal notice, here is a blessing:
+#
+#    May you do good and not evil.
+#    May you find forgiveness for yourself and forgive others.
+#    May you share freely, never taking more than you give.
+#
+#*************************************************************************
+# This file implements regression tests for SQLite library.  The
+# focus of this script is testing UPDATE statements with FROM clauses
+# against FTS4 tables.
+#
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix fts4upfrom
+
+# If SQLITE_ENABLE_FTS3 is defined, omit this file.
+ifcapable !fts3 {
+  finish_test
+  return
+}
+
+foreach {tn create_table} {
+  1 { CREATE VIRTUAL TABLE ft USING fts3(a, b, c) }
+  2 { CREATE TABLE ft(a, b, c) }
+  3 { 
+    CREATE TABLE real(a, b, c);
+    CREATE INDEX i1 ON real(a);
+    CREATE VIEW ft AS SELECT rowid, a, b, c FROM real;
+    CREATE TRIGGER tr1 INSTEAD OF INSERT ON ft BEGIN
+      INSERT INTO real(rowid, a, b, c) VALUES(new.rowid, new.a, new.b, new.c);
+    END;
+    CREATE TRIGGER tr2 INSTEAD OF UPDATE ON ft BEGIN
+      UPDATE real SET rowid=new.rowid, a=new.a, b=new.b, c=new.c 
+      WHERE rowid=old.rowid;
+    END;
+  }
+} {
+  catchsql { DROP VIEW IF EXISTS changes }
+  catchsql { DROP TABLE IF EXISTS ft }
+  catchsql { DROP VIEW IF EXISTS ft }
+  execsql $create_table
+
+  do_execsql_test 1.$tn.0 {
+    INSERT INTO ft(a, b, c) VALUES('a', NULL, 'apple');
+    INSERT INTO ft(a, b, c) VALUES('b', NULL, 'banana');
+    INSERT INTO ft(a, b, c) VALUES('c', NULL, 'cherry');
+    INSERT INTO ft(a, b, c) VALUES('d', NULL, 'damson plum');
+  }
+  
+  do_execsql_test 1.$tn.1 {
+    SELECT a, b, c FROM ft ORDER BY rowid;
+  } {
+    a {} apple
+    b {} banana
+    c {} cherry
+    d {} {damson plum}
+  }
+  
+  do_execsql_test 1.$tn.2 {
+    UPDATE ft SET b=o.c FROM ft AS o WHERE (ft.a == char(unicode(o.a)+1))
+  }
+  
+  do_execsql_test 1.$tn.3 {
+    SELECT a, b, c FROM ft ORDER BY rowid;
+  } {
+    a {} apple
+    b apple banana
+    c banana cherry
+    d cherry {damson plum}
+  }
+
+  do_catchsql_test 1.$tn.4 {
+    UPDATE ft SET c=v FROM changes WHERE a=k;
+  } {1 {no such table: changes}}
+
+  do_execsql_test 1.$tn.5 {
+    create view changes(k, v) AS 
+      VALUES( 'd', 'dewberry' ) UNION ALL
+      VALUES( 'c', 'clementine' ) UNION ALL
+      VALUES( 'b', 'blueberry' ) UNION ALL
+      VALUES( 'a', 'apricot' ) 
+    ;
+  }
+
+  do_execsql_test 1.$tn.6 {
+    UPDATE ft SET c=v FROM changes WHERE a=k;
+  }
+
+  do_execsql_test 1.$tn.7 {
+    SELECT a, b, c FROM ft ORDER BY rowid;
+  } {
+    a {} apricot
+    b apple blueberry
+    c banana clementine
+    d cherry dewberry
+  }
+}
+
+finish_test
+
index b3f35cd0ed1dc396f3c1ebf59ee93b6c5998c224..dd16659a671012dfb952d2fe4156eafc35b2c74f 100644 (file)
@@ -18,6 +18,8 @@ sqlite3 sqlite ""
 
 proc execsql {sql} {
 
+  set sql [string map {{WITHOUT ROWID} {}} $sql]
+
   set lSql [list]
   set frag ""
   while {[string length $sql]>0} {
diff --git a/test/upfrom1.tcl b/test/upfrom1.tcl
new file mode 100644 (file)
index 0000000..5edbb94
--- /dev/null
@@ -0,0 +1,80 @@
+# 2020 April 22
+#
+# 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.
+#
+#***********************************************************************
+#
+
+source [file join [file dirname $argv0] pg_common.tcl]
+
+#=========================================================================
+
+start_test upfrom1 "2020 April 22"
+
+foreach {tn wo} {
+  1 "WITHOUT ROWID"
+  2 ""
+} {
+eval [string map [list %TN% $tn %WITHOUT_ROWID% $wo] {
+execsql_test 1.%TN%.0 {
+  DROP TABLE IF EXISTS t2;
+  CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER, c INTEGER) %WITHOUT_ROWID%;
+  INSERT INTO t2 VALUES(1, 2, 3);
+  INSERT INTO t2 VALUES(4, 5, 6);
+  INSERT INTO t2 VALUES(7, 8, 9);
+
+  DROP TABLE IF EXISTS chng;
+  CREATE TABLE chng(a INTEGER, b INTEGER, c INTEGER);
+  INSERT INTO chng VALUES(1, 100, 1000);
+  INSERT INTO chng VALUES(7, 700, 7000);
+}
+
+execsql_test 1.%TN%.1 {
+  SELECT * FROM t2;
+}
+
+execsql_test 1.%TN%.2 {
+  UPDATE t2 SET b = chng.b, c = chng.c FROM chng WHERE chng.a = t2.a;
+  SELECT * FROM t2 ORDER BY a;
+}
+
+execsql_test 1.%TN%.3 {
+  DELETE FROM t2;
+  INSERT INTO t2 VALUES(1, 2, 3);
+  INSERT INTO t2 VALUES(4, 5, 6);
+  INSERT INTO t2 VALUES(7, 8, 9);
+}
+
+execsql_test 1.%TN%.4 {
+  UPDATE t2 SET (b, c) = (SELECT b, c FROM chng WHERE a=t2.a) 
+    WHERE a IN (SELECT a FROM chng);
+  SELECT * FROM t2 ORDER BY a;
+}
+
+execsql_test 1.%TN%.5 {
+  DROP TABLE IF EXISTS t3;
+  CREATE TABLE t3(a INTEGER PRIMARY KEY, b INTEGER, c TEXT) %WITHOUT_ROWID%;
+  INSERT INTO t3 VALUES(1, 1, 'one');
+  INSERT INTO t3 VALUES(2, 2, 'two');
+  INSERT INTO t3 VALUES(3, 3, 'three');
+
+  DROP TABLE IF EXISTS t4;
+  CREATE TABLE t4(x TEXT);
+  INSERT INTO t4 VALUES('five');
+
+  SELECT * FROM t3 ORDER BY a;
+}
+
+execsql_test 1.%TN%.6 {
+  UPDATE t3 SET c=x FROM t4;
+  SELECT * FROM t3 ORDER BY a;
+}
+}]}
+
+finish_test
+
diff --git a/test/upfrom1.test b/test/upfrom1.test
new file mode 100644 (file)
index 0000000..da873f1
--- /dev/null
@@ -0,0 +1,130 @@
+# 2020 April 22
+#
+# 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.
+#
+
+####################################################
+# DO NOT EDIT! THIS FILE IS AUTOMATICALLY GENERATED!
+####################################################
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix upfrom1
+
+do_execsql_test 1.1.0 {
+  DROP TABLE IF EXISTS t2;
+  CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER, c INTEGER) WITHOUT ROWID;
+  INSERT INTO t2 VALUES(1, 2, 3);
+  INSERT INTO t2 VALUES(4, 5, 6);
+  INSERT INTO t2 VALUES(7, 8, 9);
+
+  DROP TABLE IF EXISTS chng;
+  CREATE TABLE chng(a INTEGER, b INTEGER, c INTEGER);
+  INSERT INTO chng VALUES(1, 100, 1000);
+  INSERT INTO chng VALUES(7, 700, 7000);
+} {}
+
+do_execsql_test 1.1.1 {
+  SELECT * FROM t2;
+} {1 2 3   4 5 6   7 8 9}
+
+do_execsql_test 1.1.2 {
+  UPDATE t2 SET b = chng.b, c = chng.c FROM chng WHERE chng.a = t2.a;
+  SELECT * FROM t2 ORDER BY a;
+} {1 100 1000   4 5 6   7 700 7000}
+
+do_execsql_test 1.1.3 {
+  DELETE FROM t2;
+  INSERT INTO t2 VALUES(1, 2, 3);
+  INSERT INTO t2 VALUES(4, 5, 6);
+  INSERT INTO t2 VALUES(7, 8, 9);
+} {}
+
+do_execsql_test 1.1.4 {
+  UPDATE t2 SET (b, c) = (SELECT b, c FROM chng WHERE a=t2.a) 
+    WHERE a IN (SELECT a FROM chng);
+  SELECT * FROM t2 ORDER BY a;
+} {1 100 1000   4 5 6   7 700 7000}
+
+do_execsql_test 1.1.5 {
+  DROP TABLE IF EXISTS t3;
+  CREATE TABLE t3(a INTEGER PRIMARY KEY, b INTEGER, c TEXT) WITHOUT ROWID;
+  INSERT INTO t3 VALUES(1, 1, 'one');
+  INSERT INTO t3 VALUES(2, 2, 'two');
+  INSERT INTO t3 VALUES(3, 3, 'three');
+
+  DROP TABLE IF EXISTS t4;
+  CREATE TABLE t4(x TEXT);
+  INSERT INTO t4 VALUES('five');
+
+  SELECT * FROM t3 ORDER BY a;
+} {1 1 one   2 2 two   3 3 three}
+
+do_execsql_test 1.1.6 {
+  UPDATE t3 SET c=x FROM t4;
+  SELECT * FROM t3 ORDER BY a;
+} {1 1 five   2 2 five   3 3 five}
+
+do_execsql_test 1.2.0 {
+  DROP TABLE IF EXISTS t2;
+  CREATE TABLE t2(a INTEGER PRIMARY KEY, b INTEGER, c INTEGER) ;
+  INSERT INTO t2 VALUES(1, 2, 3);
+  INSERT INTO t2 VALUES(4, 5, 6);
+  INSERT INTO t2 VALUES(7, 8, 9);
+
+  DROP TABLE IF EXISTS chng;
+  CREATE TABLE chng(a INTEGER, b INTEGER, c INTEGER);
+  INSERT INTO chng VALUES(1, 100, 1000);
+  INSERT INTO chng VALUES(7, 700, 7000);
+} {}
+
+do_execsql_test 1.2.1 {
+  SELECT * FROM t2;
+} {1 2 3   4 5 6   7 8 9}
+
+do_execsql_test 1.2.2 {
+  UPDATE t2 SET b = chng.b, c = chng.c FROM chng WHERE chng.a = t2.a;
+  SELECT * FROM t2 ORDER BY a;
+} {1 100 1000   4 5 6   7 700 7000}
+
+do_execsql_test 1.2.3 {
+  DELETE FROM t2;
+  INSERT INTO t2 VALUES(1, 2, 3);
+  INSERT INTO t2 VALUES(4, 5, 6);
+  INSERT INTO t2 VALUES(7, 8, 9);
+} {}
+
+do_execsql_test 1.2.4 {
+  UPDATE t2 SET (b, c) = (SELECT b, c FROM chng WHERE a=t2.a) 
+    WHERE a IN (SELECT a FROM chng);
+  SELECT * FROM t2 ORDER BY a;
+} {1 100 1000   4 5 6   7 700 7000}
+
+do_execsql_test 1.2.5 {
+  DROP TABLE IF EXISTS t3;
+  CREATE TABLE t3(a INTEGER PRIMARY KEY, b INTEGER, c TEXT) ;
+  INSERT INTO t3 VALUES(1, 1, 'one');
+  INSERT INTO t3 VALUES(2, 2, 'two');
+  INSERT INTO t3 VALUES(3, 3, 'three');
+
+  DROP TABLE IF EXISTS t4;
+  CREATE TABLE t4(x TEXT);
+  INSERT INTO t4 VALUES('five');
+
+  SELECT * FROM t3 ORDER BY a;
+} {1 1 one   2 2 two   3 3 three}
+
+do_execsql_test 1.2.6 {
+  UPDATE t3 SET c=x FROM t4;
+  SELECT * FROM t3 ORDER BY a;
+} {1 1 five   2 2 five   3 3 five}
+
+finish_test