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); }
static void OpenStatTable(Parse parse, int db, int statCur, string where_, string whereType) { int[] roots = new int[] { 0, 0 }; byte[] createTbls = new byte[] { 0, 0 }; Context ctx = parse.Ctx; Vdbe v = parse.GetVdbe(); if (v == null) { return; } Debug.Assert(Btree.HoldsAllMutexes(ctx)); Debug.Assert(v.Ctx == ctx); Context.DB dbObj = ctx.DBs[db]; for (int i = 0; i < _tables.Length; i++) { string tableName = _tables[i].Name; Table stat; if ((stat = Parse.FindTable(ctx, tableName, dbObj.Name)) == null) { // The sqlite_stat[12] table does not exist. Create it. Note that a side-effect of the CREATE TABLE statement is to leave the rootpage // of the new table in register pParse.regRoot. This is important because the OpenWrite opcode below will be needing it. parse.NestedParse("CREATE TABLE %Q.%s(%s)", dbObj.Name, tableName, _tables[i].Cols); roots[i] = parse.RegRoot; createTbls[i] = Vdbe::OPFLAG_P2ISREG; } else { // The table already exists. If zWhere is not NULL, delete all entries associated with the table zWhere. If zWhere is NULL, delete the // entire contents of the table. roots[i] = stat.Id; sqlite3TableLock(parse, db, roots[i], 1, tableName); if (where_ == null) { parse.NestedParse("DELETE FROM %Q.%s WHERE %s=%Q", dbObj.Name, tableName, whereType, where_); } else { v.AddOp2(OP.Clear, roots[i], db); // The sqlite_stat[12] table already exists. Delete all rows. } } } // Open the sqlite_stat[12] tables for writing. for (int i = 0; i < _tables.Length; i++) { v.AddOp3(OP.OpenWrite, statCur + i, roots[i], db); v.ChangeP4(-1, 3, Vdbe.P4T.INT32); v.ChangeP5(createTbls[i]); } }
static void CodeAttach(Parse parse, AUTH type, FuncDef func, Expr authArg, Expr filename, Expr dbName, Expr key) { Context ctx = parse.Ctx; NameContext sName; sName = new NameContext(); sName.Parse = parse; if (ResolveAttachExpr(sName, filename) != RC.OK || ResolveAttachExpr(sName, dbName) != RC.OK || ResolveAttachExpr(sName, key) != RC.OK) { parse.Errs++; goto attach_end; } #if !OMIT_AUTHORIZATION if (authArg != null) { string authArgToken = (authArg.OP == TK.STRING ? authArg.u.Token : null); ARC arc = Auth.Check(parse, type, authArgToken, null, null); if (arc != ARC.OK) { goto attach_end; } } #endif Vdbe v = parse.GetVdbe(); int regArgs = Expr.GetTempRange(parse, 4); Expr.Code(parse, filename, regArgs); Expr.Code(parse, dbName, regArgs + 1); Expr.Code(parse, key, regArgs + 2); Debug.Assert(v != null || ctx.MallocFailed); if (v != null) { v.AddOp3(OP.Function, 0, regArgs + 3 - func.Args, regArgs + 3); Debug.Assert(func.Args == -1 || (func.Args & 0xff) == func.Args); v.ChangeP5((byte)(func.Args)); v.ChangeP4(-1, func, Vdbe.P4T.FUNCDEF); // Code an OP_Expire. For an ATTACH statement, set P1 to true (expire this statement only). For DETACH, set it to false (expire all existing statements). v.AddOp1(OP.Expire, (type == AUTH.ATTACH ? 1 : 0)); } attach_end: Expr.Delete(ctx, ref filename); Expr.Delete(ctx, ref dbName); Expr.Delete(ctx, ref key); }
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; }
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); }