예제 #1
0
        public void FKDropTable(SrcList name, Table table)
        {
            Context ctx = Ctx;

            if ((ctx.Flags & Context.FLAG.ForeignKeys) != 0 && !IsVirtual(table) && table.Select == null)
            {
                int  skipId = 0;
                Vdbe v      = GetVdbe();

                Debug.Assert(v != null); // VDBE has already been allocated
                if (FKReferences(table) == null)
                {
                    // Search for a deferred foreign key constraint for which this table is the child table. If one cannot be found, return without
                    // generating any VDBE code. If one can be found, then jump over the entire DELETE if there are no outstanding deferred constraints
                    // when this statement is run.
                    FKey p;
                    for (p = table.FKeys; p != null; p = p.NextFrom)
                    {
                        if (p.IsDeferred)
                        {
                            break;
                        }
                    }
                    if (p == null)
                    {
                        return;
                    }
                    skipId = v.MakeLabel();
                    v.AddOp2(OP.FkIfZero, 1, skipId);
                }

                DisableTriggers = true;
                DeleteFrom(this, SrcListDup(ctx, name, 0), null);
                DisableTriggers = false;

                // If the DELETE has generated immediate foreign key constraint violations, halt the VDBE and return an error at this point, before
                // any modifications to the schema are made. This is because statement transactions are not able to rollback schema changes.
                v.AddOp2(OP.FkIfZero, 0, v.CurrentAddr() + 2);
                HaltConstraint(this, OE.Abort, "foreign key constraint failed", Vdbe.P4T.STATIC);

                if (skipId != 0)
                {
                    v.ResolveLabel(skipId);
                }
            }
        }
예제 #2
0
        public static void DropTriggerPtr(Parse parse, Trigger trigger)
        {
            Context ctx = parse.Ctx;
            int     db  = Prepare.SchemaToIndex(ctx, trigger.Schema);

            Debug.Assert(db >= 0 && db < ctx.DBs.length);
            Table table = TableOfTrigger(trigger);

            Debug.Assert(table != null);
            Debug.Assert(table.Schema == trigger.Schema || db == 1);
#if !OMIT_AUTHORIZATION
            {
                AUTH   code      = AUTH.DROP_TRIGGER;
                string dbName    = ctx.DBs[db].Name;
                string tableName = E.SCHEMA_TABLE(db);
                if (db == 1)
                {
                    code = AUTH.DROP_TEMP_TRIGGER;
                }
                if (Auth.Check(parse, code, trigger.Name, table.Name, dbName) || Auth.Check(parse, AUTH.DELETE, tableName, null, dbName))
                {
                    return;
                }
            }
#endif

            // Generate code to destroy the database record of the trigger.
            Debug.Assert(table != null);
            Vdbe v = parse.GetVdbe();
            if (v != null)
            {
                parse.BeginWriteOperation(0, db);
                parse.OpenMasterTable(db);
                int base_ = v.AddOpList(_dropTrigger.Length, _dropTrigger);
                v.ChangeP4(base_ + 1, trigger.Name, Vdbe.P4T.TRANSIENT);
                v.ChangeP4(base_ + 4, "trigger", Vdbe.P4T.STATIC);
                parse.ChangeCookie(db);
                v.AddOp2(Core.OP.Close, 0, 0);
                v.AddOp4(Core.OP.DropTrigger, db, 0, 0, trigger.Name, 0);
                if (parse.Mems < 3)
                {
                    parse.Mems = 3;
                }
            }
        }
