Пример #1
0
        public static void GenerateRowDelete(Parse parse, Table table, int curId, int rowid, int count, Trigger trigger, OE onconf)
        {
            // Vdbe is guaranteed to have been allocated by this stage.
            Vdbe v = parse.V;
            Debug.Assert(v != null);

            // Seek cursor iCur to the row to delete. If this row no longer exists (this can happen if a trigger program has already deleted it), do
            // not attempt to delete it or fire any DELETE triggers.
            int label = v.MakeLabel(); // Label resolved to end of generated code
            v.AddOp3(OP.NotExists, curId, label, rowid);

            // If there are any triggers to fire, allocate a range of registers to use for the old.* references in the triggers.
            if (FKey.FkRequired(parse, table, null, 0) != 0 || trigger != null)
            {
                // TODO: Could use temporary registers here. Also could attempt to avoid copying the contents of the rowid register.
                uint mask = sqlite3TriggerColmask(parse, trigger, null, 0, TRIGGER.BEFORE | TRIGGER.AFTER, table, onconf); // Mask of OLD.* columns in use
                mask |= sqlite3FkOldmask(parse, table);
                int oldId = parse.Mems + 1; // First register in OLD.* array
                parse.Mems += (1 + table.Cols.length);

                // Populate the OLD.* pseudo-table register array. These values will be used by any BEFORE and AFTER triggers that exist.
                v.AddOp2(OP.Copy, rowid, oldId);
                for (int col = 0; col < table.Cols.length; col++) // Iterator used while populating OLD.*
                    if (mask == 0xffffffff || (mask & (1 << col)) != 0)
                        Expr.CodeGetColumnOfTable(v, table, curId, col, oldId + col + 1);

                // Invoke BEFORE DELETE trigger programs.
                sqlite3CodeRowTrigger(parse, trigger, TK.DELETE, null, TRIGGER.BEFORE, table, oldId, onconf, label);

                // Seek the cursor to the row to be deleted again. It may be that the BEFORE triggers coded above have already removed the row
                // being deleted. Do not attempt to delete the row a second time, and do not fire AFTER triggers.
                v.AddOp3(OP.NotExists, curId, label, rowid);

                // Do FK processing. This call checks that any FK constraints that refer to this table (i.e. constraints attached to other tables) are not violated by deleting this row.
                FKey.FkCheck(parse, table, oldId, 0);
            }

            // Delete the index and table entries. Skip this step if table is really a view (in which case the only effect of the DELETE statement is to fire the INSTEAD OF triggers).
            if (table.Select == null)
            {
                GenerateRowIndexDelete(parse, table, curId, null);
                v.AddOp2(OP.Delete, curId, (count != 0 ? (int)OPFLAG.NCHANGE : 0));
                if (count != 0)
                    v.ChangeP4(-1, table.Name, Vdbe.P4T.TRANSIENT);
            }

            // Do any ON CASCADE, SET NULL or SET DEFAULT operations required to handle rows (possibly in other tables) that refer via a foreign key to the row just deleted.
            FKey.FkActions(parse, table, null, oldId);

            // Invoke AFTER DELETE trigger programs.
            sqlite3CodeRowTrigger(parse, trigger, TK.DELETE, null, TRIGGER.AFTER, table, oldId, onconf, label);

            // Jump here if the row had already been deleted before any BEFORE trigger programs were invoked. Or if a trigger program throws a RAISE(IGNORE) exception.
            v.ResolveLabel(label);
        }
