1
0
mirror of https://github.com/sqlite/sqlite.git synced 2025-08-01 06:27:03 +03:00

Enhance UPSERT so that it allows multiple ON CONFLICT clauses and does

not require a conflict target for DO UPDATE.

FossilOrigin-Name: 6b01a24daab1e5bcb0768ebf994368d941b1dfc217bf6b661211d900331e68cf
This commit is contained in:
drh
2020-12-14 15:39:12 +00:00
7 changed files with 772 additions and 169 deletions

View File

@ -1,5 +1,5 @@
C Fix\san\sinteger\soverflow\sproblem\sin\snew\sVACUUM\scode. C Enhance\sUPSERT\sso\sthat\sit\sallows\smultiple\sON\sCONFLICT\sclauses\sand\sdoes\nnot\srequire\sa\sconflict\starget\sfor\sDO\sUPDATE.
D 2020-12-14T15:25:14.417 D 2020-12-14T15:39:12.267
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1 F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724 F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@ -501,7 +501,7 @@ F src/hash.c 8d7dda241d0ebdafb6ffdeda3149a412d7df75102cecfc1021c98d6219823b19
F src/hash.h 9d56a9079d523b648774c1784b74b89bd93fac7b365210157482e4319a468f38 F src/hash.h 9d56a9079d523b648774c1784b74b89bd93fac7b365210157482e4319a468f38
F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144 F src/hwtime.h cb1d7e3e1ed94b7aa6fde95ae2c2daccc3df826be26fc9ed7fd90d1750ae6144
F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71 F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
F src/insert.c 153c5b438d44ba0b8ae7ca467f8f6390b18958d14e681a26e5e861a2ac480750 F src/insert.c 7300982986b0aae32382ce57438998b92efa64e9a7169378e83c1c5d0e2ecdb3
F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
F src/loadext.c 8c9c8cd2bd8eecdb06d9b6e89de7e9e65bae45cc8fc33609cc74023a5c296067 F src/loadext.c 8c9c8cd2bd8eecdb06d9b6e89de7e9e65bae45cc8fc33609cc74023a5c296067
F src/main.c 97e9f137354bc1f76dc9bb60a0a24f8c45cf73b33e80d3ee4c64155336fb820d F src/main.c 97e9f137354bc1f76dc9bb60a0a24f8c45cf73b33e80d3ee4c64155336fb820d
@ -529,7 +529,7 @@ F src/os_win.c 77d39873836f1831a9b0b91894fec45ab0e9ca8e067dc8c549e1d1eca1566fe9
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
F src/pager.c c49952ac5e9cc536778eff528091d79d38b3e45cbeeed4695dc05e207dc6547d F src/pager.c c49952ac5e9cc536778eff528091d79d38b3e45cbeeed4695dc05e207dc6547d
F src/pager.h 4bf9b3213a4b2bebbced5eaa8b219cf25d4a82f385d093cd64b7e93e5285f66f F src/pager.h 4bf9b3213a4b2bebbced5eaa8b219cf25d4a82f385d093cd64b7e93e5285f66f
F src/parse.y 9ce4dfb772608ed5bd3c32f33e943e021e3b06cfd2c01932d4280888fdd2ebed F src/parse.y 6c8aa09a7fa6e0867c3a3d67ef61b911aa392c9b084a61dc632cd93732aef8ad
F src/pcache.c 385ff064bca69789d199a98e2169445dc16e4291fa807babd61d4890c3b34177 F src/pcache.c 385ff064bca69789d199a98e2169445dc16e4291fa807babd61d4890c3b34177
F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586 F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586
F src/pcache1.c 6596e10baf3d8f84cc1585d226cf1ab26564a5f5caf85a15757a281ff977d51a F src/pcache1.c 6596e10baf3d8f84cc1585d226cf1ab26564a5f5caf85a15757a281ff977d51a
@ -545,7 +545,7 @@ F src/shell.c.in e9f674ee4ec6c345679e8a5b16c869c6c59eb1540dd98ac69e4736ecddce009
F src/sqlite.h.in 0e2b4259e49a0eda54d9118eb18a04fcd60e0727a2fd2c81aade0bf57520e706 F src/sqlite.h.in 0e2b4259e49a0eda54d9118eb18a04fcd60e0727a2fd2c81aade0bf57520e706
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8 F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
F src/sqlite3ext.h 61b38c073d5e1e96a3d45271b257aef27d0d13da2bea5347692ae579475cd95e F src/sqlite3ext.h 61b38c073d5e1e96a3d45271b257aef27d0d13da2bea5347692ae579475cd95e
F src/sqliteInt.h 6f99431f5b6f90948fb991907c35d11ca4e7eed43da48e790f8facd92c820711 F src/sqliteInt.h ef8b4b3992d9460c9676c4695416f4141d5c5db4d3d78f89d20662361d6e21f1
F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657 F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1 F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1 F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
@ -608,7 +608,7 @@ F src/tokenize.c 01dba3023659dc6f6b1e054c14b35a0074bd35de10466b99454d33278191d97
F src/treeview.c 4b92992176fb2caefbe06ba5bd06e0e0ebcde3d5564758da672631f17aa51cda F src/treeview.c 4b92992176fb2caefbe06ba5bd06e0e0ebcde3d5564758da672631f17aa51cda
F src/trigger.c 515e79206d40d1d4149129318582e79a6e9db590a7b74e226fdb5b2a6c7e1b10 F src/trigger.c 515e79206d40d1d4149129318582e79a6e9db590a7b74e226fdb5b2a6c7e1b10
F src/update.c 9f126204a6acb96bbe47391ae48e0fc579105d8e76a6d9c4fab3271367476580 F src/update.c 9f126204a6acb96bbe47391ae48e0fc579105d8e76a6d9c4fab3271367476580
F src/upsert.c 2920de71b20f04fe25eb00b655d086f0ba60ea133c59d7fa3325c49838818e78 F src/upsert.c df8f1727d62b5987c4fd302cd4d7c0c84ae57cd65683c5a34a740dfe24039235
F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0 F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0
F src/util.c c0c7977de7ef9b8cb10f6c85f2d0557889a658f817b0455909a49179ba4c8002 F src/util.c c0c7977de7ef9b8cb10f6c85f2d0557889a658f817b0455909a49179ba4c8002
F src/vacuum.c 492422c1463c076473bae1858799c7a0a5fe87a133d1223239447c422cd26286 F src/vacuum.c 492422c1463c076473bae1858799c7a0a5fe87a133d1223239447c422cd26286
@ -1643,6 +1643,7 @@ F test/upsert1.test 88f9e258c6a0eeeb85937b08831e8daad440ba41f125af48439e9d33f266
F test/upsert2.test 9c3cdbb1a890227f6504ce4b0e3de68f4cdfa16bb21d8641208a9239896c5a09 F test/upsert2.test 9c3cdbb1a890227f6504ce4b0e3de68f4cdfa16bb21d8641208a9239896c5a09
F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c F test/upsert3.test 88d7d590a1948a9cb6eac1b54b0642f67a9f35a1fc0f19b200e97d5d39e3179c
F test/upsert4.test 25d2a1da92f149331ae0c51ca6e3eee78189577585eab92de149900d62994fa5 F test/upsert4.test 25d2a1da92f149331ae0c51ca6e3eee78189577585eab92de149900d62994fa5
F test/upsert5.test f49faf5f15b5c3641c6f5d7c7cc531ef5ac997567b2b6bb7bc96f7c88753ca0b
F test/upsertfault.test f21ca47740841fdb4d61acfa7b17646d773e67724fe8c185b71c018db8a94b35 F test/upsertfault.test f21ca47740841fdb4d61acfa7b17646d773e67724fe8c185b71c018db8a94b35
F test/uri.test 3481026f00ade6dfe8adb7acb6e1e47b04369568 F test/uri.test 3481026f00ade6dfe8adb7acb6e1e47b04369568
F test/uri2.test 9d3ba7a53ee167572d53a298ee4a5d38ec4a8fb7 F test/uri2.test 9d3ba7a53ee167572d53a298ee4a5d38ec4a8fb7
@ -1889,7 +1890,8 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0 F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
P dd058da85ca54ae70e26cb0bdc75ff42998d4a8b29a5e2dcac44ee0e45776a85 P 59b4367fd852ba1bfefdff99a27b11657495a3f114ed6f85fdcf6c532f4a19fa e5a8fa50f4e5e5c24664452eda4af80904f75e5123b8f84353347dbd505d416d
R 0d937f0ec9f044837ba6918ca3838d82 R 5965c0546552f5972781a0c11e3dea88
U dan T +closed e5a8fa50f4e5e5c24664452eda4af80904f75e5123b8f84353347dbd505d416d
Z 57f4f0625729074fba29cd7d21629db2 U drh
Z 3b5c01b64f44977242fcbbbded19a49d