예제 #3
0
파일: Select.cs 프로젝트: BclEx/GpuStructs
        static void GenerateSortTail(Parse parse, Select p, Vdbe v, int columns, SelectDest dest)
        {
            int addrBreak = v.MakeLabel(); // Jump here to exit loop
            int addrContinue = v.MakeLabel(); // Jump here for next cycle
            ExprList orderBy = p.OrderBy;
            SRT dest2 = dest.Dest;
            int parmId = dest.SDParmId;

            int tabId = orderBy.ECursor;
            int regRow = Expr.GetTempReg(parse);
            int pseudoTab = 0;
            int regRowid;
            if (dest2 == SRT.Output || dest2 == SRT.Coroutine)
            {
                pseudoTab = parse.Tabs++;
                v.AddOp3(OP.OpenPseudo, pseudoTab, regRow, columns);
                regRowid = 0;
            }
            else
                regRowid = Expr.GetTempReg(parse);
            int addr;
            if ((p.SelFlags & SF.UseSorter) != 0)
            {
                int regSortOut = ++parse.Mems;
                int ptab2 = parse.Tabs++;
                v.AddOp3(OP.OpenPseudo, ptab2, regSortOut, orderBy.Exprs + 2);
                addr = 1 + v.AddOp2(OP.SorterSort, tabId, addrBreak);
                CodeOffset(v, p, addrContinue);
                v.AddOp2(OP.SorterData, tabId, regSortOut);
                v.AddOp3(OP.Column, ptab2, orderBy.Exprs + 1, regRow);
                v.ChangeP5(Vdbe.OPFLAG.CLEARCACHE);
            }
            else
            {
                addr = 1 + v.AddOp2(OP.Sort, tabId, addrBreak);
                CodeOffset(v, p, addrContinue);
                v.AddOp3(OP.Column, tabId, orderBy.Exprs + 1, regRow);
            }
            switch (dest2)
            {
                case SRT.Table:
                case SRT.EphemTab:
                    {
                        C.ASSERTCOVERAGE(dest2 == SRT.Table);
                        C.ASSERTCOVERAGE(dest2 == SRT.EphemTab);
                        v.AddOp2(OP.NewRowid, parmId, regRowid);
                        v.AddOp3(OP.Insert, parmId, regRow, regRowid);
                        v.ChangeP5(Vdbe.OPFLAG.APPEND);
                        break;
                    }
#if !OMIT_SUBQUERY
                case SRT.Set:
                    {
                        Debug.Assert(columns == 1);
                        v.AddOp4(OP.MakeRecord, regRow, 1, regRowid, dest->AffSdst, 1);
                        Expr.CacheAffinityChange(parse, regRow, 1);
                        v.AddOp2(OP.IdxInsert, parmId, regRowid);
                        break;
                    }
                case SRT.Mem:
                    {
                        Debug.Assert(columns == 1);
                        Expr.CodeMove(parse, regRow, parmId, 1);
                        // The LIMIT clause will terminate the loop for us
                        break;
                    }
#endif
                default:
                    {
                        Debug.Assert(dest2 == SRT.Output || dest2 == SRT.Coroutine);
                        C.ASSERTCOVERAGE(dest2 == SRT.Output);
                        C.ASSERTCOVERAGE(dest2 == SRT.Coroutine);
                        for (int i = 0; i < columns; i++)
                        {
                            Debug.Assert(regRow != dest.SdstId + i);
                            v.AddOp3(OP.Column, pseudoTab, i, dest.SdstId + i);
                            if (i == 0)
                                v.ChangeP5(Vdbe.OPFLAG.CLEARCACHE);
                        }
                        if (dest2 == SRT.Output)
                        {
                            v.AddOp2(OP.ResultRow, dest.SdstId, columns);
                            Expr.CacheAffinityChange(parse, dest.SdstId, columns);
                        }
                        else
                            v.AddOp1(OP.Yield, dest.SDParmId);
                        break;
                    }
            }
            Expr.ReleaseTempReg(parse, regRow);
            Expr.ReleaseTempReg(parse, regRowid);

            // The bottom of the loop
            v.ResolveLabel(addrContinue);
            v.AddOp2(OP.Next, tabId, addr);
            v.ResolveLabel(addrBreak);
            if (dest2 == SRT.Output || dest2 == SRT.Coroutine)
                v.AddOp2(OP.Close, pseudoTab, 0);
        }
