Пример #1
0
        static void ReloadTableSchema(Parse parse, Table table, string name)
        {
            Context ctx = parse.Ctx;
            string  where_;

#if !SQLITE_OMIT_TRIGGER
            Trigger trig;
#endif

            Vdbe v = parse.GetVdbe();
            if (C._NEVER(v == null))
            {
                return;
            }
            Debug.Assert(Btree.HoldsAllMutexes(ctx));
            int db = Prepare.SchemaToIndex(ctx, table.Schema); // Index of database containing pTab
            Debug.Assert(db >= 0);

#if !OMIT_TRIGGER
            // Drop any table triggers from the internal schema.
            for (trig = Trigger.List(parse, table); trig != null; trig = trig.Next)
            {
                int trigDb = Prepare.SchemaToIndex(ctx, trig.Schema);
                Debug.Assert(trigDb == db || trigDb == 1);
                v.AddOp4(OP.DropTrigger, trigDb, 0, 0, trig.Name, 0);
            }
#endif

            // Drop the table and index from the internal schema.
            v.AddOp4(OP.DropTable, db, 0, 0, table.Name, 0);

            // Reload the table, index and permanent trigger schemas.
            where_ = C._mtagprintf(ctx, "tbl_name=%Q", name);
            if (where_ == null)
            {
                return;
            }
            v.AddParseSchemaOp(db, where_);

#if !OMIT_TRIGGER
            // Now, if the table is not stored in the temp database, reload any temp triggers. Don't use IN(...) in case SQLITE_OMIT_SUBQUERY is defined.
            if ((where_ = WhereTempTriggers(parse, table)) != "")
            {
                v.AddParseSchemaOp(1, where_);
            }
#endif
        }
Пример #2
0
        static void UpdateVirtualTable(Parse parse, SrcList src, Table table, ExprList changes, Expr rowid, int[] xrefs, Expr where_, int onError)
        {
            int        i;
            Context    ctx    = parse.Ctx; // Database connection
            VTable     vtable = VTable.GetVTable(ctx, table);
            SelectDest dest   = new SelectDest();

            // Construct the SELECT statement that will find the new values for all updated rows.
            ExprList list = ExprList.Append(parse, 0, Expr.Expr(ctx, TK.ID, "_rowid_")); // The result set of the SELECT statement

            if (rowid != null)
            {
                list = ExprList.Append(parse, list, Expr.Dup(ctx, rowid, 0));
            }
            Debug.Assert(table.PKey < 0);
            for (i = 0; i < table.Cols.length; i++)
            {
                Expr expr = (xrefs[i] >= 0 ? Expr.Dup(ctx, changes.Ids[xrefs[i]].Expr, 0) : Expr.Expr(ctx, TK.ID, table.Cols[i].Name)); // Temporary expression
                list = ExprList.Append(parse, list, expr);
            }
            Select select = Select.New(parse, list, src, where_, null, null, null, 0, null, null); // The SELECT statement

            // Create the ephemeral table into which the update results will be stored.
            Vdbe v = parse.V; // Virtual machine under construction

            Debug.Assert(v != null);
            int ephemTab = parse.Tabs++; // Table holding the result of the SELECT

            v.AddOp2(OP.OpenEphemeral, ephemTab, table.Cols.length + 1 + (rowid != null ? 1 : 0));
            v.ChangeP5(BTREE_UNORDERED);

            // fill the ephemeral table
            Select.DestInit(dest, SRT.Table, ephemTab);
            Select.Select(parse, select, ref dest);

            // Generate code to scan the ephemeral table and call VUpdate.
            int regId = ++parse.Mems;// First register in set passed to OP_VUpdate

            parse.Mems += table.Cols.length + 1;
            int addr = v.AddOp2(OP.Rewind, ephemTab, 0); // Address of top of loop

            v.AddOp3(OP.Column, ephemTab, 0, regId);
            v.AddOp3(OP.Column, ephemTab, (rowid != null ? 1 : 0), regId + 1);
            for (i = 0; i < table.nCol; i++)
            {
                v.AddOp3(OP.Column, ephemTab, i + 1 + (rowid != null ? 1 : 0), regId + 2 + i);
            }
            sqlite3VtabMakeWritable(parse, table);
            v.AddOp4(OP_VUpdate, 0, table.Cols.length + 2, regId, vtable, P4_VTAB);
            v.ChangeP5((byte)(onError == OE_Default ? OE_Abort : onError));
            parse.MayAbort();
            v.AddOp2(OP.Next, ephemTab, addr + 1);
            v.JumpHere(addr);
            v.AddOp2(OP.Close, ephemTab, 0);

            // Cleanup
            Select.Delete(ctx, ref select);
        }
