]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Enhance UPSERT parsing to allow multiple ON CONFLICT clauses. Only the
authordrh <drh@noemail.net>
Tue, 8 Dec 2020 14:29:03 +0000 (14:29 +0000)
committerdrh <drh@noemail.net>
Tue, 8 Dec 2020 14:29:03 +0000 (14:29 +0000)
very last clause may omit the conflict target, but the conflict target may
now be omitted for the DO UPDATE resolution.

FossilOrigin-Name: 2ca62f4c71df6544cb8039bdc80e3701d09697c38800534371f6d44532fcffae

manifest
manifest.uuid
src/parse.y
src/sqliteInt.h
src/upsert.c

index 18c17599b404fa84743096457b8b54fa4abe9070..9471059ac9290b8192e1d1f42e4e8e204b83744d 100644 (file)
--- a/manifest
+++ b/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
index 7e5c04ae2666a4a3f1c13a8b9866ae4fe46e9242..0eeaf11f2a892697a58eda14cbd8a45d6eec550f 100644 (file)
@@ -1 +1 @@
-4b286129138d44e6f8e9b3450289941e01d20fdfb9d0b5d846031425e8ca6b49
\ No newline at end of file
+2ca62f4c71df6544cb8039bdc80e3701d09697c38800534371f6d44532fcffae
\ No newline at end of file
index d3ec2b3da65ae44f495e395811839763a50a080d..f28bb474141975cff0b869ba745d727df5dfa290 100644 (file)
@@ -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;}
index 438f79c0925649c9e71d3e9e9c0143beb5ec7d23..68c868060022ea70f15e83be263abad62d5e46ff 100644 (file)
@@ -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
 
 
index 9a33f75d0ac84c31842042025a2b0c89c4affbb8..ddd7c18428c310446e1f4eb9ca33eb885f638a5f 100644 (file)
 /*
 ** 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;
-      }
+  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;
     }
-    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];
+  
+    /* 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;
         }
-      }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 */
+      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( 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( ii<nn ){
-      /* Column ii of the index did not match any term of the conflict target.
-      ** Continue the search with the next index. */
-      continue;
+    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;
     }
-    pUpsert->pUpsertIdx = pIdx;
-    return SQLITE_OK;
   }
-  sqlite3ErrorMsg(pParse, "ON CONFLICT clause does not match any "
-                          "PRIMARY KEY or UNIQUE constraint");
-  return SQLITE_ERROR;
+  return SQLITE_OK;
 }
 
 /*