예제 #4
0
파일: Select.cs 프로젝트: BclEx/GpuStructs
 static void CodeOffset(Vdbe v, Select p, int continueId)
 {
     if (p.OffsetId != 0 && continueId != 0)
     {
         v.AddOp2(OP.AddImm, p.OffsetId, -1);
         int addr = v.AddOp1(OP.IfNeg, p.OffsetId);
         v.AddOp2(OP.Goto, 0, continueId);
         v.Comment("skip OFFSET records");
         v.JumpHere(addr);
     }
 }
예제 #5
0
        public void FKCheck(Table table, int regOld, int regNew)
        {
            Context ctx            = Ctx; // Database handle
            bool    isIgnoreErrors = DisableTriggers;

            // Exactly one of regOld and regNew should be non-zero.
            Debug.Assert((regOld == 0) != (regNew == 0));

            // If foreign-keys are disabled, this function is a no-op.
            if ((ctx.Flags & Context.FLAG.ForeignKeys) == 0)
            {
                return;
            }

            int    db     = SchemaToIndex(ctx, table.Schema); // Index of database containing pTab
            string dbName = ctx.DBs[db].Name;                 // Name of database containing pTab

            // Loop through all the foreign key constraints for which pTab is the child table (the table that the foreign key definition is part of).
            FKey fkey; // Used to iterate through FKs

            for (fkey = table.FKeys; fkey != null; fkey = fkey.NextFrom)
            {
                bool isIgnore = false;
                // Find the parent table of this foreign key. Also find a unique index on the parent key columns in the parent table. If either of these
                // schema items cannot be located, set an error in pParse and return early.
                Table to    = (DisableTriggers ? FindTable(ctx, fkey.To, dbName) : LocateTable(false, fkey.To, dbName)); // Parent table of foreign key pFKey
                Index index = null;                                                                                      // Index on key columns in pTo
                int[] frees = null;
                if (to == null || LocateFkeyIndex(to, fkey, out index, out frees) != 0)
                {
                    Debug.Assert(!isIgnoreErrors || (regOld != 0 && regNew == 0));
                    if (!isIgnoreErrors || ctx.MallocFailed)
                    {
                        return;
                    }
                    if (to == null)
                    {
                        // If isIgnoreErrors is true, then a table is being dropped. In this se SQLite runs a "DELETE FROM xxx" on the table being dropped
                        // before actually dropping it in order to check FK constraints. If the parent table of an FK constraint on the current table is
                        // missing, behave as if it is empty. i.e. decrement the FK counter for each row of the current table with non-NULL keys.
                        Vdbe v      = GetVdbe();
                        int  jumpId = v.CurrentAddr() + fkey.Cols.length + 1;
                        for (int i = 0; i < fkey.Cols.length; i++)
                        {
                            int regId = fkey.Cols[i].From + regOld + 1;
                            v.AddOp2(OP.IsNull, regId, jumpId);
                        }
                        v.AddOp2(OP.FkCounter, fkey.IsDeferred, -1);
                    }
                    continue;
                }
                Debug.Assert(fkey.Cols.length == 1 || (frees != null && index != null));

                int[] cols;
                if (frees != null)
                {
                    cols = frees;
                }
                else
                {
                    int col = fkey.Cols[0].From;
                    cols    = new int[1];
                    cols[0] = col;
                }
                for (int i = 0; i < fkey.Cols.length; i++)
                {
                    if (cols[i] == table.PKey)
                    {
                        cols[i] = -1;
                    }
#if !OMIT_AUTHORIZATION
                    // Request permission to read the parent key columns. If the authorization callback returns SQLITE_IGNORE, behave as if any
                    // values read from the parent table are NULL.
                    if (ctx.Auth != null)
                    {
                        string colName = to.Cols[index != null ? index.Columns[i] : to.PKey].Name;
                        ARC    rcauth  = Auth.ReadColumn(this, to.Name, colName, db);
                        isIgnore = (rcauth == ARC.IGNORE);
                    }
#endif
                }

                // Take a shared-cache advisory read-lock on the parent table. Allocate a cursor to use to search the unique index on the parent key columns
                // in the parent table.
                TableLock(db, to.Id, false, to.Name);
                Tabs++;

                if (regOld != 0) // A row is being removed from the child table. Search for the parent. If the parent does not exist, removing the child row resolves an outstanding foreign key constraint violation.
                {
                    FKLookupParent(this, db, to, index, fkey, cols, regOld, -1, isIgnore);
                }
                if (regNew != 0) // A row is being added to the child table. If a parent row cannot be found, adding the child row has violated the FK constraint.
                {
                    FKLookupParent(this, db, to, index, fkey, cols, regNew, +1, isIgnore);
                }

                C._tagfree(ctx, ref frees);
            }

            // Loop through all the foreign key constraints that refer to this table
            for (fkey = FKReferences(table); fkey != null; fkey = fkey.NextTo)
            {
                if (!fkey.IsDeferred && Toplevel == null && !IsMultiWrite)
                {
                    Debug.Assert(regOld == 0 && regNew != 0);
                    // Inserting a single row into a parent table cannot cause an immediate foreign key violation. So do nothing in this case.
                    continue;
                }

                Index index = null; // Foreign key index for pFKey
                int[] cols  = null;
                if (LocateFkeyIndex(table, fkey, out index, out cols) != 0)
                {
                    if (isIgnoreErrors || ctx.MallocFailed)
                    {
                        return;
                    }
                    continue;
                }
                Debug.Assert(cols != null || fkey.Cols.length == 1);

                // Create a SrcList structure containing a single table (the table the foreign key that refers to this table is attached to). This
                // is required for the sqlite3WhereXXX() interface.
                SrcList src = SrcListAppend(ctx, null, null, null);
                if (src != null)
                {
                    SrcList.SrcListItem item = src.Ids[0];
                    item.Table = fkey.From;
                    item.Name  = fkey.From.Name;
                    item.Table.Refs++;
                    item.Cursor = Tabs++;

                    if (regNew != 0)
                    {
                        FKScanChildren(this, src, table, index, fkey, cols, regNew, -1);
                    }
                    if (regOld != 0)
                    {
                        // If there is a RESTRICT action configured for the current operation on the parent table of this FK, then throw an exception
                        // immediately if the FK constraint is violated, even if this is a deferred trigger. That's what RESTRICT means. To defer checking
                        // the constraint, the FK should specify NO ACTION (represented using OE_None). NO ACTION is the default.
                        FKScanChildren(this, src, table, index, fkey, cols, regOld, 1);
                    }
                    item.Name = null;
                    SrcListDelete(ctx, ref src);
                }
                C._tagfree(ctx, ref cols);
            }
        }
