wasCSharpSQLite – Rev 1

Subversion Repositories:
Rev:
using System;
using System.Diagnostics;
using System.Text;

using u8 = System.Byte;
using u32 = System.UInt32;
namespace Community.CsharpSqlite
{
  public partial class Sqlite3
  {
    /*
    **
    ** 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.
    **
    *************************************************************************
    ** This file contains the implementation for TRIGGERs
    *************************************************************************
    **  Included in SQLite3 port to C#-SQLite;  2008 Noah B Hart
    **  C#-SQLite is an independent reimplementation of the SQLite software library
    **
    **  SQLITE_SOURCE_ID: 2011-06-23 19:49:22 4374b7e83ea0a3fbc3691f9c0c936272862f32f2
    **
    *************************************************************************
    */
    //#include "sqliteInt.h"

#if !SQLITE_OMIT_TRIGGER
    /*
** Delete a linked list of TriggerStep structures.
*/
    static void sqlite3DeleteTriggerStep( sqlite3 db, ref TriggerStep pTriggerStep )
    {
      while ( pTriggerStep != null )
      {
        TriggerStep pTmp = pTriggerStep;
        pTriggerStep = pTriggerStep.pNext;

        sqlite3ExprDelete( db, ref pTmp.pWhere );
        sqlite3ExprListDelete( db, ref pTmp.pExprList );
        sqlite3SelectDelete( db, ref pTmp.pSelect );
        sqlite3IdListDelete( db, ref pTmp.pIdList );

        pTriggerStep = null;
        sqlite3DbFree( db, ref pTmp );
      }
    }

    /*
    ** Given table pTab, return a list of all the triggers attached to
    ** the table. The list is connected by Trigger.pNext pointers.
    **
    ** All of the triggers on pTab that are in the same database as pTab
    ** are already attached to pTab.pTrigger.  But there might be additional
    ** triggers on pTab in the TEMP schema.  This routine prepends all
    ** TEMP triggers on pTab to the beginning of the pTab.pTrigger list
    ** and returns the combined list.
    **
    ** To state it another way:  This routine returns a list of all triggers
    ** that fire off of pTab.  The list will include any TEMP triggers on
    ** pTab as well as the triggers lised in pTab.pTrigger.
    */
    static Trigger sqlite3TriggerList( Parse pParse, Table pTab )
    {
      Schema pTmpSchema = pParse.db.aDb[1].pSchema;
      Trigger pList = null;                  /* List of triggers to return */

      if ( pParse.disableTriggers != 0 )
      {
        return null;
      }

      if ( pTmpSchema != pTab.pSchema )
      {
        HashElem p;
        Debug.Assert( sqlite3SchemaMutexHeld( pParse.db, 0, pTmpSchema ) );
        for ( p = sqliteHashFirst( pTmpSchema.trigHash ); p != null; p = sqliteHashNext( p ) )
        {
          Trigger pTrig = (Trigger)sqliteHashData( p );
          if ( pTrig.pTabSchema == pTab.pSchema
          && pTrig.table.Equals( pTab.zName, StringComparison.OrdinalIgnoreCase ) )
          {
            pTrig.pNext = ( pList != null ? pList : pTab.pTrigger );
            pList = pTrig;
          }
        }
      }

      return ( pList != null ? pList : pTab.pTrigger );
    }

