From: dan Date: Tue, 14 Jul 2020 19:51:01 +0000 (+0000) Subject: Support UPDATE...FROM statements in trigger programs. X-Git-Tag: version-3.33.0~53^2~9^2~4 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=e7877b2d6b49f3e1dc304717cf6af75527f66e26;p=thirdparty%2Fsqlite.git Support UPDATE...FROM statements in trigger programs. FossilOrigin-Name: 4f6d8d0ebf40029218a1d3b05ea657c0c5953b01c6f0b6a628465aa44c67e7f3 --- diff --git a/manifest b/manifest index 57ea212457..c58d6a26e0 100644 --- 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 diff --git a/manifest.uuid b/manifest.uuid index 9e5ac42c47..ad90553719 100644 --- a/manifest.uuid +++ b/manifest.uuid @@ -1 +1 @@ -4f3dff045ab90e80479960fed64cf36f23dd0e13144edbde15043913ad3faac5 \ No newline at end of file +4f6d8d0ebf40029218a1d3b05ea657c0c5953b01c6f0b6a628465aa44c67e7f3 \ No newline at end of file diff --git a/src/alter.c b/src/alter.c index 8109545d77..0ab7c38863 100644 --- a/src/alter.c +++ b/src/alter.c @@ -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; inSrc; 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; } } } diff --git a/src/attach.c b/src/attach.c index 628a8bc83b..545159d3ea 100644 --- a/src/attach.c +++ b/src/attach.c @@ -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; diff --git a/src/parse.y b/src/parse.y index f444aea626..fb72d6549a 100644 --- a/src/parse.y +++ b/src/parse.y @@ -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 diff --git a/src/sqliteInt.h b/src/sqliteInt.h index 82dcc9de25..0240294299 100644 --- a/src/sqliteInt.h +++ b/src/sqliteInt.h @@ -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*); diff --git a/src/trigger.c b/src/trigger.c index 50de878dd5..883eddc2d1 100644 --- a/src/trigger.c +++ b/src/trigger.c @@ -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( iDbnDb ); - 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; diff --git a/test/altertab3.test b/test/altertab3.test index b39065589c..005a0ee891 100644 --- a/test/altertab3.test +++ b/test/altertab3.test @@ -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 index 0000000000..41f4a1481a --- /dev/null +++ b/test/triggerupfrom.test @@ -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 + diff --git a/test/upfrom3.test b/test/upfrom3.test index 516ba0982c..cd1e8c6852 100644 --- a/test/upfrom3.test +++ b/test/upfrom3.test @@ -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"