Пример #3
0
        public static void RenameTable(Parse parse, SrcList src, Token name)
        {
            Context ctx = parse.Ctx;               // Database connection

            Context.FLAG savedDbFlags = ctx.Flags; // Saved value of db->flags
            //if (C._NEVER(ctx.MallocFailed)) goto exit_rename_table;
            Debug.Assert(src.Srcs == 1);
            Debug.Assert(Btree.HoldsAllMutexes(ctx));

            Table table = parse.LocateTableItem(false, src.Ids[0]); // Table being renamed

            if (table == null)
            {
                goto exit_rename_table;
            }
            int    db     = Prepare.SchemaToIndex(ctx, table.Schema); // Database that contains the table
            string dbName = ctx.DBs[db].Name;                         // Name of database iDb

            ctx.Flags |= Context.FLAG.PreferBuiltin;

            // Get a NULL terminated version of the new table name.
            string nameAsString = Parse.NameFromToken(ctx, name); // NULL-terminated version of pName

            if (nameAsString == null)
            {
                goto exit_rename_table;
            }

            // Check that a table or index named 'zName' does not already exist in database iDb. If so, this is an error.
            if (Parse.FindTable(ctx, nameAsString, dbName) != null || Parse.FindIndex(ctx, nameAsString, dbName) != null)
            {
                parse.ErrorMsg("there is already another table or index with this name: %s", nameAsString);
                goto exit_rename_table;
            }

            // Make sure it is not a system table being altered, or a reserved name that the table is being renamed to.
            if (IsSystemTable(parse, table.Name) || parse->CheckObjectName(nameAsString) != RC.OK)
            {
                goto exit_rename_table;
            }

#if !OMIT_VIEW
            if (table.Select != null)
            {
                parse.ErrorMsg("view %s may not be altered", table.Name);
                goto exit_rename_table;
            }
#endif

#if !OMIT_AUTHORIZATION
            // Invoke the authorization callback.
            if (Auth.Check(parse, AUTH.ALTER_TABLE, dbName, table.Name, null))
            {
                goto exit_rename_table;
            }
#endif

            VTable vtable = null; // Non-zero if this is a v-tab with an xRename()
#if !OMIT_VIRTUALTABLE
            if (parse->ViewGetColumnNames(table) != 0)
            {
                goto exit_rename_table;
            }
            if (E.IsVirtual(table))
            {
                vtable = VTable.GetVTable(ctx, table);
                if (vtable.IVTable.IModule.Rename == null)
                {
                    vtable = null;
                }
            }
#endif

            // Begin a transaction and code the VerifyCookie for database iDb. Then modify the schema cookie (since the ALTER TABLE modifies the
            // schema). Open a statement transaction if the table is a virtual table.
            Vdbe v = parse.GetVdbe();
            if (v == null)
            {
                goto exit_rename_table;
            }
            parse.BeginWriteOperation((vtable != null ? 1 : 0), db);
            parse.ChangeCookie(db);

            // If this is a virtual table, invoke the xRename() function if one is defined. The xRename() callback will modify the names
            // of any resources used by the v-table implementation (including other SQLite tables) that are identified by the name of the virtual table.
#if  !OMIT_VIRTUALTABLE
            if (vtable != null)
            {
                int i = ++parse.Mems;
                v.AddOp4(OP.String8, 0, i, 0, nameAsString, 0);
                v.AddOp4(OP.VRename, i, 0, 0, vtable, Vdbe.P4T.VTAB);
                parse.MayAbort();
            }
#endif

            // figure out how many UTF-8 characters are in zName
            string tableName       = table.Name;                       // Original name of the table
            int    tableNameLength = C._utf8charlength(tableName, -1); // Number of UTF-8 characters in zTabName

#if !OMIT_TRIGGER
            string where_ = string.Empty; // Where clause to locate temp triggers
#endif

#if !OMIT_FOREIGN_KEY && !OMIT_TRIGGER
            if ((ctx.Flags & Context.FLAG.ForeignKeys) != 0)
            {
                // If foreign-key support is enabled, rewrite the CREATE TABLE statements corresponding to all child tables of foreign key constraints
                // for which the renamed table is the parent table.
                if ((where_ = WhereForeignKeys(parse, table)) != null)
                {
                    parse.NestedParse(
                        "UPDATE \"%w\".%s SET " +
                        "sql = sqlite_rename_parent(sql, %Q, %Q) " +
                        "WHERE %s;", dbName, E.SCHEMA_TABLE(db), tableName, nameAsString, where_);
                    C._tagfree(ctx, ref where_);
                }
            }
#endif

            // Modify the sqlite_master table to use the new table name.
            parse.NestedParse(
                "UPDATE %Q.%s SET " +
#if OMIT_TRIGGER
                "sql = sqlite_rename_table(sql, %Q), " +
#else
                "sql = CASE " +
                "WHEN type = 'trigger' THEN sqlite_rename_trigger(sql, %Q)" +
                "ELSE sqlite_rename_table(sql, %Q) END, " +
#endif
                "tbl_name = %Q, " +
                "name = CASE " +
                "WHEN type='table' THEN %Q " +
                "WHEN name LIKE 'sqlite_autoindex%%' AND type='index' THEN " +
                "'sqlite_autoindex_' || %Q || substr(name,%d+18) " +
                "ELSE name END " +
                "WHERE tbl_name=%Q AND " +
                "(type='table' OR type='index' OR type='trigger');",
                dbName, SCHEMA_TABLE(db), nameAsString, nameAsString, nameAsString,
#if !OMIT_TRIGGER
                nameAsString,
#endif
                nameAsString, tableNameLength, tableName);

#if !OMIT_AUTOINCREMENT
            // If the sqlite_sequence table exists in this database, then update it with the new table name.
            if (Parse.FindTable(ctx, "sqlite_sequence", dbName) != null)
            {
                parse.NestedParse(
                    "UPDATE \"%w\".sqlite_sequence set name = %Q WHERE name = %Q",
                    dbName, nameAsString, table.Name);
            }
#endif

#if !OMIT_TRIGGER
            // If there are TEMP triggers on this table, modify the sqlite_temp_master table. Don't do this if the table being ALTERed is itself located in the temp database.
            if ((where_ = WhereTempTriggers(parse, table)) != "")
            {
                parse.NestedParse(
                    "UPDATE sqlite_temp_master SET " +
                    "sql = sqlite_rename_trigger(sql, %Q), " +
                    "tbl_name = %Q " +
                    "WHERE %s;", nameAsString, nameAsString, where_);
                C._tagfree(ctx, ref where_);
            }
#endif

#if !(OMIT_FOREIGN_KEY) && !(OMIT_TRIGGER)
            if ((ctx.Flags & Context.FLAG.ForeignKeys) != 0)
            {
                for (FKey p = Parse.FkReferences(table); p != null; p = p.NextTo)
                {
                    Table from = p.From;
                    if (from != table)
                    {
                        ReloadTableSchema(parse, p.From, from.Name);
                    }
                }
            }
#endif

            // Drop and reload the internal table schema.
            ReloadTableSchema(parse, table, nameAsString);

exit_rename_table:
            Expr.SrcListDelete(ctx, ref src);
            C._tagfree(ctx, ref nameAsString);
            ctx.Flags = savedDbFlags;
        }
