Esempio n. 1
0
        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
        }
Esempio n. 2
0
        public static void DropTriggerPtr(Parse parse, Trigger trigger)
        {
            Context ctx = parse.Ctx;
            int     db  = Prepare.SchemaToIndex(ctx, trigger.Schema);

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

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

            // Generate code to destroy the database record of the trigger.
            Debug.Assert(table != null);
            Vdbe v = parse.GetVdbe();
            if (v != null)
            {
                parse.BeginWriteOperation(0, db);
                parse.OpenMasterTable(db);
                int base_ = v.AddOpList(_dropTrigger.Length, _dropTrigger);
                v.ChangeP4(base_ + 1, trigger.Name, Vdbe.P4T.TRANSIENT);
                v.ChangeP4(base_ + 4, "trigger", Vdbe.P4T.STATIC);
                parse.ChangeCookie(db);
                v.AddOp2(Core.OP.Close, 0, 0);
                v.AddOp4(Core.OP.DropTrigger, db, 0, 0, trigger.Name, 0);
                if (parse.Mems < 3)
                {
                    parse.Mems = 3;
                }
            }
        }
Esempio n. 3
0
        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);
        }
Esempio n. 4
0
        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;
            }
        }
Esempio n. 5
0
        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);
            }
        }
Esempio n. 6
0
        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);
        }
Esempio n. 7
0
        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;
            }
        }
Esempio n. 8
0
        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);
        }