View File

@ -1 +1 @@
59b4367fd852ba1bfefdff99a27b11657495a3f114ed6f85fdcf6c532f4a19fa 6b01a24daab1e5bcb0768ebf994368d941b1dfc217bf6b661211d900331e68cf

View File

@ -975,6 +975,7 @@ void sqlite3Insert(
} }
#ifndef SQLITE_OMIT_UPSERT #ifndef SQLITE_OMIT_UPSERT
if( pUpsert ){ if( pUpsert ){
Upsert *pNx;
if( IsVirtual(pTab) ){ if( IsVirtual(pTab) ){
sqlite3ErrorMsg(pParse, "UPSERT not implemented for virtual table \"%s\"", sqlite3ErrorMsg(pParse, "UPSERT not implemented for virtual table \"%s\"",
pTab->zName); pTab->zName);
@ -988,13 +989,17 @@ void sqlite3Insert(
goto insert_cleanup; goto insert_cleanup;
} }
pTabList->a[0].iCursor = iDataCur; pTabList->a[0].iCursor = iDataCur;
pUpsert->pUpsertSrc = pTabList; pNx = pUpsert;
pUpsert->regData = regData; do{
pUpsert->iDataCur = iDataCur; pNx->pUpsertSrc = pTabList;
pUpsert->iIdxCur = iIdxCur; pNx->regData = regData;
if( pUpsert->pUpsertTarget ){ pNx->iDataCur = iDataCur;
sqlite3UpsertAnalyzeTarget(pParse, pTabList, pUpsert); pNx->iIdxCur = iIdxCur;
} if( pNx->pUpsertTarget ){
sqlite3UpsertAnalyzeTarget(pParse, pTabList, pNx);
}
pNx = pNx->pNextUpsert;
}while( pNx!=0 );
} }
#endif #endif
@ -1399,6 +1404,70 @@ int sqlite3ExprReferencesUpdatedColumn(
return w.eCode!=0; return w.eCode!=0;
} }
/*
** The sqlite3GenerateConstraintChecks() routine usually wants to visit
** the indexes of a table in the order provided in the Table->pIndex list.
** However, sometimes (rarely - when there is an upsert) it wants to visit
** the indexes in a different order. The following data structures accomplish
** this.
**
** The IndexIterator object is used to walk through all of the indexes
** of a table in either Index.pNext order, or in some other order established
** by an array of IndexListTerm objects.
*/
typedef struct IndexListTerm IndexListTerm;
typedef struct IndexIterator IndexIterator;
struct IndexIterator {
int eType; /* 0 for Index.pNext list. 1 for an array of IndexListTerm */
int i; /* Index of the current item from the list */
union {
struct { /* Use this object for eType==0: A Index.pNext list */
Index *pIdx; /* The current Index */
} lx;
struct { /* Use this object for eType==1; Array of IndexListTerm */
int nIdx; /* Size of the array */
IndexListTerm *aIdx; /* Array of IndexListTerms */
} ax;
} u;
};
/* When IndexIterator.eType==1, then each index is an array of instances
** of the following object
*/
struct IndexListTerm {
Index *p; /* The index */
int ix; /* Which entry in the original Table.pIndex list is this index*/
};
/* Return the first index on the list */
static Index *indexIteratorFirst(IndexIterator *pIter, int *pIx){
assert( pIter->i==0 );
if( pIter->eType ){
*pIx = pIter->u.ax.aIdx[0].ix;
return pIter->u.ax.aIdx[0].p;
}else{
*pIx = 0;
return pIter->u.lx.pIdx;
}
}
/* Return the next index from the list. Return NULL when out of indexes */
static Index *indexIteratorNext(IndexIterator *pIter, int *pIx){
if( pIter->eType ){
int i = ++pIter->i;
if( i>=pIter->u.ax.nIdx ){
*pIx = i;
return 0;
}
*pIx = pIter->u.ax.aIdx[i].ix;
return pIter->u.ax.aIdx[i].p;
}else{
++(*pIx);
pIter->u.lx.pIdx = pIter->u.lx.pIdx->pNext;
return pIter->u.lx.pIdx;
}
}
/* /*
** Generate code to do constraint checks prior to an INSERT or an UPDATE ** Generate code to do constraint checks prior to an INSERT or an UPDATE
** on table pTab. ** on table pTab.
@ -1507,7 +1576,7 @@ void sqlite3GenerateConstraintChecks(
){ ){
Vdbe *v; /* VDBE under constrution */ Vdbe *v; /* VDBE under constrution */
Index *pIdx; /* Pointer to one of the indices */ Index *pIdx; /* Pointer to one of the indices */
Index *pPk = 0; /* The PRIMARY KEY index */ Index *pPk = 0; /* The PRIMARY KEY index for WITHOUT ROWID tables */
sqlite3 *db; /* Database connection */ sqlite3 *db; /* Database connection */
int i; /* loop counter */ int i; /* loop counter */
int ix; /* Index loop counter */ int ix; /* Index loop counter */
@ -1515,11 +1584,11 @@ void sqlite3GenerateConstraintChecks(
int onError; /* Conflict resolution strategy */ int onError; /* Conflict resolution strategy */
int seenReplace = 0; /* True if REPLACE is used to resolve INT PK conflict */ int seenReplace = 0; /* True if REPLACE is used to resolve INT PK conflict */
int nPkField; /* Number of fields in PRIMARY KEY. 1 for ROWID tables */ int nPkField; /* Number of fields in PRIMARY KEY. 1 for ROWID tables */
Index *pUpIdx = 0; /* Index to which to apply the upsert */ Upsert *pUpsertClause = 0; /* The specific ON CONFLICT clause for pIdx */
u8 isUpdate; /* True if this is an UPDATE operation */ u8 isUpdate; /* True if this is an UPDATE operation */
u8 bAffinityDone = 0; /* True if the OP_Affinity operation has been run */ u8 bAffinityDone = 0; /* True if the OP_Affinity operation has been run */
int upsertBypass = 0; /* Address of Goto to bypass upsert subroutine */ int upsertIpkReturn = 0; /* Address of Goto at end of IPK uniqueness check */
int upsertJump = 0; /* Address of Goto that jumps into upsert subroutine */ int upsertIpkDelay = 0; /* Address of Goto to bypass initial IPK check */
int ipkTop = 0; /* Top of the IPK uniqueness check */ int ipkTop = 0; /* Top of the IPK uniqueness check */
int ipkBottom = 0; /* OP_Goto at the end of the IPK uniqueness check */ int ipkBottom = 0; /* OP_Goto at the end of the IPK uniqueness check */
/* Variables associated with retesting uniqueness constraints after /* Variables associated with retesting uniqueness constraints after
@ -1529,6 +1598,7 @@ void sqlite3GenerateConstraintChecks(
int lblRecheckOk = 0; /* Each recheck jumps to this label if it passes */ int lblRecheckOk = 0; /* Each recheck jumps to this label if it passes */
Trigger *pTrigger; /* List of DELETE triggers on the table pTab */ Trigger *pTrigger; /* List of DELETE triggers on the table pTab */
int nReplaceTrig = 0; /* Number of replace triggers coded */ int nReplaceTrig = 0; /* Number of replace triggers coded */
IndexIterator sIdxIter; /* Index iterator */
isUpdate = regOldData!=0; isUpdate = regOldData!=0;
db = pParse->db; db = pParse->db;
@ -1726,19 +1796,63 @@ void sqlite3GenerateConstraintChecks(
** list of indexes attached to a table puts all OE_Replace indexes last ** list of indexes attached to a table puts all OE_Replace indexes last
** in the list. See sqlite3CreateIndex() for where that happens. ** in the list. See sqlite3CreateIndex() for where that happens.
*/ */
sIdxIter.eType = 0;
sIdxIter.i = 0;
sIdxIter.u.ax.aIdx = 0; /* Silence harmless compiler warning */
sIdxIter.u.lx.pIdx = pTab->pIndex;
if( pUpsert ){ if( pUpsert ){
if( pUpsert->pUpsertTarget==0 ){ if( pUpsert->pUpsertTarget==0 ){
/* An ON CONFLICT DO NOTHING clause, without a constraint-target. /* There is just on ON CONFLICT clause and it has no constraint-target */
** Make all unique constraint resolution be OE_Ignore */ assert( pUpsert->pNextUpsert==0 );
assert( pUpsert->pUpsertSet==0 ); if( pUpsert->isDoUpdate==0 ){
overrideError = OE_Ignore; /* A single ON CONFLICT DO NOTHING clause, without a constraint-target.
pUpsert = 0; ** Make all unique constraint resolution be OE_Ignore */
}else if( (pUpIdx = pUpsert->pUpsertIdx)!=0 ){ overrideError = OE_Ignore;
/* If the constraint-target uniqueness check must be run first. pUpsert = 0;
** Jump to that uniqueness check now */ }else{
upsertJump = sqlite3VdbeAddOp0(v, OP_Goto); /* A single ON CONFLICT DO UPDATE. Make all resolutions OE_Update */
VdbeComment((v, "UPSERT constraint goes first")); overrideError = OE_Update;
}
}else if( pTab->pIndex!=0 ){
/* Otherwise, we'll need to run the IndexListTerm array version of the
** iterator to ensure that all of the ON CONFLICT conditions are
** checked first and in order. */
int nIdx, jj;
u64 nByte;
Upsert *pTerm;
u8 *bUsed;
for(nIdx=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, nIdx++){
assert( aRegIdx[nIdx]>0 );
}
sIdxIter.eType = 1;
sIdxIter.u.ax.nIdx = nIdx;
nByte = (sizeof(IndexListTerm)+1)*nIdx + nIdx;
sIdxIter.u.ax.aIdx = sqlite3DbMallocZero(db, nByte);
if( sIdxIter.u.ax.aIdx==0 ) return; /* OOM */
bUsed = (u8*)&sIdxIter.u.ax.aIdx[nIdx];
pUpsert->pToFree = sIdxIter.u.ax.aIdx;
for(i=0, pTerm=pUpsert; pTerm; pTerm=pTerm->pNextUpsert){
if( pTerm->pUpsertTarget==0 ) break;
if( pTerm->pUpsertIdx==0 ) continue; /* Skip ON CONFLICT for the IPK */
jj = 0;
pIdx = pTab->pIndex;
while( ALWAYS(pIdx!=0) && pIdx!=pTerm->pUpsertIdx ){
pIdx = pIdx->pNext;
jj++;
}
if( bUsed[jj] ) continue; /* Duplicate ON CONFLICT clause ignored */
bUsed[jj] = 1;
sIdxIter.u.ax.aIdx[i].p = pIdx;
sIdxIter.u.ax.aIdx[i].ix = jj;
i++;
}
for(jj=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, jj++){
if( bUsed[jj] ) continue;
sIdxIter.u.ax.aIdx[i].p = pIdx;
sIdxIter.u.ax.aIdx[i].ix = jj;
i++;
}
assert( i==nIdx );
} }
} }
@ -1801,11 +1915,20 @@ void sqlite3GenerateConstraintChecks(
} }
/* figure out whether or not upsert applies in this case */ /* figure out whether or not upsert applies in this case */
if( pUpsert && pUpsert->pUpsertIdx==0 ){ if( pUpsert ){
if( pUpsert->pUpsertSet==0 ){ pUpsertClause = sqlite3UpsertOfIndex(pUpsert,0);
onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */ if( pUpsertClause!=0 ){
}else{ if( pUpsertClause->isDoUpdate==0 ){
onError = OE_Update; /* DO UPDATE */ onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */
}else{
onError = OE_Update; /* DO UPDATE */
}
}
if( pUpsertClause!=pUpsert ){
/* The first ON CONFLICT clause has a conflict target other than
** the IPK. We have to jump ahead to that first ON CONFLICT clause
** and then come back here and deal with the IPK afterwards */
upsertIpkDelay = sqlite3VdbeAddOp0(v, OP_Goto);
} }
} }
@ -1912,7 +2035,9 @@ void sqlite3GenerateConstraintChecks(
} }
} }
sqlite3VdbeResolveLabel(v, addrRowidOk); sqlite3VdbeResolveLabel(v, addrRowidOk);
if( ipkTop ){ if( pUpsert && pUpsertClause!=pUpsert ){
upsertIpkReturn = sqlite3VdbeAddOp0(v, OP_Goto);
}else if( ipkTop ){
ipkBottom = sqlite3VdbeAddOp0(v, OP_Goto); ipkBottom = sqlite3VdbeAddOp0(v, OP_Goto);
sqlite3VdbeJumpHere(v, ipkTop-1); sqlite3VdbeJumpHere(v, ipkTop-1);
} }
@ -1925,7 +2050,10 @@ void sqlite3GenerateConstraintChecks(
** This loop also handles the case of the PRIMARY KEY index for a ** This loop also handles the case of the PRIMARY KEY index for a
** WITHOUT ROWID table. ** WITHOUT ROWID table.
*/ */
for(ix=0, pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext, ix++){ for(pIdx = indexIteratorFirst(&sIdxIter, &ix);
pIdx;
pIdx = indexIteratorNext(&sIdxIter, &ix)
){
int regIdx; /* Range of registers hold conent for pIdx */ int regIdx; /* Range of registers hold conent for pIdx */
int regR; /* Range of registers holding conflicting PK */ int regR; /* Range of registers holding conflicting PK */
int iThisCur; /* Cursor for this UNIQUE index */ int iThisCur; /* Cursor for this UNIQUE index */
@ -1933,15 +2061,14 @@ void sqlite3GenerateConstraintChecks(
int addrConflictCk; /* First opcode in the conflict check logic */ int addrConflictCk; /* First opcode in the conflict check logic */
if( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */ if( aRegIdx[ix]==0 ) continue; /* Skip indices that do not change */
if( pUpIdx==pIdx ){ if( pUpsert ){
addrUniqueOk = upsertJump+1; pUpsertClause = sqlite3UpsertOfIndex(pUpsert, pIdx);
upsertBypass = sqlite3VdbeGoto(v, 0); if( upsertIpkDelay && pUpsertClause==pUpsert ){
VdbeComment((v, "Skip upsert subroutine")); sqlite3VdbeJumpHere(v, upsertIpkDelay);
sqlite3VdbeJumpHere(v, upsertJump); }
}else{
addrUniqueOk = sqlite3VdbeMakeLabel(pParse);
} }
if( bAffinityDone==0 && (pUpIdx==0 || pUpIdx==pIdx) ){ addrUniqueOk = sqlite3VdbeMakeLabel(pParse);
if( bAffinityDone==0 ){
sqlite3TableAffinity(v, pTab, regNewData+1); sqlite3TableAffinity(v, pTab, regNewData+1);
bAffinityDone = 1; bAffinityDone = 1;
} }
@ -2012,8 +2139,8 @@ void sqlite3GenerateConstraintChecks(
} }
/* Figure out if the upsert clause applies to this index */ /* Figure out if the upsert clause applies to this index */
if( pUpIdx==pIdx ){ if( pUpsertClause ){
if( pUpsert->pUpsertSet==0 ){ if( pUpsertClause->isDoUpdate==0 ){
onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */ onError = OE_Ignore; /* DO NOTHING is the same as INSERT OR IGNORE */
}else{ }else{
onError = OE_Update; /* DO UPDATE */ onError = OE_Update; /* DO UPDATE */
@ -2051,7 +2178,7 @@ void sqlite3GenerateConstraintChecks(
regIdx, pIdx->nKeyCol); VdbeCoverage(v); regIdx, pIdx->nKeyCol); VdbeCoverage(v);
/* Generate code to handle collisions */ /* Generate code to handle collisions */
regR = (pIdx==pPk) ? regIdx : sqlite3GetTempRange(pParse, nPkField); regR = pIdx==pPk ? regIdx : sqlite3GetTempRange(pParse, nPkField);
if( isUpdate || onError==OE_Replace ){ if( isUpdate || onError==OE_Replace ){
if( HasRowid(pTab) ){ if( HasRowid(pTab) ){
sqlite3VdbeAddOp2(v, OP_IdxRowid, iThisCur, regR); sqlite3VdbeAddOp2(v, OP_IdxRowid, iThisCur, regR);
@ -2203,13 +2330,16 @@ void sqlite3GenerateConstraintChecks(
break; break;
} }
} }
if( pUpIdx==pIdx ){ sqlite3VdbeResolveLabel(v, addrUniqueOk);
sqlite3VdbeGoto(v, upsertJump+1);
sqlite3VdbeJumpHere(v, upsertBypass);
}else{
sqlite3VdbeResolveLabel(v, addrUniqueOk);
}
if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField); if( regR!=regIdx ) sqlite3ReleaseTempRange(pParse, regR, nPkField);
if( pUpsertClause
&& upsertIpkReturn
&& sqlite3UpsertNextIsIPK(pUpsertClause)
){
sqlite3VdbeGoto(v, upsertIpkDelay+1);
sqlite3VdbeJumpHere(v, upsertIpkReturn);
upsertIpkReturn = 0;
}
} }
/* If the IPK constraint is a REPLACE, run it last */ /* If the IPK constraint is a REPLACE, run it last */

View File

@ -960,12 +960,14 @@ cmd ::= with insert_cmd(R) INTO xfullname(X) idlist_opt(F) DEFAULT VALUES.
//%destructor upsert {sqlite3UpsertDelete(pParse->db,$$);} //%destructor upsert {sqlite3UpsertDelete(pParse->db,$$);}
upsert(A) ::= . { A = 0; } upsert(A) ::= . { A = 0; }
upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW) upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW)
DO UPDATE SET setlist(Z) where_opt(W). DO UPDATE SET setlist(Z) where_opt(W) upsert(N).
{ A = sqlite3UpsertNew(pParse->db,T,TW,Z,W);} { A = sqlite3UpsertNew(pParse->db,T,TW,Z,W,N);}
upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW) DO NOTHING. upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW) DO NOTHING upsert(N).
{ A = sqlite3UpsertNew(pParse->db,T,TW,0,0); } { A = sqlite3UpsertNew(pParse->db,T,TW,0,0,N); }
upsert(A) ::= ON CONFLICT DO NOTHING. upsert(A) ::= ON CONFLICT DO NOTHING.
{ A = sqlite3UpsertNew(pParse->db,0,0,0,0); } { A = sqlite3UpsertNew(pParse->db,0,0,0,0,0); }
upsert(A) ::= ON CONFLICT DO UPDATE SET setlist(Z) where_opt(W).
{ A = sqlite3UpsertNew(pParse->db,0,0,Z,W,0);}
%type insert_cmd {int} %type insert_cmd {int}
insert_cmd(A) ::= INSERT orconf(R). {A = R;} insert_cmd(A) ::= INSERT orconf(R). {A = R;}

View File

@ -2316,16 +2316,22 @@ struct FKey {
** is returned. REPLACE means that preexisting database rows that caused ** is returned. REPLACE means that preexisting database rows that caused
** a UNIQUE constraint violation are removed so that the new insert or ** a UNIQUE constraint violation are removed so that the new insert or
** update can proceed. Processing continues and no error is reported. ** update can proceed. Processing continues and no error is reported.
** UPDATE applies to insert operations only and means that the insert
** is omitted and the DO UPDATE clause of an upsert is run instead.
** **
** RESTRICT, SETNULL, and CASCADE actions apply only to foreign keys. ** RESTRICT, SETNULL, SETDFLT, and CASCADE actions apply only to foreign keys.
** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the ** RESTRICT is the same as ABORT for IMMEDIATE foreign keys and the
** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign ** same as ROLLBACK for DEFERRED keys. SETNULL means that the foreign
** key is set to NULL. CASCADE means that a DELETE or UPDATE of the ** key is set to NULL. SETDFLT means that the foreign key is set
** to its default value. CASCADE means that a DELETE or UPDATE of the
** referenced table row is propagated into the row that holds the ** referenced table row is propagated into the row that holds the
** foreign key. ** foreign key.
** **
** The OE_Default value is a place holder that means to use whatever
** conflict resolution algorthm is required from context.
**
** The following symbolic values are used to record which type ** The following symbolic values are used to record which type
** of action to take. ** of conflict resolution action to take.
*/ */
#define OE_None 0 /* There is no constraint to check */ #define OE_None 0 /* There is no constraint to check */
#define OE_Rollback 1 /* Fail the operation and rollback the transaction */ #define OE_Rollback 1 /* Fail the operation and rollback the transaction */
@ -3079,15 +3085,21 @@ struct NameContext {
** WHERE clause is omitted. ** WHERE clause is omitted.
*/ */
struct Upsert { struct Upsert {
ExprList *pUpsertTarget; /* Optional description of conflicting index */ ExprList *pUpsertTarget; /* Optional description of conflict target */
Expr *pUpsertTargetWhere; /* WHERE clause for partial index targets */ Expr *pUpsertTargetWhere; /* WHERE clause for partial index targets */
ExprList *pUpsertSet; /* The SET clause from an ON CONFLICT UPDATE */ ExprList *pUpsertSet; /* The SET clause from an ON CONFLICT UPDATE */
Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */ Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */
/* The fields above comprise the parse tree for the upsert clause. Upsert *pNextUpsert; /* Next ON CONFLICT clause in the list */
** The fields below are used to transfer information from the INSERT u8 isDoUpdate; /* True for DO UPDATE. False for DO NOTHING */
** processing down into the UPDATE processing while generating code. /* Above this point is the parse tree for the ON CONFLICT clauses.
** Upsert owns the memory allocated above, but not the memory below. */ ** The next group of fields stores intermediate data. */
Index *pUpsertIdx; /* Constraint that pUpsertTarget identifies */ void *pToFree; /* Free memory when deleting the Upsert object */
/* All fields above are owned by the Upsert object and must be freed
** when the Upsert is destroyed. The fields below are used to transfer
** information from the INSERT processing down into the UPDATE processing
** while generating code. The fields below are owned by the INSERT
** statement and will be freed by INSERT processing. */
Index *pUpsertIdx; /* UNIQUE constraint specified by pUpsertTarget */
SrcList *pUpsertSrc; /* Table to be updated */ SrcList *pUpsertSrc; /* Table to be updated */
int regData; /* First register holding array of VALUES */ int regData; /* First register holding array of VALUES */
int iDataCur; /* Index of the data cursor */ int iDataCur; /* Index of the data cursor */
@ -4837,15 +4849,19 @@ const char *sqlite3JournalModename(int);
#define sqlite3WithDelete(x,y) #define sqlite3WithDelete(x,y)
#endif #endif
#ifndef SQLITE_OMIT_UPSERT #ifndef SQLITE_OMIT_UPSERT
Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*); Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*);
void sqlite3UpsertDelete(sqlite3*,Upsert*); void sqlite3UpsertDelete(sqlite3*,Upsert*);
Upsert *sqlite3UpsertDup(sqlite3*,Upsert*); Upsert *sqlite3UpsertDup(sqlite3*,Upsert*);
int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*); int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*);
void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int); void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int);
Upsert *sqlite3UpsertOfIndex(Upsert*,Index*);
int sqlite3UpsertNextIsIPK(Upsert*);
#else #else
#define sqlite3UpsertNew(v,w,x,y,z) ((Upsert*)0) #define sqlite3UpsertNew(u,v,w,x,y,z) ((Upsert*)0)
#define sqlite3UpsertDelete(x,y) #define sqlite3UpsertDelete(x,y)
#define sqlite3UpsertDup(x,y) ((Upsert*)0) #define sqlite3UpsertDup(x,y) ((Upsert*)0)
#define sqlite3UpsertOfIndex(x,y) ((Upsert*)0)
#define sqlite3UpsertNextIsIPK(x) 0
#endif #endif

