mirror of
				https://github.com/sqlite/sqlite.git
				synced 2025-11-03 16:53:36 +03:00 
			
		
		
		
	indices belong to and so that it reports when actions are taken in response to a trigger. (CVS 928) FossilOrigin-Name: c675a5504138f34cae6def782b5d3add2c67d2bc
		
			
				
	
	
		
			838 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			838 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
**
 | 
						|
** 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.
 | 
						|
**
 | 
						|
*************************************************************************
 | 
						|
*
 | 
						|
*/
 | 
						|
#include "sqliteInt.h"
 | 
						|
 | 
						|
/*
 | 
						|
** Delete a linked list of TriggerStep structures.
 | 
						|
*/
 | 
						|
void sqliteDeleteTriggerStep(TriggerStep *pTriggerStep){
 | 
						|
  while( pTriggerStep ){
 | 
						|
    TriggerStep * pTmp = pTriggerStep;
 | 
						|
    pTriggerStep = pTriggerStep->pNext;
 | 
						|
 | 
						|
    if( pTmp->target.dyn ) sqliteFree((char*)pTmp->target.z);
 | 
						|
    sqliteExprDelete(pTmp->pWhere);
 | 
						|
    sqliteExprListDelete(pTmp->pExprList);
 | 
						|
    sqliteSelectDelete(pTmp->pSelect);
 | 
						|
    sqliteIdListDelete(pTmp->pIdList);
 | 
						|
 | 
						|
    sqliteFree(pTmp);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** This is called by the parser when it sees a CREATE TRIGGER statement
 | 
						|
** up to the point of the BEGIN before the trigger actions.  A Trigger
 | 
						|
** structure is generated based on the information available and stored
 | 
						|
** in pParse->pNewTrigger.  After the trigger actions have been parsed, the
 | 
						|
** sqliteFinishTrigger() function is called to complete the trigger
 | 
						|
** construction process.
 | 
						|
*/
 | 
						|
void sqliteBeginTrigger(
 | 
						|
  Parse *pParse,      /* The parse context of the CREATE TRIGGER statement */
 | 
						|
  Token *pName,       /* The name of the trigger */
 | 
						|
  int tr_tm,          /* One of TK_BEFORE, TK_AFTER , TK_INSTEAD */
 | 
						|
  int op,             /* One of TK_INSERT, TK_UPDATE, TK_DELETE */
 | 
						|
  IdList *pColumns,   /* column list if this is an UPDATE OF trigger */
 | 
						|
  SrcList *pTableName,/* The name of the table/view the trigger applies to */
 | 
						|
  int foreach,        /* One of TK_ROW or TK_STATEMENT */
 | 
						|
  Expr *pWhen,        /* WHEN clause */
 | 
						|
  int isTemp          /* True if the TEMPORARY keyword is present */
 | 
						|
){
 | 
						|
  Trigger *nt;
 | 
						|
  Table   *tab;
 | 
						|
  char *zName = 0;        /* Name of the trigger */
 | 
						|
  sqlite *db = pParse->db;
 | 
						|
  int iDb;                /* When database to store the trigger in */
 | 
						|
 | 
						|
  /* Check that: 
 | 
						|
  ** 1. the trigger name does not already exist.
 | 
						|
  ** 2. the table (or view) does exist in the same database as the trigger.
 | 
						|
  ** 3. that we are not trying to create a trigger on the sqlite_master table
 | 
						|
  ** 4. That we are not trying to create an INSTEAD OF trigger on a table.
 | 
						|
  ** 5. That we are not trying to create a BEFORE or AFTER trigger on a view.
 | 
						|
  */
 | 
						|
  if( sqlite_malloc_failed ) goto trigger_cleanup;
 | 
						|
  assert( pTableName->nSrc==1 );
 | 
						|
  tab = sqliteSrcListLookup(pParse, pTableName);
 | 
						|
  if( !tab ){
 | 
						|
    goto trigger_cleanup;
 | 
						|
  }
 | 
						|
  iDb = isTemp ? 1 : tab->iDb;
 | 
						|
  if( iDb>=2 && !pParse->initFlag ){
 | 
						|
    sqliteErrorMsg(pParse, "triggers may not be added to auxiliary "
 | 
						|
       "database %s", db->aDb[tab->iDb].zName);
 | 
						|
    goto trigger_cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  zName = sqliteStrNDup(pName->z, pName->n);
 | 
						|
  if( sqliteHashFind(&(db->aDb[iDb].trigHash), zName,pName->n+1) ){
 | 
						|
    sqliteErrorMsg(pParse, "trigger %T already exists", pName);
 | 
						|
    goto trigger_cleanup;
 | 
						|
  }
 | 
						|
  if( sqliteStrNICmp(tab->zName, "sqlite_", 7)==0 ){
 | 
						|
    sqliteErrorMsg(pParse, "cannot create trigger on system table");
 | 
						|
    pParse->nErr++;
 | 
						|
    goto trigger_cleanup;
 | 
						|
  }
 | 
						|
  if( tab->pSelect && tr_tm != TK_INSTEAD ){
 | 
						|
    sqliteErrorMsg(pParse, "cannot create %s trigger on view: %S", 
 | 
						|
        (tr_tm == TK_BEFORE)?"BEFORE":"AFTER", pTableName, 0);
 | 
						|
    goto trigger_cleanup;
 | 
						|
  }
 | 
						|
  if( !tab->pSelect && tr_tm == TK_INSTEAD ){
 | 
						|
    sqliteErrorMsg(pParse, "cannot create INSTEAD OF"
 | 
						|
        " trigger on table: %S", pTableName, 0);
 | 
						|
    goto trigger_cleanup;
 | 
						|
  }
 | 
						|
#ifndef SQLITE_OMIT_AUTHORIZATION
 | 
						|
  {
 | 
						|
    int code = SQLITE_CREATE_TRIGGER;
 | 
						|
    const char *zDb = db->aDb[tab->iDb].zName;
 | 
						|
    const char *zDbTrig = isTemp ? db->aDb[1].zName : zDb;
 | 
						|
    if( tab->iDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER;
 | 
						|
    if( sqliteAuthCheck(pParse, code, zName, tab->zName, zDbTrig) ){
 | 
						|
      goto trigger_cleanup;
 | 
						|
    }
 | 
						|
    if( sqliteAuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(tab->iDb), 0, zDb)){
 | 
						|
      goto trigger_cleanup;
 | 
						|
    }
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  if (tr_tm == TK_INSTEAD){
 | 
						|
    tr_tm = TK_BEFORE;
 | 
						|
  }
 | 
						|
 | 
						|
  /* Build the Trigger object */
 | 
						|
  nt = (Trigger*)sqliteMalloc(sizeof(Trigger));
 | 
						|
  if( nt==0 ) goto trigger_cleanup;
 | 
						|
  nt->name = zName;
 | 
						|
  zName = 0;
 | 
						|
  nt->table = sqliteStrDup(pTableName->a[0].zName);
 | 
						|
  if( sqlite_malloc_failed ) goto trigger_cleanup;
 | 
						|
  nt->iDb = iDb;
 | 
						|
  nt->op = op;
 | 
						|
  nt->tr_tm = tr_tm;
 | 
						|
  nt->pWhen = sqliteExprDup(pWhen);
 | 
						|
  nt->pColumns = sqliteIdListDup(pColumns);
 | 
						|
  nt->foreach = foreach;
 | 
						|
  assert( pParse->pNewTrigger==0 );
 | 
						|
  pParse->pNewTrigger = nt;
 | 
						|
 | 
						|
trigger_cleanup:
 | 
						|
  sqliteFree(zName);
 | 
						|
  sqliteSrcListDelete(pTableName);
 | 
						|
  sqliteIdListDelete(pColumns);
 | 
						|
  sqliteExprDelete(pWhen);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** This routine is called after all of the trigger actions have been parsed
 | 
						|
** in order to complete the process of building the trigger.
 | 
						|
*/
 | 
						|
void sqliteFinishTrigger(
 | 
						|
  Parse *pParse,          /* Parser context */
 | 
						|
  TriggerStep *pStepList, /* The triggered program */
 | 
						|
  Token *pAll             /* Token that describes the complete CREATE TRIGGER */
 | 
						|
){
 | 
						|
  Trigger *nt;              /* The trigger whose construction is finishing up */
 | 
						|
  sqlite *db = pParse->db;  /* The database */
 | 
						|
 | 
						|
  if( pParse->nErr || pParse->pNewTrigger==0 ) goto triggerfinish_cleanup;
 | 
						|
  nt = pParse->pNewTrigger;
 | 
						|
  pParse->pNewTrigger = 0;
 | 
						|
  nt->step_list = pStepList;
 | 
						|
  while( pStepList ){
 | 
						|
    pStepList->pTrig = nt;
 | 
						|
    pStepList = pStepList->pNext;
 | 
						|
  }
 | 
						|
 | 
						|
  /* if we are not initializing, and this trigger is not on a TEMP table, 
 | 
						|
  ** build the sqlite_master entry
 | 
						|
  */
 | 
						|
  if( !pParse->initFlag ){
 | 
						|
    static VdbeOp insertTrig[] = {
 | 
						|
      { OP_NewRecno,   0, 0,  0          },
 | 
						|
      { OP_String,     0, 0,  "trigger"  },
 | 
						|
      { OP_String,     0, 0,  0          },  /* 2: trigger name */
 | 
						|
      { OP_String,     0, 0,  0          },  /* 3: table name */
 | 
						|
      { OP_Integer,    0, 0,  0          },
 | 
						|
      { OP_String,     0, 0,  0          },  /* 5: SQL */
 | 
						|
      { OP_MakeRecord, 5, 0,  0          },
 | 
						|
      { OP_PutIntKey,  0, 0,  0          },
 | 
						|
    };
 | 
						|
    int addr;
 | 
						|
    Vdbe *v;
 | 
						|
 | 
						|
    /* Make an entry in the sqlite_master table */
 | 
						|
    v = sqliteGetVdbe(pParse);
 | 
						|
    if( v==0 ) goto triggerfinish_cleanup;
 | 
						|
    sqliteBeginWriteOperation(pParse, 0, 0);
 | 
						|
    sqliteOpenMasterTable(v, nt->iDb==1);
 | 
						|
    addr = sqliteVdbeAddOpList(v, ArraySize(insertTrig), insertTrig);
 | 
						|
    sqliteVdbeChangeP3(v, addr+2, nt->name, 0); 
 | 
						|
    sqliteVdbeChangeP3(v, addr+3, nt->table, 0); 
 | 
						|
    sqliteVdbeChangeP3(v, addr+5, pAll->z, pAll->n);
 | 
						|
    if( nt->iDb==0 ){
 | 
						|
      sqliteChangeCookie(db, v);
 | 
						|
    }
 | 
						|
    sqliteVdbeAddOp(v, OP_Close, 0, 0);
 | 
						|
    sqliteEndWriteOperation(pParse);
 | 
						|
  }
 | 
						|
 | 
						|
  if( !pParse->explain ){
 | 
						|
    Table *pTab;
 | 
						|
    sqliteHashInsert(&db->aDb[nt->iDb].trigHash, 
 | 
						|
                     nt->name, strlen(nt->name)+1, nt);
 | 
						|
    pTab = sqliteLocateTable(pParse, nt->table, 0);
 | 
						|
    assert( pTab!=0 );
 | 
						|
    nt->pNext = pTab->pTrigger;
 | 
						|
    pTab->pTrigger = nt;
 | 
						|
  }else{
 | 
						|
    sqliteDeleteTrigger(nt);
 | 
						|
  }
 | 
						|
 | 
						|
triggerfinish_cleanup:
 | 
						|
  sqliteDeleteTrigger(pParse->pNewTrigger);
 | 
						|
  pParse->pNewTrigger = 0;
 | 
						|
  sqliteDeleteTriggerStep(pStepList);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** Make a copy of all components of the given trigger step.  This has
 | 
						|
** the effect of copying all Expr.token.z values into memory obtained
 | 
						|
** from sqliteMalloc().  As initially created, the Expr.token.z values
 | 
						|
** all point to the input string that was fed to the parser.  But that
 | 
						|
** string is ephemeral - it will go away as soon as the sqlite_exec()
 | 
						|
** call that started the parser exits.  This routine makes a persistent
 | 
						|
** copy of all the Expr.token.z strings so that the TriggerStep structure
 | 
						|
** will be valid even after the sqlite_exec() call returns.
 | 
						|
*/
 | 
						|
static void sqlitePersistTriggerStep(TriggerStep *p){
 | 
						|
  if( p->target.z ){
 | 
						|
    p->target.z = sqliteStrNDup(p->target.z, p->target.n);
 | 
						|
    p->target.dyn = 1;
 | 
						|
  }
 | 
						|
  if( p->pSelect ){
 | 
						|
    Select *pNew = sqliteSelectDup(p->pSelect);
 | 
						|
    sqliteSelectDelete(p->pSelect);
 | 
						|
    p->pSelect = pNew;
 | 
						|
  }
 | 
						|
  if( p->pWhere ){
 | 
						|
    Expr *pNew = sqliteExprDup(p->pWhere);
 | 
						|
    sqliteExprDelete(p->pWhere);
 | 
						|
    p->pWhere = pNew;
 | 
						|
  }
 | 
						|
  if( p->pExprList ){
 | 
						|
    ExprList *pNew = sqliteExprListDup(p->pExprList);
 | 
						|
    sqliteExprListDelete(p->pExprList);
 | 
						|
    p->pExprList = pNew;
 | 
						|
  }
 | 
						|
  if( p->pIdList ){
 | 
						|
    IdList *pNew = sqliteIdListDup(p->pIdList);
 | 
						|
    sqliteIdListDelete(p->pIdList);
 | 
						|
    p->pIdList = pNew;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** Turn a SELECT statement (that the pSelect parameter points to) into
 | 
						|
** a trigger step.  Return a pointer to a TriggerStep structure.
 | 
						|
**
 | 
						|
** The parser calls this routine when it finds a SELECT statement in
 | 
						|
** body of a TRIGGER.  
 | 
						|
*/
 | 
						|
TriggerStep *sqliteTriggerSelectStep(Select *pSelect){
 | 
						|
  TriggerStep *pTriggerStep = sqliteMalloc(sizeof(TriggerStep));
 | 
						|
  if( pTriggerStep==0 ) return 0;
 | 
						|
 | 
						|
  pTriggerStep->op = TK_SELECT;
 | 
						|
  pTriggerStep->pSelect = pSelect;
 | 
						|
  pTriggerStep->orconf = OE_Default;
 | 
						|
  sqlitePersistTriggerStep(pTriggerStep);
 | 
						|
 | 
						|
  return pTriggerStep;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** Build a trigger step out of an INSERT statement.  Return a pointer
 | 
						|
** to the new trigger step.
 | 
						|
**
 | 
						|
** The parser calls this routine when it sees an INSERT inside the
 | 
						|
** body of a trigger.
 | 
						|
*/
 | 
						|
TriggerStep *sqliteTriggerInsertStep(
 | 
						|
  Token *pTableName,  /* Name of the table into which we insert */
 | 
						|
  IdList *pColumn,    /* List of columns in pTableName to insert into */
 | 
						|
  ExprList *pEList,   /* The VALUE clause: a list of values to be inserted */
 | 
						|
  Select *pSelect,    /* A SELECT statement that supplies values */
 | 
						|
  int orconf          /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */
 | 
						|
){
 | 
						|
  TriggerStep *pTriggerStep = sqliteMalloc(sizeof(TriggerStep));
 | 
						|
  if( pTriggerStep==0 ) return 0;
 | 
						|
 | 
						|
  assert(pEList == 0 || pSelect == 0);
 | 
						|
  assert(pEList != 0 || pSelect != 0);
 | 
						|
 | 
						|
  pTriggerStep->op = TK_INSERT;
 | 
						|
  pTriggerStep->pSelect = pSelect;
 | 
						|
  pTriggerStep->target  = *pTableName;
 | 
						|
  pTriggerStep->pIdList = pColumn;
 | 
						|
  pTriggerStep->pExprList = pEList;
 | 
						|
  pTriggerStep->orconf = orconf;
 | 
						|
  sqlitePersistTriggerStep(pTriggerStep);
 | 
						|
 | 
						|
  return pTriggerStep;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** Construct a trigger step that implements an UPDATE statement and return
 | 
						|
** a pointer to that trigger step.  The parser calls this routine when it
 | 
						|
** sees an UPDATE statement inside the body of a CREATE TRIGGER.
 | 
						|
*/
 | 
						|
TriggerStep *sqliteTriggerUpdateStep(
 | 
						|
  Token *pTableName,   /* Name of the table to be updated */
 | 
						|
  ExprList *pEList,    /* The SET clause: list of column and new values */
 | 
						|
  Expr *pWhere,        /* The WHERE clause */
 | 
						|
  int orconf           /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */
 | 
						|
){
 | 
						|
  TriggerStep *pTriggerStep = sqliteMalloc(sizeof(TriggerStep));
 | 
						|
  if( pTriggerStep==0 ) return 0;
 | 
						|
 | 
						|
  pTriggerStep->op = TK_UPDATE;
 | 
						|
  pTriggerStep->target  = *pTableName;
 | 
						|
  pTriggerStep->pExprList = pEList;
 | 
						|
  pTriggerStep->pWhere = pWhere;
 | 
						|
  pTriggerStep->orconf = orconf;
 | 
						|
  sqlitePersistTriggerStep(pTriggerStep);
 | 
						|
 | 
						|
  return pTriggerStep;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** Construct a trigger step that implements a DELETE statement and return
 | 
						|
** a pointer to that trigger step.  The parser calls this routine when it
 | 
						|
** sees a DELETE statement inside the body of a CREATE TRIGGER.
 | 
						|
*/
 | 
						|
TriggerStep *sqliteTriggerDeleteStep(Token *pTableName, Expr *pWhere){
 | 
						|
  TriggerStep *pTriggerStep = sqliteMalloc(sizeof(TriggerStep));
 | 
						|
  if( pTriggerStep==0 ) return 0;
 | 
						|
 | 
						|
  pTriggerStep->op = TK_DELETE;
 | 
						|
  pTriggerStep->target  = *pTableName;
 | 
						|
  pTriggerStep->pWhere = pWhere;
 | 
						|
  pTriggerStep->orconf = OE_Default;
 | 
						|
  sqlitePersistTriggerStep(pTriggerStep);
 | 
						|
 | 
						|
  return pTriggerStep;
 | 
						|
}
 | 
						|
 | 
						|
/* 
 | 
						|
** Recursively delete a Trigger structure
 | 
						|
*/
 | 
						|
void sqliteDeleteTrigger(Trigger *pTrigger){
 | 
						|
  if( pTrigger==0 ) return;
 | 
						|
  sqliteDeleteTriggerStep(pTrigger->step_list);
 | 
						|
  sqliteFree(pTrigger->name);
 | 
						|
  sqliteFree(pTrigger->table);
 | 
						|
  sqliteExprDelete(pTrigger->pWhen);
 | 
						|
  sqliteIdListDelete(pTrigger->pColumns);
 | 
						|
  sqliteFree(pTrigger);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * This function is called to drop a trigger from the database schema. 
 | 
						|
 *
 | 
						|
 * This may be called directly from the parser, or from within 
 | 
						|
 * sqliteDropTable(). In the latter case the "nested" argument is true.
 | 
						|
 *
 | 
						|
 * Note that this function does not delete the trigger entirely. Instead it
 | 
						|
 * removes it from the internal schema and places it in the trigDrop hash 
 | 
						|
 * table. This is so that the trigger can be restored into the database schema
 | 
						|
 * if the transaction is rolled back.
 | 
						|
 */
 | 
						|
void sqliteDropTrigger(Parse *pParse, SrcList *pName, int nested){
 | 
						|
  Trigger *pTrigger;
 | 
						|
  Table   *pTable;
 | 
						|
  Vdbe *v;
 | 
						|
  int i;
 | 
						|
  const char *zDb;
 | 
						|
  const char *zName;
 | 
						|
  int nName;
 | 
						|
  sqlite *db = pParse->db;
 | 
						|
 | 
						|
  if( sqlite_malloc_failed ) goto drop_trigger_cleanup;
 | 
						|
  assert( pName->nSrc==1 );
 | 
						|
  zDb = pName->a[0].zDatabase;
 | 
						|
  zName = pName->a[0].zName;
 | 
						|
  nName = strlen(zName);
 | 
						|
  for(i=0; i<db->nDb; i++){
 | 
						|
    int j = (i<2) ? i^1 : i;  /* Search TEMP before MAIN */
 | 
						|
    if( zDb && sqliteStrICmp(db->aDb[j].zName, zDb) ) continue;
 | 
						|
    pTrigger = sqliteHashFind(&(db->aDb[j].trigHash), zName, nName+1);
 | 
						|
    if( pTrigger ) break;
 | 
						|
  }
 | 
						|
  if( !pTrigger ){
 | 
						|
    sqliteErrorMsg(pParse, "no such trigger: %S", pName, 0);
 | 
						|
    goto drop_trigger_cleanup;
 | 
						|
  }
 | 
						|
  assert( pTrigger->iDb<db->nDb );
 | 
						|
  if( pTrigger->iDb>=2 ){
 | 
						|
    sqliteErrorMsg(pParse, "triggers may not be removed from "
 | 
						|
       "auxiliary database %s", db->aDb[pTrigger->iDb].zName);
 | 
						|
    goto drop_trigger_cleanup;
 | 
						|
  }
 | 
						|
  pTable = sqliteFindTable(db, pTrigger->table, db->aDb[pTrigger->iDb].zName);
 | 
						|
  assert(pTable);
 | 
						|
  assert( pTable->iDb==pTrigger->iDb || pTrigger->iDb==1 );
 | 
						|
#ifndef SQLITE_OMIT_AUTHORIZATION
 | 
						|
  {
 | 
						|
    int code = SQLITE_DROP_TRIGGER;
 | 
						|
    const char *zDb = db->aDb[pTrigger->iDb].zName;
 | 
						|
    const char *zTab = SCHEMA_TABLE(pTrigger->iDb);
 | 
						|
    if( pTrigger->iDb ) code = SQLITE_DROP_TEMP_TRIGGER;
 | 
						|
    if( sqliteAuthCheck(pParse, code, pTrigger->name, pTable->zName, zDb) ||
 | 
						|
      sqliteAuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
 | 
						|
      goto drop_trigger_cleanup;
 | 
						|
    }
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  /* Generate code to destroy the database record of the trigger.
 | 
						|
  */
 | 
						|
  if( pTable!=0 && !nested && (v = sqliteGetVdbe(pParse))!=0 ){
 | 
						|
    int base;
 | 
						|
    static VdbeOp dropTrigger[] = {
 | 
						|
      { OP_Rewind,     0, ADDR(8),  0},
 | 
						|
      { OP_String,     0, 0,        0}, /* 1 */
 | 
						|
      { OP_MemStore,   1, 1,        0},
 | 
						|
      { OP_MemLoad,    1, 0,        0}, /* 3 */
 | 
						|
      { OP_Column,     0, 1,        0},
 | 
						|
      { OP_Ne,         0, ADDR(7),  0},
 | 
						|
      { OP_Delete,     0, 0,        0},
 | 
						|
      { OP_Next,       0, ADDR(3),  0}, /* 7 */
 | 
						|
    };
 | 
						|
 | 
						|
    sqliteBeginWriteOperation(pParse, 0, 0);
 | 
						|
    sqliteOpenMasterTable(v, pTrigger->iDb);
 | 
						|
    base = sqliteVdbeAddOpList(v,  ArraySize(dropTrigger), dropTrigger);
 | 
						|
    sqliteVdbeChangeP3(v, base+1, zName, 0);
 | 
						|
    if( pTrigger->iDb==0 ){
 | 
						|
      sqliteChangeCookie(db, v);
 | 
						|
    }
 | 
						|
    sqliteVdbeAddOp(v, OP_Close, 0, 0);
 | 
						|
    sqliteEndWriteOperation(pParse);
 | 
						|
  }
 | 
						|
 | 
						|
  /*
 | 
						|
   * If this is not an "explain", then delete the trigger structure.
 | 
						|
   */
 | 
						|
  if( !pParse->explain ){
 | 
						|
    if( pTable->pTrigger == pTrigger ){
 | 
						|
      pTable->pTrigger = pTrigger->pNext;
 | 
						|
    }else{
 | 
						|
      Trigger *cc = pTable->pTrigger;
 | 
						|
      while( cc ){ 
 | 
						|
        if( cc->pNext == pTrigger ){
 | 
						|
          cc->pNext = cc->pNext->pNext;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
        cc = cc->pNext;
 | 
						|
      }
 | 
						|
      assert(cc);
 | 
						|
    }
 | 
						|
    sqliteHashInsert(&(db->aDb[pTrigger->iDb].trigHash), zName, nName+1, 0);
 | 
						|
    sqliteDeleteTrigger(pTrigger);
 | 
						|
  }
 | 
						|
 | 
						|
drop_trigger_cleanup:
 | 
						|
  sqliteSrcListDelete(pName);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** pEList is the SET clause of an UPDATE statement.  Each entry
 | 
						|
** in pEList is of the format <id>=<expr>.  If any of the entries
 | 
						|
** in pEList have an <id> which matches an identifier in pIdList,
 | 
						|
** then return TRUE.  If pIdList==NULL, then it is considered a
 | 
						|
** wildcard that matches anything.  Likewise if pEList==NULL then
 | 
						|
** it matches anything so always return true.  Return false only
 | 
						|
** if there is no match.
 | 
						|
*/
 | 
						|
static int checkColumnOverLap(IdList *pIdList, ExprList *pEList){
 | 
						|
  int e;
 | 
						|
  if( !pIdList || !pEList ) return 1;
 | 
						|
  for(e=0; e<pEList->nExpr; e++){
 | 
						|
    if( sqliteIdListIndex(pIdList, pEList->a[e].zName)>=0 ) return 1;
 | 
						|
  }
 | 
						|
  return 0; 
 | 
						|
}
 | 
						|
 | 
						|
/* A global variable that is TRUE if we should always set up temp tables for
 | 
						|
 * for triggers, even if there are no triggers to code. This is used to test 
 | 
						|
 * how much overhead the triggers algorithm is causing.
 | 
						|
 *
 | 
						|
 * This flag can be set or cleared using the "trigger_overhead_test" pragma.
 | 
						|
 * The pragma is not documented since it is not really part of the interface
 | 
						|
 * to SQLite, just the test procedure.
 | 
						|
*/
 | 
						|
int always_code_trigger_setup = 0;
 | 
						|
 | 
						|
/*
 | 
						|
 * Returns true if a trigger matching op, tr_tm and foreach that is NOT already
 | 
						|
 * on the Parse objects trigger-stack (to prevent recursive trigger firing) is
 | 
						|
 * found in the list specified as pTrigger.
 | 
						|
 */
 | 
						|
int sqliteTriggersExist(
 | 
						|
  Parse *pParse,          /* Used to check for recursive triggers */
 | 
						|
  Trigger *pTrigger,      /* A list of triggers associated with a table */
 | 
						|
  int op,                 /* one of TK_DELETE, TK_INSERT, TK_UPDATE */
 | 
						|
  int tr_tm,              /* one of TK_BEFORE, TK_AFTER */
 | 
						|
  int foreach,            /* one of TK_ROW or TK_STATEMENT */
 | 
						|
  ExprList *pChanges      /* Columns that change in an UPDATE statement */
 | 
						|
){
 | 
						|
  Trigger * pTriggerCursor;
 | 
						|
 | 
						|
  if( always_code_trigger_setup ){
 | 
						|
    return 1;
 | 
						|
  }
 | 
						|
 | 
						|
  pTriggerCursor = pTrigger;
 | 
						|
  while( pTriggerCursor ){
 | 
						|
    if( pTriggerCursor->op == op && 
 | 
						|
	pTriggerCursor->tr_tm == tr_tm && 
 | 
						|
	pTriggerCursor->foreach == foreach &&
 | 
						|
	checkColumnOverLap(pTriggerCursor->pColumns, pChanges) ){
 | 
						|
      TriggerStack * ss;
 | 
						|
      ss = pParse->trigStack;
 | 
						|
      while( ss && ss->pTrigger != pTrigger ){
 | 
						|
	ss = ss->pNext;
 | 
						|
      }
 | 
						|
      if( !ss )return 1;
 | 
						|
    }
 | 
						|
    pTriggerCursor = pTriggerCursor->pNext;
 | 
						|
  }
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** Generate VDBE code for zero or more statements inside the body of a
 | 
						|
** trigger.  
 | 
						|
*/
 | 
						|
static int codeTriggerProgram(
 | 
						|
  Parse *pParse,            /* The parser context */
 | 
						|
  TriggerStep *pStepList,   /* List of statements inside the trigger body */
 | 
						|
  int orconfin              /* Conflict algorithm. (OE_Abort, etc) */  
 | 
						|
){
 | 
						|
  TriggerStep * pTriggerStep = pStepList;
 | 
						|
  int orconf;
 | 
						|
 | 
						|
  while( pTriggerStep ){
 | 
						|
    int saveNTab = pParse->nTab;
 | 
						|
    int saveUseDb = pParse->useDb;
 | 
						|
    orconf = (orconfin == OE_Default)?pTriggerStep->orconf:orconfin;
 | 
						|
    pParse->trigStack->orconf = orconf;
 | 
						|
    pParse->useDb = pTriggerStep->pTrig->iDb;
 | 
						|
    if( pParse->useDb==1 ) pParse->useDb = -1;
 | 
						|
    switch( pTriggerStep->op ){
 | 
						|
      case TK_SELECT: {
 | 
						|
	Select * ss = sqliteSelectDup(pTriggerStep->pSelect);		  
 | 
						|
	assert(ss);
 | 
						|
	assert(ss->pSrc);
 | 
						|
	sqliteSelect(pParse, ss, SRT_Discard, 0, 0, 0, 0);
 | 
						|
	sqliteSelectDelete(ss);
 | 
						|
	break;
 | 
						|
      }
 | 
						|
      case TK_UPDATE: {
 | 
						|
        SrcList *pSrc;
 | 
						|
        pSrc = sqliteSrcListAppend(0, &pTriggerStep->target, 0);
 | 
						|
        sqliteVdbeAddOp(pParse->pVdbe, OP_ListPush, 0, 0);
 | 
						|
        sqliteUpdate(pParse, pSrc,
 | 
						|
		sqliteExprListDup(pTriggerStep->pExprList), 
 | 
						|
		sqliteExprDup(pTriggerStep->pWhere), orconf);
 | 
						|
        sqliteVdbeAddOp(pParse->pVdbe, OP_ListPop, 0, 0);
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      case TK_INSERT: {
 | 
						|
        SrcList *pSrc;
 | 
						|
        pSrc = sqliteSrcListAppend(0, &pTriggerStep->target, 0);
 | 
						|
        sqliteInsert(pParse, pSrc,
 | 
						|
          sqliteExprListDup(pTriggerStep->pExprList), 
 | 
						|
          sqliteSelectDup(pTriggerStep->pSelect), 
 | 
						|
          sqliteIdListDup(pTriggerStep->pIdList), orconf);
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      case TK_DELETE: {
 | 
						|
        SrcList *pSrc;
 | 
						|
        sqliteVdbeAddOp(pParse->pVdbe, OP_ListPush, 0, 0);
 | 
						|
        pSrc = sqliteSrcListAppend(0, &pTriggerStep->target, 0);
 | 
						|
        sqliteDeleteFrom(pParse, pSrc, sqliteExprDup(pTriggerStep->pWhere));
 | 
						|
        sqliteVdbeAddOp(pParse->pVdbe, OP_ListPop, 0, 0);
 | 
						|
        break;
 | 
						|
      }
 | 
						|
      default:
 | 
						|
        assert(0);
 | 
						|
    } 
 | 
						|
    pParse->nTab = saveNTab;
 | 
						|
    pParse->useDb = saveUseDb;
 | 
						|
    pTriggerStep = pTriggerStep->pNext;
 | 
						|
  }
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
** This is called to code FOR EACH ROW triggers.
 | 
						|
**
 | 
						|
** When the code that this function generates is executed, the following 
 | 
						|
** must be true:
 | 
						|
**
 | 
						|
** 1. No cursors may be open in the main database.  (But newIdx and oldIdx
 | 
						|
**    can be indices of cursors in temporary tables.  See below.)
 | 
						|
**
 | 
						|
** 2. If the triggers being coded are ON INSERT or ON UPDATE triggers, then
 | 
						|
**    a temporary vdbe cursor (index newIdx) must be open and pointing at
 | 
						|
**    a row containing values to be substituted for new.* expressions in the
 | 
						|
**    trigger program(s).
 | 
						|
**
 | 
						|
** 3. If the triggers being coded are ON DELETE or ON UPDATE triggers, then
 | 
						|
**    a temporary vdbe cursor (index oldIdx) must be open and pointing at
 | 
						|
**    a row containing values to be substituted for old.* expressions in the
 | 
						|
**    trigger program(s).
 | 
						|
**
 | 
						|
*/
 | 
						|
int sqliteCodeRowTrigger(
 | 
						|
  Parse *pParse,       /* Parse context */
 | 
						|
  int op,              /* One of TK_UPDATE, TK_INSERT, TK_DELETE */
 | 
						|
  ExprList *pChanges,  /* Changes list for any UPDATE OF triggers */
 | 
						|
  int tr_tm,           /* One of TK_BEFORE, TK_AFTER */
 | 
						|
  Table *pTab,         /* The table to code triggers from */
 | 
						|
  int newIdx,          /* The indice of the "new" row to access */
 | 
						|
  int oldIdx,          /* The indice of the "old" row to access */
 | 
						|
  int orconf,          /* ON CONFLICT policy */
 | 
						|
  int ignoreJump       /* Instruction to jump to for RAISE(IGNORE) */
 | 
						|
){
 | 
						|
  Trigger * pTrigger;
 | 
						|
  TriggerStack * pTriggerStack;
 | 
						|
 | 
						|
  assert(op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE);
 | 
						|
  assert(tr_tm == TK_BEFORE || tr_tm == TK_AFTER);
 | 
						|
 | 
						|
  assert(newIdx != -1 || oldIdx != -1);
 | 
						|
 | 
						|
  pTrigger = pTab->pTrigger;
 | 
						|
  while( pTrigger ){
 | 
						|
    int fire_this = 0;
 | 
						|
 | 
						|
    /* determine whether we should code this trigger */
 | 
						|
    if( pTrigger->op == op && pTrigger->tr_tm == tr_tm && 
 | 
						|
        pTrigger->foreach == TK_ROW ){
 | 
						|
      fire_this = 1;
 | 
						|
      pTriggerStack = pParse->trigStack;
 | 
						|
      while( pTriggerStack ){
 | 
						|
        if( pTriggerStack->pTrigger == pTrigger ){
 | 
						|
	  fire_this = 0;
 | 
						|
	}
 | 
						|
        pTriggerStack = pTriggerStack->pNext;
 | 
						|
      }
 | 
						|
      if( op == TK_UPDATE && pTrigger->pColumns &&
 | 
						|
          !checkColumnOverLap(pTrigger->pColumns, pChanges) ){
 | 
						|
        fire_this = 0;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if( fire_this && (pTriggerStack = sqliteMalloc(sizeof(TriggerStack)))!=0 ){
 | 
						|
      int endTrigger;
 | 
						|
      SrcList dummyTablist;
 | 
						|
      Expr * whenExpr;
 | 
						|
 | 
						|
      dummyTablist.nSrc = 0;
 | 
						|
 | 
						|
      /* Push an entry on to the trigger stack */
 | 
						|
      pTriggerStack->pTrigger = pTrigger;
 | 
						|
      pTriggerStack->newIdx = newIdx;
 | 
						|
      pTriggerStack->oldIdx = oldIdx;
 | 
						|
      pTriggerStack->pTab = pTab;
 | 
						|
      pTriggerStack->pNext = pParse->trigStack;
 | 
						|
      pTriggerStack->ignoreJump = ignoreJump;
 | 
						|
      pParse->trigStack = pTriggerStack;
 | 
						|
 | 
						|
      /* code the WHEN clause */
 | 
						|
      endTrigger = sqliteVdbeMakeLabel(pParse->pVdbe);
 | 
						|
      whenExpr = sqliteExprDup(pTrigger->pWhen);
 | 
						|
      if( sqliteExprResolveIds(pParse, 0, &dummyTablist, 0, whenExpr) ){
 | 
						|
        pParse->trigStack = pParse->trigStack->pNext;
 | 
						|
        sqliteFree(pTriggerStack);
 | 
						|
        sqliteExprDelete(whenExpr);
 | 
						|
        return 1;
 | 
						|
      }
 | 
						|
      sqliteExprIfFalse(pParse, whenExpr, endTrigger, 1);
 | 
						|
      sqliteExprDelete(whenExpr);
 | 
						|
 | 
						|
      codeTriggerProgram(pParse, pTrigger->step_list, orconf); 
 | 
						|
 | 
						|
      /* Pop the entry off the trigger stack */
 | 
						|
      pParse->trigStack = pParse->trigStack->pNext;
 | 
						|
      sqliteFree(pTriggerStack);
 | 
						|
 | 
						|
      sqliteVdbeResolveLabel(pParse->pVdbe, endTrigger);
 | 
						|
    }
 | 
						|
    pTrigger = pTrigger->pNext;
 | 
						|
  }
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * This function is called to code ON UPDATE and ON DELETE triggers on 
 | 
						|
 * views. 
 | 
						|
 *
 | 
						|
 * This function deletes the data pointed at by the pWhere and pChanges
 | 
						|
 * arguments before it completes.
 | 
						|
 */
 | 
						|
void sqliteViewTriggers(
 | 
						|
  Parse *pParse, 
 | 
						|
  Table *pTab,         /* The view to code triggers on */
 | 
						|
  Expr *pWhere,        /* The WHERE clause of the statement causing triggers*/
 | 
						|
  int orconf,          /* The ON CONFLICT policy specified as part of the
 | 
						|
			  statement causing these triggers */
 | 
						|
  ExprList *pChanges   /* If this is an statement causing triggers to fire
 | 
						|
			  is an UPDATE, then this list holds the columns
 | 
						|
			  to update and the expressions to update them to.
 | 
						|
			  See comments for sqliteUpdate(). */
 | 
						|
){
 | 
						|
  int oldIdx = -1;
 | 
						|
  int newIdx = -1;
 | 
						|
  int *aXRef = 0;   
 | 
						|
  Vdbe *v;
 | 
						|
  int endOfLoop;
 | 
						|
  int startOfLoop;
 | 
						|
  Select theSelect;
 | 
						|
  Token tblNameToken;
 | 
						|
 | 
						|
  assert(pTab->pSelect);
 | 
						|
 | 
						|
  tblNameToken.z = pTab->zName;
 | 
						|
  tblNameToken.n = strlen(pTab->zName);
 | 
						|
 | 
						|
  theSelect.isDistinct = 0;
 | 
						|
  theSelect.pEList = sqliteExprListAppend(0, sqliteExpr(TK_ALL, 0, 0, 0), 0);
 | 
						|
  theSelect.pSrc   = sqliteSrcListAppend(0, &tblNameToken, 0);
 | 
						|
  theSelect.pWhere = pWhere;    pWhere = 0;
 | 
						|
  theSelect.pGroupBy = 0;
 | 
						|
  theSelect.pHaving = 0;
 | 
						|
  theSelect.pOrderBy = 0;
 | 
						|
  theSelect.op = TK_SELECT; /* ?? */
 | 
						|
  theSelect.pPrior = 0;
 | 
						|
  theSelect.nLimit = -1;
 | 
						|
  theSelect.nOffset = -1;
 | 
						|
  theSelect.zSelect = 0;
 | 
						|
  theSelect.base = 0;
 | 
						|
 | 
						|
  v = sqliteGetVdbe(pParse);
 | 
						|
  assert(v);
 | 
						|
  sqliteBeginWriteOperation(pParse, 1, 0);
 | 
						|
 | 
						|
  /* Allocate temp tables */
 | 
						|
  oldIdx = pParse->nTab++;
 | 
						|
  sqliteVdbeAddOp(v, OP_OpenPseudo, oldIdx, 0);
 | 
						|
  if( pChanges ){
 | 
						|
    newIdx = pParse->nTab++;
 | 
						|
    sqliteVdbeAddOp(v, OP_OpenPseudo, newIdx, 0);
 | 
						|
  }
 | 
						|
 | 
						|
  /* Snapshot the view */
 | 
						|
  if( sqliteSelect(pParse, &theSelect, SRT_Table, oldIdx, 0, 0, 0) ){
 | 
						|
    goto trigger_cleanup;
 | 
						|
  }
 | 
						|
 | 
						|
  /* loop thru the view snapshot, executing triggers for each row */
 | 
						|
  endOfLoop = sqliteVdbeMakeLabel(v);
 | 
						|
  sqliteVdbeAddOp(v, OP_Rewind, oldIdx, endOfLoop);
 | 
						|
 | 
						|
  /* Loop thru the view snapshot, executing triggers for each row */
 | 
						|
  startOfLoop = sqliteVdbeCurrentAddr(v);
 | 
						|
 | 
						|
  /* Build the updated row if required */
 | 
						|
  if( pChanges ){
 | 
						|
    int ii;
 | 
						|
 | 
						|
    aXRef = sqliteMalloc( sizeof(int) * pTab->nCol );
 | 
						|
    if( aXRef==0 ) goto trigger_cleanup;
 | 
						|
    for(ii = 0; ii < pTab->nCol; ii++){
 | 
						|
      aXRef[ii] = -1;
 | 
						|
    }
 | 
						|
 | 
						|
    for(ii=0; ii<pChanges->nExpr; ii++){
 | 
						|
      int jj;
 | 
						|
      if( sqliteExprResolveIds(pParse, oldIdx, theSelect.pSrc , 0, 
 | 
						|
            pChanges->a[ii].pExpr) ){
 | 
						|
        goto trigger_cleanup;
 | 
						|
      }
 | 
						|
 | 
						|
      if( sqliteExprCheck(pParse, pChanges->a[ii].pExpr, 0, 0) )
 | 
						|
        goto trigger_cleanup;
 | 
						|
 | 
						|
      for(jj=0; jj<pTab->nCol; jj++){
 | 
						|
        if( sqliteStrICmp(pTab->aCol[jj].zName, pChanges->a[ii].zName)==0 ){
 | 
						|
          aXRef[jj] = ii;
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      if( jj>=pTab->nCol ){
 | 
						|
        sqliteErrorMsg(pParse, "no such column: %s", pChanges->a[ii].zName);
 | 
						|
        goto trigger_cleanup;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    sqliteVdbeAddOp(v, OP_Integer, 13, 0);
 | 
						|
 | 
						|
    for(ii = 0; ii<pTab->nCol; ii++){
 | 
						|
      if( aXRef[ii] < 0 ){ 
 | 
						|
        sqliteVdbeAddOp(v, OP_Column, oldIdx, ii);
 | 
						|
      }else{
 | 
						|
        sqliteExprCode(pParse, pChanges->a[aXRef[ii]].pExpr);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    sqliteVdbeAddOp(v, OP_MakeRecord, pTab->nCol, 0);
 | 
						|
    sqliteVdbeAddOp(v, OP_PutIntKey, newIdx, 0);
 | 
						|
    sqliteVdbeAddOp(v, OP_Rewind, newIdx, 0);
 | 
						|
 | 
						|
    sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_BEFORE, 
 | 
						|
        pTab, newIdx, oldIdx, orconf, endOfLoop);
 | 
						|
    sqliteCodeRowTrigger(pParse, TK_UPDATE, pChanges, TK_AFTER, 
 | 
						|
        pTab, newIdx, oldIdx, orconf, endOfLoop);
 | 
						|
  }else{
 | 
						|
    sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_BEFORE, pTab, -1, oldIdx, 
 | 
						|
        orconf, endOfLoop);
 | 
						|
    sqliteCodeRowTrigger(pParse, TK_DELETE, 0, TK_AFTER, pTab, -1, oldIdx, 
 | 
						|
        orconf, endOfLoop);
 | 
						|
  }
 | 
						|
 | 
						|
  sqliteVdbeAddOp(v, OP_Next, oldIdx, startOfLoop);
 | 
						|
 | 
						|
  sqliteVdbeResolveLabel(v, endOfLoop);
 | 
						|
  sqliteEndWriteOperation(pParse);
 | 
						|
 | 
						|
trigger_cleanup:
 | 
						|
  sqliteFree(aXRef);
 | 
						|
  sqliteExprListDelete(pChanges);
 | 
						|
  sqliteExprDelete(pWhere);
 | 
						|
  sqliteExprListDelete(theSelect.pEList);
 | 
						|
  sqliteSrcListDelete(theSelect.pSrc);
 | 
						|
  sqliteExprDelete(theSelect.pWhere);
 | 
						|
  return;
 | 
						|
}
 |