예제 #6
0
        static void FKScanChildren(Parse parse, SrcList src, Table table, Index index, FKey fkey, int[] cols, int regDataId, int incr)
        {
            Context ctx    = parse.Ctx; // Database handle
            Vdbe    v      = parse.GetVdbe();
            Expr    where_ = null;      // WHERE clause to scan with

            Debug.Assert(index == null || index.Table == table);
            int fkIfZero = 0; // Address of OP_FkIfZero

            if (incr < 0)
            {
                fkIfZero = v.AddOp2(OP.FkIfZero, fkey.IsDeferred, 0);
            }

            // Create an Expr object representing an SQL expression like:
            //
            //   <parent-key1> = <child-key1> AND <parent-key2> = <child-key2> ...
            //
            // The collation sequence used for the comparison should be that of the parent key columns. The affinity of the parent key column should
            // be applied to each child key value before the comparison takes place.
            for (int i = 0; i < fkey.Cols.length; i++)
            {
                int  col;                                      // Index of column in child table
                Expr left = Expr.Expr(ctx, TK.REGISTER, null); // Value from parent table row
                if (left != null)
                {
                    // Set the collation sequence and affinity of the LHS of each TK_EQ expression to the parent key column defaults.
                    if (index != null)
                    {
                        col = index.Columns[i];
                        Column colObj = table.Cols[col];
                        if (table.PKey == col)
                        {
                            col = -1;
                        }
                        left.TableId = regDataId + col + 1;
                        left.Aff     = colObj.Affinity;
                        string collName = colObj.Coll;
                        if (collName == null)
                        {
                            collName = ctx.DefaultColl.Name;
                        }
                        left = Expr.AddCollateString(parse, left, collName);
                    }
                    else
                    {
                        left.TableId = regDataId;
                        left.Aff     = AFF.INTEGER;
                    }
                }
                col = (cols != null ? cols[i] : fkey.Cols[0].From);
                Debug.Assert(col >= 0);
                string colName = fkey.From.Cols[col].Name;                 // Name of column in child table
                Expr   right   = Expr.Expr(ctx, TK.ID, colName);           // Column ref to child table
                Expr   eq      = Expr.PExpr(parse, TK.EQ, left, right, 0); // Expression (pLeft = pRight)
                where_ = Expr.And(ctx, where_, eq);
            }

            // If the child table is the same as the parent table, and this scan is taking place as part of a DELETE operation (operation D.2), omit the
            // row being deleted from the scan by adding ($rowid != rowid) to the WHERE clause, where $rowid is the rowid of the row being deleted.
            if (table == fkey.From && incr > 0)
            {
                Expr left  = Expr.Expr(ctx, TK.REGISTER, null); // Value from parent table row
                Expr right = Expr.Expr(ctx, TK.COLUMN, null);   // Column ref to child table
                if (left != null && right != null)
                {
                    left.TableId   = regDataId;
                    left.Aff       = AFF.INTEGER;
                    right.TableId  = src.Ids[0].Cursor;
                    right.ColumnId = -1;
                }
                Expr eq = Expr.PExpr(parse, TK.NE, left, right, 0); // Expression (pLeft = pRight)
                where_ = Expr.And(ctx, where_, eq);
            }

            // Resolve the references in the WHERE clause.
            NameContext nameContext;                 // Context used to resolve WHERE clause

            nameContext         = new NameContext(); // memset( &sNameContext, 0, sizeof( NameContext ) );
            nameContext.SrcList = src;
            nameContext.Parse   = parse;
            ResolveExprNames(nameContext, ref where_);

            // Create VDBE to loop through the entries in src that match the WHERE clause. If the constraint is not deferred, throw an exception for
            // each row found. Otherwise, for deferred constraints, increment the deferred constraint counter by incr for each row selected.
            ExprList  dummy     = null;
            WhereInfo whereInfo = Where.Begin(parse, src, where_, ref dummy, 0); // Context used by sqlite3WhereXXX()

            if (incr > 0 && !fkey.IsDeferred)
            {
                E.Parse_Toplevel(parse).MayAbort = true;
            }
            v.AddOp2(OP.FkCounter, fkey.IsDeferred, incr);
            if (whereInfo != null)
            {
                Where.End(whereInfo);
            }

            // Clean up the WHERE clause constructed above.
            Expr.Delete(ctx, ref where_);
            if (fkIfZero != 0)
            {
                v.JumpHere(fkIfZero);
            }
        }