Пример #4
0
        public static void sqlite3Update(Parse parse, SrcList tabList, ExprList changes, Expr where_, OE onError)
        {
            int i, j;                                 // Loop counters

            AuthContext sContext = new AuthContext(); // The authorization context
            Context     ctx      = parse.Ctx;         // The database structure

            if (parse.Errs != 0 || ctx.MallocFailed)
            {
                goto update_cleanup;
            }
            Debug.Assert(tabList.Srcs == 1);

            // Locate the table which we want to update.
            Table table = sqlite3SrcListLookup(parse, tabList); // The table to be updated

            if (table == null)
            {
                goto update_cleanup;
            }
            int db = sqlite3SchemaToIndex(ctx, table.Schema); // Database containing the table being updated

            // Figure out if we have any triggers and if the table being updated is a view.
#if !OMIT_TRIGGER
            int     tmask   = 0;                                                                 // Mask of TRIGGER_BEFORE|TRIGGER_AFTER
            Trigger trigger = sqlite3TriggersExist(parse, table, TK.UPDATE, changes, out tmask); // List of triggers on pTab, if required
#if OMIT_VIEW
            const bool isView = false;
#else
            bool isView = (table.Select != null); // True when updating a view (INSTEAD OF trigger)
#endif
            Debug.Assert(trigger != null || tmask == 0);
#else
            const Trigger trigger = null;
            const int     tmask   = 0;
            const bool    isView  = false;
#endif

            if (sqlite3ViewGetColumnNames(parse, table) != 0 || sqlite3IsReadOnly(parse, table, tmask))
            {
                goto update_cleanup;
            }

            int[] xrefs = new int[table.Cols.length]; // xrefs[i] is the index in pChanges->a[] of the an expression for the i-th column of the table. xrefs[i]==-1 if the i-th column is not changed.
            if (xrefs == null)
            {
                goto update_cleanup;
            }
            for (i = 0; i < table.Cols.length; i++)
            {
                xrefs[i] = -1;
            }

            // Allocate a cursors for the main database table and for all indices. The index cursors might not be used, but if they are used they
            // need to occur right after the database cursor.  So go ahead and allocate enough space, just in case.
            int curId; // VDBE Cursor number of pTab
            tabList.Ids[0].Cursor = curId = parse.Tabs++;
            Index idx; // For looping over indices
            for (idx = table.Index; idx != null; idx = idx.Next)
            {
                parse.Tabs++;
            }

            // Initialize the name-context
            NameContext sNC = new NameContext(); // The name-context to resolve expressions in
            sNC.Parse   = parse;
            sNC.SrcList = tabList;

            // Resolve the column names in all the expressions of the of the UPDATE statement.  Also find the column index
            // for each column to be updated in the pChanges array.  For each column to be updated, make sure we have authorization to change that column.
            bool chngRowid = false; // True if the record number is being changed
            Expr rowidExpr = null;  // Expression defining the new record number
            for (i = 0; i < changes.Exprs; i++)
            {
                if (sqlite3ResolveExprNames(sNC, ref changes.Ids[i].Expr) != 0)
                {
                    goto update_cleanup;
                }
                for (j = 0; j < table.Cols.length; j++)
                {
                    if (string.Equals(table.Cols[j].Name, changes.Ids[i].Name, StringComparison.OrdinalIgnoreCase))
                    {
                        if (j == table.PKey)
                        {
                            chngRowid = true;
                            rowidExpr = changes.Ids[i].Expr;
                        }
                        xrefs[j] = i;
                        break;
                    }
                }
                if (j >= table.Cols.length)
                {
                    if (Expr::IsRowid(changes.Ids[i].Name))
                    {
                        chngRowid = true;
                        rowidExpr = changes.Ids[i].Expr;
                    }
                    else
                    {
                        parse.ErrorMsg("no such column: %s", changes.Ids[i].Name);
                        parse.CheckSchema = 1;
                        goto update_cleanup;
                    }
                }
#if !OMIT_AUTHORIZATION
                {
                    ARC rc = Auth.Check(parse, AUTH.UPDATE, table.Name, table.Cols[j].Name, ctx.DBs[db].Name);
                    if (rc == ARC.DENY)
                    {
                        goto update_cleanup;
                    }
                    else if (rc == ARC.IGNORE)
                    {
                        xrefs[j] = -1;
                    }
                }
#endif
            }

            bool hasFK = sqlite3FkRequired(parse, table, xrefs, chngRowid ? 1 : 0); // True if foreign key processing is required

            // Allocate memory for the array aRegIdx[].  There is one entry in the array for each index associated with table being updated.  Fill in
            // the value with a register number for indices that are to be used and with zero for unused indices.
            int idxLength;                   // Number of indices that need updating
            for (idxLength = 0, idx = table.Index; idx != null; idx = idx.Next, idxLength++)
            {
                ;
            }
            int[] regIdxs = null; // One register assigned to each index to be updated
            if (idxLength > 0)
            {
                regIdxs = new int[idxLength];
                if (regIdxs == null)
                {
                    goto update_cleanup;
                }
            }
            for (j = 0, idx = table.Index; idx != null; idx = idx.Next, j++)
            {
                int regId;
                if (hasFK || chngRowid)
                {
                    regId = ++parse.Mems;
                }
                else
                {
                    regId = 0;
                    for (i = 0; i < idx.Columns.length; i++)
                    {
                        if (xrefs[idx.Columns[i]] >= 0)
                        {
                            regId = ++parse.Mems;
                            break;
                        }
                    }
                }
                regIdxs[j] = regId;
            }

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

#if !OMIT_VIRTUALTABLE
            // Virtual tables must be handled separately
            if (IsVirtual(table))
            {
                UpdateVirtualTable(parse, tabList, table, changes, rowidExpr, xrefs, where_, onError);
                where_  = null;
                tabList = null;
                goto update_cleanup;
            }
#endif

            // Register Allocations
            int regRowCount = 0;         // A count of rows changed
            int regOldRowid;             // The old rowid
            int regNewRowid;             // The new rowid
            int regNew;
            int regOld    = 0;
            int regRowSet = 0;           // Rowset of rows to be updated

            // Allocate required registers.
            regOldRowid = regNewRowid = ++parse.Mems;
            if (trigger != null || hasFK)
            {
                regOld      = parse.Mems + 1;
                parse.Mems += table.Cols.length;
            }
            if (chngRowid || trigger != null || hasFK)
            {
                regNewRowid = ++parse.Mems;
            }
            regNew      = parse.Mems + 1;
            parse.Mems += table.Cols.length;

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

            // If we are trying to update a view, realize that view into a ephemeral table.
#if !OMIT_VIEW && !OMIT_TRIGGER
            if (isView)
            {
                sqlite3MaterializeView(parse, table, where_, curId);
            }
#endif

            // Resolve the column names in all the expressions in the WHERE clause.
            if (sqlite3ResolveExprNames(sNC, ref where_) != 0)
            {
                goto update_cleanup;
            }

            // Begin the database scan
            v.AddOp2(OP.Null, 0, regOldRowid);
            ExprList  dummy = null;
            WhereInfo winfo = Where.Begin(parse, tabList, where_, ref dummy, WHERE.ONEPASS_DESIRED); // Information about the WHERE clause
            if (winfo == null)
            {
                goto update_cleanup;
            }
            bool okOnePass = winfo.OkOnePass; // True for one-pass algorithm without the FIFO

            // Remember the rowid of every item to be updated.
            v.AddOp2(OP.Rowid, curId, regOldRowid);
            if (!okOnePass)
            {
                v.AddOp2(OP.RowSetAdd, regRowSet, regOldRowid);
            }

            // End the database scan loop.
            Where.End(winfo);

            // Initialize the count of updated rows
            if ((ctx.Flags & Context.FLAG.CountRows) != 0 && parse.TriggerTab == null)
            {
                regRowCount = ++parse.Mems;
                v.AddOp2(OP.Integer, 0, regRowCount);
            }

            bool openAll = false; // True if all indices need to be opened
            if (!isView)
            {
                // Open every index that needs updating.  Note that if any index could potentially invoke a REPLACE conflict resolution
                // action, then we need to open all indices because we might need to be deleting some records.
                if (!okOnePass)
                {
                    sqlite3OpenTable(parse, curId, db, table, OP.OpenWrite);
                }
                if (onError == OE.Replace)
                {
                    openAll = true;
                }
                else
                {
                    openAll = false;
                    for (idx = table.Index; idx != null; idx = idx.Next)
                    {
                        if (idx.OnError == OE.Replace)
                        {
                            openAll = true;
                            break;
                        }
                    }
                }
                for (i = 0, idx = table.Index; idx != null; idx = idx.Next, i++)
                {
                    if (openAll || regIdxs[i] > 0)
                    {
                        KeyInfo key = sqlite3IndexKeyinfo(parse, idx);
                        v.AddOp4(OP.OpenWrite, curId + i + 1, idx.Id, db, key, Vdbe.P4T.KEYINFO_HANDOFF);
                        Debug.Assert(parse.Tabs > curId + i + 1);
                    }
                }
            }

            // Top of the update loop
            int addr = 0; // VDBE instruction address of the start of the loop
            if (okOnePass)
            {
                int a1 = v.AddOp1(OP.NotNull, regOldRowid);
                addr = v.AddOp0(OP.Goto);
                v.JumpHere(a1);
            }
            else
            {
                addr = v.AddOp3(OP.RowSetRead, regRowSet, 0, regOldRowid);
            }

            // Make cursor iCur point to the record that is being updated. If this record does not exist for some reason (deleted by a trigger,
            // for example, then jump to the next iteration of the RowSet loop.
            v.AddOp3(OP.NotExists, curId, addr, regOldRowid);

            // If the record number will change, set register regNewRowid to contain the new value. If the record number is not being modified,
            // then regNewRowid is the same register as regOldRowid, which is already populated.
            Debug.Assert(chngRowid || trigger != null || hasFK || regOldRowid == regNewRowid);
            if (chngRowid)
            {
                Expr.Code(parse, rowidExpr, regNewRowid);
                v.AddOp1(OP.MustBeInt, regNewRowid);
            }

            // If there are triggers on this table, populate an array of registers with the required old.* column data.
            if (hasFK || trigger != null)
            {
                uint oldmask = (hasFK ? sqlite3FkOldmask(parse, table) : 0);
                oldmask |= sqlite3TriggerColmask(parse, trigger, changes, 0, TRIGGER_BEFORE | TRIGGER_AFTER, table, onError);
                for (i = 0; i < table.Cols.length; i++)
                {
                    if (xrefs[i] < 0 || oldmask == 0xffffffff || (i < 32 && 0 != (oldmask & (1 << i))))
                    {
                        Expr.CodeGetColumnOfTable(v, table, curId, i, regOld + i);
                    }
                    else
                    {
                        v.AddOp2(OP.Null, 0, regOld + i);
                    }
                }
                if (!chngRowid)
                {
                    v.AddOp2(OP.Copy, regOldRowid, regNewRowid);
                }
            }

            // Populate the array of registers beginning at regNew with the new row data. This array is used to check constaints, create the new
            // table and index records, and as the values for any new.* references made by triggers.
            //
            // If there are one or more BEFORE triggers, then do not populate the registers associated with columns that are (a) not modified by
            // this UPDATE statement and (b) not accessed by new.* references. The values for registers not modified by the UPDATE must be reloaded from
            // the database after the BEFORE triggers are fired anyway (as the trigger may have modified them). So not loading those that are not going to
            // be used eliminates some redundant opcodes.
            int newmask = (int)sqlite3TriggerColmask(parse, trigger, changes, 1, TRIGGER_BEFORE, table, onError); // Mask of NEW.* columns accessed by BEFORE triggers
            for (i = 0; i < table.Cols.length; i++)
            {
                if (i == table.PKey)
                {
                    //v.AddOp2(OP.Null, 0, regNew + i);
                }
                else
                {
                    j = xrefs[i];
                    if (j >= 0)
                    {
                        Expr.Code(parse, changes.Ids[j].Expr, regNew + i);
                    }
                    else if ((tmask & TRIGGER_BEFORE) == 0 || i > 31 || (newmask & (1 << i)) != 0)
                    {
                        // This branch loads the value of a column that will not be changed into a register. This is done if there are no BEFORE triggers, or
                        // if there are one or more BEFORE triggers that use this value via a new.* reference in a trigger program.
                        C.ASSERTCOVERAGE(i == 31);
                        C.ASSERTCOVERAGE(i == 32);
                        v.AddOp3(OP.Column, curId, i, regNew + i);
                        v.ColumnDefault(table, i, regNew + i);
                    }
                }
            }

            // Fire any BEFORE UPDATE triggers. This happens before constraints are verified. One could argue that this is wrong.
            if ((tmask & TRIGGER_BEFORE) != 0)
            {
                v.AddOp2(OP.Affinity, regNew, table.Cols.length);
                sqlite3TableAffinityStr(v, table);
                sqlite3CodeRowTrigger(parse, trigger, TK.UPDATE, changes, TRIGGER_BEFORE, table, regOldRowid, onError, addr);

                // The row-trigger may have deleted the row being updated. In this case, jump to the next row. No updates or AFTER triggers are
                // required. This behavior - what happens when the row being updated is deleted or renamed by a BEFORE trigger - is left undefined in the documentation.
                v.AddOp3(OP.NotExists, curId, addr, regOldRowid);

                // If it did not delete it, the row-trigger may still have modified some of the columns of the row being updated. Load the values for
                // all columns not modified by the update statement into their registers in case this has happened.
                for (i = 0; i < table.Cols.length; i++)
                {
                    if (xrefs[i] < 0 && i != table.PKey)
                    {
                        v.AddOp3(OP.Column, curId, i, regNew + i);
                        v.ColumnDefault(table, i, regNew + i);
                    }
                }
            }

            if (!isView)
            {
                // Do constraint checks.
                int dummy2;
                sqlite3GenerateConstraintChecks(parse, table, curId, regNewRowid, regIdxs, (chngRowid ? regOldRowid : 0), true, onError, addr, out dummy2);

                // Do FK constraint checks.
                if (hasFK)
                {
                    sqlite3FkCheck(parse, table, regOldRowid, 0);
                }

                // Delete the index entries associated with the current record.
                int j1 = v.AddOp3(OP.NotExists, curId, 0, regOldRowid); // Address of jump instruction
                sqlite3GenerateRowIndexDelete(parse, table, curId, regIdxs);

                // If changing the record number, delete the old record.
                if (hasFK || chngRowid)
                {
                    v.AddOp2(OP.Delete, curId, 0);
                }
                v.JumpHere(j1);

                if (hasFK)
                {
                    sqlite3FkCheck(parse, table, 0, regNewRowid);
                }

                // Insert the new index entries and the new record.
                sqlite3CompleteInsertion(parse, table, curId, regNewRowid, regIdxs, true, false, false);

                // 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 updated.
                if (hasFK)
                {
                    sqlite3FkActions(parse, table, changes, regOldRowid);
                }
            }

            // Increment the row counter
            if ((ctx.Flags & Context.FLAG.CountRows) != 0 && parse.TriggerTab == null)
            {
                v.AddOp2(OP.AddImm, regRowCount, 1);
            }

            sqlite3CodeRowTrigger(parse, trigger, TK.UPDATE, changes, TRIGGER_AFTER, table, regOldRowid, onError, addr);

            // Repeat the above with the next record to be updated, until all record selected by the WHERE clause have been updated.
            v.AddOp2(OP.Goto, 0, addr);
            v.JumpHere(addr);

            // Close all tables
            Debug.Assert(regIdxs != null);
            for (i = 0, idx = table.Index; idx != null; idx = idx.Next, i++)
            {
                if (openAll || regIdxs[i] > 0)
                {
                    v.AddOp2(OP.Close, curId + i + 1, 0);
                }
            }
            v.AddOp2(OP.Close, curId, 0);

            // 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 changed. 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.TriggerTab == null && parse.Nested == 0)
            {
                v.AddOp2(OP.ResultRow, regRowCount, 1);
                v.SetNumCols(1);
                v.SetColName(0, COLNAME_NAME, "rows updated", SQLITE_STATIC);
            }

update_cleanup:
#if !OMIT_AUTHORIZATION
            Auth.ContextPop(sContext);
#endif
            C._tagfree(ctx, ref regIdxs);
            C._tagfree(ctx, ref xrefs);
            SrcList.Delete(ctx, ref tabList);
            ExprList.Delete(ctx, ref changes);
            Expr.Delete(ctx, ref where_);
            return;
        }
Пример #5
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;
        }
Пример #6
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);
        }