Enhance UPSERT parsing to allow multiple ON CONFLICT clauses. Only the
very last clause may omit the conflict target, but the conflict target may now be omitted for the DO UPDATE resolution. FossilOrigin-Name: 2ca62f4c71df6544cb8039bdc80e3701d09697c38800534371f6d44532fcffae
This commit is contained in:
parent
0dffe465f7
commit
2549e4cc2f
19
manifest
19
manifest
@ -1,5 +1,5 @@
|
||||
C Fix\sa\sbad\sassert()\sin\smath1Func().
|
||||
D 2020-12-07T23:14:25.210
|
||||
C Enhance\sUPSERT\sparsing\sto\sallow\smultiple\sON\sCONFLICT\sclauses.\s\sOnly\sthe\nvery\slast\sclause\smay\somit\sthe\sconflict\starget,\sbut\sthe\sconflict\starget\smay\nnow\sbe\somitted\sfor\sthe\sDO\sUPDATE\sresolution.
|
||||
D 2020-12-08T14:29:03.533
|
||||
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
|
||||
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
|
||||
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
|
||||
@ -529,7 +529,7 @@ F src/os_win.c 77d39873836f1831a9b0b91894fec45ab0e9ca8e067dc8c549e1d1eca1566fe9
|
||||
F src/os_win.h 7b073010f1451abe501be30d12f6bc599824944a
|
||||
F src/pager.c c49952ac5e9cc536778eff528091d79d38b3e45cbeeed4695dc05e207dc6547d
|
||||
F src/pager.h 4bf9b3213a4b2bebbced5eaa8b219cf25d4a82f385d093cd64b7e93e5285f66f
|
||||
F src/parse.y 9ce4dfb772608ed5bd3c32f33e943e021e3b06cfd2c01932d4280888fdd2ebed
|
||||
F src/parse.y 72b884c73f2b446e7dc4c7169ec7fbb82e0e292eec733fcf554f0fde46f269f6
|
||||
F src/pcache.c 385ff064bca69789d199a98e2169445dc16e4291fa807babd61d4890c3b34177
|
||||
F src/pcache.h 4f87acd914cef5016fae3030343540d75f5b85a1877eed1a2a19b9f284248586
|
||||
F src/pcache1.c 6596e10baf3d8f84cc1585d226cf1ab26564a5f5caf85a15757a281ff977d51a
|
||||
@ -545,7 +545,7 @@ F src/shell.c.in e9f674ee4ec6c345679e8a5b16c869c6c59eb1540dd98ac69e4736ecddce009
|
||||
F src/sqlite.h.in 0e2b4259e49a0eda54d9118eb18a04fcd60e0727a2fd2c81aade0bf57520e706
|
||||
F src/sqlite3.rc 5121c9e10c3964d5755191c80dd1180c122fc3a8
|
||||
F src/sqlite3ext.h 61b38c073d5e1e96a3d45271b257aef27d0d13da2bea5347692ae579475cd95e
|
||||
F src/sqliteInt.h 6ab40b33a1f5edbb7d71c78e82e0f9c5291dcff4704df8e4f0ab0d9c1a0c06af
|
||||
F src/sqliteInt.h 351d29fad669d5c98066a89ab48259d451379edac3c24773c3c8ac5df66fd8ff
|
||||
F src/sqliteLimit.h d7323ffea5208c6af2734574bae933ca8ed2ab728083caa117c9738581a31657
|
||||
F src/status.c 4b8bc2a6905163a38b739854a35b826c737333fab5b1f8e03fa7eb9a4799c4c1
|
||||
F src/table.c 0f141b58a16de7e2fbe81c308379e7279f4c6b50eb08efeec5892794a0ba30d1
|
||||
@ -608,7 +608,7 @@ F src/tokenize.c 01dba3023659dc6f6b1e054c14b35a0074bd35de10466b99454d33278191d97
|
||||
F src/treeview.c 4b92992176fb2caefbe06ba5bd06e0e0ebcde3d5564758da672631f17aa51cda
|
||||
F src/trigger.c 515e79206d40d1d4149129318582e79a6e9db590a7b74e226fdb5b2a6c7e1b10
|
||||
F src/update.c 9f126204a6acb96bbe47391ae48e0fc579105d8e76a6d9c4fab3271367476580
|
||||
F src/upsert.c 2920de71b20f04fe25eb00b655d086f0ba60ea133c59d7fa3325c49838818e78
|
||||
F src/upsert.c 25673d007c2408fec47a6326b6d7ac265abd2cbc162d11f3b3c333de27d3c78a
|
||||
F src/utf.c ee39565f0843775cc2c81135751ddd93eceb91a673ea2c57f61c76f288b041a0
|
||||
F src/util.c c0c7977de7ef9b8cb10f6c85f2d0557889a658f817b0455909a49179ba4c8002
|
||||
F src/vacuum.c 492422c1463c076473bae1858799c7a0a5fe87a133d1223239447c422cd26286
|
||||
@ -1888,7 +1888,10 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
|
||||
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
|
||||
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
|
||||
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
|
||||
P 99ff6418492adcbaf2be728737735afa1c2997de5868395e69c53d08fc14491f
|
||||
R 747ed0f69719082482a4ff82786fa288
|
||||
P 4b286129138d44e6f8e9b3450289941e01d20fdfb9d0b5d846031425e8ca6b49
|
||||
R 204aa888c3a8b96c5008bc9a579603f7
|
||||
T *branch * generalized-upsert
|
||||
T *sym-generalized-upsert *
|
||||
T -sym-trunk *
|
||||
U drh
|
||||
Z ac855459dfaf775bb9ffc51142afdf45
|
||||
Z d13edca14ac31a65fc7ae7d7fdc7eaff
|
||||
|
@ -1 +1 @@
|
||||
4b286129138d44e6f8e9b3450289941e01d20fdfb9d0b5d846031425e8ca6b49
|
||||
2ca62f4c71df6544cb8039bdc80e3701d09697c38800534371f6d44532fcffae
|
19
src/parse.y
19
src/parse.y
@ -952,20 +952,17 @@ cmd ::= with insert_cmd(R) INTO xfullname(X) idlist_opt(F) DEFAULT VALUES.
|
||||
}
|
||||
|
||||
%type upsert {Upsert*}
|
||||
|
||||
// Because upsert only occurs at the tip end of the INSERT rule for cmd,
|
||||
// there is never a case where the value of the upsert pointer will not
|
||||
// be destroyed by the cmd action. So comment-out the destructor to
|
||||
// avoid unreachable code.
|
||||
//%destructor upsert {sqlite3UpsertDelete(pParse->db,$$);}
|
||||
%destructor upsert {sqlite3UpsertDelete(pParse->db,$$);}
|
||||
upsert(A) ::= . { A = 0; }
|
||||
upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW)
|
||||
DO UPDATE SET setlist(Z) where_opt(W).
|
||||
{ A = sqlite3UpsertNew(pParse->db,T,TW,Z,W);}
|
||||
upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW) DO NOTHING.
|
||||
{ A = sqlite3UpsertNew(pParse->db,T,TW,0,0); }
|
||||
DO UPDATE SET setlist(Z) where_opt(W) upsert(N).
|
||||
{ A = sqlite3UpsertNew(pParse->db,T,TW,Z,W,N);}
|
||||
upsert(A) ::= ON CONFLICT LP sortlist(T) RP where_opt(TW) DO NOTHING upsert(N).
|
||||
{ A = sqlite3UpsertNew(pParse->db,T,TW,0,0,N); }
|
||||
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}
|
||||
insert_cmd(A) ::= INSERT orconf(R). {A = R;}
|
||||
|
@ -3071,6 +3071,7 @@ struct Upsert {
|
||||
Expr *pUpsertTargetWhere; /* WHERE clause for partial index targets */
|
||||
ExprList *pUpsertSet; /* The SET clause from an ON CONFLICT UPDATE */
|
||||
Expr *pUpsertWhere; /* WHERE clause for the ON CONFLICT UPDATE */
|
||||
Upsert *pNextUpsert; /* Next ON CONFLICT clause in the list */
|
||||
/* The fields above comprise the parse tree for the upsert clause.
|
||||
** The fields below are used to transfer information from the INSERT
|
||||
** processing down into the UPDATE processing while generating code.
|
||||
@ -4824,15 +4825,15 @@ const char *sqlite3JournalModename(int);
|
||||
#define sqlite3WithDelete(x,y)
|
||||
#endif
|
||||
#ifndef SQLITE_OMIT_UPSERT
|
||||
Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*);
|
||||
Upsert *sqlite3UpsertNew(sqlite3*,ExprList*,Expr*,ExprList*,Expr*,Upsert*);
|
||||
void sqlite3UpsertDelete(sqlite3*,Upsert*);
|
||||
Upsert *sqlite3UpsertDup(sqlite3*,Upsert*);
|
||||
int sqlite3UpsertAnalyzeTarget(Parse*,SrcList*,Upsert*);
|
||||
void sqlite3UpsertDoUpdate(Parse*,Upsert*,Table*,Index*,int);
|
||||
#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 sqlite3UpsertDup(x,y) ((Upsert*)0)
|
||||
#define sqlite3UpsertDup(x,y) ((Upsert*)0)
|
||||
#endif
|
||||
|
||||
|
||||
|
189
src/upsert.c
189
src/upsert.c
@ -18,15 +18,21 @@
|
||||
/*
|
||||
** Free a list of Upsert objects
|
||||
*/
|
||||
void sqlite3UpsertDelete(sqlite3 *db, Upsert *p){
|
||||
if( p ){
|
||||
static void SQLITE_NOINLINE upsertDelete(sqlite3 *db, Upsert *p){
|
||||
do{
|
||||
Upsert *pNext = p->pNextUpsert;
|
||||
sqlite3ExprListDelete(db, p->pUpsertTarget);
|
||||
sqlite3ExprDelete(db, p->pUpsertTargetWhere);
|
||||
sqlite3ExprListDelete(db, p->pUpsertSet);
|
||||
sqlite3ExprDelete(db, p->pUpsertWhere);
|
||||
sqlite3DbFree(db, p);
|
||||
}
|
||||
p = pNext;
|
||||
}while( p );
|
||||
}
|
||||
void sqlite3UpsertDelete(sqlite3 *db, Upsert *p){
|
||||
if( p ) upsertDelete(db, p);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Duplicate an Upsert object.
|
||||
@ -37,7 +43,8 @@ Upsert *sqlite3UpsertDup(sqlite3 *db, Upsert *p){
|
||||
sqlite3ExprListDup(db, p->pUpsertTarget, 0),
|
||||
sqlite3ExprDup(db, p->pUpsertTargetWhere, 0),
|
||||
sqlite3ExprListDup(db, p->pUpsertSet, 0),
|
||||
sqlite3ExprDup(db, p->pUpsertWhere, 0)
|
||||
sqlite3ExprDup(db, p->pUpsertWhere, 0),
|
||||
sqlite3UpsertDup(db, p->pNextUpsert)
|
||||
);
|
||||
}
|
||||
|
||||
@ -49,7 +56,8 @@ Upsert *sqlite3UpsertNew(
|
||||
ExprList *pTarget, /* Target argument to ON CONFLICT, or NULL */
|
||||
Expr *pTargetWhere, /* Optional WHERE clause on the target */
|
||||
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;
|
||||
pNew = sqlite3DbMallocRaw(db, sizeof(Upsert));
|
||||
@ -58,6 +66,7 @@ Upsert *sqlite3UpsertNew(
|
||||
sqlite3ExprDelete(db, pTargetWhere);
|
||||
sqlite3ExprListDelete(db, pSet);
|
||||
sqlite3ExprDelete(db, pWhere);
|
||||
sqlite3UpsertDelete(db, pNext);
|
||||
return 0;
|
||||
}else{
|
||||
pNew->pUpsertTarget = pTarget;
|
||||
@ -65,6 +74,7 @@ Upsert *sqlite3UpsertNew(
|
||||
pNew->pUpsertSet = pSet;
|
||||
pNew->pUpsertWhere = pWhere;
|
||||
pNew->pUpsertIdx = 0;
|
||||
pNew->pNextUpsert = pNext;
|
||||
}
|
||||
return pNew;
|
||||
}
|
||||
@ -89,6 +99,7 @@ int sqlite3UpsertAnalyzeTarget(
|
||||
Expr *pTerm; /* One term of the conflict-target clause */
|
||||
NameContext sNC; /* Context for resolving symbolic names */
|
||||
Expr sCol[2]; /* Index column converted into an Expr */
|
||||
int nClause = 0; /* Counter of ON CONFLICT clauses */
|
||||
|
||||
assert( pTabList->nSrc==1 );
|
||||
assert( pTabList->a[0].pTab!=0 );
|
||||
@ -102,87 +113,99 @@ int sqlite3UpsertAnalyzeTarget(
|
||||
memset(&sNC, 0, sizeof(sNC));
|
||||
sNC.pParse = pParse;
|
||||
sNC.pSrcList = pTabList;
|
||||
rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget);
|
||||
if( rc ) return rc;
|
||||
rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere);
|
||||
if( rc ) return rc;
|
||||
|
||||
/* Check to see if the conflict target matches the rowid. */
|
||||
pTab = pTabList->a[0].pTab;
|
||||
pTarget = pUpsert->pUpsertTarget;
|
||||
iCursor = pTabList->a[0].iCursor;
|
||||
if( HasRowid(pTab)
|
||||
&& pTarget->nExpr==1
|
||||
&& (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN
|
||||
&& pTerm->iColumn==XN_ROWID
|
||||
){
|
||||
/* The conflict-target is the rowid of the primary table */
|
||||
assert( pUpsert->pUpsertIdx==0 );
|
||||
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. */
|
||||
for(; pUpsert && pUpsert->pUpsertTarget;
|
||||
pUpsert=pUpsert->pNextUpsert, nClause++){
|
||||
rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget);
|
||||
if( rc ) return rc;
|
||||
rc = sqlite3ResolveExprNames(&sNC, pUpsert->pUpsertTargetWhere);
|
||||
if( rc ) return rc;
|
||||
|
||||
/* Check to see if the conflict target matches the rowid. */
|
||||
pTab = pTabList->a[0].pTab;
|
||||
pTarget = pUpsert->pUpsertTarget;
|
||||
iCursor = pTabList->a[0].iCursor;
|
||||
if( HasRowid(pTab)
|
||||
&& pTarget->nExpr==1
|
||||
&& (pTerm = pTarget->a[0].pExpr)->op==TK_COLUMN
|
||||
&& pTerm->iColumn==XN_ROWID
|
||||
){
|
||||
/* The conflict-target is the rowid of the primary table */
|
||||
assert( pUpsert->pUpsertIdx==0 );
|
||||
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 "
|
||||
"PRIMARY KEY or UNIQUE constraint");
|
||||
return SQLITE_ERROR;
|
||||
return SQLITE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user