示例#1
0
        static WRC LookupName(Parse parse, string dbName, string tableName, string colName, NameContext nc, Expr expr)
        {
            int     cnt       = 0;            // Number of matching column names
            int     cntTab    = 0;            // Number of matching table names
            int     subquerys = 0;            // How many levels of subquery
            Context ctx       = parse.Ctx;    // The database connection

            SrcList.SrcListItem item;         // Use for looping over pSrcList items
            SrcList.SrcListItem match = null; // The matching pSrcList item
            NameContext         topNC = nc;   // First namecontext in the list
            Schema schema = null;             // Schema of the expression
            bool   isTrigger = false;
            int    i, j;

            Debug.Assert(nc != null);      // the name context cannot be NULL.
            Debug.Assert(colName != null); // The Z in X.Y.Z cannot be NULL
            Debug.Assert(!E.ExprHasAnyProperty(expr, EP.TokenOnly | EP.Reduced));

            // Initialize the node to no-match
            expr.TableId = -1;
            expr.Table   = null;
            E.ExprSetIrreducible(expr);

            // Translate the schema name in zDb into a pointer to the corresponding schema.  If not found, pSchema will remain NULL and nothing will match
            // resulting in an appropriate error message toward the end of this routine
            if (dbName != null)
            {
                for (i = 0; i < ctx.DBs.length; i++)
                {
                    Debug.Assert(ctx.DBs[i].Name != null);
                    if (string.Compare(ctx.DBs[i].Name, dbName) == 0)
                    {
                        schema = ctx.DBs[i].Schema;
                        break;
                    }
                }
            }

            // Start at the inner-most context and move outward until a match is found
            while (nc != null && cnt == 0)
            {
                ExprList list;
                SrcList  srcList = nc.SrcList;
                if (srcList != null)
                {
                    for (i = 0; i < srcList.Srcs; i++)
                    {
                        item = srcList.Ids[i];
                        Table table = item.Table;
                        Debug.Assert(table != null && table.Name != null);
                        Debug.Assert(table.Cols.length > 0);
                        if (item.Select != null && (item.Select.SelFlags & SF.NestedFrom) != 0)
                        {
                            bool hit = false;
                            list = item.Select.EList;
                            for (j = 0; j < list.Exprs; j++)
                            {
                                if (Walker.MatchSpanName(list.Ids[j].Span, colName, tableName, dbName))
                                {
                                    cnt++;
                                    cntTab        = 2;
                                    match         = item;
                                    expr.ColumnId = j;
                                    hit           = true;
                                }
                            }
                            if (hit || table == null)
                            {
                                continue;
                            }
                        }
                        if (dbName != null && table.Schema != schema)
                        {
                            continue;
                        }
                        if (tableName != null)
                        {
                            string tableName2 = (item.Alias != null ? item.Alias : table.Name);
                            Debug.Assert(tableName2 != null);
                            if (!string.Equals(tableName2, tableName, StringComparison.OrdinalIgnoreCase))
                            {
                                continue;
                            }
                        }
                        if (cntTab++ == 0)
                        {
                            match = item;
                        }
                        Column col;
                        for (j = 0; j < table.Cols.length; j++)
                        {
                            col = table.Cols[j];
                            if (string.Equals(col.Name, colName, StringComparison.InvariantCultureIgnoreCase))
                            {
                                // If there has been exactly one prior match and this match is for the right-hand table of a NATURAL JOIN or is in a
                                // USING clause, then skip this match.
                                if (cnt == 1)
                                {
                                    if ((item.Jointype & JT.NATURAL) != 0)
                                    {
                                        continue;
                                    }
                                    if (NameInUsingClause(item.Using, colName))
                                    {
                                        continue;
                                    }
                                }
                                cnt++;
                                match = item;
                                // Substitute the rowid (column -1) for the INTEGER PRIMARY KEY
                                expr.ColumnId = (j == table.PKey ? -1 : (short)j);
                                break;
                            }
                        }
                    }
                    if (match != null)
                    {
                        expr.TableId = match.Cursor;
                        expr.Table   = match.Table;
                        schema       = expr.Table.Schema;
                    }
                }

#if !OMIT_TRIGGER
                // If we have not already resolved the name, then maybe it is a new.* or old.* trigger argument reference
                if (dbName == null && tableName != null && cnt == 0 && parse.TriggerTab != null)
                {
                    TK    op    = parse.TriggerOp;
                    Table table = null;
                    Debug.Assert(op == TK.DELETE || op == TK.UPDATE || op == TK.INSERT);
                    if (op != TK.DELETE && string.Equals("new", tableName, StringComparison.InvariantCultureIgnoreCase))
                    {
                        expr.TableId = 1;
                        table        = parse.TriggerTab;
                    }
                    else if (op != TK.INSERT && string.Equals("old", tableName, StringComparison.InvariantCultureIgnoreCase))
                    {
                        expr.TableId = 0;
                        table        = parse.TriggerTab;
                    }
                    if (table != null)
                    {
                        int colId;
                        schema = table.Schema;
                        cntTab++;
                        for (colId = 0; colId < table.Cols.length; colId++)
                        {
                            Column col = table.Cols[colId];
                            if (string.Equals(col.Name, colName, StringComparison.InvariantCultureIgnoreCase))
                            {
                                if (colId == table.PKey)
                                {
                                    colId = -1;
                                }
                                break;
                            }
                        }
                        if (colId >= table.Cols.length && Expr.IsRowid(colName))
                        {
                            colId = -1; // IMP: R-44911-55124
                        }
                        if (colId < table.Cols.length)
                        {
                            cnt++;
                            if (colId < 0)
                            {
                                expr.Aff = AFF.INTEGER;
                            }
                            else if (expr.TableId == 0)
                            {
                                C.ASSERTCOVERAGE(colId == 31);
                                C.ASSERTCOVERAGE(colId == 32);
                                parse.Oldmask |= (colId >= 32 ? 0xffffffff : (((uint)1) << colId));
                            }
                            else
                            {
                                C.ASSERTCOVERAGE(colId == 31);
                                C.ASSERTCOVERAGE(colId == 32);
                                parse.Newmask |= (colId >= 32 ? 0xffffffff : (((uint)1) << colId));
                            }
                            expr.ColumnId = (short)colId;
                            expr.Table    = table;
                            isTrigger     = true;
                        }
                    }
                }
#endif

                // Perhaps the name is a reference to the ROWID
                if (cnt == 0 && cntTab == 1 && Expr.IsRowid(colName))
                {
                    cnt           = 1;
                    expr.ColumnId = -1; // IMP: R-44911-55124
                    expr.Aff      = AFF.INTEGER;
                }

                // If the input is of the form Z (not Y.Z or X.Y.Z) then the name Z might refer to an result-set alias.  This happens, for example, when
                // we are resolving names in the WHERE clause of the following command:
                //
                //     SELECT a+b AS x FROM table WHERE x<10;
                //
                // In cases like this, replace pExpr with a copy of the expression that forms the result set entry ("a+b" in the example) and return immediately.
                // Note that the expression in the result set should have already been resolved by the time the WHERE clause is resolved.
                if (cnt == 0 && (list = nc.EList) != null && tableName == null)
                {
                    for (j = 0; j < list.Exprs; j++)
                    {
                        string asName = list.Ids[j].Name;
                        if (asName != null && string.Equals(asName, colName, StringComparison.InvariantCultureIgnoreCase))
                        {
                            Debug.Assert(expr.Left == null && expr.Right == null);
                            Debug.Assert(expr.x.List == null);
                            Debug.Assert(expr.x.Select == null);
                            Expr orig = list.Ids[j].Expr;
                            if ((nc.NCFlags & NC.AllowAgg) == 0 && E.ExprHasProperty(orig, EP.Agg))
                            {
                                parse.ErrorMsg("misuse of aliased aggregate %s", asName);
                                return(WRC.Abort);
                            }
                            ResolveAlias(parse, list, j, expr, "", subquerys);
                            cnt   = 1;
                            match = null;
                            Debug.Assert(tableName == null && dbName == null);
                            goto lookupname_end;
                        }
                    }
                }

                // Advance to the next name context.  The loop will exit when either we have a match (cnt>0) or when we run out of name contexts.
                if (cnt == 0)
                {
                    nc = nc.Next;
                    subquerys++;
                }
            }

            // If X and Y are NULL (in other words if only the column name Z is supplied) and the value of Z is enclosed in double-quotes, then
            // Z is a string literal if it doesn't match any column names.  In that case, we need to return right away and not make any changes to
            // pExpr.
            //
            // Because no reference was made to outer contexts, the pNC->nRef fields are not changed in any context.
            if (cnt == 0 && tableName == null && E.ExprHasProperty(expr, EP.DblQuoted))
            {
                expr.OP    = TK.STRING;
                expr.Table = null;
                return(WRC.Prune);
            }

            // cnt==0 means there was not match.  cnt>1 means there were two or more matches.  Either way, we have an error.
            if (cnt != 1)
            {
                string err = (cnt == 0 ? "no such column" : "ambiguous column name");
                if (dbName != null)
                {
                    parse.ErrorMsg("%s: %s.%s.%s", err, dbName, tableName, colName);
                }
                else if (tableName != null)
                {
                    parse.ErrorMsg("%s: %s.%s", err, tableName, colName);
                }
                else
                {
                    parse.ErrorMsg("%s: %s", err, colName);
                }
                parse.CheckSchema = 1;
                topNC.Errs++;
            }

            // If a column from a table in pSrcList is referenced, then record this fact in the pSrcList.a[].colUsed bitmask.  Column 0 causes
            // bit 0 to be set.  Column 1 sets bit 1.  And so forth.  If the column number is greater than the number of bits in the bitmask
            // then set the high-order bit of the bitmask.
            if (expr.ColumnId >= 0 && match != null)
            {
                int n = expr.ColumnId;
                C.ASSERTCOVERAGE(n == BMS - 1);
                if (n >= BMS)
                {
                    n = BMS - 1;
                }
                Debug.Assert(match.Cursor == expr.TableId);
                match.ColUsed |= ((Bitmask)1) << n;
            }

            // Clean up and return
            Expr.Delete(ctx, ref expr.Left);
            expr.Left = null;
            Expr.Delete(ctx, ref expr.Right);
            expr.Right = null;
            expr.OP    = (isTrigger ? TK.TRIGGER : TK.COLUMN);
lookupname_end:
            if (cnt == 1)
            {
                Debug.Assert(nc != null);
                Auth.Read(parse, expr, schema, nc.SrcList);
                // Increment the nRef value on all name contexts from TopNC up to the point where the name matched.
                for (; ;)
                {
                    Debug.Assert(topNC != null);
                    topNC.Refs++;
                    if (topNC == nc)
                    {
                        break;
                    }
                    topNC = topNC.Next;
                }
                return(WRC.Prune);
            }
            return(WRC.Abort);
        }
