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:
22
manifest
22
manifest
@ -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
|
||||||
|
@ -1 +1 @@
|
|||||||
59b4367fd852ba1bfefdff99a27b11657495a3f114ed6f85fdcf6c532f4a19fa
|
6b01a24daab1e5bcb0768ebf994368d941b1dfc217bf6b661211d900331e68cf
|
224
src/insert.c
224
src/insert.c
@ -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 */
|
||||||
|
12
src/parse.y
12
src/parse.y
@ -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;}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
244
src/upsert.c
244
src/upsert.c
@ -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
397
test/upsert5.test
Normal 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
|
Reference in New Issue
Block a user