예제 #7
0
        static void FKLookupParent(Parse parse, int db, Table table, Index index, FKey fkey, int[] cols, int regDataId, int incr, bool isIgnore)
        {
            Vdbe v     = parse.GetVdbe(); // Vdbe to add code to
            int  curId = parse.Tabs - 1;  // Cursor number to use
            int  okId  = v.MakeLabel();   // jump here if parent key found

            // If nIncr is less than zero, then check at runtime if there are any outstanding constraints to resolve. If there are not, there is no need
            // to check if deleting this row resolves any outstanding violations.
            //
            // Check if any of the key columns in the child table row are NULL. If any are, then the constraint is considered satisfied. No need to
            // search for a matching row in the parent table.
            int i;

            if (incr < 0)
            {
                v.AddOp2(OP.FkIfZero, fkey.IsDeferred, okId);
            }
            for (i = 0; i < fkey.Cols.length; i++)
            {
                int regId = cols[i] + regDataId + 1;
                v.AddOp2(OP.IsNull, regId, okId);
            }

            if (!isIgnore)
            {
                if (index == null)
                {
                    // If pIdx is NULL, then the parent key is the INTEGER PRIMARY KEY column of the parent table (table pTab).
                    int mustBeIntId; // Address of MustBeInt instruction
                    int regTempId = parse.GetTempReg();

                    // Invoke MustBeInt to coerce the child key value to an integer (i.e. apply the affinity of the parent key). If this fails, then there
                    // is no matching parent key. Before using MustBeInt, make a copy of the value. Otherwise, the value inserted into the child key column
                    // will have INTEGER affinity applied to it, which may not be correct.
                    v.AddOp2(OP.SCopy, cols[0] + 1 + regDataId, regTempId);
                    mustBeIntId = v.AddOp2(OP.MustBeInt, regTempId, 0);

                    // If the parent table is the same as the child table, and we are about to increment the constraint-counter (i.e. this is an INSERT operation),
                    // then check if the row being inserted matches itself. If so, do not increment the constraint-counter.
                    if (table == fkey.From && incr == 1)
                    {
                        v.AddOp3(OP.Eq, regDataId, okId, regTempId);
                    }

                    parse.OpenTable(parse, curId, db, table, OP.OpenRead);
                    v.AddOp3(OP.NotExists, curId, 0, regTempId);
                    v.AddOp2(OP.Goto, 0, okId);
                    v.JumpHere(v.CurrentAddr() - 2);
                    v.JumpHere(mustBeIntId);
                    parse.ReleaseTempReg(regTempId);
                }
                else
                {
                    int     colsLength = fkey.Cols.length;
                    int     regTempId  = parse.GetTempRange(colsLength);
                    int     regRecId   = parse.GetTempReg();
                    KeyInfo key        = IndexKeyinfo(parse, index);

                    v.AddOp3(OP.OpenRead, curId, index.Id, db);
                    v.ChangeP4(v, -1, key, Vdbe.P4T.KEYINFO_HANDOFF);
                    for (i = 0; i < colsLength; i++)
                    {
                        v.AddOp2(OP.Copy, cols[i] + 1 + regDataId, regTempId + i);
                    }

                    // If the parent table is the same as the child table, and we are about to increment the constraint-counter (i.e. this is an INSERT operation),
                    // then check if the row being inserted matches itself. If so, do not increment the constraint-counter.

                    // If any of the parent-key values are NULL, then the row cannot match itself. So set JUMPIFNULL to make sure we do the OP_Found if any
                    // of the parent-key values are NULL (at this point it is known that none of the child key values are).
                    if (table == fkey.From && incr == 1)
                    {
                        int jumpId = v.CurrentAddr() + colsLength + 1;
                        for (i = 0; i < colsLength; i++)
                        {
                            int childId  = cols[i] + 1 + regDataId;
                            int parentId = index.Columns[i] + 1 + regDataId;
                            Debug.Assert(cols[i] != table.PKey);
                            if (index.Columns[i] == table.PKey)
                            {
                                parentId = regDataId;  // The parent key is a composite key that includes the IPK column
                            }
                            v.AddOp3(OP.Ne, childId, jumpId, parentId);
                            v.ChangeP5(SQLITE_JUMPIFNULL);
                        }
                        v.AddOp2(OP.Goto, 0, okId);
                    }

                    v.AddOp3(OP.MakeRecord, regTempId, colsLength, regRecId);
                    v.ChangeP4(-1, IndexAffinityStr(v, index), Vdbe.P4T.TRANSIENT);
                    v.AddOp4Int(OP.Found, curId, okId, regRecId, 0);

                    parse.ReleaseTempReg(regRecId);
                    parse.ReleaseTempRange(regTempId, colsLength);
                }
            }

            if (!fkey.IsDeferred && parse.Toplevel == null && parse.IsMultiWrite == 0)
            {
                // Special case: If this is an INSERT statement that will insert exactly one row into the table, raise a constraint immediately instead of
                // incrementing a counter. This is necessary as the VM code is being generated for will not open a statement transaction.
                Debug.Assert(incr == 1);
                HaltConstraint(parse, OE.Abort, "foreign key constraint failed", Vdbe.P4T.STATIC);
            }
            else
            {
                if (incr > 0 && !fkey.IsDeferred)
                {
                    E.Parse_Toplevel(parse).MayAbort = true;
                }
                v.AddOp2(OP.FkCounter, fkey.IsDeferred, incr);
            }

            v.ResolveLabel(v, okId);
            v.AddOp1(OP.Close, curId);
        }