Пример #2
0
        public static void DeleteFrom(Parse parse, SrcList tabList, Expr where_)
        {
            AuthContext sContext = new AuthContext(); // Authorization context
            Context ctx = parse.Ctx; // Main database structure
            if (parse.Errs != 0 || ctx.MallocFailed)
                goto delete_from_cleanup;
            Debug.Assert(tabList.Srcs == 1);

            // Locate the table which we want to delete.  This table has to be put in an SrcList structure because some of the subroutines we
            // will be calling are designed to work with multiple tables and expect an SrcList* parameter instead of just a Table* parameter.
            Table table = SrcList.Lookup(parse, tabList); // The table from which records will be deleted
            if (table == null) goto delete_from_cleanup;

            // Figure out if we have any triggers and if the table being deleted from is a view
#if !OMIT_TRIGGER
            int dummy;
            Trigger trigger = Triggers.Exist(parse, table, TK.DELETE, null, out dummy); // List of table triggers, if required
#if OMIT_VIEW
            const bool isView = false;
#else
            bool isView = (table.Select != null); // True if attempting to delete from a view
#endif
#else
            const Trigger trigger = null;
            bool isView = false;
#endif

            // If pTab is really a view, make sure it has been initialized.
            if (sqlite3ViewGetColumnNames(parse, table) != null || IsReadOnly(parse, table, (trigger != null)))
                goto delete_from_cleanup;
            int db = sqlite3SchemaToIndex(ctx, table.Schema); // Database number
            Debug.Assert(db < ctx.DBs.length);
            string dbName = ctx.DBs[db].Name; // Name of database holding pTab
            ARC rcauth = Auth.Check(parse, AUTH.DELETE, table.Name, 0, dbName); // Value returned by authorization callback
            Debug.Assert(rcauth == ARC.OK || rcauth == ARC.DENY || rcauth == ARC.IGNORE);
            if (rcauth == ARC.DENY)
                goto delete_from_cleanup;
            Debug.Assert(!isView || trigger != null);

            // Assign cursor number to the table and all its indices.
            Debug.Assert(tabList.Srcs == 1);
            int curId = tabList.Ids[0].Cursor = parse.Tabs++; // VDBE VdbeCursor number for pTab
            Index idx; // For looping over indices of the table
            for (idx = table.Index; idx != null; idx = idx.Next)
                parse.Tabs++;

            // Start the view context
            if (isView)
                Auth.ContextPush(parse, sContext, table.Name);

            // Begin generating code.
            Vdbe v = parse.GetVdbe(); // The virtual database engine 
            if (v == null)
                goto delete_from_cleanup;
            if (parse.Nested == 0) v.CountChanges();
            parse.BeginWriteOperation(1, db);

            // If we are trying to delete from a view, realize that view into a ephemeral table.
#if !OMIT_VIEW && !OMIT_TRIGGER
            if (isView)
                MaterializeView(parse, table, where_, curId);
#endif
            // Resolve the column names in the WHERE clause.
            NameContext sNC = new NameContext(); // Name context to resolve expressions in
            sNC.Parse = parse;
            sNC.SrcList = tabList;
            if (sqlite3ResolveExprNames(sNC, ref where_) != 0)
                goto delete_from_cleanup;

            // Initialize the counter of the number of rows deleted, if we are counting rows.
            int memCnt = -1; // Memory cell used for change counting
            if ((ctx.Flags & Context.FLAG.CountRows) != 0)
            {
                memCnt = ++parse.Mems;
                v.AddOp2(OP.Integer, 0, memCnt);
            }

#if !OMIT_TRUNCATE_OPTIMIZATION
            // Special case: A DELETE without a WHERE clause deletes everything. It is easier just to erase the whole table. Prior to version 3.6.5,
            // this optimization caused the row change count (the value returned by API function sqlite3_count_changes) to be set incorrectly.
            if (rcauth == ARC.OK && where_ == null && trigger == null && !IsVirtual(table) && !FKey.FkRequired(parse, table, null, 0))
            {
                Debug.Assert(!isView);
                v.AddOp4(OP.Clear, table.Id, db, memCnt, table.Name, Vdbe.P4T.STATIC);
                for (idx = table.Index; idx != null; idx = idx.Next)
                {
                    Debug.Assert(idx.Schema == table.Schema);
                    v.AddOp2(OP.Clear, idx.Id, db);
                }
            }
            else
#endif
            // The usual case: There is a WHERE clause so we have to scan through the table and pick which records to delete.
            {
                int rowSet = ++parse.Mems; // Register for rowset of rows to delete
                int rowid = ++parse.Mems; // Used for storing rowid values.

                // Collect rowids of every row to be deleted.
                v.AddOp2(OP.Null, 0, rowSet);
                ExprList dummy = null;
                WhereInfo winfo = Where.Begin(parse, tabList, where_, ref dummy, WHERE_DUPLICATES_OK, 0); // Information about the WHERE clause
                if (winfo == null) goto delete_from_cleanup;
                int regRowid = Expr.CodeGetColumn(parse, table, -1, curId, rowid); // Actual register containing rowids
                v.AddOp2(OP.RowSetAdd, rowSet, regRowid);
                if ((ctx.Flags & Context.FLAG.CountRows) != 0)
                    v.AddOp2(OP.AddImm, memCnt, 1);
                Where.End(winfo);

                // Delete every item whose key was written to the list during the database scan.  We have to delete items after the scan is complete
                // because deleting an item can change the scan order.
                int end = v.MakeLabel();

                // Unless this is a view, open cursors for the table we are deleting from and all its indices. If this is a view, then the
                // only effect this statement has is to fire the INSTEAD OF triggers.
                if (!isView)
                    sqlite3OpenTableAndIndices(parse, table, curId, OP.OpenWrite);
                int addr = v.AddOp3(OP.RowSetRead, rowSet, end, rowid);

                // Delete the row
#if !OMIT_VIRTUALTABLE
                if (IsVirtual(table))
                {
                    VTable vtable = VTable.GetVTable(ctx, table);
                    VTable.MakeWritable(parse, table);
                    v.AddOp4(OP.VUpdate, 0, 1, rowid, vtable, Vdbe.P4T.VTAB);
                    v.ChangeP5(OE.Abort);
                    sqlite3MayAbort(parse);
                }
                else
#endif
                {
                    int count = (parse.Nested == 0; // True to count changes
                    GenerateRowDelete(parse, table, curId, rowid, count, trigger, OE.Default);
                }

                // End of the delete loop
                v.AddOp2(OP.Goto, 0, addr);
                v.ResolveLabel(end);

                // Close the cursors open on the table and its indexes.
                if (!isView && !IsVirtual(table))
                {
                    for (int i = 1, idx = table.Index; idx != null; i++, idx = idx.Next)
                        v.AddOp2(OP.Close, curId + i, idx.Id);
                    v.AddOp1(OP.Close, curId);
                }
            }

            // Update the sqlite_sequence table by storing the content of the maximum rowid counter values recorded while inserting into
		    // autoincrement tables.
            if (parse.Nested == 0 && parse.TriggerTab == null)
                sqlite3AutoincrementEnd(parse);

            // Return the number of rows that were deleted. If this routine is generating code because of a call to sqlite3NestedParse(), do not
		    // invoke the callback function.
            if ((ctx.Flags & Context.FLAG.CountRows) != 0 && parse.Nested == 0 && parse.TriggerTab == null)
            {
                v.AddOp2(OP.ResultRow, memCnt, 1);
                v.SetNumCols(1);
                v.SetColName(0, COLNAME_NAME, "rows deleted", SQLITE_STATIC);
            }

        delete_from_cleanup:
            Auth.ContextPop(sContext);
            SrcList.Delete(ctx, ref tabList);
            Expr.Delete(ctx, ref where_);
            return;
        }
Пример #3
0
        static void AnalyzeOneTable(Parse parse, Table table, Index onlyIdx, int statCurId, int memId)
        {
            Context ctx = parse.Ctx;       // Database handle
            int     i;                     // Loop counter
            int     regTabname  = memId++; // Register containing table name
            int     regIdxname  = memId++; // Register containing index name
            int     regSampleno = memId++; // Register containing next sample number
            int     regCol      = memId++; // Content of a column analyzed table
            int     regRec      = memId++; // Register holding completed record
            int     regTemp     = memId++; // Temporary use register
            int     regRowid    = memId++; // Rowid for the inserted record

            Vdbe v = parse.GetVdbe();      // The virtual machine being built up

            if (v == null || C._NEVER(table == null))
            {
                return;
            }
            // Do not gather statistics on views or virtual tables or system tables
            if (table.Id == 0 || table.Name.StartsWith("sqlite_", StringComparison.OrdinalIgnoreCase))
            {
                return;
            }
            Debug.Assert(Btree.HoldsAllMutexes(ctx));
            int db = sqlite3SchemaToIndex(ctx, table.Schema); // Index of database containing pTab

            Debug.Assert(db >= 0);
            Debug.Assert(Btree.SchemaMutexHeld(ctx, db, null));
#if !OMIT_AUTHORIZATION
            if (Auth.Check(parse, AUTH.ANALYZE, table.Name, 0, ctx.DBs[db].Name))
            {
                return;
            }
#endif

            // Establish a read-lock on the table at the shared-cache level.
            sqlite3TableLock(parse, db, table.Id, 0, table.Name);

            int zeroRows = -1;                                         // Jump from here if number of rows is zero
            int idxCurId = parse.Tabs++;                               // Cursor open on index being analyzed
            v.AddOp4(OP.String8, 0, regTabname, 0, table.Name, 0);
            for (Index idx = table.Index; idx != null; idx = idx.Next) // An index to being analyzed
            {
                if (onlyIdx != null && onlyIdx != idx)
                {
                    continue;
                }
                v.NoopComment("Begin analysis of %s", idx.Name);
                int   cols      = idx.Columns.length;
                int[] chngAddrs = C._tagalloc <int>(ctx, cols); // Array of jump instruction addresses
                if (chngAddrs == null)
                {
                    continue;
                }
                KeyInfo key = sqlite3IndexKeyinfo(parse, idx);
                if (memId + 1 + (cols * 2) > parse.Mems)
                {
                    parse.Mems = memId + 1 + (cols * 2);
                }

                // Open a cursor to the index to be analyzed.
                Debug.Assert(db == sqlite3SchemaToIndex(ctx, idx.Schema));
                v.AddOp4(OP.OpenRead, idxCurId, idx.Id, db, key, Vdbe.P4T.KEYINFO_HANDOFF);
                v.VdbeComment("%s", idx.Name);

                // Populate the registers containing the index names.
                v.AddOp4(OP.String8, 0, regIdxname, 0, idx.Name, 0);

#if ENABLE_STAT3
                bool once = false; // One-time initialization
                if (once)
                {
                    once = false;
                    sqlite3OpenTable(parse, tabCurId, db, table, OP.OpenRead);
                }
                v.AddOp2(OP.Count, idxCurId, regCount);
                v.AddOp2(OP.Integer, STAT3_SAMPLES, regTemp1);
                v.AddOp2(OP.Integer, 0, regNumEq);
                v.AddOp2(OP.Integer, 0, regNumLt);
                v.AddOp2(OP.Integer, -1, regNumDLt);
                v.AddOp3(OP.Null, 0, regSample, regAccum);
                v.AddOp4(OP.Function, 1, regCount, regAccum, (object)_stat3InitFuncdef, Vdbe.P4T.FUNCDEF);
                v.ChangeP5(2);
#endif

                // The block of memory cells initialized here is used as follows.
                //
                //    iMem:
                //        The total number of rows in the table.
                //
                //    iMem+1 .. iMem+nCol:
                //        Number of distinct entries in index considering the left-most N columns only, where N is between 1 and nCol,
                //        inclusive.
                //
                //    iMem+nCol+1 .. Mem+2*nCol:
                //        Previous value of indexed columns, from left to right.
                //
                // Cells iMem through iMem+nCol are initialized to 0. The others are initialized to contain an SQL NULL.
                for (i = 0; i <= cols; i++)
                {
                    v.AddOp2(OP.Integer, 0, memId + i);
                }
                for (i = 0; i < cols; i++)
                {
                    v.AddOp2(OP.Null, 0, memId + cols + i + 1);
                }

                // Start the analysis loop. This loop runs through all the entries in the index b-tree.
                int endOfLoop = v.MakeLabel();   // The end of the loop
                v.AddOp2(OP.Rewind, idxCurId, endOfLoop);
                int topOfLoop = v.CurrentAddr(); // The top of the loop
                v.AddOp2(OP.AddImm, memId, 1);

                int addrIfNot = 0; // address of OP_IfNot
                for (i = 0; i < cols; i++)
                {
                    v.AddOp3(OP.Column, idxCurId, i, regCol);
                    if (i == 0) // Always record the very first row
                    {
                        addrIfNot = v.AddOp1(OP.IfNot, memId + 1);
                    }
                    Debug.Assert(idx.CollNames != null && idx.CollNames[i] != null);
                    CollSeq coll = sqlite3LocateCollSeq(parse, idx.CollNames[i]);
                    chngAddrs[i] = v.AddOp4(OP.Ne, regCol, 0, memId + cols + i + 1, coll, Vdbe.P4T.COLLSEQ);
                    v.ChangeP5(SQLITE_NULLEQ);
                    v.VdbeComment("jump if column %d changed", i);
#if ENABLE_STAT3
                    if (i == 0)
                    {
                        v.AddOp2(OP.AddImm, regNumEq, 1);
                        v.VdbeComment("incr repeat count");
                    }
#endif
                }
                v.AddOp2(OP.Goto, 0, endOfLoop);
                for (i = 0; i < cols; i++)
                {
                    v.JumpHere(chngAddrs[i]); // Set jump dest for the OP_Ne
                    if (i == 0)
                    {
                        v.JumpHere(addrIfNot); // Jump dest for OP_IfNot
#if ENABLE_STAT3
                        v.AddOp4(OP.Function, 1, regNumEq, regTemp2, (object)Stat3PushFuncdef, Vdbe.P4T.FUNCDEF);
                        v.ChangeP5(5);
                        v.AddOp3(OP.Column, idxCurId, idx.Columns.length, regRowid);
                        v.AddOp3(OP.Add, regNumEq, regNumLt, regNumLt);
                        v.AddOp2(OP.AddImm, regNumDLt, 1);
                        v.AddOp2(OP.Integer, 1, regNumEq);
#endif
                    }
                    v.AddOp2(OP.AddImm, memId + i + 1, 1);
                    v.AddOp3(OP.Column, idxCurId, i, memId + cols + i + 1);
                }
                C._tagfree(ctx, chngAddrs);

                // Always jump here after updating the iMem+1...iMem+1+nCol counters
                v.ResolveLabel(endOfLoop);

                v.AddOp2(OP.Next, idxCurId, topOfLoop);
                v.AddOp1(OP.Close, idxCurId);
#if ENABLE_STAT3
                v.AddOp4(OP.Function, 1, regNumEq, regTemp2, (object)Stat3PushFuncdef, Vdbe.P4T.FUNCDEF);
                v.ChangeP5(5);
                v.AddOp2(OP.Integer, -1, regLoop);
                int shortJump = v.AddOp2(OP_AddImm, regLoop, 1); // Instruction address
                v.AddOp4(OP.Function, 1, regAccum, regTemp1, (object)Stat3GetFuncdef, Vdbe.P4T.FUNCDEF);
                v.ChangeP5(2);
                v.AddOp1(OP.IsNull, regTemp1);
                v.AddOp3(OP.NotExists, tabCurId, shortJump, regTemp1);
                v.AddOp3(OP.Column, tabCurId, idx->Columns[0], regSample);
                sqlite3ColumnDefault(v, table, idx->Columns[0], regSample);
                v.AddOp4(OP.Function, 1, regAccum, regNumEq, (object)Stat3GetFuncdef, Vdbe.P4T.FUNCDEF);
                v.ChangeP5(3);
                v.AddOp4(OP.Function, 1, regAccum, regNumLt, (object)Stat3GetFuncdef, Vdbe.P4T.FUNCDEF);
                v.ChangeP5(4);
                v.AddOp4(OP.Function, 1, regAccum, regNumDLt, (object)Stat3GetFuncdef, Vdbe.P4T.FUNCDEF);
                v.ChangeP5(5);
                v.AddOp4(OP.MakeRecord, regTabname, 6, regRec, "bbbbbb", 0);
                v.AddOp2(OP.NewRowid, statCurId + 1, regNewRowid);
                v.AddOp3(OP.Insert, statCurId + 1, regRec, regNewRowid);
                v.AddOp2(OP.Goto, 0, shortJump);
                v.JumpHere(shortJump + 2);
#endif

                // Store the results in sqlite_stat1.
                //
                // The result is a single row of the sqlite_stat1 table.  The first two columns are the names of the table and index.  The third column
                // is a string composed of a list of integer statistics about the index.  The first integer in the list is the total number of entries
                // in the index.  There is one additional integer in the list for each column of the table.  This additional integer is a guess of how many
                // rows of the table the index will select.  If D is the count of distinct values and K is the total number of rows, then the integer is computed
                // as:
                //        I = (K+D-1)/D
                // If K==0 then no entry is made into the sqlite_stat1 table.
                // If K>0 then it is always the case the D>0 so division by zero is never possible.
                v.AddOp2(OP_SCopy, memId, regStat1);
                if (zeroRows < 0)
                {
                    zeroRows = v.AddOp1(OP.IfNot, memId);
                }
                for (i = 0; i < cols; i++)
                {
                    v.AddOp4(OP.String8, 0, regTemp, 0, " ", 0);
                    v.AddOp3(OP.Concat, regTemp, regStat1, regStat1);
                    v.AddOp3(OP.Add, memId, memId + i + 1, regTemp);
                    v.AddOp2(OP.AddImm, regTemp, -1);
                    v.AddOp3(OP.Divide, memId + i + 1, regTemp, regTemp);
                    v.AddOp1(OP.ToInt, regTemp);
                    v.AddOp3(OP.Concat, regTemp, regStat1, regStat1);
                }
                v.AddOp4(OP.MakeRecord, regTabname, 3, regRec, "aaa", 0);
                v.AddOp2(OP.NewRowid, statCurId, regNewRowid);
                v.AddOp3(OP.Insert, statCurId, regRec, regNewRowid);
                v.ChangeP5(OPFLAG_APPEND);
            }

            // If the table has no indices, create a single sqlite_stat1 entry containing NULL as the index name and the row count as the content.
            if (table.Index == null)
            {
                v.AddOp3(OP.OpenRead, idxCurId, table->Id, db);
                v.VdbeComment("%s", table->Name);
                v.AddOp2(OP.Count, idxCurId, regStat1);
                v.AddOp1(OP.Close, idxCurId);
                zeroRows = v.AddOp1(OP.IfNot, regStat1);
            }
            else
            {
                v.JumpHere(zeroRows);
                zeroRows = v.AddOp0(OP_Goto);
            }
            v.AddOp2(OP.Null, 0, regIdxname);
            v.AddOp4(OP.MakeRecord, regTabname, 3, regRec, "aaa", 0);
            v.AddOp2(OP.NewRowid, statCurId, regNewRowid);
            v.AddOp3(OP.Insert, statCurId, regRec, regNewRowid);
            v.ChangeP5(OPFLAG_APPEND);
            if (parse.Mems < regRec)
            {
                parse.Mems = regRec;
            }
            v.JumpHere(zeroRows);
        }