Allow a FROM clause in UPDATE statements.

FossilOrigin-Name: f353a1a613bb7ad8cedcda377a7fe6fd05ee03b1f50665b00b84a868a71c5bec
This commit is contained in:
dan 2020-04-27 20:55:33 +00:00
parent e238e31413
commit 69887c99d4
12 changed files with 717 additions and 200 deletions

View File

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

View File

@ -1 +1 @@
b73d9a7d6f7fec0ffc9640902a849289c305f8651e891388c01255c4da7a6c4b
f353a1a613bb7ad8cedcda377a7fe6fd05ee03b1f50665b00b84a868a71c5bec

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

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

View 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

View 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);
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

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

View 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,22 +591,34 @@ 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( nChangeFrom ){
sqlite3MultiWrite(pParse);
eOnePass = ONEPASS_OFF;
}else{
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
@ -596,9 +699,10 @@ void sqlite3Update(
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);
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 ){
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,6 +870,7 @@ void sqlite3Update(
sqlite3CodeRowTrigger(pParse, pTrigger, TK_UPDATE, pChanges,
TRIGGER_BEFORE, pTab, regOldRowid, onError, labelContinue);
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
@ -778,6 +909,7 @@ void sqlite3Update(
}
#endif
}
}
if( !isView ){
/* Do constraint checks. */
@ -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,6 +1114,35 @@ static void updateVirtualTable(
addr= sqlite3VdbeAddOp2(v, OP_OpenEphemeral, ephemTab, nArg);
regArg = pParse->nMem + 1;
pParse->nMem += nArg;
if( pSrc->nSrc>1 ){
ExprList *pList = 0;
if( pRowid ){
pList = sqlite3ExprListAppend(pParse, pList, sqlite3ExprDup(db,pRowid,0));
}else{
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{
regRec = ++pParse->nMem;
regRowid = ++pParse->nMem;
@ -996,7 +1157,7 @@ static void updateVirtualTable(
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() */
sqlite3VdbeChangeP5(v, OPFLAG_NOCHNG);/* For sqlite3_vtab_nochange() */
}
}
if( HasRowid(pTab) ){
@ -1040,11 +1201,14 @@ static void updateVirtualTable(
sqlite3VdbeAddOp2(v, OP_NewRowid, ephemTab, regRowid);
sqlite3VdbeAddOp3(v, OP_Insert, ephemTab, regRec, regRowid);
}
}
if( eOnePass==ONEPASS_OFF ){
/* End the virtual table scan */
if( pSrc->nSrc==1 ){
sqlite3WhereEnd(pWInfo);
}
/* Begin scannning through the ephemeral table. */
addr = sqlite3VdbeAddOp1(v, OP_Rewind, ephemTab); VdbeCoverage(v);

105
test/fts4upfrom.test Normal file
View File

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

View 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} {

80
test/upfrom1.tcl Normal file
View File

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

130
test/upfrom1.test Normal file
View File

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