public static void BeginParse(Parse parse, Token name1, Token name2, Token moduleName, bool ifNotExists) { parse.StartTable(name1, name2, false, false, true, false); Table table = parse.NewTable; // The new virtual table if (table == null) { return; } Debug.Assert(table.Index == null); Context ctx = parse.Ctx; // Database connection int db = Prepare.SchemaToIndex(ctx, table.Schema); // The database the table is being created in Debug.Assert(db >= 0); table.TabFlags |= TF.Virtual; table.ModuleArgs.length = 0; AddModuleArgument(ctx, table, Parse.NameFromToken(ctx, moduleName)); AddModuleArgument(ctx, table, null); AddModuleArgument(ctx, table, table.Name); parse.NameToken.length = (uint)parse.NameToken.data.Length; //: (int)(&moduleName[moduleName->length] - name1); #if !OMIT_AUTHORIZATION // Creating a virtual table invokes the authorization callback twice. The first invocation, to obtain permission to INSERT a row into the // sqlite_master table, has already been made by sqlite3StartTable(). The second call, to obtain permission to create the table, is made now. if (table.ModuleArgs.data != null) { Auth.Check(parse, AUTH.CREATE_VTABLE, table.Name, table.ModuleArgs[0], ctx.DBs[db].Name); } #endif }
public static void DropTriggerPtr(Parse parse, Trigger trigger) { Context ctx = parse.Ctx; int db = Prepare.SchemaToIndex(ctx, trigger.Schema); Debug.Assert(db >= 0 && db < ctx.DBs.length); Table table = TableOfTrigger(trigger); Debug.Assert(table != null); Debug.Assert(table.Schema == trigger.Schema || db == 1); #if !OMIT_AUTHORIZATION { AUTH code = AUTH.DROP_TRIGGER; string dbName = ctx.DBs[db].Name; string tableName = E.SCHEMA_TABLE(db); if (db == 1) { code = AUTH.DROP_TEMP_TRIGGER; } if (Auth.Check(parse, code, trigger.Name, table.Name, dbName) || Auth.Check(parse, AUTH.DELETE, tableName, null, dbName)) { return; } } #endif // Generate code to destroy the database record of the trigger. Debug.Assert(table != null); Vdbe v = parse.GetVdbe(); if (v != null) { parse.BeginWriteOperation(0, db); parse.OpenMasterTable(db); int base_ = v.AddOpList(_dropTrigger.Length, _dropTrigger); v.ChangeP4(base_ + 1, trigger.Name, Vdbe.P4T.TRANSIENT); v.ChangeP4(base_ + 4, "trigger", Vdbe.P4T.STATIC); parse.ChangeCookie(db); v.AddOp2(Core.OP.Close, 0, 0); v.AddOp4(Core.OP.DropTrigger, db, 0, 0, trigger.Name, 0); if (parse.Mems < 3) { parse.Mems = 3; } } }
static SrcList TargetSrcList(Parse parse, TriggerStep step) { Context ctx = parse.Ctx; SrcList src = Parse.SrcListAppend(parse.Ctx, null, step.Target, null); // SrcList to be returned if (src != null) { Debug.Assert(src.Srcs > 0); Debug.Assert(src.Ids != null); int db = Prepare.SchemaToIndex(ctx, step.Trig.Schema); // Index of the database to use if (db == 0 || db >= 2) { Debug.Assert(db < ctx.DBs.length); src.Ids[src.Srcs - 1].Database = ctx.DBs[db].Name; //: _tagstrdup(ctx, ctx->DBs[db].Name); } } return(src); }
public static void Read(Parse parse, Expr expr, Schema schema, SrcList tableList) { Context ctx = parse.Ctx; if (ctx.Auth == null) { return; } int db = Prepare.SchemaToIndex(ctx, schema);// The index of the database the expression refers to if (db < 0) { return; // An attempt to read a column out of a subquery or other temporary table. } Debug.Assert(expr.OP == TK.COLUMN || expr.OP == TK.TRIGGER); Table table = null; // The table being read if (expr.OP == TK.TRIGGER) { table = parse.TriggerTab; } else { Debug.Assert(tableList != null); for (int src = 0; C._ALWAYS(src < tableList.Srcs); src++) { if (expr.TableId == tableList.Ids[src].Cursor) { table = tableList.Ids[src].Table; break; } } } int col = expr.ColumnIdx; // Index of column in table if (C._NEVER(table == null)) { return; } string colName; // Name of the column of the table if (col >= 0) { Debug.Assert(col < table.Cols.length); colName = table.Cols[col].Name; } else if (table.PKey >= 0) { Debug.Assert(table.PKey < table.Cols.length); colName = table.Cols[table.PKey].Name; } else { colName = "ROWID"; } Debug.Assert(db >= 0 && db < ctx.DBs.length); if (ReadColumn(parse, table.Name, colName, db) == ARC.IGNORE) { expr.OP = TK.NULL; } }
public static void BeginTrigger(Parse parse, Token name1, Token name2, TK trTm, TK op, IdList columns, SrcList tableName, Expr when, bool isTemp, int noErr) { Context ctx = parse.Ctx; // The database connection Debug.Assert(name1 != null); // pName1.z might be NULL, but not pName1 itself Debug.Assert(name2 != null); Debug.Assert(op == TK.INSERT || op == TK.UPDATE || op == TK.DELETE); Debug.Assert(op > 0 && op < (TK)0xff); Trigger trigger = null; // The new trigger int db; // The database to store the trigger in Token name = null; // The unqualified db name if (isTemp) { // If TEMP was specified, then the trigger name may not be qualified. if (name2.length > 0) { parse.ErrorMsg("temporary trigger may not have qualified name"); goto trigger_cleanup; } db = 1; name = name1; } else { // Figure out the db that the the trigger will be created in db = parse.TwoPartName(name1, name2, ref name); if (db < 0) { goto trigger_cleanup; } } if (tableName == null || ctx.MallocFailed) { goto trigger_cleanup; } // A long-standing parser bug is that this syntax was allowed: // CREATE TRIGGER attached.demo AFTER INSERT ON attached.tab .... // ^^^^^^^^ // To maintain backwards compatibility, ignore the database name on pTableName if we are reparsing our of SQLITE_MASTER. if (ctx.Init.Busy && db != 1) { C._tagfree(ctx, ref tableName.Ids[0].Database); tableName.Ids[0].Database = null; } // If the trigger name was unqualified, and the table is a temp table, then set iDb to 1 to create the trigger in the temporary database. // If sqlite3SrcListLookup() returns 0, indicating the table does not exist, the error is caught by the block below. //? if (tableName == null) goto trigger_cleanup; Table table = Delete.SrcListLookup(parse, tableName); // Table that the trigger fires off of if (ctx.Init.Busy == null && name2.length == 0 && table != null && table.Schema == ctx.DBs[1].Schema) { db = 1; } // Ensure the table name matches database name and that the table exists if (ctx.MallocFailed) { goto trigger_cleanup; } Debug.Assert(tableName.Srcs == 1); DbFixer sFix = new DbFixer(); // State vector for the DB fixer if (sFix.FixInit(parse, db, "trigger", name) && sFix.FixSrcList(tableName)) { goto trigger_cleanup; } table = Delete.SrcListLookup(parse, tableName); if (table == null) { // The table does not exist. if (ctx.Init.DB == 1) { // Ticket #3810. // Normally, whenever a table is dropped, all associated triggers are dropped too. But if a TEMP trigger is created on a non-TEMP table // and the table is dropped by a different database connection, the trigger is not visible to the database connection that does the // drop so the trigger cannot be dropped. This results in an "orphaned trigger" - a trigger whose associated table is missing. ctx.Init.OrphanTrigger = true; } goto trigger_cleanup; } if (E.IsVirtual(table)) { parse.ErrorMsg("cannot create triggers on virtual tables"); goto trigger_cleanup; } // Check that the trigger name is not reserved and that no trigger of the specified name exists string nameAsString = Parse.NameFromToken(ctx, name); if (nameAsString == null || parse.CheckObjectName(nameAsString) != RC.OK) { goto trigger_cleanup; } Debug.Assert(Btree.SchemaMutexHeld(ctx, db, null)); if (ctx.DBs[db].Schema.TriggerHash.Find(nameAsString, nameAsString.Length, (Trigger)null) != null) { if (noErr == 0) { parse.ErrorMsg("trigger %T already exists", name); } else { Debug.Assert(!ctx.Init.Busy); parse.CodeVerifySchema(db); } goto trigger_cleanup; } // Do not create a trigger on a system table if (table.Name.StartsWith("sqlite_", StringComparison.InvariantCultureIgnoreCase)) { parse.ErrorMsg("cannot create trigger on system table"); parse.Errs++; goto trigger_cleanup; } // INSTEAD of triggers are only for views and views only support INSTEAD of triggers. if (table.Select != null && trTm != TK.INSTEAD) { parse.ErrorMsg("cannot create %s trigger on view: %S", (trTm == TK.BEFORE ? "BEFORE" : "AFTER"), tableName, 0); goto trigger_cleanup; } if (table.Select == null && trTm == TK.INSTEAD) { parse.ErrorMsg("cannot create INSTEAD OF trigger on table: %S", tableName, 0); goto trigger_cleanup; } #if !OMIT_AUTHORIZATION { int tabDb = Prepare.SchemaToIndex(ctx, table.Schema); // Index of the database holding pTab AUTH code = AUTH.CREATE_TRIGGER; string dbName = ctx.DBs[tabDb].Name; string dbTrigName = (isTemp ? ctx.DBs[1].Name : dbName); if (tabDb == 1 || isTemp) { code = AUTH.CREATE_TEMP_TRIGGER; } if (Auth.Check(parse, code, nameAsString, table.Name, dbTrigName) != 0 || Auth.Check(parse, AUTH.INSERT, E.SCHEMA_TABLE(tabDb), 0, dbName)) { goto trigger_cleanup; } } #endif // INSTEAD OF triggers can only appear on views and BEFORE triggers cannot appear on views. So we might as well translate every // INSTEAD OF trigger into a BEFORE trigger. It simplifies code elsewhere. if (trTm == TK.INSTEAD) { trTm = TK.BEFORE; } // Build the Trigger object trigger = new Trigger(); //: (Trigger *)_tagalloc(db, sizeof(Trigger), true); if (trigger == null) { goto trigger_cleanup; } trigger.Name = name; trigger.Table = tableName.Ids[0].Name; //: _tagstrdup(ctx, tableName->Ids[0].Name); trigger.Schema = ctx.DBs[db].Schema; trigger.TabSchema = table.Schema; trigger.OP = op; trigger.TRtm = (trTm == TK.BEFORE ? TRIGGER.BEFORE : TRIGGER.AFTER); trigger.When = Expr.Dup(db, when, E.EXPRDUP_REDUCE); trigger.Columns = Expr.IdListDup(ctx, columns); Debug.Assert(parse.NewTrigger == null); parse.NewTrigger = trigger; trigger_cleanup: C._tagfree(ctx, ref name); Expr.SrcListDelete(ctx, ref tableName); Expr.IdListDelete(ctx, ref columns); Expr.Delete(ctx, ref when); if (parse.NewTrigger == null) { DeleteTrigger(ctx, ref trigger); } else { Debug.Assert(parse.NewTrigger == trigger); } }
public static void FinishTrigger(Parse parse, TriggerStep stepList, Token all) { Trigger trig = parse.NewTrigger; // Trigger being finished Context ctx = parse.Ctx; // The database Token nameToken = new Token(); // Trigger name for error reporting parse.NewTrigger = null; if (C._NEVER(parse.Errs != 0) || trig == null) { goto triggerfinish_cleanup; } string name = trig.Name; // Name of trigger int db = Prepare.SchemaToIndex(parse.Ctx, trig.Schema); // Database containing the trigger trig.StepList = stepList; while (stepList != null) { stepList.Trig = trig; stepList = stepList.Next; } nameToken.data = trig.Name; nameToken.length = (uint)nameToken.data.Length; DbFixer sFix = new DbFixer(); // Fixer object if (sFix.FixInit(parse, db, "trigger", nameToken) && sFix.FixTriggerStep(trig.StepList)) { goto triggerfinish_cleanup; } // if we are not initializing, build the sqlite_master entry if (ctx.Init.Busy) { // Make an entry in the sqlite_master table Vdbe v = parse.GetVdbe(); if (v == null) { goto triggerfinish_cleanup; } parse.BeginWriteOperation(0, db); string z = all.data.Substring(0, (int)all.length); //: _tagstrndup(ctx, (char *)all->data, all->length); parse.NestedParse("INSERT INTO %Q.%s VALUES('trigger',%Q,%Q,0,'CREATE TRIGGER %q')", ctx.DBs[db].Name, E.SCHEMA_TABLE(db), name, trig.Table, z); C._tagfree(ctx, ref z); parse.ChangeCookie(db); v.AddParseSchemaOp(db, C._mtagprintf(ctx, "type='trigger' AND name='%q'", name)); } if (!ctx.Init.Busy) { Trigger link = trig; Debug.Assert(Btree.SchemaMutexHeld(ctx, db, null)); trig = ctx.DBs[db].Schema.TriggerHash.Insert(name, name.Length, trig); if (trig != null) { ctx.MallocFailed = true; } else if (link.Schema == link.TabSchema) { int tableLength = link.Table.Length; Table table = (Table)link.TabSchema.TableHash.Find(link.Table, tableLength, (Table)null); Debug.Assert(table != null); link.Next = table.Triggers; table.Triggers = link; } } triggerfinish_cleanup: DeleteTrigger(ctx, ref trig); Debug.Assert(parse.NewTrigger == null); DeleteTriggerStep(ctx, ref stepList); }
public static void FinishParse(Parse parse, Token end) { Table table = parse.NewTable; // The table being constructed Context ctx = parse.Ctx; // The database connection if (table == null) { return; } AddArgumentToVtab(parse); parse.Arg.data = null; if (table.ModuleArgs.length < 1) { return; } // If the CREATE VIRTUAL TABLE statement is being entered for the first time (in other words if the virtual table is actually being // created now instead of just being read out of sqlite_master) then do additional initialization work and store the statement text // in the sqlite_master table. if (!ctx.Init.Busy) { // Compute the complete text of the CREATE VIRTUAL TABLE statement if (end != null) { parse.NameToken.length = (uint)parse.NameToken.data.Length; //: (int)(end->data - parse->NameToken) + end->length; } string stmt = C._mtagprintf(ctx, "CREATE VIRTUAL TABLE %T", parse.NameToken); //.Z.Substring(0, parse.NameToken.length)); // A slot for the record has already been allocated in the SQLITE_MASTER table. We just need to update that slot with all // the information we've collected. // // The VM register number pParse->regRowid holds the rowid of an entry in the sqlite_master table tht was created for this vtab // by sqlite3StartTable(). int db = Prepare.SchemaToIndex(ctx, table.Schema); parse.NestedParse("UPDATE %Q.%s SET type='table', name=%Q, tbl_name=%Q, rootpage=0, sql=%Q WHERE rowid=#%d", ctx.DBs[db].Name, E.SCHEMA_TABLE(db), table.Name, table.Name, stmt, parse.RegRowid ); C._tagfree(ctx, ref stmt); Vdbe v = parse.GetVdbe(); parse.ChangeCookie(db); v.AddOp2(OP.Expire, 0, 0); string where_ = C._mtagprintf(ctx, "name='%q' AND type='table'", table.Name); v.AddParseSchemaOp(db, where_); v.AddOp4(OP.VCreate, db, 0, 0, table.Name, (Vdbe.P4T)table.Name.Length + 1); } // If we are rereading the sqlite_master table create the in-memory record of the table. The xConnect() method is not called until // the first time the virtual table is used in an SQL statement. This allows a schema that contains virtual tables to be loaded before // the required virtual table implementations are registered. else { Schema schema = table.Schema; string name = table.Name; int nameLength = name.Length; Debug.Assert(Btree.SchemaMutexHeld(ctx, 0, schema)); Table oldTable = schema.TableHash.Insert(name, nameLength, table); if (oldTable != null) { ctx.MallocFailed = true; Debug.Assert(table == oldTable); // Malloc must have failed inside HashInsert() return; } parse.NewTable = null; } }
public static RC Exec(Context ctx, string sql, Func <object, int, string[], string[], bool> callback, object arg, ref string errmsg) { RC rc = RC.OK; // Return code if (!SafetyCheckOk(ctx)) { return(SysEx.MISUSE_BKPT()); } if (sql == null) { sql = string.Empty; } MutexEx.Enter(ctx.Mutex); Error(ctx, RC.OK, null); Vdbe stmt = null; // The current SQL statement int retrys = 0; // Number of retry attempts string[] colsNames = null; // Names of result columns while ((rc == RC.OK || (rc == RC.SCHEMA && (++retrys) < 2)) && sql.Length > 0) { stmt = null; string leftover = null; // Tail of unprocessed SQL rc = Prepare.Prepare_(ctx, sql, -1, ref stmt, ref leftover); Debug.Assert(rc == RC.OK || stmt == null); if (rc != RC.OK) { continue; } if (stmt == null) { sql = leftover; // this happens for a comment or white-space continue; } bool callbackIsInit = false; // True if callback data is initialized int cols = Vdbe.Column_Count(stmt); while (true) { rc = stmt.Step(); // Invoke the callback function if required int i; if (callback != null && (rc == RC.ROW || (rc == RC.DONE && !callbackIsInit && (ctx.Flags & Context.FLAG.NullCallback) != 0))) { if (!callbackIsInit) { colsNames = new string[cols]; if (colsNames == null) { goto exec_out; } for (i = 0; i < cols; i++) { colsNames[i] = Vdbe.Column_Name(stmt, i); // Vdbe::SetColName() installs column names as UTF8 strings so there is no way for sqlite3_column_name() to fail. Debug.Assert(colsNames[i] != null); } callbackIsInit = true; } string[] colsValues = null; if (rc == RC.ROW) { colsValues = new string[cols]; for (i = 0; i < cols; i++) { colsValues[i] = Vdbe.Column_Text(stmt, i); if (colsValues[i] == null && Vdbe.Column_Type(stmt, i) != TYPE.NULL) { ctx.MallocFailed = true; goto exec_out; } } } if (callback(arg, cols, colsValues, colsNames)) { rc = RC.ABORT; stmt.Finalize(); stmt = null; Error(ctx, RC.ABORT, null); goto exec_out; } } if (rc != RC.ROW) { rc = stmt.Finalize(); stmt = null; if (rc != RC.SCHEMA) { retrys = 0; if ((sql = leftover) != string.Empty) { int idx = 0; while (idx < sql.Length && char.IsWhiteSpace(sql[idx])) { idx++; } if (idx != 0) { sql = (idx < sql.Length ? sql.Substring(idx) : string.Empty); } } } break; } } C._tagfree(ctx, ref colsNames); colsNames = null; } exec_out: if (stmt != null) { stmt.Finalize(); } C._tagfree(ctx, ref colsNames); rc = ApiExit(ctx, rc); if (rc != RC.OK && C._ALWAYS(rc == ErrCode(ctx)) && errmsg != null) { errmsg = ErrMsg(ctx); } else if (errmsg != null) { errmsg = null; } Debug.Assert((rc & (RC)ctx.ErrMask) == rc); MutexEx.Leave(ctx.Mutex); return(rc); }