    /*
    ** 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
    ** sqlite3FinishTrigger() function is called to complete the trigger
    ** construction process.
    */
    static void sqlite3BeginTrigger(
    Parse pParse,      /* The parse context of the CREATE TRIGGER statement */
    Token pName1,      /* The name of the trigger */
    Token pName2,      /* 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 */
    Expr pWhen,        /* WHEN clause */
    int isTemp,        /* True if the TEMPORARY keyword is present */
    int noErr          /* Suppress errors if the trigger already exists */
    )
    {
      Trigger pTrigger = null;      /* The new trigger */
      Table pTab;                   /* Table that the trigger fires off of */
      string zName = null;          /* Name of the trigger */
      sqlite3 db = pParse.db;       /* The database connection */
      int iDb;                      /* The database to store the trigger in */
      Token pName = null;           /* The unqualified db name */
      DbFixer sFix = new DbFixer(); /* State vector for the DB fixer */

      Debug.Assert( pName1 != null );   /* pName1.z might be NULL, but not pName1 itself */
      Debug.Assert( pName2 != null );
      Debug.Assert( op == TK_INSERT || op == TK_UPDATE || op == TK_DELETE );
      Debug.Assert( op > 0 && op < 0xff );
      if ( isTemp != 0 )
      {
        /* If TEMP was specified, then the trigger name may not be qualified. */
        if ( pName2.n > 0 )
        {
          sqlite3ErrorMsg( pParse, "temporary trigger may not have qualified name" );
          goto trigger_cleanup;
        }
        iDb = 1;
        pName = pName1;
      }
      else
      {
        /* Figure out the db that the the trigger will be created in */
        iDb = sqlite3TwoPartName( pParse, pName1, pName2, ref pName );
        if ( iDb < 0 )
        {
          goto trigger_cleanup;
        }
      }
      if ( null == pTableName ) //|| db.mallocFailed 
      {
        goto trigger_cleanup;
      }

      /* A long-standing parser bug is that this syntax was allowed:
      **
      **    CREATE TRIGGER attached.demo AFTER INSERT ON attached.tab ....
      **                                                 ^^^^^^^^
      **
      ** To maintain backwards compatibility, ignore the database
      ** name on pTableName if we are reparsing our of SQLITE_MASTER.
      */
      if ( db.init.busy != 0 && iDb != 1 )
      {
        //sqlite3DbFree( db, pTableName.a[0].zDatabase );
        pTableName.a[0].zDatabase = null;
      }

      /* If the trigger name was unqualified, and the table is a temp table,
      ** then set iDb to 1 to create the trigger in the temporary database.
      ** If sqlite3SrcListLookup() returns 0, indicating the table does not
      ** exist, the error is caught by the block below.
      */
      if ( pTableName == null /*|| db.mallocFailed != 0 */ )
      {
        goto trigger_cleanup;
      }
      pTab = sqlite3SrcListLookup( pParse, pTableName );
      if ( db.init.busy == 0 && pName2.n == 0 && pTab != null
            && pTab.pSchema == db.aDb[1].pSchema )
      {
        iDb = 1;
      }

      /* Ensure the table name matches database name and that the table exists */
      //      if ( db.mallocFailed != 0 ) goto trigger_cleanup;
      Debug.Assert( pTableName.nSrc == 1 );
      if ( sqlite3FixInit( sFix, pParse, iDb, "trigger", pName ) != 0 &&
      sqlite3FixSrcList( sFix, pTableName ) != 0 )
      {
        goto trigger_cleanup;
      }
      pTab = sqlite3SrcListLookup( pParse, pTableName );
      if ( pTab == null )
      {
        /* The table does not exist. */
        if ( db.init.iDb == 1 )
        {
          /* Ticket #3810.
          ** Normally, whenever a table is dropped, all associated triggers are
          ** dropped too.  But if a TEMP trigger is created on a non-TEMP table
          ** and the table is dropped by a different database connection, the
          ** trigger is not visible to the database connection that does the
          ** drop so the trigger cannot be dropped.  This results in an
          ** "orphaned trigger" - a trigger whose associated table is missing.
          */
          db.init.orphanTrigger = 1;
        }
        goto trigger_cleanup;
      }
      if ( IsVirtual( pTab ) )
      {
        sqlite3ErrorMsg( pParse, "cannot create triggers on virtual tables" );
        goto trigger_cleanup;
      }

      /* Check that the trigger name is not reserved and that no trigger of the
      ** specified name exists */
      zName = sqlite3NameFromToken( db, pName );
      if ( zName == null || SQLITE_OK != sqlite3CheckObjectName( pParse, zName ) )
      {
        goto trigger_cleanup;
      }
      Debug.Assert( sqlite3SchemaMutexHeld( db, iDb, null ) );
      if ( sqlite3HashFind( ( db.aDb[iDb].pSchema.trigHash ),
      zName, sqlite3Strlen30( zName ), (Trigger)null ) != null )
      {
        if ( noErr == 0 )
        {
          sqlite3ErrorMsg( pParse, "trigger %T already exists", pName );
        }
        else
        {
          Debug.Assert( 0==db.init.busy );
          sqlite3CodeVerifySchema( pParse, iDb );
        }
        goto trigger_cleanup;
      }

      /* Do not create a trigger on a system table */
      if ( pTab.zName.StartsWith( "sqlite_", System.StringComparison.OrdinalIgnoreCase ) )
      {
        sqlite3ErrorMsg( pParse, "cannot create trigger on system table" );
        pParse.nErr++;
        goto trigger_cleanup;
      }

      /* INSTEAD of triggers are only for views and views only support INSTEAD
      ** of triggers.
      */
      if ( pTab.pSelect != null && tr_tm != TK_INSTEAD )
      {
        sqlite3ErrorMsg( pParse, "cannot create %s trigger on view: %S",
        ( tr_tm == TK_BEFORE ) ? "BEFORE" : "AFTER", pTableName, 0 );
        goto trigger_cleanup;
      }
      if ( pTab.pSelect == null && tr_tm == TK_INSTEAD )
      {
        sqlite3ErrorMsg( pParse, "cannot create INSTEAD OF" +
        " trigger on table: %S", pTableName, 0 );
        goto trigger_cleanup;
      }

#if !SQLITE_OMIT_AUTHORIZATION
{
      int iTabDb = sqlite3SchemaToIndex( db, pTab.pSchema );
int code = SQLITE_CREATE_TRIGGER;
string zDb = db.aDb[iTabDb].zName;
string zDbTrig = isTemp ? db.aDb[1].zName : zDb;
if( iTabDb==1 || isTemp ) code = SQLITE_CREATE_TEMP_TRIGGER;
if( sqlite3AuthCheck(pParse, code, zName, pTab.zName, zDbTrig) ){
goto trigger_cleanup;
}
if( sqlite3AuthCheck(pParse, SQLITE_INSERT, SCHEMA_TABLE(iTabDb),0,zDb)){
goto trigger_cleanup;
}
}
#endif

      /* INSTEAD OF triggers can only appear on views and BEFORE triggers
** cannot appear on views.  So we might as well translate every
** INSTEAD OF trigger into a BEFORE trigger.  It simplifies code
** elsewhere.
*/
      if ( tr_tm == TK_INSTEAD )
      {
        tr_tm = TK_BEFORE;
      }

      /* Build the Trigger object */
      pTrigger = new Trigger();// (Trigger*)sqlite3DbMallocZero( db, sizeof(Trigger ))
      if ( pTrigger == null )
        goto trigger_cleanup;
      pTrigger.zName = zName;
      pTrigger.table = pTableName.a[0].zName;// sqlite3DbStrDup( db, pTableName.a[0].zName );
      pTrigger.pSchema = db.aDb[iDb].pSchema;
      pTrigger.pTabSchema = pTab.pSchema;
      pTrigger.op = (u8)op;
      pTrigger.tr_tm = tr_tm == TK_BEFORE ? TRIGGER_BEFORE : TRIGGER_AFTER;
      pTrigger.pWhen = sqlite3ExprDup( db, pWhen, EXPRDUP_REDUCE );
      pTrigger.pColumns = sqlite3IdListDup( db, pColumns );
      Debug.Assert( pParse.pNewTrigger == null );
      pParse.pNewTrigger = pTrigger;

trigger_cleanup:
      sqlite3DbFree( db, ref zName );
      sqlite3SrcListDelete( db, ref pTableName );
      sqlite3IdListDelete( db, ref pColumns );
      sqlite3ExprDelete( db, ref pWhen );
      if ( pParse.pNewTrigger == null )
      {
        sqlite3DeleteTrigger( db, ref pTrigger );
      }
      else
      {
        Debug.Assert( pParse.pNewTrigger == pTrigger );
      }
    }

