]> git.ipfire.org Git - thirdparty/sqlite.git/commitdiff
Support UPDATE...FROM statements in trigger programs.
authordan <dan@noemail.net>
Tue, 14 Jul 2020 19:51:01 +0000 (19:51 +0000)
committerdan <dan@noemail.net>
Tue, 14 Jul 2020 19:51:01 +0000 (19:51 +0000)
FossilOrigin-Name: 4f6d8d0ebf40029218a1d3b05ea657c0c5953b01c6f0b6a628465aa44c67e7f3

manifest
manifest.uuid
src/alter.c
src/attach.c
src/parse.y
src/sqliteInt.h
src/trigger.c
test/altertab3.test
test/triggerupfrom.test [new file with mode: 0644]
test/upfrom3.test

index 57ea2124579e56c79dcc0b5665614035d2688cb5..c58d6a26e0284e03ae454c060e9e62c8566d9173 100644 (file)
--- a/manifest
+++ b/manifest
@@ -1,13 +1,20 @@
 B 5ee3c27e20d12a126fb773b428bb864102b949a5b26a8d5c523753dcedf4be10
-C Add\stest\sfor\sLEFT\sJOIN\sin\sUPDATE...FROM\sstatement.
-D 2020-07-13T20:43:13.733
+C Support\sUPDATE...FROM\sstatements\sin\strigger\sprograms.
+D 2020-07-14T19:51:01.128
+F src/alter.c c63fb72ae0ca39175996fcf7008d44022a7ea99e03c0af3e1d968505ceff7120
+F src/attach.c 0b11e00c166b622c84ec176773b1d691c61ad07d247809e3e1635d4e99e71d30
+F src/parse.y ecb9abdd79ec86c8dca7cb126bbdcf322c0e282f87a6d0d734ea5f2c57ced516
 F src/resolve.c 04f4710d3ab0070073b60b5b963158dadfbd049db55d10742d9fbd3f28a3f4b8
+F src/sqliteInt.h 9b73bd726b935568f081d2d77443102f663dbcef872eea4e7fbf56e697f3b53c
+F src/trigger.c 6ff9c64a06e6354df8eba08ae18bc809e79931175d39dda32bf1101adee238e5
 F src/update.c ab137d08171e2bb18a4c6af815bbd6dbe8bfb483ddd7a2b97dfbfb4d95906ee0
+F test/altertab3.test d0d51e652aaa11e37de1f1215181d88334fefcb185f3b9bd91e06e98260c4694
 F test/fts4upfrom.test 8df5acb6e10ad73f393d1add082b042ab1db72567888847d098152121e507b34
+F test/triggerupfrom.test 3645a325026619db117683ed1f9da63ec0b9313ff5857f097088c163b291d3fe
 F test/upfrom1.tcl df984cb88010af1555812af55e9db44c4df50677395b45d1f30b69b1b6c07b73
 F test/upfrom1.test 2ba1ed45b4a2161fc79f234b9ce4300b0d3deba545e3f23b938fb3ebdbf8758d
-F test/upfrom3.test 8f107ed4dc863e75694867fa38c4f3fccd77a35939f65137ead8a55b6ee45152
-P 47c87af3e52bce10fbcc2cbe832d659b0c204bfb3368d9314fa1b01120129254
-R 65a8d9c8ed218ad0d5909deb2fd7a990
+F test/upfrom3.test 2619374c4806913414cc39514630d85bb12489a5f09a5ebdf6fad0fb4df242e2
+P 4f3dff045ab90e80479960fed64cf36f23dd0e13144edbde15043913ad3faac5
+R e4f9685bb72c377807eea72f701df546
 U dan