View File

@ -18,15 +18,22 @@
/* /*
** Free a list of Upsert objects ** Free a list of Upsert objects
*/ */
void sqlite3UpsertDelete(sqlite3 *db, Upsert *p){ static void SQLITE_NOINLINE upsertDelete(sqlite3 *db, Upsert *p){
if( p ){ do{
Upsert *pNext = p->pNextUpsert;
sqlite3ExprListDelete(db, p->pUpsertTarget); sqlite3ExprListDelete(db, p->pUpsertTarget);
sqlite3ExprDelete(db, p->pUpsertTargetWhere); sqlite3ExprDelete(db, p->pUpsertTargetWhere);
sqlite3ExprListDelete(db, p->pUpsertSet); sqlite3ExprListDelete(db, p->pUpsertSet);
sqlite3ExprDelete(db, p->pUpsertWhere); sqlite3ExprDelete(db, p->pUpsertWhere);
sqlite3DbFree(db, p->pToFree);
sqlite3DbFree(db, p); sqlite3DbFree(db, p);
} p = pNext;
}while( p );
} }
void sqlite3UpsertDelete(sqlite3 *db, Upsert *p){
if( p ) upsertDelete(db, p);
}
/* /*
** Duplicate an Upsert object. ** Duplicate an Upsert object.
@ -37,7 +44,8 @@ Upsert *sqlite3UpsertDup(sqlite3 *db, Upsert *p){
sqlite3ExprListDup(db, p->pUpsertTarget, 0), sqlite3ExprListDup(db, p->pUpsertTarget, 0),
sqlite3ExprDup(db, p->pUpsertTargetWhere, 0), sqlite3ExprDup(db, p->pUpsertTargetWhere, 0),
sqlite3ExprListDup(db, p->pUpsertSet, 0), sqlite3ExprListDup(db, p->pUpsertSet, 0),
sqlite3ExprDup(db, p->pUpsertWhere, 0) sqlite3ExprDup(db, p->pUpsertWhere, 0),
sqlite3UpsertDup(db, p->pNextUpsert)
); );
} }
@ -49,22 +57,25 @@ Upsert *sqlite3UpsertNew(
ExprList *pTarget, /* Target argument to ON CONFLICT, or NULL */ ExprList *pTarget, /* Target argument to ON CONFLICT, or NULL */
Expr *pTargetWhere, /* Optional WHERE clause on the target */ Expr *pTargetWhere, /* Optional WHERE clause on the target */
ExprList *pSet, /* UPDATE columns, or NULL for a DO NOTHING */ ExprList *pSet, /* UPDATE columns, or NULL for a DO NOTHING */
Expr *pWhere /* WHERE clause for the ON CONFLICT UPDATE */ Expr *pWhere, /* WHERE clause for the ON CONFLICT UPDATE */
Upsert *pNext /* Next ON CONFLICT clause in the list */
){ ){
Upsert *pNew; Upsert *pNew;
pNew = sqlite3DbMallocRaw(db, sizeof(Upsert)); pNew = sqlite3DbMallocZero(db, sizeof(Upsert));
if( pNew==0 ){ if( pNew==0 ){
sqlite3ExprListDelete(db, pTarget); sqlite3ExprListDelete(db, pTarget);
sqlite3ExprDelete(db, pTargetWhere); sqlite3ExprDelete(db, pTargetWhere);
sqlite3ExprListDelete(db, pSet); sqlite3ExprListDelete(db, pSet);
sqlite3ExprDelete(db, pWhere); sqlite3ExprDelete(db, pWhere);
sqlite3UpsertDelete(db, pNext);
return 0; return 0;
}else{ }else{
pNew->pUpsertTarget = pTarget; pNew->pUpsertTarget = pTarget;
pNew->pUpsertTargetWhere = pTargetWhere; pNew->pUpsertTargetWhere = pTargetWhere;
pNew->pUpsertSet = pSet; pNew->pUpsertSet = pSet;
pNew->pUpsertWhere = pWhere; pNew->pUpsertWhere = pWhere;
pNew->pUpsertIdx = 0; pNew->isDoUpdate = pSet!=0;
pNew->pNextUpsert = pNext;
} }
return pNew; return pNew;
} }
@ -89,6 +100,7 @@ int sqlite3UpsertAnalyzeTarget(
Expr *pTerm; /* One term of the conflict-target clause */ Expr *pTerm; /* One term of the conflict-target clause */
NameContext sNC; /* Context for resolving symbolic names */ NameContext sNC; /* Context for resolving symbolic names */
Expr sCol[2]; /* Index column converted into an Expr */ Expr sCol[2]; /* Index column converted into an Expr */
int nClause = 0; /* Counter of ON CONFLICT clauses */
assert( pTabList->nSrc==1 ); assert( pTabList->nSrc==1 );
assert( pTabList->a[0].pTab!=0 ); assert( pTabList->a[0].pTab!=0 );
@ -102,87 +114,131 @@ int sqlite3UpsertAnalyzeTarget(
memset(&sNC, 0, sizeof(sNC)); memset(&sNC, 0, sizeof(sNC));
sNC.pParse = pParse; sNC.pParse = pParse;
sNC.pSrcList = pTabList; sNC.pSrcList = pTabList;
rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget); for(; pUpsert && pUpsert->pUpsertTarget;
if( rc ) return rc; pUpsert=pUpsert->pNextUpsert, nClause++){
rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere); rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget);
if( rc ) return rc; if( rc ) return rc;
rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere);
/* Check to see if the conflict target matches the rowid. */ if( rc ) return rc;
pTab = pTabList->a[0].pTab;
pTarget = pUpsert->pUpsertTarget; /* Check to see if the conflict target matches the rowid. */
iCursor = pTabList->a[0].iCursor; pTab = pTabList->a[0].pTab;
if( HasRowid(pTab) pTarget = pUpsert->pUpsertTarget;
&& pTarget->nExpr==1 iCursor = pTabList->a[0].iCursor;
&& (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN if( HasRowid(pTab)
&& pTerm->iColumn==XN_ROWID && pTarget->nExpr==1
){ && (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN
/* The conflict-target is the rowid of the primary table */ && pTerm->iColumn==XN_ROWID
assert( pUpsert->pUpsertIdx==0 ); ){
return SQLITE_OK; /* The conflict-target is the rowid of the primary table */
} assert( pUpsert->pUpsertIdx==0 );
/* Initialize sCol[0..1] to be an expression parse tree for a
** single column of an index. The sCol[0] node will be the TK_COLLATE
** operator and sCol[1] will be the TK_COLUMN operator. Code below
** will populate the specific collation and column number values
** prior to comparing against the conflict-target expression.
*/
memset(sCol, 0, sizeof(sCol));
sCol[0].op = TK_COLLATE;
sCol[0].pLeft = &sCol[1];
sCol[1].op = TK_COLUMN;
sCol[1].iTable = pTabList->a[0].iCursor;
/* Check for matches against other indexes */
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
int ii, jj, nn;
if( !IsUniqueIndex(pIdx) ) continue;
if( pTarget->nExpr!=pIdx->nKeyCol ) continue;
if( pIdx->pPartIdxWhere ){
if( pUpsert->pUpsertTargetWhere==0 ) continue;
if( sqlite3ExprCompare(pParse, pUpsert->pUpsertTargetWhere,
pIdx->pPartIdxWhere, iCursor)!=0 ){
continue;
}
}
nn = pIdx->nKeyCol;
for(ii=0; ii<nn; ii++){
Expr *pExpr;
sCol[0].u.zToken = (char*)pIdx->azColl[ii];
if( pIdx->aiColumn[ii]==XN_EXPR ){
assert( pIdx->aColExpr!=0 );
assert( pIdx->aColExpr->nExpr>ii );
pExpr = pIdx->aColExpr->a[ii].pExpr;
if( pExpr->op!=TK_COLLATE ){
sCol[0].pLeft = pExpr;
pExpr = &sCol[0];
}
}else{
sCol[0].pLeft = &sCol[1];
sCol[1].iColumn = pIdx->aiColumn[ii];
pExpr = &sCol[0];
}
for(jj=0; jj<nn; jj++){
if( sqlite3ExprCompare(pParse, pTarget->a[jj].pExpr, pExpr,iCursor)<2 ){
break; /* Column ii of the index matches column jj of target */
}
}
if( jj>=nn ){
/* The target contains no match for column jj of the index */
break;
}
}
if( ii<nn ){
/* Column ii of the index did not match any term of the conflict target.
** Continue the search with the next index. */
continue; continue;
} }
pUpsert->pUpsertIdx = pIdx;
return SQLITE_OK; /* Initialize sCol[0..1] to be an expression parse tree for a
** single column of an index. The sCol[0] node will be the TK_COLLATE
** operator and sCol[1] will be the TK_COLUMN operator. Code below
** will populate the specific collation and column number values
** prior to comparing against the conflict-target expression.
*/
memset(sCol, 0, sizeof(sCol));
sCol[0].op = TK_COLLATE;
sCol[0].pLeft = &sCol[1];
sCol[1].op = TK_COLUMN;
sCol[1].iTable = pTabList->a[0].iCursor;
/* Check for matches against other indexes */
for(pIdx=pTab->pIndex; pIdx; pIdx=pIdx->pNext){
int ii, jj, nn;
if( !IsUniqueIndex(pIdx) ) continue;
if( pTarget->nExpr!=pIdx->nKeyCol ) continue;
if( pIdx->pPartIdxWhere ){
if( pUpsert->pUpsertTargetWhere==0 ) continue;
if( sqlite3ExprCompare(pParse, pUpsert->pUpsertTargetWhere,
pIdx->pPartIdxWhere, iCursor)!=0 ){
continue;
}
}
nn = pIdx->nKeyCol;
for(ii=0; ii<nn; ii++){
Expr *pExpr;
sCol[0].u.zToken = (char*)pIdx->azColl[ii];
if( pIdx->aiColumn[ii]==XN_EXPR ){
assert( pIdx->aColExpr!=0 );
assert( pIdx->aColExpr->nExpr>ii );
pExpr = pIdx->aColExpr->a[ii].pExpr;
if( pExpr->op!=TK_COLLATE ){
sCol[0].pLeft = pExpr;
pExpr = &sCol[0];
}
}else{
sCol[0].pLeft = &sCol[1];
sCol[1].iColumn = pIdx->aiColumn[ii];
pExpr = &sCol[0];
}
for(jj=0; jj<nn; jj++){
if( sqlite3ExprCompare(pParse,pTarget->a[jj].pExpr,pExpr,iCursor)<2 ){
break; /* Column ii of the index matches column jj of target */
}
}
if( jj>=nn ){
/* The target contains no match for column jj of the index */
break;
}
}
if( ii<nn ){
/* Column ii of the index did not match any term of the conflict target.
** Continue the search with the next index. */
continue;
}
pUpsert->pUpsertIdx = pIdx;
break;
}
if( pUpsert->pUpsertIdx==0 ){
char zWhich[16];
if( nClause==0 && pUpsert->pNextUpsert==0 ){
zWhich[0] = 0;
}else{
sqlite3_snprintf(sizeof(zWhich),zWhich,"%r ", nClause+1);
}
sqlite3ErrorMsg(pParse, "%sON CONFLICT clause does not match any "
"PRIMARY KEY or UNIQUE constraint", zWhich);
return SQLITE_ERROR;
}
} }
sqlite3ErrorMsg(pParse, "ON CONFLICT clause does not match any " return SQLITE_OK;
"PRIMARY KEY or UNIQUE constraint"); }
return SQLITE_ERROR;
/*
** Return true if pUpsert is the last ON CONFLICT clause with a
** conflict target, or if pUpsert is followed by another ON CONFLICT
** clause that targets the INTEGER PRIMARY KEY.
*/
int sqlite3UpsertNextIsIPK(Upsert *pUpsert){
Upsert *pNext;
if( NEVER(pUpsert==0) ) return 0;
pNext = pUpsert->pNextUpsert;
if( pNext==0 ) return 1;
if( pNext->pUpsertTarget==0 ) return 1;
if( pNext->pUpsertIdx==0 ) return 1;
return 0;
}
/*
** Given the list of ON CONFLICT clauses described by pUpsert, and
** a particular index pIdx, return a pointer to the particular ON CONFLICT
** clause that applies to the index. Or, if the index is not subject to
** any ON CONFLICT clause, return NULL.
*/
Upsert *sqlite3UpsertOfIndex(Upsert *pUpsert, Index *pIdx){
while(
pUpsert
&& pUpsert->pUpsertTarget!=0
&& pUpsert->pUpsertIdx!=pIdx
){
pUpsert = pUpsert->pNextUpsert;
}
return pUpsert;
} }
/* /*
@ -206,11 +262,13 @@ void sqlite3UpsertDoUpdate(
SrcList *pSrc; /* FROM clause for the UPDATE */ SrcList *pSrc; /* FROM clause for the UPDATE */
int iDataCur; int iDataCur;
int i; int i;
Upsert *pTop = pUpsert;
assert( v!=0 ); assert( v!=0 );
assert( pUpsert!=0 ); assert( pUpsert!=0 );
VdbeNoopComment((v, "Begin DO UPDATE of UPSERT"));
iDataCur = pUpsert->iDataCur; iDataCur = pUpsert->iDataCur;
pUpsert = sqlite3UpsertOfIndex(pTop, pIdx);
VdbeNoopComment((v, "Begin DO UPDATE of UPSERT"));
if( pIdx && iCur!=iDataCur ){ if( pIdx && iCur!=iDataCur ){
if( HasRowid(pTab) ){ if( HasRowid(pTab) ){
int regRowid = sqlite3GetTempReg(pParse); int regRowid = sqlite3GetTempReg(pParse);
@ -240,19 +298,17 @@ void sqlite3UpsertDoUpdate(
sqlite3VdbeJumpHere(v, i); sqlite3VdbeJumpHere(v, i);
} }
} }
/* pUpsert does not own pUpsertSrc - the outer INSERT statement does. So /* pUpsert does not own pTop->pUpsertSrc - the outer INSERT statement does.
** we have to make a copy before passing it down into sqlite3Update() */ ** So we have to make a copy before passing it down into sqlite3Update() */
pSrc = sqlite3SrcListDup(db, pUpsert->pUpsertSrc, 0); pSrc = sqlite3SrcListDup(db, pTop->pUpsertSrc, 0);
/* excluded.* columns of type REAL need to be converted to a hard real */ /* excluded.* columns of type REAL need to be converted to a hard real */
for(i=0; i<pTab->nCol; i++){ for(i=0; i<pTab->nCol; i++){
if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){ if( pTab->aCol[i].affinity==SQLITE_AFF_REAL ){
sqlite3VdbeAddOp1(v, OP_RealAffinity, pUpsert->regData+i); sqlite3VdbeAddOp1(v, OP_RealAffinity, pTop->regData+i);
} }
} }
sqlite3Update(pParse, pSrc, pUpsert->pUpsertSet, sqlite3Update(pParse, pSrc, sqlite3ExprListDup(db,pUpsert->pUpsertSet,0),
pUpsert->pUpsertWhere, OE_Abort, 0, 0, pUpsert); sqlite3ExprDup(db,pUpsert->pUpsertWhere,0), OE_Abort, 0, 0, pUpsert);
pUpsert->pUpsertSet = 0; /* Will have been deleted by sqlite3Update() */
pUpsert->pUpsertWhere = 0; /* Will have been deleted by sqlite3Update() */
VdbeNoopComment((v, "End DO UPDATE of UPSERT")); VdbeNoopComment((v, "End DO UPDATE of UPSERT"));
} }