예제 #8
0
파일: VTable.cs 프로젝트: BclEx/GpuStructs
        public static void FinishParse(Parse parse, Token end)
        {
            Table   table = parse.NewTable; // The table being constructed
            Context ctx   = parse.Ctx;      // The database connection

            if (table == null)
            {
                return;
            }
            AddArgumentToVtab(parse);
            parse.Arg.data = null;
            if (table.ModuleArgs.length < 1)
            {
                return;
            }

            // If the CREATE VIRTUAL TABLE statement is being entered for the first time (in other words if the virtual table is actually being
            // created now instead of just being read out of sqlite_master) then do additional initialization work and store the statement text
            // in the sqlite_master table.
            if (!ctx.Init.Busy)
            {
                // Compute the complete text of the CREATE VIRTUAL TABLE statement
                if (end != null)
                {
                    parse.NameToken.length = (uint)parse.NameToken.data.Length;               //: (int)(end->data - parse->NameToken) + end->length;
                }
                string stmt = C._mtagprintf(ctx, "CREATE VIRTUAL TABLE %T", parse.NameToken); //.Z.Substring(0, parse.NameToken.length));

                // A slot for the record has already been allocated in the SQLITE_MASTER table.  We just need to update that slot with all
                // the information we've collected.
                //
                // The VM register number pParse->regRowid holds the rowid of an entry in the sqlite_master table tht was created for this vtab
                // by sqlite3StartTable().
                int db = Prepare.SchemaToIndex(ctx, table.Schema);
                parse.NestedParse("UPDATE %Q.%s SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q WHERE rowid=#%d",
                                  ctx.DBs[db].Name, E.SCHEMA_TABLE(db),
                                  table.Name, table.Name,
                                  stmt,
                                  parse.RegRowid
                                  );
                C._tagfree(ctx, ref stmt);
                Vdbe v = parse.GetVdbe();
                parse.ChangeCookie(db);

                v.AddOp2(OP.Expire, 0, 0);
                string where_ = C._mtagprintf(ctx, "name='%q' AND type='table'", table.Name);
                v.AddParseSchemaOp(db, where_);
                v.AddOp4(OP.VCreate, db, 0, 0, table.Name, (Vdbe.P4T)table.Name.Length + 1);
            }

            // If we are rereading the sqlite_master table create the in-memory record of the table. The xConnect() method is not called until
            // the first time the virtual table is used in an SQL statement. This allows a schema that contains virtual tables to be loaded before
            // the required virtual table implementations are registered.
            else
            {
                Schema schema     = table.Schema;
                string name       = table.Name;
                int    nameLength = name.Length;
                Debug.Assert(Btree.SchemaMutexHeld(ctx, 0, schema));
                Table oldTable = schema.TableHash.Insert(name, nameLength, table);
                if (oldTable != null)
                {
                    ctx.MallocFailed = true;
                    Debug.Assert(table == oldTable); // Malloc must have failed inside HashInsert()
                    return;
                }
                parse.NewTable = null;
            }
        }