示例#2
0
        public uint TriggerColmask(Parse parse, ExprList changes, bool isNew, TRIGGER trtm, Table table, OE orconf)
        {
            TK   op      = (changes != null ? TK.UPDATE : TK.DELETE);
            int  isNewId = (isNew ? 1 : 0);
            uint mask    = 0;

            for (Trigger p = this; p != null; p = p.Next)
            {
                if (p.OP == op && (trtm & p.TRtm) != 0 && CheckColumnOverlap(p.Columns, changes))
                {
                    TriggerPrg prg = GetRowTrigger(parse, p, table, orconf);
                    if (prg != null)
                    {
                        mask |= prg.Colmasks[isNewId];
                    }
                }
            }
            return(mask);
        }
示例#3
0
        public void CodeRowTrigger(Parse parse, TK op, ExprList changes, TRIGGER trtm, Table table, int reg, OE orconf, int ignoreJump)
        {
            Debug.Assert(op == TK.UPDATE || op == TK.INSERT || op == TK.DELETE);
            Debug.Assert(trtm == TRIGGER.BEFORE || trtm == TRIGGER.AFTER);
            Debug.Assert((op == TK.UPDATE) == (changes != null));

            for (Trigger p = this; p != null; p = p.Next)
            {
                // 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.Schema != null);
                Debug.Assert(p.TabSchema != null);
                Debug.Assert(p.Schema == p.TabSchema || p.Schema == parse.Ctx.DBs[1].Schema);

                // Determine whether we should code this trigger
                if (p.OP == op && p.TRtm == trtm && CheckColumnOverlap(p.Columns, changes))
                {
                    p.CodeRowTriggerDirect(parse, table, reg, orconf, ignoreJump);
                }
            }
        }