397
test/upsert5.test Normal file
View File

@ -0,0 +1,397 @@
# 2020-12-11
#
# 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.
#
#***********************************************************************
#
# Test cases for generalized UPSERT
set testdir [file dirname $argv0]
source $testdir/tester.tcl
set testprefix upsert5
foreach {tn sql} {
1 { CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c UNIQUE, d UNIQUE, e UNIQUE) }
2 { CREATE TABLE t1(a INT PRIMARY KEY, b, c UNIQUE, d UNIQUE, e UNIQUE) }
3 { CREATE TABLE t1(a INT PRIMARY KEY, b, c UNIQUE, d UNIQUE, e UNIQUE) WITHOUT ROWID}
4 { CREATE TABLE t1(e UNIQUE, d UNIQUE, c UNIQUE, a INTEGER PRIMARY KEY, b) }
5 { CREATE TABLE t1(e UNIQUE, d UNIQUE, c UNIQUE, a INT PRIMARY KEY, b) }
6 { CREATE TABLE t1(e UNIQUE, d UNIQUE, c UNIQUE, a INT PRIMARY KEY, b) WITHOUT ROWID}
} {
reset_db
execsql $sql
do_execsql_test 1.$tn.100 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,3,4,5)
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 a 3 4 5}
do_execsql_test 1.$tn.101 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,4,5)
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 c 3 4 5}
do_execsql_test 1.$tn.102 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,4,5)
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 d 3 4 5}
do_execsql_test 1.$tn.103 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5)
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 e 3 4 5}
do_execsql_test 1.$tn.200 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 a 3 4 5}
do_execsql_test 1.$tn.201 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,3,94,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 c 3 4 5}
do_execsql_test 1.$tn.202 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,3,4,5)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 c 3 4 5}
do_execsql_test 1.$tn.203 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,5)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 a 3 4 5}
do_execsql_test 1.$tn.204 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,4,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 a 3 4 5}
do_execsql_test 1.$tn.210 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 a 3 4 5}
do_execsql_test 1.$tn.211 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,4,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 d 3 4 5}
do_execsql_test 1.$tn.212 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,5)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 a 3 4 5}
do_execsql_test 1.$tn.213 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(a) DO UPDATE SET b='a'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 e 3 4 5}
do_execsql_test 1.$tn.214 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(e) DO UPDATE SET b='e'
ON CONFLICT(a) DO UPDATE SET b='a';
SELECT a,b,c,d,e FROM t1;
} {1 e 3 4 5}
do_execsql_test 1.$tn.215 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,5)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(e) DO UPDATE SET b='e'
ON CONFLICT(a) DO UPDATE SET b='a';
SELECT a,b,c,d,e FROM t1;
} {1 e 3 4 5}
do_execsql_test 1.$tn.216 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(e) DO UPDATE SET b='e'
ON CONFLICT(a) DO UPDATE SET b='a';
SELECT a,b,c,d,e FROM t1;
} {1 a 3 4 5}
do_execsql_test 1.$tn.300 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(a) DO UPDATE SET b='a1'
ON CONFLICT(a) DO UPDATE SET b='a2'
ON CONFLICT(a) DO UPDATE SET b='a3'
ON CONFLICT(a) DO UPDATE SET b='a4'
ON CONFLICT(a) DO UPDATE SET b='a5'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 a1 3 4 5}
do_execsql_test 1.$tn.301 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT(a) DO UPDATE SET b='a1'
ON CONFLICT(a) DO UPDATE SET b='a2'
ON CONFLICT(a) DO UPDATE SET b='a3'
ON CONFLICT(a) DO UPDATE SET b='a4'
ON CONFLICT(a) DO UPDATE SET b='a5'
ON CONFLICT(e) DO UPDATE SET b='e';
SELECT a,b,c,d,e FROM t1;
} {1 e 3 4 5}
do_execsql_test 1.$tn.400 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 x 3 4 5}
do_execsql_test 1.$tn.401 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 x 3 4 5}
do_execsql_test 1.$tn.402 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 x 3 4 5}
do_execsql_test 1.$tn.403 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,94,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 c 3 4 5}
do_execsql_test 1.$tn.404 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,4,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 c 3 4 5}
do_execsql_test 1.$tn.405 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,4,5)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 d 3 4 5}
do_execsql_test 1.$tn.410 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95)
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 x 3 4 5}
do_execsql_test 1.$tn.411 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5)
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 x 3 4 5}
do_execsql_test 1.$tn.412 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,4,95)
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 x 3 4 5}
do_execsql_test 1.$tn.413 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,94,95)
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 x 3 4 5}
do_execsql_test 1.$tn.420 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95)
ON CONFLICT(c) DO NOTHING
ON CONFLICT(d) DO NOTHING
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 x 3 4 5}
do_execsql_test 1.$tn.421 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5)
ON CONFLICT(c) DO NOTHING
ON CONFLICT(d) DO NOTHING
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 x 3 4 5}
do_execsql_test 1.$tn.422 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,4,95)
ON CONFLICT(c) DO NOTHING
ON CONFLICT(d) DO NOTHING
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 2 3 4 5}
do_execsql_test 1.$tn.423 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,94,95)
ON CONFLICT(c) DO NOTHING
ON CONFLICT(d) DO NOTHING
ON CONFLICT DO UPDATE set b='x';
SELECT a,b,c,d,e FROM t1;
} {1 2 3 4 5}
do_execsql_test 1.$tn.500 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT DO NOTHING;
SELECT a,b,c,d,e FROM t1;
} {1 2 3 4 5}
do_execsql_test 1.$tn.501 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,93,94,5)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT DO NOTHING;
SELECT a,b,c,d,e FROM t1;
} {1 2 3 4 5}
do_execsql_test 1.$tn.502 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,94,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT DO NOTHING;
SELECT a,b,c,d,e FROM t1;
} {1 2 3 4 5}
do_execsql_test 1.$tn.503 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,94,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT DO NOTHING;
SELECT a,b,c,d,e FROM t1;
} {1 c 3 4 5}
do_execsql_test 1.$tn.504 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(91,NULL,3,4,95)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT DO NOTHING;
SELECT a,b,c,d,e FROM t1;
} {1 c 3 4 5}
do_execsql_test 1.$tn.505 {
DELETE FROM t1;
INSERT INTO t1(a,b,c,d,e) VALUES(1,2,3,4,5);
INSERT INTO t1(a,b,c,d,e) VALUES(1,NULL,93,4,5)
ON CONFLICT(c) DO UPDATE SET b='c'
ON CONFLICT(d) DO UPDATE SET b='d'
ON CONFLICT DO NOTHING;
SELECT a,b,c,d,e FROM t1;
} {1 d 3 4 5}
}
finish_test