    /*
    ** This routine is called after all of the trigger actions have been parsed
    ** in order to complete the process of building the trigger.
    */
    static void sqlite3FinishTrigger(
    Parse pParse,          /* Parser context */
    TriggerStep pStepList, /* The triggered program */
    Token pAll             /* Token that describes the complete CREATE TRIGGER */
    )
    {
      Trigger pTrig = pParse.pNewTrigger; /* Trigger being finished */
      string zName;                       /* Name of trigger */

      sqlite3 db = pParse.db;             /* The database */
      DbFixer sFix = new DbFixer();       /* Fixer object */
      int iDb;                            /* Database containing the trigger */
      Token nameToken = new Token();      /* Trigger name for error reporting */

      pParse.pNewTrigger = null;
      if ( NEVER( pParse.nErr != 0 ) || pTrig == null )
        goto triggerfinish_cleanup;
      zName = pTrig.zName;
      iDb = sqlite3SchemaToIndex( pParse.db, pTrig.pSchema );
      pTrig.step_list = pStepList;
      while ( pStepList != null )
      {
        pStepList.pTrig = pTrig;
        pStepList = pStepList.pNext;
      }
      nameToken.z = pTrig.zName;
      nameToken.n = sqlite3Strlen30( nameToken.z );
      if ( sqlite3FixInit( sFix, pParse, iDb, "trigger", nameToken ) != 0
      && sqlite3FixTriggerStep( sFix, pTrig.step_list ) != 0 )
      {
        goto triggerfinish_cleanup;
      }

      /* if we are not initializing,
      ** build the sqlite_master entry
      */
      if ( 0 == db.init.busy )
      {
        Vdbe v;
        string z;

        /* Make an entry in the sqlite_master table */
        v = sqlite3GetVdbe( pParse );
        if ( v == null )
          goto triggerfinish_cleanup;
        sqlite3BeginWriteOperation( pParse, 0, iDb );
        z = pAll.z.Substring( 0, pAll.n );//sqlite3DbStrNDup( db, (char*)pAll.z, pAll.n );
        sqlite3NestedParse( pParse,
        "INSERT INTO %Q.%s VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')",
        db.aDb[iDb].zName, SCHEMA_TABLE( iDb ), zName,
        pTrig.table, z );
        sqlite3DbFree( db, ref z );
        sqlite3ChangeCookie( pParse, iDb );
        sqlite3VdbeAddParseSchemaOp( v, iDb,
            sqlite3MPrintf( db, "type='trigger' AND name='%q'", zName ) );
      }

      if ( db.init.busy != 0 )
      {
        Trigger pLink = pTrig;
        Hash pHash = db.aDb[iDb].pSchema.trigHash;
        Debug.Assert( sqlite3SchemaMutexHeld( db, iDb, null ) );
        pTrig = sqlite3HashInsert( ref pHash, zName, sqlite3Strlen30( zName ), pTrig );
        if ( pTrig != null )
        {
          //db.mallocFailed = 1;
        }
        else if ( pLink.pSchema == pLink.pTabSchema )
        {
          Table pTab;
          int n = sqlite3Strlen30( pLink.table );
          pTab = sqlite3HashFind( pLink.pTabSchema.tblHash, pLink.table, n, (Table)null );
          Debug.Assert( pTab != null );
          pLink.pNext = pTab.pTrigger;
          pTab.pTrigger = pLink;
        }
      }

triggerfinish_cleanup:
      sqlite3DeleteTrigger( db, ref pTrig );
      Debug.Assert( pParse.pNewTrigger == null );
      sqlite3DeleteTriggerStep( db, ref pStepList );
    }

    /*
    ** 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.
    */
    static TriggerStep sqlite3TriggerSelectStep( sqlite3 db, Select pSelect )
    {
      TriggerStep pTriggerStep = new TriggerStep();// sqlite3DbMallocZero( db, sizeof(TriggerStep ))
      if ( pTriggerStep == null )
      {
        sqlite3SelectDelete( db, ref pSelect );
        return null;
      }

      pTriggerStep.op = TK_SELECT;
      pTriggerStep.pSelect = pSelect;
      pTriggerStep.orconf = OE_Default;
      return pTriggerStep;
    }