示例#4
0
        static TriggerPrg CodeRowTrigger(Parse parse, Trigger trigger, Table table, OE orconf)
        {
            Parse   top = E.Parse_Toplevel(parse);
            Context ctx = parse.Ctx; // Database handle

            Debug.Assert(trigger.Name == null || table == TableOfTrigger(trigger));
            Debug.Assert(top.V != 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.
            TriggerPrg prg = new TriggerPrg(); // Value to return //: _tagalloc(ctx, sizeof(TriggerPrg), true);

            if (prg == null)
            {
                return(null);
            }
            prg.Next       = top.TriggerPrg;
            top.TriggerPrg = prg;
            Vdbe.SubProgram program;                       // Sub-vdbe for trigger program
            prg.Program = program = new Vdbe.SubProgram(); // sqlite3DbMallocZero( db, sizeof( SubProgram ) );
            if (program == null)
            {
                return(null);
            }
            top.V.LinkSubProgram(program);
            prg.Trigger     = trigger;
            prg.Orconf      = orconf;
            prg.Colmasks[0] = 0xffffffff;
            prg.Colmasks[1] = 0xffffffff;

            // Allocate and populate a new Parse context to use for coding the trigger sub-program.
            Parse subParse = new Parse(); // Parse context for sub-vdbe //: _scratchalloc(ctx, sizeof(Parse), true);

            if (subParse == null)
            {
                return(null);
            }
            NameContext sNC = new NameContext(); // Name context for sub-vdbe

            sNC.Parse            = subParse;
            subParse.Ctx         = ctx;
            subParse.TriggerTab  = table;
            subParse.Toplevel    = top;
            subParse.AuthContext = trigger.Name;
            subParse.TriggerOp   = trigger.OP;
            subParse.QueryLoops  = parse.QueryLoops;

            int  endTrigger = 0;                  // Label to jump to if WHEN is false
            Vdbe v          = subParse.GetVdbe(); // Temporary VM

            if (v != null)
            {
#if DEBUG
                v.Comment("Start: %s.%s (%s %s%s%s ON %s)",
                          trigger.Name, OnErrorText(orconf),
                          (trigger.TRtm == TRIGGER.BEFORE ? "BEFORE" : "AFTER"),
                          (trigger.OP == TK.UPDATE ? "UPDATE" : string.Empty),
                          (trigger.OP == TK.INSERT ? "INSERT" : string.Empty),
                          (trigger.OP == TK.DELETE ? "DELETE" : string.Empty),
                          table.Name);
#endif
#if !OMIT_TRACE
                v.ChangeP4(-1, C._mtagprintf(ctx, "-- TRIGGER %s", trigger.Name), Vdbe.P4T.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 (trigger.When != null)
                {
                    Expr when = Expr.Dup(ctx, trigger.When, 0); // Duplicate of trigger WHEN expression
                    if (ResolveExprNames(sNC, ref when) == RC.OK && !ctx.MallocFailed)
                    {
                        endTrigger = v.MakeLabel();
                        subParse.IfFalse(when, endTrigger, RC_JUMPIFNULL);
                    }
                    Expr.Delete(ctx, ref when);
                }

                // Code the trigger program into the sub-vdbe.
                CodeTriggerProgram(subParse, trigger.StepList, orconf);

                // Insert an OP_Halt at the end of the sub-program.
                if (endTrigger != 0)
                {
                    v.ResolveLabel(endTrigger);
                }
                v.AddOp0(Core.OP.Halt);
#if DEBUG
                v.Comment("End: %s.%s", trigger.Name, OnErrorText(orconf));
#endif
                TransferParseError(parse, subParse);
                if (!ctx.MallocFailed)
                {
                    program.Ops.data = v.TakeOpArray(ref program.Ops.length, ref top.MaxArgs);
                }
                program.Mems    = subParse.Mems;
                program.Csrs    = subParse.Tabs;
                program.Token   = trigger.GetHashCode();
                prg.Colmasks[0] = subParse.Oldmask;
                prg.Colmasks[1] = subParse.Newmask;
                Vdbe.Delete(v);
            }

            Debug.Assert(subParse.Ainc == null && subParse.ZombieTab == null);
            Debug.Assert(subParse.TriggerPrg == null && subParse.MaxArgs == 0);
            C._scratchfree(ctx, ref subParse);

            return(prg);
        }
示例#5
0
        public static void BeginTrigger(Parse parse, Token name1, Token name2, TK trTm, TK op, IdList columns, SrcList tableName, Expr when, bool isTemp, int noErr)
        {
            Context ctx = parse.Ctx;     // The database connection

            Debug.Assert(name1 != null); // pName1.z might be NULL, but not pName1 itself
            Debug.Assert(name2 != null);
            Debug.Assert(op == TK.INSERT || op == TK.UPDATE || op == TK.DELETE);
            Debug.Assert(op > 0 && op < (TK)0xff);
            Trigger trigger = null; // The new trigger

            int   db;               // The database to store the trigger in
            Token name = null;      // The unqualified db name

            if (isTemp)
            {
                // If TEMP was specified, then the trigger name may not be qualified.
                if (name2.length > 0)
                {
                    parse.ErrorMsg("temporary trigger may not have qualified name");
                    goto trigger_cleanup;
                }
                db   = 1;
                name = name1;
            }
            else
            {
                // Figure out the db that the the trigger will be created in
                db = parse.TwoPartName(name1, name2, ref name);
                if (db < 0)
                {
                    goto trigger_cleanup;
                }
            }
            if (tableName == null || ctx.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 (ctx.Init.Busy && db != 1)
            {
                C._tagfree(ctx, ref tableName.Ids[0].Database);
                tableName.Ids[0].Database = 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 (tableName == null) goto trigger_cleanup;
            Table table = Delete.SrcListLookup(parse, tableName); // Table that the trigger fires off of

            if (ctx.Init.Busy == null && name2.length == 0 && table != null && table.Schema == ctx.DBs[1].Schema)
            {
                db = 1;
            }

            // Ensure the table name matches database name and that the table exists
            if (ctx.MallocFailed)
            {
                goto trigger_cleanup;
            }
            Debug.Assert(tableName.Srcs == 1);
            DbFixer sFix = new DbFixer(); // State vector for the DB fixer

            if (sFix.FixInit(parse, db, "trigger", name) && sFix.FixSrcList(tableName))
            {
                goto trigger_cleanup;
            }
            table = Delete.SrcListLookup(parse, tableName);
            if (table == null)
            {
                // The table does not exist.
                if (ctx.Init.DB == 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.
                    ctx.Init.OrphanTrigger = true;
                }
                goto trigger_cleanup;
            }
            if (E.IsVirtual(table))
            {
                parse.ErrorMsg("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
            string nameAsString = Parse.NameFromToken(ctx, name);

            if (nameAsString == null || parse.CheckObjectName(nameAsString) != RC.OK)
            {
                goto trigger_cleanup;
            }
            Debug.Assert(Btree.SchemaMutexHeld(ctx, db, null));
            if (ctx.DBs[db].Schema.TriggerHash.Find(nameAsString, nameAsString.Length, (Trigger)null) != null)
            {
                if (noErr == 0)
                {
                    parse.ErrorMsg("trigger %T already exists", name);
                }
                else
                {
                    Debug.Assert(!ctx.Init.Busy);
                    parse.CodeVerifySchema(db);
                }
                goto trigger_cleanup;
            }

            // Do not create a trigger on a system table
            if (table.Name.StartsWith("sqlite_", StringComparison.InvariantCultureIgnoreCase))
            {
                parse.ErrorMsg("cannot create trigger on system table");
                parse.Errs++;
                goto trigger_cleanup;
            }

            // INSTEAD of triggers are only for views and views only support INSTEAD of triggers.
            if (table.Select != null && trTm != TK.INSTEAD)
            {
                parse.ErrorMsg("cannot create %s trigger on view: %S", (trTm == TK.BEFORE ? "BEFORE" : "AFTER"), tableName, 0);
                goto trigger_cleanup;
            }
            if (table.Select == null && trTm == TK.INSTEAD)
            {
                parse.ErrorMsg("cannot create INSTEAD OF trigger on table: %S", tableName, 0);
                goto trigger_cleanup;
            }

#if !OMIT_AUTHORIZATION
            {
                int    tabDb      = Prepare.SchemaToIndex(ctx, table.Schema); // Index of the database holding pTab
                AUTH   code       = AUTH.CREATE_TRIGGER;
                string dbName     = ctx.DBs[tabDb].Name;
                string dbTrigName = (isTemp ? ctx.DBs[1].Name : dbName);
                if (tabDb == 1 || isTemp)
                {
                    code = AUTH.CREATE_TEMP_TRIGGER;
                }
                if (Auth.Check(parse, code, nameAsString, table.Name, dbTrigName) != 0 || Auth.Check(parse, AUTH.INSERT, E.SCHEMA_TABLE(tabDb), 0, dbName))
                {
                    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 (trTm == TK.INSTEAD)
            {
                trTm = TK.BEFORE;
            }

            // Build the Trigger object
            trigger = new Trigger(); //: (Trigger *)_tagalloc(db, sizeof(Trigger), true);
            if (trigger == null)
            {
                goto trigger_cleanup;
            }
            trigger.Name      = name;
            trigger.Table     = tableName.Ids[0].Name; //: _tagstrdup(ctx, tableName->Ids[0].Name);
            trigger.Schema    = ctx.DBs[db].Schema;
            trigger.TabSchema = table.Schema;
            trigger.OP        = op;
            trigger.TRtm      = (trTm == TK.BEFORE ? TRIGGER.BEFORE : TRIGGER.AFTER);
            trigger.When      = Expr.Dup(db, when, E.EXPRDUP_REDUCE);
            trigger.Columns   = Expr.IdListDup(ctx, columns);
            Debug.Assert(parse.NewTrigger == null);
            parse.NewTrigger = trigger;

trigger_cleanup:
            C._tagfree(ctx, ref name);
            Expr.SrcListDelete(ctx, ref tableName);
            Expr.IdListDelete(ctx, ref columns);
            Expr.Delete(ctx, ref when);
            if (parse.NewTrigger == null)
            {
                DeleteTrigger(ctx, ref trigger);
            }
            else
            {
                Debug.Assert(parse.NewTrigger == trigger);
            }
        }
示例#6
0
        public static void FinishTrigger(Parse parse, TriggerStep stepList, Token all)
        {
            Trigger trig      = parse.NewTrigger; // Trigger being finished
            Context ctx       = parse.Ctx;        // The database
            Token   nameToken = new Token();      // Trigger name for error reporting

            parse.NewTrigger = null;
            if (C._NEVER(parse.Errs != 0) || trig == null)
            {
                goto triggerfinish_cleanup;
            }
            string name = trig.Name;                                     // Name of trigger
            int    db   = Prepare.SchemaToIndex(parse.Ctx, trig.Schema); // Database containing the trigger

            trig.StepList = stepList;
            while (stepList != null)
            {
                stepList.Trig = trig;
                stepList      = stepList.Next;
            }
            nameToken.data   = trig.Name;
            nameToken.length = (uint)nameToken.data.Length;
            DbFixer sFix = new DbFixer(); // Fixer object

            if (sFix.FixInit(parse, db, "trigger", nameToken) && sFix.FixTriggerStep(trig.StepList))
            {
                goto triggerfinish_cleanup;
            }

            // if we are not initializing, build the sqlite_master entry
            if (ctx.Init.Busy)
            {
                // Make an entry in the sqlite_master table
                Vdbe v = parse.GetVdbe();
                if (v == null)
                {
                    goto triggerfinish_cleanup;
                }
                parse.BeginWriteOperation(0, db);
                string z = all.data.Substring(0, (int)all.length); //: _tagstrndup(ctx, (char *)all->data, all->length);
                parse.NestedParse("INSERT INTO %Q.%s VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')", ctx.DBs[db].Name, E.SCHEMA_TABLE(db), name, trig.Table, z);
                C._tagfree(ctx, ref z);
                parse.ChangeCookie(db);
                v.AddParseSchemaOp(db, C._mtagprintf(ctx, "type='trigger' AND name='%q'", name));
            }

            if (!ctx.Init.Busy)
            {
                Trigger link = trig;
                Debug.Assert(Btree.SchemaMutexHeld(ctx, db, null));
                trig = ctx.DBs[db].Schema.TriggerHash.Insert(name, name.Length, trig);
                if (trig != null)
                {
                    ctx.MallocFailed = true;
                }
                else if (link.Schema == link.TabSchema)
                {
                    int   tableLength = link.Table.Length;
                    Table table       = (Table)link.TabSchema.TableHash.Find(link.Table, tableLength, (Table)null);
                    Debug.Assert(table != null);
                    link.Next      = table.Triggers;
                    table.Triggers = link;
                }
            }

triggerfinish_cleanup:
            DeleteTrigger(ctx, ref trig);
            Debug.Assert(parse.NewTrigger == null);
            DeleteTriggerStep(ctx, ref stepList);
        }