-Z 74476aad6c58b82c53e5aec1a3d47ff5
+Z c7df6a3e81fce323937b8f7723933acd
index 9e5ac42c4718048b8e6d80113463365fd9c3d7f2..ad905537193bb385c7698c97a763416b7921df95 100644 (file)
@@ -1 +1 @@
-4f3dff045ab90e80479960fed64cf36f23dd0e13144edbde15043913ad3faac5
\ No newline at end of file
+4f6d8d0ebf40029218a1d3b05ea657c0c5953b01c6f0b6a628465aa44c67e7f3
\ No newline at end of file
index 8109545d776b20deb9091fac07b09cfd1e459a73..0ab7c38863ed59ae06b95d57a62a6be6291244f7 100644 (file)
@@ -1191,17 +1191,22 @@ static int renameResolveTrigger(Parse *pParse, const char *zDb){
       if( pParse->nErr ) rc = pParse->rc;
     }
     if( rc==SQLITE_OK && pStep->zTarget ){
-      Table *pTarget = sqlite3LocateTable(pParse, 0, pStep->zTarget, zDb);
-      if( pTarget==0 ){
-        rc = SQLITE_ERROR;
-      }else if( SQLITE_OK==(rc = sqlite3ViewGetColumnNames(pParse, pTarget)) ){
-        SrcList sSrc;
-        memset(&sSrc, 0, sizeof(sSrc));
-        sSrc.nSrc = 1;
-        sSrc.a[0].zName = pStep->zTarget;
-        sSrc.a[0].pTab = pTarget;
-        sNC.pSrcList = &sSrc;
-        if( pStep->pWhere ){
+      SrcList *pSrc = sqlite3TriggerStepSrc(pParse, pStep);
+      if( pSrc ){
+        int i;
+        for(i=0; i<pSrc->nSrc; i++){
+          struct SrcList_item *p = &pSrc->a[i];
+          p->pTab = sqlite3LocateTableItem(pParse, 0, p);
+          p->iCursor = pParse->nTab++;
+          if( p->pTab==0 ){
+            rc = SQLITE_ERROR;
+          }else{
+            p->pTab->nTabRef++;
+            rc = sqlite3ViewGetColumnNames(pParse, p->pTab);
+          }
+        }
+        sNC.pSrcList = pSrc;
+        if( rc==SQLITE_OK && pStep->pWhere ){
           rc = sqlite3ResolveExprNames(&sNC, pStep->pWhere);
         }
         if( rc==SQLITE_OK ){
@@ -1211,7 +1216,7 @@ static int renameResolveTrigger(Parse *pParse, const char *zDb){
         if( pStep->pUpsert ){
           Upsert *pUpsert = pStep->pUpsert;
           assert( rc==SQLITE_OK );
-          pUpsert->pUpsertSrc = &sSrc;
+          pUpsert->pUpsertSrc = pSrc;
           sNC.uNC.pUpsert = pUpsert;
           sNC.ncFlags = NC_UUpsert;
           rc = sqlite3ResolveExprListNames(&sNC, pUpsert->pUpsertTarget);
@@ -1228,6 +1233,9 @@ static int renameResolveTrigger(Parse *pParse, const char *zDb){
           sNC.ncFlags = 0;
         }
         sNC.pSrcList = 0;
+        sqlite3SrcListDelete(db, pSrc);
+      }else{
+        rc = SQLITE_NOMEM;
       }
     }
   }
index 628a8bc83b174e4716e881798183efea33c06e07..545159d3ea7dc6a53e6c7e05077e51f51b9a4fe7 100644 (file)
@@ -599,6 +599,9 @@ int sqlite3FixTriggerStep(
     if( sqlite3FixExprList(pFix, pStep->pExprList) ){
       return 1;
     }
+    if( pStep->pFrom && sqlite3FixSrcList(pFix, pStep->pFrom) ){
+      return 1;
+    }
 #ifndef SQLITE_OMIT_UPSERT
     if( pStep->pUpsert ){
       Upsert *pUp = pStep->pUpsert;
index f444aea6262d049ed6464f7c1922f6a30c745914..fb72d6549ac5138909690a7f304f3f552f289114 100644 (file)
@@ -1517,8 +1517,8 @@ tridxby ::= NOT INDEXED. {
 %destructor trigger_cmd {sqlite3DeleteTriggerStep(pParse->db, $$);}
 // UPDATE 
 trigger_cmd(A) ::=
-   UPDATE(B) orconf(R) trnm(X) tridxby SET setlist(Y) where_opt(Z) scanpt(E).  
-   {A = sqlite3TriggerUpdateStep(pParse, &X, Y, Z, R, B.z, E);}
+   UPDATE(B) orconf(R) trnm(X) tridxby SET setlist(Y) from(F) where_opt(Z) scanpt(E).  
+   {A = sqlite3TriggerUpdateStep(pParse, &X, F, Y, Z, R, B.z, E);}
 
 // INSERT
 trigger_cmd(A) ::= scanpt(B) insert_cmd(R) INTO
index 82dcc9de2534ba07cf1df17253c84e2b67322a7b..0240294299e5845e6465e3a25a3449bd1282b52c 100644 (file)
@@ -3564,6 +3564,7 @@ struct TriggerStep {
   Trigger *pTrig;      /* The trigger that this step is a part of */
   Select *pSelect;     /* SELECT statement or RHS of INSERT INTO SELECT ... */
   char *zTarget;       /* Target table for DELETE, UPDATE, INSERT */
+  SrcList *pFrom;      /* FROM clause for UPDATE statement (if any) */
   Expr *pWhere;        /* The WHERE clause for DELETE or UPDATE steps */
   ExprList *pExprList; /* SET clause for UPDATE */
   IdList *pIdList;     /* Column names for INSERT */
@@ -4402,13 +4403,14 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int);
   TriggerStep *sqlite3TriggerInsertStep(Parse*,Token*, IdList*,
                                         Select*,u8,Upsert*,
                                         const char*,const char*);
-  TriggerStep *sqlite3TriggerUpdateStep(Parse*,Token*,ExprList*, Expr*, u8,
-                                        const char*,const char*);
+  TriggerStep *sqlite3TriggerUpdateStep(Parse*,Token*,SrcList*,ExprList*,
+                                        Expr*, u8, const char*,const char*);
   TriggerStep *sqlite3TriggerDeleteStep(Parse*,Token*, Expr*,
                                         const char*,const char*);
   void sqlite3DeleteTrigger(sqlite3*, Trigger*);
   void sqlite3UnlinkAndDeleteTrigger(sqlite3*,int,const char*);
   u32 sqlite3TriggerColmask(Parse*,Trigger*,ExprList*,int,int,Table*,int);
+  SrcList *sqlite3TriggerStepSrc(Parse*, TriggerStep*);
 # define sqlite3ParseToplevel(p) ((p)->pToplevel ? (p)->pToplevel : (p))
 # define sqlite3IsToplevel(p) ((p)->pToplevel==0)
 #else
@@ -4422,6 +4424,7 @@ void sqlite3MaterializeView(Parse*, Table*, Expr*, ExprList*,Expr*,int);
 # define sqlite3ParseToplevel(p) p
 # define sqlite3IsToplevel(p) 1
 # define sqlite3TriggerColmask(A,B,C,D,E,F,G) 0
+# define sqlite3TriggerStepSrc(A,B) 0
 #endif
 
 int sqlite3JoinType(Parse*, Token*, Token*, Token*);
index 50de878dd5a56ed9f0b943901edca56c20dd0901..883eddc2d11093213419d7cb193862d6abf290ca 100644 (file)
@@ -26,6 +26,7 @@ void sqlite3DeleteTriggerStep(sqlite3 *db, TriggerStep *pTriggerStep){
     sqlite3SelectDelete(db, pTmp->pSelect);
     sqlite3IdListDelete(db, pTmp->pIdList);
     sqlite3UpsertDelete(db, pTmp->pUpsert);
+    sqlite3SrcListDelete(db, pTmp->pFrom);
     sqlite3DbFree(db, pTmp->zSpan);
 
     sqlite3DbFree(db, pTmp);
@@ -486,6 +487,7 @@ TriggerStep *sqlite3TriggerInsertStep(
 TriggerStep *sqlite3TriggerUpdateStep(
   Parse *pParse,          /* Parser */
   Token *pTableName,   /* Name of the table to be updated */
+  SrcList *pFrom,
   ExprList *pEList,    /* The SET clause: list of column and new values */
   Expr *pWhere,        /* The WHERE clause */
   u8 orconf,           /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */
@@ -500,16 +502,22 @@ TriggerStep *sqlite3TriggerUpdateStep(
     if( IN_RENAME_OBJECT ){
       pTriggerStep->pExprList = pEList;
       pTriggerStep->pWhere = pWhere;
+      pTriggerStep->pFrom = pFrom;
       pEList = 0;
       pWhere = 0;
+      pFrom = 0;
     }else{
       pTriggerStep->pExprList = sqlite3ExprListDup(db, pEList, EXPRDUP_REDUCE);
       pTriggerStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE);
+      pTriggerStep->pFrom = sqlite3SrcListDup(db, pFrom, EXPRDUP_REDUCE);
     }
     pTriggerStep->orconf = orconf;
+  }else{
+    sqlite3SrcListDelete(db, pFrom);
   }
   sqlite3ExprListDelete(db, pEList);
   sqlite3ExprDelete(db, pWhere);
+  sqlite3SrcListDelete(db, pFrom);
   return pTriggerStep;
 }
 
@@ -736,25 +744,28 @@ Trigger *sqlite3TriggersExist(
 ** trigger is in TEMP in which case it can refer to any other database it
 ** wants.
 */
-static SrcList *targetSrcList(
+SrcList *sqlite3TriggerStepSrc(
   Parse *pParse,       /* The parsing context */
   TriggerStep *pStep   /* The trigger containing the target token */
 ){
   sqlite3 *db = pParse->db;
-  int iDb;             /* Index of the database to use */
-  SrcList *pSrc;       /* SrcList to be returned */
-
+  SrcList *pSrc;                  /* SrcList to be returned */
+  char *zName = sqlite3DbStrDup(db, pStep->zTarget);
   pSrc = sqlite3SrcListAppend(pParse, 0, 0, 0);
+  assert( pSrc==0 || pSrc->nSrc==1 );
+  assert( zName || pSrc==0 );
   if( pSrc ){
-    assert( pSrc->nSrc>0 );
-    pSrc->a[pSrc->nSrc-1].zName = sqlite3DbStrDup(db, pStep->zTarget);
-    iDb = sqlite3SchemaToIndex(db, pStep->pTrig->pSchema);
-    if( iDb==0 || iDb>=2 ){
-      const char *zDb;
-      assert( iDb<db->nDb );
-      zDb = db->aDb[iDb].zDbSName;
-      pSrc->a[pSrc->nSrc-1].zDatabase =  sqlite3DbStrDup(db, zDb);
+    Schema *pSchema = pStep->pTrig->pSchema;
+    pSrc->a[0].zName = zName;
+    if( pSchema!=db->aDb[1].pSchema ){
+      pSrc->a[0].pSchema = pSchema;
     }
+    if( pStep->pFrom ){
+      SrcList *pDup = sqlite3SrcListDup(db, pStep->pFrom, 0);
+      pSrc = sqlite3SrcListAppendList(pParse, pSrc, pDup);
+    }
+  }else{
+    sqlite3DbFree(db, zName);
   }
   return pSrc;
 }
@@ -803,7 +814,7 @@ static int codeTriggerProgram(
     switch( pStep->op ){
       case TK_UPDATE: {
         sqlite3Update(pParse, 
-          targetSrcList(pParse, pStep),
+          sqlite3TriggerStepSrc(pParse, pStep),
           sqlite3ExprListDup(db, pStep->pExprList, 0), 
           sqlite3ExprDup(db, pStep->pWhere, 0), 
           pParse->eOrconf, 0, 0, 0
@@ -812,7 +823,7 @@ static int codeTriggerProgram(
       }
       case TK_INSERT: {
         sqlite3Insert(pParse, 
-          targetSrcList(pParse, pStep),
+          sqlite3TriggerStepSrc(pParse, pStep),
           sqlite3SelectDup(db, pStep->pSelect, 0), 
           sqlite3IdListDup(db, pStep->pIdList), 
           pParse->eOrconf,
@@ -822,7 +833,7 @@ static int codeTriggerProgram(
       }
       case TK_DELETE: {
         sqlite3DeleteFrom(pParse, 
-          targetSrcList(pParse, pStep),
+          sqlite3TriggerStepSrc(pParse, pStep),
           sqlite3ExprDup(db, pStep->pWhere, 0), 0, 0
         );
         break;
index b39065589c8787f61ac051a13c3d3481e883790c..005a0ee891180e6d64e781ccbecccd34e61d5756 100644 (file)
@@ -586,5 +586,19 @@ do_execsql_test 24.4 {
     DELETE FROM v2;
   END}}
 
+#------------------------------------------------------------------------
+#
+reset_db
+do_execsql_test 25.1 {
+  CREATE TABLE t1(a, b, c);
+  CREATE TABLE t2(a, b, c);
+  CREATE TRIGGER ttt AFTER INSERT ON t1 BEGIN
+    UPDATE t1 SET a=t2.a FROM t2 WHERE t1.a=t2.a; 
+  END;
+}
+#do_execsql_test 25.2 {
+#  ALTER TABLE t2 RENAME COLUMN a TO aaa;
+#}
+
 finish_test
 
diff --git a/test/triggerupfrom.test b/test/triggerupfrom.test
new file mode 100644 (file)
index 0000000..41f4a14
--- /dev/null
@@ -0,0 +1,120 @@
+# 2020 July 14
+#
+# 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.
+#
+#***********************************************************************
+#
+
+set testdir [file dirname $argv0]
+source $testdir/tester.tcl
+set testprefix triggerupfrom
+
+do_execsql_test 1.0 {
+  CREATE TABLE map(k, v);
+  INSERT INTO map VALUES(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four');
+
+  CREATE TABLE t1(a INTEGER PRIMARY KEY, b, c);
+
+  CREATE TRIGGER tr AFTER INSERT ON t1 BEGIN
+    UPDATE t1 SET c = v FROM map WHERE k=new.a AND a=new.a;
+  END;
+}
+
+do_execsql_test 1.1 {
+  INSERT INTO t1(a) VALUES(1);
+}
+
+do_execsql_test 1.2 {
+  SELECT a, c FROM t1 ORDER BY a;
+} {1 one}
+
+do_execsql_test 1.3 {
+  INSERT INTO t1(a) VALUES(2), (3), (4), (5);
+  SELECT a, c FROM t1 ORDER BY a;
+} {1 one 2 two 3 three 4 four 5 {}}
+
+forcedelete test.db2
+do_execsql_test 2.0 {
+  ATTACH 'test.db2' AS aux;
+  CREATE TABLE aux.t3(x, y);
+  INSERT INTO aux.t3 VALUES('x', 'y');
+}
+
+do_catchsql_test 2.1 {
+  CREATE TRIGGER tr2 AFTER INSERT ON t1 BEGIN
+    UPDATE t1 SET b = y FROM aux.t3 WHERE k=new.a;
+  END;
+} {1 {trigger tr2 cannot reference objects in database aux}}
+
+do_execsql_test 2.2 {
+  CREATE TEMP TRIGGER tr2 AFTER INSERT ON t1 BEGIN
+    UPDATE t1 SET b = y FROM aux.t3 WHERE a=new.a;
+  END;
+  INSERT INTO t1(a) VALUES(10), (20);
+  SELECT * FROM t1;
+} {
+  1 {} one 
+  2 {} two 
+  3 {} three 
+  4 {} four 
+  5 {} {} 
+  10 y {} 
+  20 y {}
+}
+
+do_execsql_test 2.3 {
+  CREATE TABLE link(f, t);
+  INSERT INTO link VALUES(5, 2), (20, 10), (2, 1);
+  CREATE TRIGGER tr3 BEFORE DELETE ON t1 BEGIN
+    UPDATE t1 SET b=coalesce(old.b,old.c) FROM main.link WHERE a=t AND old.a=f;
+  END;
+  DELETE FROM t1 WHERE a=2;
+  SELECT * FROM t1;
+} {
+  1 two one 
+  3 {} three 
+  4 {} four 
+  5 {} {} 
+  10 y {} 
+  20 y {}
+}
+
+db close
+sqlite3 db ""
+do_catchsql_test 2.4 {
+  ATTACH 'test.db' AS yyy;
+  SELECT * FROM t1;
+} {1 {malformed database schema (tr3) - trigger tr3 cannot reference objects in database main}}
+
+#-------------------------------------------------------------------------
+reset_db
+forcedelete test.db2
+do_execsql_test 3.0 {
+  CREATE TABLE mmm(x, y);
+  INSERT INTO mmm VALUES(1, 'one');
+  INSERT INTO mmm VALUES(2, 'two');
+  INSERT INTO mmm VALUES(3, 'three');
+
+  ATTACH 'test.db2' AS aux;
+  CREATE TABLE aux.t1(a, b);
+  CREATE TABLE aux.mmm(x, y);
+  INSERT INTO aux.mmm VALUES(1, 'ONE');
+  INSERT INTO aux.mmm VALUES(2, 'TWO');
+  INSERT INTO aux.mmm VALUES(3, 'THREE');
+
+  CREATE TRIGGER aux.ttt AFTER INSERT ON t1 BEGIN
+    UPDATE t1 SET b=y FROM mmm WHERE x=new.a AND a=new.a;
+  END;
+  
+  INSERT INTO t1(a) VALUES (2);
+  SELECT * FROM t1;
+} {2 TWO}
+
+
+finish_test
+
index 516ba0982c62150accc979104f34f07a8b5e37be..cd1e8c68521c5e93b32d7ddb1968ffbd6e3dce9f 100644 (file)
@@ -14,6 +14,15 @@ set testdir [file dirname $argv0]
 source $testdir/tester.tcl
 set testprefix upfrom3
 
+# Test plan:
+#
+#   1.*: Test UPDATE ... FROM statements that modify IPK fields. And that
+#        modify "INTEGER PRIMARY KEY" fields on WITHOUT ROWID tables.
+#
+#   2.*: Test UPDATE ... FROM statements that modify PK fields of WITHOUT
+#        ROWID tables.
+#
+
 foreach {tn wo} {
   1 ""
   2 "WITHOUT ROWID"