mirror of
https://github.com/sqlite/sqlite.git
synced 2025-11-12 13:01:09 +03:00
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:
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;
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user