Index: src/fkey.c ================================================================== --- src/fkey.c +++ src/fkey.c @@ -1016,14 +1016,14 @@ ** that the "OLD.zToCol" term is on the LHS of the = operator, so ** that the affinity and collation sequence associated with the ** parent table are used for the comparison. */ pEq = sqlite3PExpr(pParse, TK_EQ, sqlite3PExpr(pParse, TK_DOT, - sqlite3PExpr(pParse, TK_ID, 0, 0, &tOld), - sqlite3PExpr(pParse, TK_ID, 0, 0, &tToCol) + sqlite3ExprAlloc(db, TK_ID, &tOld, 0), + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0) , 0), - sqlite3PExpr(pParse, TK_ID, 0, 0, &tFromCol) + sqlite3ExprAlloc(db, TK_ID, &tFromCol, 0) , 0); pWhere = sqlite3ExprAnd(db, pWhere, pEq); /* For ON UPDATE, construct the next term of the WHEN clause. ** The final WHEN clause will be like this: @@ -1031,27 +1031,27 @@ ** WHEN NOT(old.col1 IS new.col1 AND ... AND old.colN IS new.colN) */ if( pChanges ){ pEq = sqlite3PExpr(pParse, TK_IS, sqlite3PExpr(pParse, TK_DOT, - sqlite3PExpr(pParse, TK_ID, 0, 0, &tOld), - sqlite3PExpr(pParse, TK_ID, 0, 0, &tToCol), + sqlite3ExprAlloc(db, TK_ID, &tOld, 0), + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0), 0), sqlite3PExpr(pParse, TK_DOT, - sqlite3PExpr(pParse, TK_ID, 0, 0, &tNew), - sqlite3PExpr(pParse, TK_ID, 0, 0, &tToCol), + sqlite3ExprAlloc(db, TK_ID, &tNew, 0), + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0), 0), 0); pWhen = sqlite3ExprAnd(db, pWhen, pEq); } if( action!=OE_Restrict && (action!=OE_Cascade || pChanges) ){ Expr *pNew; if( action==OE_Cascade ){ pNew = sqlite3PExpr(pParse, TK_DOT, - sqlite3PExpr(pParse, TK_ID, 0, 0, &tNew), - sqlite3PExpr(pParse, TK_ID, 0, 0, &tToCol) + sqlite3ExprAlloc(db, TK_ID, &tNew, 0), + sqlite3ExprAlloc(db, TK_ID, &tToCol, 0) , 0); }else if( action==OE_SetDflt ){ Expr *pDflt = pFKey->pFrom->aCol[iFromCol].pDflt; if( pDflt ){ pNew = sqlite3ExprDup(db, pDflt, 0); @@ -1094,17 +1094,16 @@ db->lookaside.bEnabled = 0; pTrigger = (Trigger *)sqlite3DbMallocZero(db, sizeof(Trigger) + /* struct Trigger */ sizeof(TriggerStep) + /* Single step in trigger program */ - nFrom + 1 /* Space for pStep->target.z */ + nFrom + 1 /* Space for pStep->zTarget */ ); if( pTrigger ){ pStep = pTrigger->step_list = (TriggerStep *)&pTrigger[1]; - pStep->target.z = (char *)&pStep[1]; - pStep->target.n = nFrom; - memcpy((char *)pStep->target.z, zFrom, nFrom); + pStep->zTarget = (char *)&pStep[1]; + memcpy((char *)pStep->zTarget, zFrom, nFrom); pStep->pWhere = sqlite3ExprDup(db, pWhere, EXPRDUP_REDUCE); pStep->pExprList = sqlite3ExprListDup(db, pList, EXPRDUP_REDUCE); pStep->pSelect = sqlite3SelectDup(db, pSelect, EXPRDUP_REDUCE); if( pWhen ){ Index: src/sqliteInt.h ================================================================== --- src/sqliteInt.h +++ src/sqliteInt.h @@ -2339,24 +2339,24 @@ * * (op == TK_INSERT) * orconf -> stores the ON CONFLICT algorithm * pSelect -> If this is an INSERT INTO ... SELECT ... statement, then * this stores a pointer to the SELECT statement. Otherwise NULL. - * target -> A token holding the quoted name of the table to insert into. + * zTarget -> Dequoted name of the table to insert into. * pExprList -> If this is an INSERT INTO ... VALUES ... statement, then * this stores values to be inserted. Otherwise NULL. * pIdList -> If this is an INSERT INTO ... () VALUES ... * statement, then this stores the column-names to be * inserted into. * * (op == TK_DELETE) - * target -> A token holding the quoted name of the table to delete from. + * zTarget -> Dequoted name of the table to delete from. * pWhere -> The WHERE clause of the DELETE statement if one is specified. * Otherwise NULL. * * (op == TK_UPDATE) - * target -> A token holding the quoted name of the table to update rows of. + * zTarget -> Dequoted name of the table to update. * pWhere -> The WHERE clause of the UPDATE statement if one is specified. * Otherwise NULL. * pExprList -> A list of the columns to update and the expressions to update * them to. See sqlite3Update() documentation of "pChanges" * argument. @@ -2364,12 +2364,12 @@ */ struct TriggerStep { u8 op; /* One of TK_DELETE, TK_UPDATE, TK_INSERT, TK_SELECT */ u8 orconf; /* OE_Rollback etc. */ Trigger *pTrig; /* The trigger that this step is a part of */ - Select *pSelect; /* SELECT statment or RHS of INSERT INTO .. SELECT ... */ - Token target; /* Target table for DELETE, UPDATE, INSERT */ + Select *pSelect; /* SELECT statement or RHS of INSERT INTO SELECT ... */ + char *zTarget; /* Target table for DELETE, UPDATE, INSERT */ Expr *pWhere; /* The WHERE clause for DELETE or UPDATE steps */ ExprList *pExprList; /* SET clause for UPDATE. VALUES clause for INSERT */ IdList *pIdList; /* Column names for INSERT */ TriggerStep *pNext; /* Next in the link-list */ TriggerStep *pLast; /* Last element in link-list. Valid for 1st elem only */ Index: src/trigger.c ================================================================== --- src/trigger.c +++ src/trigger.c @@ -371,16 +371,16 @@ u8 op, /* Trigger opcode */ Token *pName /* The target name */ ){ TriggerStep *pTriggerStep; - pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n); + pTriggerStep = sqlite3DbMallocZero(db, sizeof(TriggerStep) + pName->n + 1); if( pTriggerStep ){ char *z = (char*)&pTriggerStep[1]; memcpy(z, pName->z, pName->n); - pTriggerStep->target.z = z; - pTriggerStep->target.n = pName->n; + sqlite3Dequote(z); + pTriggerStep->zTarget = z; pTriggerStep->op = op; } return pTriggerStep; } @@ -665,11 +665,11 @@ } return (mask ? pList : 0); } /* -** Convert the pStep->target token into a SrcList and return a pointer +** Convert the pStep->zTarget string into a SrcList and return a pointer ** to that SrcList. ** ** This routine adds a specific database name, if needed, to the target when ** forming the SrcList. This prevents a trigger in one database from ** referring to a target in another database. An exception is when the @@ -678,21 +678,21 @@ */ static SrcList *targetSrcList( 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 */ - pSrc = sqlite3SrcListAppend(pParse->db, 0, &pStep->target, 0); + pSrc = sqlite3SrcListAppend(db, 0, 0, 0); if( pSrc ){ assert( pSrc->nSrc>0 ); - assert( pSrc->a!=0 ); - iDb = sqlite3SchemaToIndex(pParse->db, pStep->pTrig->pSchema); + pSrc->a[pSrc->nSrc-1].zName = sqlite3DbStrDup(db, pStep->zTarget); + iDb = sqlite3SchemaToIndex(db, pStep->pTrig->pSchema); if( iDb==0 || iDb>=2 ){ - sqlite3 *db = pParse->db; - assert( iDbdb->nDb ); + assert( iDbnDb ); pSrc->a[pSrc->nSrc-1].zDatabase = sqlite3DbStrDup(db, db->aDb[iDb].zName); } } return pSrc; } Index: test/fkey1.test ================================================================== --- test/fkey1.test +++ test/fkey1.test @@ -115,7 +115,37 @@ } } [concat \ {0 0 t5 d {} {SET DEFAULT} CASCADE NONE} \ {0 1 t5 e {} {SET DEFAULT} CASCADE NONE} \ ] + +# Stress the dequoting logic. The first test is not so bad. +do_execsql_test fkey1-4.0 { + PRAGMA foreign_keys=ON; + CREATE TABLE "xx1"("xx2" TEXT PRIMARY KEY, "xx3" TEXT); + INSERT INTO "xx1"("xx2","xx3") VALUES('abc','def'); + CREATE TABLE "xx4"("xx5" TEXT REFERENCES "xx1" ON DELETE CASCADE); + INSERT INTO "xx4"("xx5") VALUES('abc'); + INSERT INTO "xx1"("xx2","xx3") VALUES('uvw','xyz'); + SELECT 1, "xx5" FROM "xx4"; + DELETE FROM "xx1"; + SELECT 2, "xx5" FROM "xx4"; +} {1 abc} + +# This case is identical to the previous except the "xx" in each name +# is changed to a single escaped double-quote character. +do_execsql_test fkey1-4.1 { + PRAGMA foreign_keys=ON; + CREATE TABLE """1"("""2" TEXT PRIMARY KEY, """3" TEXT); + INSERT INTO """1"("""2","""3") VALUES('abc','def'); + CREATE TABLE """4"("""5" TEXT REFERENCES """1" ON DELETE CASCADE); + INSERT INTO """4"("""5") VALUES('abc'); + INSERT INTO """1"("""2","""3") VALUES('uvw','xyz'); + SELECT 1, """5" FROM """4"; + DELETE FROM """1"; + SELECT 2, """5" FROM """4"; +} {1 abc} +do_execsql_test fkey1-4.2 { + PRAGMA table_info="""1"; +} {0 {"2} TEXT 0 {} 1 1 {"3} TEXT 0 {} 0} finish_test Index: test/triggerC.test ================================================================== --- test/triggerC.test +++ test/triggerC.test @@ -10,10 +10,11 @@ #*********************************************************************** # set testdir [file dirname $argv0] source $testdir/tester.tcl +set testprefix triggerC ifcapable {!trigger} { finish_test return } @@ -948,7 +949,55 @@ do_catchsql_test triggerC-13.2 { UPDATE t12 SET a=a+1, b=b+1; } {1 {too many levels of trigger recursion}} + +#------------------------------------------------------------------------- +# Check that table names used by trigger programs are dequoted exactly +# once. +# +do_execsql_test 15.1.1 { + PRAGMA recursive_triggers = 1; + CREATE TABLE node( + id int not null primary key, + pid int not null default 0 references node, + key varchar not null, + path varchar default '', + unique(pid, key) + ); + CREATE TRIGGER node_delete_referencing AFTER DELETE ON "node" + BEGIN + DELETE FROM "node" WHERE pid = old."id"; + END; +} +do_execsql_test 15.1.2 { + INSERT INTO node(id, pid, key) VALUES(9, 0, 'test'); + INSERT INTO node(id, pid, key) VALUES(90, 9, 'test1'); + INSERT INTO node(id, pid, key) VALUES(900, 90, 'test2'); + DELETE FROM node WHERE id=9; + SELECT * FROM node; +} + +do_execsql_test 15.2.1 { + CREATE TABLE x1 (x); + + CREATE TABLE x2 (a, b); + CREATE TABLE '"x2"'(a, b); + + INSERT INTO x2 VALUES(1, 2); + INSERT INTO x2 VALUES(3, 4); + INSERT INTO '"x2"' SELECT * FROM x2; + + CREATE TRIGGER x1ai AFTER INSERT ON x1 BEGIN + INSERT INTO """x2""" VALUES('x', 'y'); + DELETE FROM """x2""" WHERE a=1; + UPDATE """x2""" SET b = 11 WHERE a = 3; + END; + + INSERT INTO x1 VALUES('go!'); +} + +do_execsql_test 15.2.2 { SELECT * FROM x2; } {1 2 3 4} +do_execsql_test 15.2.3 { SELECT * FROM """x2"""; } {3 11 x y} finish_test