    /*
    ** Allocate space to hold a new trigger step.  The allocated space
    ** holds both the TriggerStep object and the TriggerStep.target.z string.
    **
    ** If an OOM error occurs, NULL is returned and db.mallocFailed is set.
    */
    static TriggerStep triggerStepAllocate(
    sqlite3 db,                /* Database connection */
    u8 op,                     /* Trigger opcode */
    Token pName                /* The target name */
    )
    {
      TriggerStep pTriggerStep;

      pTriggerStep = new TriggerStep();// sqlite3DbMallocZero( db, sizeof( TriggerStep ) + pName.n );
      //if ( pTriggerStep != null )
      //{
      string z;// = (char*)&pTriggerStep[1];
      z = pName.z;// memcpy( z, pName.z, pName.n );
      pTriggerStep.target.z = z;
      pTriggerStep.target.n = pName.n;
      pTriggerStep.op = op;
      //}
      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.
    */
    // OVERLOADS, so I don't need to rewrite parse.c
    static TriggerStep sqlite3TriggerInsertStep( sqlite3 db, Token pTableName, IdList pColumn, int null_4, int null_5, u8 orconf )
    {
      return sqlite3TriggerInsertStep( db, pTableName, pColumn, null, null, orconf );
    }
    static TriggerStep sqlite3TriggerInsertStep( sqlite3 db, Token pTableName, IdList pColumn, ExprList pEList, int null_5, u8 orconf )
    {
      return sqlite3TriggerInsertStep( db, pTableName, pColumn, pEList, null, orconf );
    }
    static TriggerStep sqlite3TriggerInsertStep( sqlite3 db, Token pTableName, IdList pColumn, int null_4, Select pSelect, u8 orconf )
    {
      return sqlite3TriggerInsertStep( db, pTableName, pColumn, null, pSelect, orconf );
    }
    static TriggerStep sqlite3TriggerInsertStep(
    sqlite3 db,        /* The database connection */
    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 */
    u8 orconf          /* The conflict algorithm (OE_Abort, OE_Replace, etc.) */
    )
    {
      TriggerStep pTriggerStep;

      Debug.Assert( pEList == null || pSelect == null );
      Debug.Assert( pEList != null || pSelect != null /*|| db.mallocFailed != 0 */ );

      pTriggerStep = triggerStepAllocate( db, TK_INSERT, pTableName );
      //if ( pTriggerStep != null )
      //{
      pTriggerStep.pSelect = sqlite3SelectDup( db, pSelect, EXPRDUP_REDUCE );
      pTriggerStep.pIdList = pColumn;
      pTriggerStep.pExprList = sqlite3ExprListDup( db, pEList, EXPRDUP_REDUCE );
      pTriggerStep.orconf = orconf;
      //}
      //else
      //{
      //  sqlite3IdListDelete( db, ref pColumn );
      //}
      sqlite3ExprListDelete( db, ref pEList );
      sqlite3SelectDelete( db, ref pSelect );

      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.
    */
    static TriggerStep sqlite3TriggerUpdateStep(
    sqlite3 db,         /* The database connection */
    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 */
    u8 orconf           /* The conflict algorithm. (OE_Abort, OE_Ignore, etc) */
    )
    {
      TriggerStep pTriggerStep;

      pTriggerStep = triggerStepAllocate( db, TK_UPDATE, pTableName );
      //if ( pTriggerStep != null )
      //{
      pTriggerStep.pExprList = sqlite3ExprListDup( db, pEList, EXPRDUP_REDUCE );
      pTriggerStep.pWhere = sqlite3ExprDup( db, pWhere, EXPRDUP_REDUCE );
      pTriggerStep.orconf = orconf;
      //}
      sqlite3ExprListDelete( db, ref pEList );
      sqlite3ExprDelete( db, ref pWhere );
      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.
    */
    static TriggerStep sqlite3TriggerDeleteStep(
    sqlite3 db,            /* Database connection */
    Token pTableName,      /* The table from which rows are deleted */
    Expr pWhere            /* The WHERE clause */
    )
    {
      TriggerStep pTriggerStep;

      pTriggerStep = triggerStepAllocate( db, TK_DELETE, pTableName );
      //if ( pTriggerStep != null )
      //{
      pTriggerStep.pWhere = sqlite3ExprDup( db, pWhere, EXPRDUP_REDUCE );
      pTriggerStep.orconf = OE_Default;
      //}
      sqlite3ExprDelete( db, ref pWhere );
      return pTriggerStep;
    }



    /*
    ** Recursively delete a Trigger structure
    */
    static void sqlite3DeleteTrigger( sqlite3 db, ref Trigger pTrigger )
    {
      if ( pTrigger == null )
        return;
      sqlite3DeleteTriggerStep( db, ref pTrigger.step_list );
      sqlite3DbFree( db, ref pTrigger.zName );
      sqlite3DbFree( db, ref pTrigger.table );
      sqlite3ExprDelete( db, ref pTrigger.pWhen );
      sqlite3IdListDelete( db, ref pTrigger.pColumns );
      pTrigger = null;
      sqlite3DbFree( db, ref pTrigger );
    }

    /*
    ** This function is called to drop a trigger from the database schema.
    **
    ** This may be called directly from the parser and therefore identifies
    ** the trigger by name.  The sqlite3DropTriggerPtr() routine does the
    ** same job as this routine except it takes a pointer to the trigger
    ** instead of the trigger name.
    **/
    static void sqlite3DropTrigger( Parse pParse, SrcList pName, int noErr )
    {
      Trigger pTrigger = null;
      int i;
      string zDb;
      string zName;
      int nName;
      sqlite3 db = pParse.db;

      //      if ( db.mallocFailed != 0 ) goto drop_trigger_cleanup;
      if ( SQLITE_OK != sqlite3ReadSchema( pParse ) )
      {
        goto drop_trigger_cleanup;
      }

      Debug.Assert( pName.nSrc == 1 );
      zDb = pName.a[0].zDatabase;
      zName = pName.a[0].zName;
      nName = sqlite3Strlen30( zName );
      Debug.Assert( zDb != null || sqlite3BtreeHoldsAllMutexes( db ) );
      for ( i = OMIT_TEMPDB; i < db.nDb; i++ )
      {
        int j = ( i < 2 ) ? i ^ 1 : i;  /* Search TEMP before MAIN */
        if ( zDb != null && !db.aDb[j].zName.Equals( zDb ,StringComparison.OrdinalIgnoreCase )  )
          continue;
        Debug.Assert( sqlite3SchemaMutexHeld( db, j, null ) );
        pTrigger = sqlite3HashFind( ( db.aDb[j].pSchema.trigHash ), zName, nName, (Trigger)null );
        if ( pTrigger != null )
          break;
      }
      if ( pTrigger == null )
      {
        if ( noErr == 0 )
        {
          sqlite3ErrorMsg( pParse, "no such trigger: %S", pName, 0 );
        }
        else
        {
          sqlite3CodeVerifyNamedSchema( pParse, zDb );
        }
        pParse.checkSchema = 1;
        goto drop_trigger_cleanup;
      }
      sqlite3DropTriggerPtr( pParse, pTrigger );

drop_trigger_cleanup:
      sqlite3SrcListDelete( db, ref pName );
    }

    /*
    ** Return a pointer to the Table structure for the table that a trigger
    ** is set on.
    */
    static Table tableOfTrigger( Trigger pTrigger )
    {
      int n = sqlite3Strlen30( pTrigger.table );
      return sqlite3HashFind( pTrigger.pTabSchema.tblHash, pTrigger.table, n, (Table)null );
    }


    /*
    ** Drop a trigger given a pointer to that trigger.
    */
    static void sqlite3DropTriggerPtr( Parse pParse, Trigger pTrigger )
    {
      Table pTable;
      Vdbe v;
      sqlite3 db = pParse.db;
      int iDb;

      iDb = sqlite3SchemaToIndex( pParse.db, pTrigger.pSchema );
      Debug.Assert( iDb >= 0 && iDb < db.nDb );
      pTable = tableOfTrigger( pTrigger );
      Debug.Assert( pTable != null );
      Debug.Assert( pTable.pSchema == pTrigger.pSchema || iDb == 1 );
#if !SQLITE_OMIT_AUTHORIZATION
{
int code = SQLITE_DROP_TRIGGER;
string zDb = db.aDb[iDb].zName;
string zTab = SCHEMA_TABLE(iDb);
if( iDb==1 ) code = SQLITE_DROP_TEMP_TRIGGER;
if( sqlite3AuthCheck(pParse, code, pTrigger.name, pTable.zName, zDb) ||
sqlite3AuthCheck(pParse, SQLITE_DELETE, zTab, 0, zDb) ){
return;
}
}
#endif

      /* Generate code to destroy the database record of the trigger.
*/
      Debug.Assert( pTable != null );
      if ( ( v = sqlite3GetVdbe( pParse ) ) != null )
      {
        int _base;
        VdbeOpList[] dropTrigger = new VdbeOpList[]  {
new VdbeOpList( OP_Rewind,     0, ADDR(9),  0),
new VdbeOpList( OP_String8,    0, 1,        0), /* 1 */
new VdbeOpList( OP_Column,     0, 1,        2),
new VdbeOpList( OP_Ne,         2, ADDR(8),  1),
new VdbeOpList( OP_String8,    0, 1,        0), /* 4: "trigger" */
new VdbeOpList( OP_Column,     0, 0,        2),
new VdbeOpList( OP_Ne,         2, ADDR(8),  1),
new VdbeOpList( OP_Delete,     0, 0,        0),
new VdbeOpList( OP_Next,       0, ADDR(1),  0), /* 8 */
};

        sqlite3BeginWriteOperation( pParse, 0, iDb );
        sqlite3OpenMasterTable( pParse, iDb );
        _base = sqlite3VdbeAddOpList( v, dropTrigger.Length, dropTrigger );
        sqlite3VdbeChangeP4( v, _base + 1, pTrigger.zName, P4_TRANSIENT );
        sqlite3VdbeChangeP4( v, _base + 4, "trigger", P4_STATIC );
        sqlite3ChangeCookie( pParse, iDb );
        sqlite3VdbeAddOp2( v, OP_Close, 0, 0 );
        sqlite3VdbeAddOp4( v, OP_DropTrigger, iDb, 0, 0, pTrigger.zName, 0 );
        if ( pParse.nMem < 3 )
        {
          pParse.nMem = 3;
        }
      }
    }

    /*
    ** Remove a trigger from the hash tables of the sqlite* pointer.
    */
    static void sqlite3UnlinkAndDeleteTrigger( sqlite3 db, int iDb, string zName )
    {
      Trigger pTrigger;
      Hash pHash;

      Debug.Assert( sqlite3SchemaMutexHeld( db, iDb, null ) );
      pHash = ( db.aDb[iDb].pSchema.trigHash );
      pTrigger = sqlite3HashInsert( ref pHash, zName, sqlite3Strlen30( zName ), (Trigger)null );
      if ( ALWAYS( pTrigger != null ) )
      {
        if ( pTrigger.pSchema == pTrigger.pTabSchema )
        {
          Table pTab = tableOfTrigger( pTrigger );
          //Trigger** pp;
          //for ( pp = &pTab.pTrigger ; *pp != pTrigger ; pp = &( (*pp).pNext ) ) ;
          //*pp = (*pp).pNext;
          if ( pTab.pTrigger == pTrigger )
          {
            pTab.pTrigger = pTrigger.pNext;
          }
          else
          {
            Trigger cc = pTab.pTrigger;
            while ( cc != null )
            {
              if ( cc.pNext == pTrigger )
              {
                cc.pNext = cc.pNext.pNext;
                break;
              }
              cc = cc.pNext;
            }
            Debug.Assert( cc != null );
          }
        }
        sqlite3DeleteTrigger( db, ref pTrigger );
        db.flags |= SQLITE_InternChanges;
      }
    }

    /*
    ** 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 == null || NEVER( pEList == null ) )
        return 1;
      for ( e = 0; e < pEList.nExpr; e++ )
      {
        if ( sqlite3IdListIndex( pIdList, pEList.a[e].zName ) >= 0 )
          return 1;
      }
      return 0;
    }

    /*
    ** Return a list of all triggers on table pTab if there exists at least
    ** one trigger that must be fired when an operation of type 'op' is
    ** performed on the table, and, if that operation is an UPDATE, if at
    ** least one of the columns in pChanges is being modified.
    */
    static Trigger sqlite3TriggersExist(
    Parse pParse,          /* Parse context */
    Table pTab,            /* The table the contains the triggers */
    int op,                /* one of TK_DELETE, TK_INSERT, TK_UPDATE */
    ExprList pChanges,     /* Columns that change in an UPDATE statement */
    out int pMask          /* OUT: Mask of TRIGGER_BEFORE|TRIGGER_AFTER */
    )
    {
      int mask = 0;
      Trigger pList = null;
      Trigger p;

      if ( ( pParse.db.flags & SQLITE_EnableTrigger ) != 0 )
      {
        pList = sqlite3TriggerList( pParse, pTab );
      }
      Debug.Assert( pList == null || IsVirtual( pTab ) == false );
      for ( p = pList; p != null; p = p.pNext )
      {
        if ( p.op == op && checkColumnOverlap( p.pColumns, pChanges ) != 0 )
        {
          mask |= p.tr_tm;
        }
      }
      //if ( pMask != 0 )
      {
        pMask = mask;
      }
      return ( mask != 0 ? pList : null );
    }


    /*
    ** Convert the pStep.target token 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
    ** trigger is in TEMP in which case it can refer to any other database it
    ** wants.
    */
    static SrcList targetSrcList(
    Parse pParse,       /* The parsing context */
    TriggerStep pStep   /* The trigger containing the target token */
    )
    {
      int iDb;             /* Index of the database to use */
      SrcList pSrc;        /* SrcList to be returned */

      pSrc = sqlite3SrcListAppend( pParse.db, 0, pStep.target, 0 );
      //if ( pSrc != null )
      //{
      Debug.Assert( pSrc.nSrc > 0 );
      Debug.Assert( pSrc.a != null );
      iDb = sqlite3SchemaToIndex( pParse.db, pStep.pTrig.pSchema );
      if ( iDb == 0 || iDb >= 2 )
      {
        sqlite3 db = pParse.db;
        Debug.Assert( iDb < pParse.db.nDb );
        pSrc.a[pSrc.nSrc - 1].zDatabase = db.aDb[iDb].zName;// sqlite3DbStrDup( db, db.aDb[iDb].zName );
      }
      //}
      return pSrc;
    }

    /*
    ** Generate VDBE code for the statements inside the body of a single 
    ** trigger.
    */
    static int codeTriggerProgram(
    Parse pParse,            /* The parser context */
    TriggerStep pStepList,   /* List of statements inside the trigger body */
    int orconf               /* Conflict algorithm. (OE_Abort, etc) */
    )
    {
      TriggerStep pStep;
      Vdbe v = pParse.pVdbe;
      sqlite3 db = pParse.db;

      Debug.Assert( pParse.pTriggerTab != null && pParse.pToplevel != null );
      Debug.Assert( pStepList != null );
      Debug.Assert( v != null );
      for ( pStep = pStepList; pStep != null; pStep = pStep.pNext )
      {
        /* Figure out the ON CONFLICT policy that will be used for this step
        ** of the trigger program. If the statement that caused this trigger
        ** to fire had an explicit ON CONFLICT, then use it. Otherwise, use
        ** the ON CONFLICT policy that was specified as part of the trigger
        ** step statement. Example:
        **
        **   CREATE TRIGGER AFTER INSERT ON t1 BEGIN;
        **     INSERT OR REPLACE INTO t2 VALUES(new.a, new.b);
        **   END;
        **
        **   INSERT INTO t1 ... ;            -- insert into t2 uses REPLACE policy
        **   INSERT OR IGNORE INTO t1 ... ;  -- insert into t2 uses IGNORE policy
        */
        pParse.eOrconf = ( orconf == OE_Default ) ? pStep.orconf : (u8)orconf;

        switch ( pStep.op )
        {
          case TK_UPDATE:
            {
              sqlite3Update( pParse,
                targetSrcList( pParse, pStep ),
                sqlite3ExprListDup( db, pStep.pExprList, 0 ),
                sqlite3ExprDup( db, pStep.pWhere, 0 ),
                pParse.eOrconf
              );
              break;
            }
          case TK_INSERT:
            {
              sqlite3Insert( pParse,
                targetSrcList( pParse, pStep ),
                sqlite3ExprListDup( db, pStep.pExprList, 0 ),
                sqlite3SelectDup( db, pStep.pSelect, 0 ),
                sqlite3IdListDup( db, pStep.pIdList ),
                pParse.eOrconf
              );
              break;
            }
          case TK_DELETE:
            {
              sqlite3DeleteFrom( pParse,
                targetSrcList( pParse, pStep ),
                sqlite3ExprDup( db, pStep.pWhere, 0 )
              );
              break;
            }
          default:
            Debug.Assert( pStep.op == TK_SELECT );
            {
              SelectDest sDest = new SelectDest();
              Select pSelect = sqlite3SelectDup( db, pStep.pSelect, 0 );
              sqlite3SelectDestInit( sDest, SRT_Discard, 0 );
              sqlite3Select( pParse, pSelect, ref sDest );
              sqlite3SelectDelete( db, ref pSelect );
              break;
            }
        }
        if ( pStep.op != TK_SELECT )
        {
          sqlite3VdbeAddOp0( v, OP_ResetCount );
        }
      }

      return 0;
    }

#if SQLITE_DEBUG
    /*
** This function is used to add VdbeComment() annotations to a VDBE
** program. It is not used in production code, only for debugging.
*/
    static string onErrorText( int onError )
    {
      switch ( onError )
      {
        case OE_Abort:
          return "abort";
        case OE_Rollback:
          return "rollback";
        case OE_Fail:
          return "fail";
        case OE_Replace:
          return "replace";
        case OE_Ignore:
          return "ignore";
        case OE_Default:
          return "default";
      }
      return "n/a";
    }
#endif

    /*
** Parse context structure pFrom has just been used to create a sub-vdbe
** (trigger program). If an error has occurred, transfer error information
** from pFrom to pTo.
*/
    static void transferParseError( Parse pTo, Parse pFrom )
    {
      Debug.Assert( string.IsNullOrEmpty( pFrom.zErrMsg ) || pFrom.nErr != 0 );
      Debug.Assert( string.IsNullOrEmpty( pTo.zErrMsg ) || pTo.nErr != 0 );
      if ( pTo.nErr == 0 )
      {
        pTo.zErrMsg = pFrom.zErrMsg;
        pTo.nErr = pFrom.nErr;
      }
      else
      {
        sqlite3DbFree( pFrom.db, ref pFrom.zErrMsg );
      }
    }

    /*
    ** Create and populate a new TriggerPrg object with a sub-program 
    ** implementing trigger pTrigger with ON CONFLICT policy orconf.
    */
    static TriggerPrg codeRowTrigger(
      Parse pParse,        /* Current parse context */
      Trigger pTrigger,    /* Trigger to code */
      Table pTab,          /* The table pTrigger is attached to */
      int orconf           /* ON CONFLICT policy to code trigger program with */
    )
    {
      Parse pTop = sqlite3ParseToplevel( pParse );
      sqlite3 db = pParse.db;     /* Database handle */
      TriggerPrg pPrg;            /* Value to return */
      Expr pWhen = null;          /* Duplicate of trigger WHEN expression */
      Vdbe v;                     /* Temporary VM */
      NameContext sNC;            /* Name context for sub-vdbe */
      SubProgram pProgram = null; /* Sub-vdbe for trigger program */
      Parse pSubParse;            /* Parse context for sub-vdbe */
      int iEndTrigger = 0;        /* Label to jump to if WHEN is false */

      Debug.Assert( pTrigger.zName == null || pTab == tableOfTrigger( pTrigger ) );
      Debug.Assert( pTop.pVdbe != null );

      /* Allocate the TriggerPrg and SubProgram objects. To ensure that they
      ** are freed if an error occurs, link them into the Parse.pTriggerPrg 
      ** list of the top-level Parse object sooner rather than later.  */
      pPrg = new TriggerPrg();// sqlite3DbMallocZero( db, sizeof( TriggerPrg ) );
      //if ( null == pPrg ) return 0;
      pPrg.pNext = pTop.pTriggerPrg;
      pTop.pTriggerPrg = pPrg;
      pPrg.pProgram = pProgram = new SubProgram();// sqlite3DbMallocZero( db, sizeof( SubProgram ) );
      //if( null==pProgram ) return 0;
      sqlite3VdbeLinkSubProgram( pTop.pVdbe, pProgram );
      pPrg.pTrigger = pTrigger;
      pPrg.orconf = orconf;
      pPrg.aColmask[0] = 0xffffffff;
      pPrg.aColmask[1] = 0xffffffff;


      /* Allocate and populate a new Parse context to use for coding the 
      ** trigger sub-program.  */
      pSubParse = new Parse();// sqlite3StackAllocZero( db, sizeof( Parse ) );
      //if ( null == pSubParse ) return null;
      sNC = new NameContext();// memset( &sNC, 0, sizeof( sNC ) );
      sNC.pParse = pSubParse;
      pSubParse.db = db;
      pSubParse.pTriggerTab = pTab;
      pSubParse.pToplevel = pTop;
      pSubParse.zAuthContext = pTrigger.zName;
      pSubParse.eTriggerOp = pTrigger.op;
      pSubParse.nQueryLoop = pParse.nQueryLoop;

      v = sqlite3GetVdbe( pSubParse );
      if ( v != null )
      {
#if SQLITE_DEBUG
        VdbeComment( v, "Start: %s.%s (%s %s%s%s ON %s)",
          pTrigger.zName ?? string.Empty, onErrorText( orconf ),
          ( pTrigger.tr_tm == TRIGGER_BEFORE ? "BEFORE" : "AFTER" ),
            ( pTrigger.op == TK_UPDATE ? "UPDATE" : string.Empty ),
            ( pTrigger.op == TK_INSERT ? "INSERT" : string.Empty ),
            ( pTrigger.op == TK_DELETE ? "DELETE" : string.Empty ),
          pTab.zName
        );
#endif
#if !SQLITE_OMIT_TRACE
        sqlite3VdbeChangeP4( v, -1,
          sqlite3MPrintf( db, "-- TRIGGER %s", pTrigger.zName ), P4_DYNAMIC
        );
#endif

        /* If one was specified, code the WHEN clause. If it evaluates to false
    ** (or NULL) the sub-vdbe is immediately halted by jumping to the 
    ** OP_Halt inserted at the end of the program.  */
        if ( pTrigger.pWhen != null )
        {
          pWhen = sqlite3ExprDup( db, pTrigger.pWhen, 0 );
          if ( SQLITE_OK == sqlite3ResolveExprNames( sNC, ref pWhen )
            //&& db.mallocFailed==0 
          )
          {
            iEndTrigger = sqlite3VdbeMakeLabel( v );
            sqlite3ExprIfFalse( pSubParse, pWhen, iEndTrigger, SQLITE_JUMPIFNULL );
          }
          sqlite3ExprDelete( db, ref pWhen );
        }

        /* Code the trigger program into the sub-vdbe. */
        codeTriggerProgram( pSubParse, pTrigger.step_list, orconf );

        /* Insert an OP_Halt at the end of the sub-program. */
        if ( iEndTrigger != 0 )
        {
          sqlite3VdbeResolveLabel( v, iEndTrigger );
        }
        sqlite3VdbeAddOp0( v, OP_Halt );
#if SQLITE_DEBUG
        VdbeComment( v, "End: %s.%s", pTrigger.zName, onErrorText( orconf ) );
#endif
        transferParseError( pParse, pSubParse );
        //if( db.mallocFailed==0 ){
        pProgram.aOp = sqlite3VdbeTakeOpArray( v, ref pProgram.nOp, ref pTop.nMaxArg );
        //}
        pProgram.nMem = pSubParse.nMem;
        pProgram.nCsr = pSubParse.nTab;
        pProgram.token = pTrigger.GetHashCode();
        pPrg.aColmask[0] = pSubParse.oldmask;
        pPrg.aColmask[1] = pSubParse.newmask;
        sqlite3VdbeDelete( ref v );
      }

      Debug.Assert( null == pSubParse.pAinc && null == pSubParse.pZombieTab );
      Debug.Assert( null == pSubParse.pTriggerPrg && 0 == pSubParse.nMaxArg );
      //sqlite3StackFree(db, pSubParse);

      return pPrg;
    }

    /*
    ** Return a pointer to a TriggerPrg object containing the sub-program for
    ** trigger pTrigger with default ON CONFLICT algorithm orconf. If no such
    ** TriggerPrg object exists, a new object is allocated and populated before
    ** being returned.
    */
    static TriggerPrg getRowTrigger(
      Parse pParse,        /* Current parse context */
      Trigger pTrigger,    /* Trigger to code */
      Table pTab,          /* The table trigger pTrigger is attached to */
      int orconf           /* ON CONFLICT algorithm. */
    )
    {
      Parse pRoot = sqlite3ParseToplevel( pParse );
      TriggerPrg pPrg;

      Debug.Assert( pTrigger.zName == null || pTab == tableOfTrigger( pTrigger ) );

      /* It may be that this trigger has already been coded (or is in the
      ** process of being coded). If this is the case, then an entry with
      ** a matching TriggerPrg.pTrigger field will be present somewhere
      ** in the Parse.pTriggerPrg list. Search for such an entry.  */
      for ( pPrg = pRoot.pTriggerPrg;
          pPrg != null && ( pPrg.pTrigger != pTrigger || pPrg.orconf != orconf );
          pPrg = pPrg.pNext
      )
        ;

      /* If an existing TriggerPrg could not be located, create a new one. */
      if ( null == pPrg )
      {
        pPrg = codeRowTrigger( pParse, pTrigger, pTab, orconf );
      }

      return pPrg;
    }

    /*
    ** Generate code for the trigger program associated with trigger p on 
    ** table pTab. The reg, orconf and ignoreJump parameters passed to this
    ** function are the same as those described in the header function for
    ** sqlite3CodeRowTrigger()
    */
    static void sqlite3CodeRowTriggerDirect(
      Parse pParse,        /* Parse context */
      Trigger p,           /* Trigger to code */
      Table pTab,          /* The table to code triggers from */
      int reg,             /* Reg array containing OLD.* and NEW.* values */
      int orconf,          /* ON CONFLICT policy */
      int ignoreJump       /* Instruction to jump to for RAISE(IGNORE) */
    )
    {
      Vdbe v = sqlite3GetVdbe( pParse ); /* Main VM */
      TriggerPrg pPrg;
      pPrg = getRowTrigger( pParse, p, pTab, orconf );
      Debug.Assert( pPrg != null || pParse.nErr != 0 );//|| pParse.db.mallocFailed );

      /* Code the OP_Program opcode in the parent VDBE. P4 of the OP_Program 
      ** is a pointer to the sub-vdbe containing the trigger program.  */
      if ( pPrg != null )
      {
        bool bRecursive = ( !string.IsNullOrEmpty( p.zName ) && 0 == ( pParse.db.flags & SQLITE_RecTriggers ) );
        sqlite3VdbeAddOp3( v, OP_Program, reg, ignoreJump, ++pParse.nMem );
        sqlite3VdbeChangeP4( v, -1, pPrg.pProgram, P4_SUBPROGRAM );
#if SQLITE_DEBUG
        VdbeComment
            ( v, "Call: %s.%s", ( !string.IsNullOrEmpty( p.zName ) ? p.zName : "fkey" ), onErrorText( orconf ) );
#endif

        /* Set the P5 operand of the OP_Program instruction to non-zero if
    ** recursive invocation of this trigger program is disallowed. Recursive
    ** invocation is disallowed if (a) the sub-program is really a trigger,
    ** not a foreign key action, and (b) the flag to enable recursive triggers
    ** is clear.  */
        sqlite3VdbeChangeP5( v, (u8)( bRecursive ? 1 : 0 ) );
      }
    }

    /*
    ** This is called to code the required FOR EACH ROW triggers for an operation
    ** on table pTab. The operation to code triggers for (INSERT, UPDATE or DELETE)
    ** is given by the op paramater. The tr_tm parameter determines whether the
    ** BEFORE or AFTER triggers are coded. If the operation is an UPDATE, then
    ** parameter pChanges is passed the list of columns being modified.
    **
    ** If there are no triggers that fire at the specified time for the specified
    ** operation on pTab, this function is a no-op.
    **
    ** The reg argument is the address of the first in an array of registers 
    ** that contain the values substituted for the new.* and old.* references
    ** in the trigger program. If N is the number of columns in table pTab
    ** (a copy of pTab.nCol), then registers are populated as follows:
    **
    **   Register       Contains
    **   ------------------------------------------------------
    **   reg+0          OLD.rowid
    **   reg+1          OLD.* value of left-most column of pTab
    **   ...            ...
    **   reg+N          OLD.* value of right-most column of pTab
    **   reg+N+1        NEW.rowid
    **   reg+N+2        OLD.* value of left-most column of pTab
    **   ...            ...
    **   reg+N+N+1      NEW.* value of right-most column of pTab
    **
    ** For ON DELETE triggers, the registers containing the NEW.* values will
    ** never be accessed by the trigger program, so they are not allocated or 
    ** populated by the caller (there is no data to populate them with anyway). 
    ** Similarly, for ON INSERT triggers the values stored in the OLD.* registers
    ** are never accessed, and so are not allocated by the caller. So, for an
    ** ON INSERT trigger, the value passed to this function as parameter reg
    ** is not a readable register, although registers (reg+N) through 
    ** (reg+N+N+1) are.
    **
    ** Parameter orconf is the default conflict resolution algorithm for the
    ** trigger program to use (REPLACE, IGNORE etc.). Parameter ignoreJump
    ** is the instruction that control should jump to if a trigger program
    ** raises an IGNORE exception.
    */
    static void sqlite3CodeRowTrigger(
    Parse pParse,        /* Parse context */
    Trigger pTrigger,    /* List of triggers on table pTab */
    int op,              /* One of TK_UPDATE, TK_INSERT, TK_DELETE */
    ExprList pChanges,   /* Changes list for any UPDATE OF triggers */
    int tr_tm,           /* One of TRIGGER_BEFORE, TRIGGER_AFTER */
    Table pTab,          /* The table to code triggers from */
    int reg,             /* The first in an array of registers (see above) */
    int orconf,          /* ON CONFLICT policy */
    int ignoreJump       /* Instruction to jump to for RAISE(IGNORE) */
    )
    {
      Trigger p;         /* Used to iterate through pTrigger list */

      Debug.Assert( op == TK_UPDATE || op == TK_INSERT || op == TK_DELETE );
      Debug.Assert( tr_tm == TRIGGER_BEFORE || tr_tm == TRIGGER_AFTER );
      Debug.Assert( ( op == TK_UPDATE ) == ( pChanges != null ) );

      for ( p = pTrigger; p != null; p = p.pNext )
      {

        /* Sanity checking:  The schema for the trigger and for the table are
        ** always defined.  The trigger must be in the same schema as the table
        ** or else it must be a TEMP trigger. */
        Debug.Assert( p.pSchema != null );
        Debug.Assert( p.pTabSchema != null );
        Debug.Assert( p.pSchema == p.pTabSchema
             || p.pSchema == pParse.db.aDb[1].pSchema );

        /* Determine whether we should code this trigger */
        if ( p.op == op
         && p.tr_tm == tr_tm
         && checkColumnOverlap( p.pColumns, pChanges ) != 0
        )
        {
          sqlite3CodeRowTriggerDirect( pParse, p, pTab, reg, orconf, ignoreJump );
        }
      }
    }

    /*
    ** Triggers may access values stored in the old.* or new.* pseudo-table. 
    ** This function returns a 32-bit bitmask indicating which columns of the 
    ** old.* or new.* tables actually are used by triggers. This information 
    ** may be used by the caller, for example, to avoid having to load the entire
    ** old.* record into memory when executing an UPDATE or DELETE command.
    **
    ** Bit 0 of the returned mask is set if the left-most column of the
    ** table may be accessed using an [old|new].<col> reference. Bit 1 is set if
    ** the second leftmost column value is required, and so on. If there
    ** are more than 32 columns in the table, and at least one of the columns
    ** with an index greater than 32 may be accessed, 0xffffffff is returned.
    **
    ** It is not possible to determine if the old.rowid or new.rowid column is 
    ** accessed by triggers. The caller must always assume that it is.
    **
    ** Parameter isNew must be either 1 or 0. If it is 0, then the mask returned
    ** applies to the old.* table. If 1, the new.* table.
    **
    ** Parameter tr_tm must be a mask with one or both of the TRIGGER_BEFORE
    ** and TRIGGER_AFTER bits set. Values accessed by BEFORE triggers are only
    ** included in the returned mask if the TRIGGER_BEFORE bit is set in the
    ** tr_tm parameter. Similarly, values accessed by AFTER triggers are only
    ** included in the returned mask if the TRIGGER_AFTER bit is set in tr_tm.
    */
    static u32 sqlite3TriggerColmask(
      Parse pParse,        /* Parse context */
      Trigger pTrigger,    /* List of triggers on table pTab */
      ExprList pChanges,   /* Changes list for any UPDATE OF triggers */
      int isNew,           /* 1 for new.* ref mask, 0 for old.* ref mask */
      int tr_tm,           /* Mask of TRIGGER_BEFORE|TRIGGER_AFTER */
      Table pTab,          /* The table to code triggers from */
      int orconf           /* Default ON CONFLICT policy for trigger steps */
    )
    {
      int op = pChanges != null ? TK_UPDATE : TK_DELETE;
      u32 mask = 0;
      Trigger p;

      Debug.Assert( isNew == 1 || isNew == 0 );
      for ( p = pTrigger; p != null; p = p.pNext )
      {
        if ( p.op == op && ( tr_tm & p.tr_tm ) != 0
         && checkColumnOverlap( p.pColumns, pChanges ) != 0
        )
        {
          TriggerPrg pPrg;
          pPrg = getRowTrigger( pParse, p, pTab, orconf );
          if ( pPrg != null )
          {
            mask |= pPrg.aColmask[isNew];
          }
        }
      }

      return mask;
    }
#endif // * !SQLITE_OMIT_TRIGGER) */

  }
}