Allow a FROM clause in UPDATE statements.
FossilOrigin-Name: f353a1a613bb7ad8cedcda377a7fe6fd05ee03b1f50665b00b84a868a71c5bec
This commit is contained in:
parent
e238e31413
commit
69887c99d4
32
manifest
32
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
|
||||
|
@ -1 +1 @@
|
||||
b73d9a7d6f7fec0ffc9640902a849289c305f8651e891388c01255c4da7a6c4b
|
||||
f353a1a613bb7ad8cedcda377a7fe6fd05ee03b1f50665b00b84a868a71c5bec
|
20
src/build.c
20
src/build.c
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
20
src/select.c
20
src/select.c
@ -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
|
||||
|
@ -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*);
|
||||
|
514
src/update.c
514
src/update.c
@ -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;
|
||||
}else{
|
||||
sqlite3VdbeAddOp4(v, OP_MakeRecord, iPk, nPk, regKey,
|
||||
sqlite3IndexAffinityStr(db, pPk), nPk);
|
||||
sqlite3VdbeAddOp4Int(v, OP_IdxInsert, iEph, regKey, iPk, nPk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}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);
|
||||
}
|
||||
|
||||
/* 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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
#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);
|
||||
|
105
test/fts4upfrom.test
Normal file
105
test/fts4upfrom.test
Normal 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
|
||||
|
@ -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
80
test/upfrom1.tcl
Normal 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
130
test/upfrom1.test
Normal 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
|
Loading…
Reference in New Issue
Block a user