public static RC CallConnect(Parse parse, Table table) { Debug.Assert(table != null); Context ctx = parse.Ctx; if ((table.TabFlags & TF.Virtual) == 0 || GetVTable(ctx, table) != null) { return(RC.OK); } // Locate the required virtual table module string moduleName = table.ModuleArgs[0]; TableModule module = (TableModule)ctx.Modules.Find(moduleName, moduleName.Length, (TableModule)null); if (module == null) { parse.ErrorMsg("no such module: %s", moduleName); return(RC.ERROR); } string error = null; RC rc = VTableCallConstructor(ctx, table, module, module.IModule.Connect, ref error); if (rc != RC.OK) { parse.ErrorMsg("%s", error); } C._tagfree(ctx, ref error); return(rc); }
public bool FixSrcList(SrcList list) { if (NEVER(list == null)) { return(false); } string db = DB; int i; SrcList.SrcListItem item; for (i = 0; i < list.Srcs; i++) { item = list.Ids[i]; if (item.Database != null && !string.Equals(item.Database, db, StringComparison.OrdinalIgnoreCase)) { Parse.ErrorMsg("%s %T cannot reference objects in database %s", Type, Name, item.Database); return(true); } item.Database = null; item.Schema = Schema; #if !OMIT_VIEW || !OMIT_TRIGGER if (FixSelect(item.Select) || FixExpr(item.On)) { return(true); } #endif } return(false); }
public static bool ResolveOrderGroupBy(Parse parse, Select select, ExprList orderBy, string type) { Context ctx = parse.Ctx; if (orderBy == null || parse.Ctx.MallocFailed) { return(0); } #if !MAX_COLUMN if (orderBy.Exprs > ctx.Limits[(int)LIMIT.COLUMN]) { parse.ErrorMsg("too many terms in %s BY clause", type); return(true); } #endif ExprList list = select.EList; Debug.Assert(list != null); // sqlite3SelectNew() guarantees this int i; ExprList.ExprListItem item; for (i = 0; i < orderBy.Exprs; i++) { item = orderBy.Ids[i]; if (item.OrderByCol != null) { if (item.OrderByCol > list.Exprs) { ResolveOutOfRangeError(parse, type, i + 1, list.Exprs); return(true); } ResolveAlias(parse, list, item.OrderByCol - 1, item.Expr, type); } } return(false); }
public static ARC Check(Parse parse, AUTH code, string arg1, string arg2, string arg3) { Context ctx = parse.Ctx; // Don't do any authorization checks if the database is initialising or if the parser is being invoked from within sqlite3_declare_vtab. if (ctx.Init.Busy || E.INDECLARE_VTABLE(parse)) { return(ARC.OK); } if (ctx.Auth == null) { return(ARC.OK); } ARC rc = ctx.Auth(ctx.AuthArg, code, arg1, arg2, arg3, parse.AuthContext); if (rc == ARC.DENY) { parse.ErrorMsg("not authorized"); parse.RC = RC.AUTH; } else if (rc != ARC.OK && rc != ARC.IGNORE) { rc = ARC.DENY; BadReturnCode(parse); } return(rc); }
public static void DropTrigger(Parse parse, SrcList name, int noErr) { Context ctx = parse.Ctx; if (ctx.MallocFailed || parse.ReadSchema(parse) != RC.OK) { goto drop_trigger_cleanup; } Debug.Assert(name.Srcs == 1); string dbName = name.Ids[0].Database; string nameAsString = name.Ids[0].Name; int nameLength = nameAsString.Length; Debug.Assert(dbName != null || Btree.HoldsAllMutexes(ctx)); Trigger trigger = null; for (int i = E.OMIT_TEMPDB; i < ctx.DBs.length; i++) { int j = (i < 2 ? i ^ 1 : i); // Search TEMP before MAIN if (dbName != null && !string.Equals(ctx.DBs[j].Name, dbName, StringComparison.InvariantCultureIgnoreCase)) { continue; } Debug.Assert(Btree.SchemaMutexHeld(ctx, j, null)); trigger = ctx.DBs[j].Schema.TriggerHash.Find(nameAsString, nameLength, (Trigger)null); if (trigger != null) { break; } } if (trigger == null) { if (noErr == 0) { parse.ErrorMsg("no such trigger: %S", name, 0); } else { parse.CodeVerifyNamedSchema(dbName); } parse.CheckSchema = true; goto drop_trigger_cleanup; } DropTriggerPtr(parse, trigger); drop_trigger_cleanup: SrcListDelete(ctx, ref name); }
public static ARC ReadColumn(Parse parse, string table, string column, int db) { Context ctx = parse.Ctx; // Database handle string dbName = ctx.DBs[db].Name; // Name of attached database ARC rc = ctx.Auth(ctx.AuthArg, (int)AUTH.READ, table, column, dbName, parse.AuthContext); // Auth callback return code if (rc == ARC.DENY) { if (ctx.DBs.length > 2 || db != 0) { parse.ErrorMsg("access to %s.%s.%s is prohibited", dbName, table, column); } else { parse.ErrorMsg("access to %s.%s is prohibited", table, column); } parse.RC = RC.AUTH; } else if (rc != ARC.IGNORE && rc != ARC.OK) { BadReturnCode(parse); } return(rc); }
public static CollSeq GetCollSeq(Parse parse, TEXTENCODE encode, CollSeq coll, string name) { Context ctx = parse.Ctx; CollSeq p = coll; if (p == null) { p = FindCollSeq(ctx, encode, name, false); } if (p == null || p.Cmp == null) { // No collation sequence of this type for this encoding is registered. Call the collation factory to see if it can supply us with one. CallCollNeeded(ctx, encode, name); p = FindCollSeq(ctx, encode, name, false); } Debug.Assert(p == null || p.Cmp != null); if (p != null) { parse.ErrorMsg("no such collation sequence: %s", name); } return(p); }
public static JT JoinType(Parse parse, Token a, Token b, Token c) { Token[] alls = new Token[3]; alls[0] = a; alls[1] = b; alls[2] = c; JT jointype = 0; for (int i = 0; i < 3 && alls[i] != null; i++) { Token p = alls[i]; int j; for (j = 0; j < _keywords.Length; j++) { if (p.length == _keywords[j].Chars && p.data.StartsWith(_keyTexts.Substring(_keywords[j].I, _keywords[j].Chars), StringComparison.OrdinalIgnoreCase)) { jointype |= _keywords[j].Code; break; } } C.ASSERTCOVERAGE(j == 0 || j == 1 || j == 2 || j == 3 || j == 4 || j == 5 || j == 6); if (j >= _keywords.Length) { jointype |= JT.ERROR; break; } } if ((jointype & (JT.INNER | JT.OUTER)) == (JT.INNER | JT.OUTER) || (jointype & JT.ERROR) != 0) { string sp = " "; Debug.Assert(b != null); if (c == null) sp = ""; parse.ErrorMsg("unknown or unsupported join type: %T %T%s%T", a, b, sp, c); jointype = JT.INNER; } else if ((jointype & JT.OUTER) != 0 && (jointype & (JT.LEFT | JT.RIGHT)) != JT.LEFT) { parse.ErrorMsg("RIGHT and FULL OUTER JOINs are not currently supported"); jointype = JT.INNER; } return jointype; }
static int ResolveCompoundOrderBy(Parse parse, Select select) { ExprList orderBy = select.OrderBy; if (orderBy == null) return 0; Context ctx = parse.Ctx; #if true || MAX_COLUMN if (orderBy.Exprs > ctx.Limits[(int)LIMIT.COLUMN]) { parse.ErrorMsg("too many terms in ORDER BY clause"); return 1; } #endif int i; for (i = 0; i < orderBy.Exprs; i++) orderBy.Ids[i].Done = false; select.Next = null; while (select.Prior != null) { select.Prior.Next = select; select = select.Prior; } bool moreToDo = true; while (select != null && moreToDo) { moreToDo = false; ExprList list = select.EList; Debug.Assert(list != null); ExprList.ExprListItem item; for (i = 0; i < orderBy.Exprs; i++) { item = orderBy.Ids[i]; Expr pDup; if (item.Done) continue; Expr expr = item.Expr; int colId = -1; if (expr.IsInteger(ref colId)) { if (colId <= 0 || colId > list.Exprs) { ResolveOutOfRangeError(parse, "ORDER", i + 1, list.Exprs); return 1; } } else { colId = ResolveAsName(parse, list, expr); if (colId == 0) { Expr dupExpr = Expr.Dup(ctx, expr, 0); if (!ctx.MallocFailed) { Debug.Assert(dupExpr != null); colId = ResolveOrderByTermToExprList(parse, select, dupExpr); } Expr.Delete(ctx, ref dupExpr); } } if (colId > 0) { // Convert the ORDER BY term into an integer column number iCol, taking care to preserve the COLLATE clause if it exists Expr newExpr = Expr.Expr_(ctx, TK.INTEGER, null); if (newExpr == null) return 1; newExpr.Flags |= EP.IntValue; newExpr.u.I = colId; if (item.Expr == expr) item.Expr = newExpr; else { Debug.Assert(item.Expr.OP == TK.COLLATE); Debug.Assert(item.Expr.Left == expr); item.Expr.Left = newExpr; } Expr.Delete(ctx, ref expr); item.OrderByCol = (ushort)colId; item.Done = true; } else moreToDo = true; } select = select.Next; } for (i = 0; i < orderBy.Exprs; i++) { if (!orderBy.Ids[i].Done) { parse.ErrorMsg("%r ORDER BY term does not match any column in the result set", i + 1); return 1; } } return 0; }
static WRC ResolveSelectStep(Walker walker, Select p) { Debug.Assert(p != null); if ((p.SelFlags & SF.Resolved) != 0) { return(WRC.Prune); } NameContext outerNC = walker.u.NC; // Context that contains this SELECT Parse parse = walker.Parse; // Parsing context Context ctx = parse.Ctx; // Database connection // Normally sqlite3SelectExpand() will be called first and will have already expanded this SELECT. However, if this is a subquery within // an expression, sqlite3ResolveExprNames() will be called without a prior call to sqlite3SelectExpand(). When that happens, let // sqlite3SelectPrep() do all of the processing for this SELECT. sqlite3SelectPrep() will invoke both sqlite3SelectExpand() and // this routine in the correct order. if ((p.SelFlags & SF.Expanded) == 0) { p.Prep(parse, outerNC); return(parse.Errs != 0 || ctx.MallocFailed ? WRC.Abort : WRC.Prune); } bool isCompound = (p.Prior != null); // True if p is a compound select int compounds = 0; // Number of compound terms processed so far Select leftmost = p; // Left-most of SELECT of a compound int i; NameContext nc; // Name context of this SELECT while (p != null) { Debug.Assert((p.SelFlags & SF.Expanded) != 0); Debug.Assert((p.SelFlags & SF.Resolved) == 0); p.SelFlags |= SF.Resolved; // Resolve the expressions in the LIMIT and OFFSET clauses. These are not allowed to refer to any names, so pass an empty NameContext. nc = new NameContext(); //: _memset(&nc, 0, sizeof(nc)); nc.Parse = parse; if (Walker.ResolveExprNames(nc, ref p.Limit) || Walker.ResolveExprNames(nc, ref p.Offset)) { return(WRC.Abort); } // Recursively resolve names in all subqueries SrcList.SrcListItem item; for (i = 0; i < p.Src.Srcs; i++) { item = p.Src.Ids[i]; if (item.Select != null) { NameContext nc2; // Used to iterate name contexts int refs = 0; // Refcount for pOuterNC and outer contexts string savedContext = parse.AuthContext; // Count the total number of references to pOuterNC and all of its parent contexts. After resolving references to expressions in // pItem->pSelect, check if this value has changed. If so, then SELECT statement pItem->pSelect must be correlated. Set the // pItem->isCorrelated flag if this is the case. for (nc2 = outerNC; nc2 != null; nc2 = nc2.Next) { refs += nc2.Refs; } if (item.Name != null) { parse.AuthContext = item.Name; } Walker.ResolveSelectNames(parse, item.Select, outerNC); parse.AuthContext = savedContext; if (parse.Errs != 0 || ctx.MallocFailed) { return(WRC.Abort); } for (nc2 = outerNC; nc2 != null; nc2 = nc2.Next) { refs -= nc2.Refs; } Debug.Assert(!item.IsCorrelated && refs <= 0); item.IsCorrelated = (refs != 0); } } // Set up the local name-context to pass to sqlite3ResolveExprNames() to resolve the result-set expression list. nc.NCFlags = NC.AllowAgg; nc.SrcList = p.Src; nc.Next = outerNC; // Resolve names in the result set. ExprList list = p.EList; // Result set expression list Debug.Assert(list != null); for (i = 0; i < list.Exprs; i++) { Expr expr = list.Ids[i].Expr; if (Walker.ResolveExprNames(nc, ref expr)) { return(WRC.Abort); } } // If there are no aggregate functions in the result-set, and no GROUP BY expression, do not allow aggregates in any of the other expressions. Debug.Assert((p.SelFlags & SF.Aggregate) == 0); ExprList groupBy = p.GroupBy; // The GROUP BY clause if (groupBy != null || (nc.NCFlags & NC.HasAgg) != 0) { p.SelFlags |= SF.Aggregate; } else { nc.NCFlags &= ~NC.AllowAgg; } // If a HAVING clause is present, then there must be a GROUP BY clause. if (p.Having != null && groupBy == null) { parse.ErrorMsg("a GROUP BY clause is required before HAVING"); return(WRC.Abort); } // Add the expression list to the name-context before parsing the other expressions in the SELECT statement. This is so that // expressions in the WHERE clause (etc.) can refer to expressions by aliases in the result set. // // Minor point: If this is the case, then the expression will be re-evaluated for each reference to it. nc.EList = p.EList; if (Walker.ResolveExprNames(nc, ref p.Where) || Walker.ResolveExprNames(nc, ref p.Having)) { return(WRC.Abort); } // The ORDER BY and GROUP BY clauses may not refer to terms in outer queries nc.Next = null; nc.NCFlags |= NC.AllowAgg; // Process the ORDER BY clause for singleton SELECT statements. The ORDER BY clause for compounds SELECT statements is handled // below, after all of the result-sets for all of the elements of the compound have been resolved. if (!isCompound && Walker.ResolveOrderGroupBy(nc, p, p.OrderBy, "ORDER")) { return(WRC.Abort); } if (ctx.MallocFailed) { return(WRC.Abort); } // Resolve the GROUP BY clause. At the same time, make sure the GROUP BY clause does not contain aggregate functions. if (groupBy != null) { if (Walker.ResolveOrderGroupBy(nc, p, groupBy, "GROUP") || ctx.MallocFailed) { return(WRC.Abort); } ExprList.ExprListItem item2; for (i = 0; i < groupBy.Exprs; i++) { item2 = groupBy.Ids[i]; if (E.ExprHasProperty(item2.Expr, EP.Agg)) { parse.ErrorMsg("aggregate functions are not allowed in the GROUP BY clause"); return(WRC.Abort); } } } // Advance to the next term of the compound p = p.Prior; compounds++; } // Resolve the ORDER BY on a compound SELECT after all terms of the compound have been resolved. return(isCompound && ResolveCompoundOrderBy(parse, leftmost) != 0 ? WRC.Abort : WRC.Prune); }
static void ResolveOutOfRangeError(Parse parse, string typeName, int i, int max) { parse.ErrorMsg("%r %s BY term out of range - should be between 1 and %d", i, typeName, max); }
static int ResolveCompoundOrderBy(Parse parse, Select select) { ExprList orderBy = select.OrderBy; if (orderBy == null) { return(0); } Context ctx = parse.Ctx; #if true || MAX_COLUMN if (orderBy.Exprs > ctx.Limits[(int)LIMIT.COLUMN]) { parse.ErrorMsg("too many terms in ORDER BY clause"); return(1); } #endif int i; for (i = 0; i < orderBy.Exprs; i++) { orderBy.Ids[i].Done = false; } select.Next = null; while (select.Prior != null) { select.Prior.Next = select; select = select.Prior; } bool moreToDo = true; while (select != null && moreToDo) { moreToDo = false; ExprList list = select.EList; Debug.Assert(list != null); ExprList.ExprListItem item; for (i = 0; i < orderBy.Exprs; i++) { item = orderBy.Ids[i]; Expr pDup; if (item.Done) { continue; } Expr expr = item.Expr; int colId = -1; if (expr.IsInteger(ref colId)) { if (colId <= 0 || colId > list.Exprs) { ResolveOutOfRangeError(parse, "ORDER", i + 1, list.Exprs); return(1); } } else { colId = ResolveAsName(parse, list, expr); if (colId == 0) { Expr dupExpr = Expr.Dup(ctx, expr, 0); if (!ctx.MallocFailed) { Debug.Assert(dupExpr != null); colId = ResolveOrderByTermToExprList(parse, select, dupExpr); } Expr.Delete(ctx, ref dupExpr); } } if (colId > 0) { // Convert the ORDER BY term into an integer column number iCol, taking care to preserve the COLLATE clause if it exists Expr newExpr = Expr.Expr_(ctx, TK.INTEGER, null); if (newExpr == null) { return(1); } newExpr.Flags |= EP.IntValue; newExpr.u.I = colId; if (item.Expr == expr) { item.Expr = newExpr; } else { Debug.Assert(item.Expr.OP == TK.COLLATE); Debug.Assert(item.Expr.Left == expr); item.Expr.Left = newExpr; } Expr.Delete(ctx, ref expr); item.OrderByCol = (ushort)colId; item.Done = true; } else { moreToDo = true; } } select = select.Next; } for (i = 0; i < orderBy.Exprs; i++) { if (!orderBy.Ids[i].Done) { parse.ErrorMsg("%r ORDER BY term does not match any column in the result set", i + 1); return(1); } } return(0); }
static WRC LookupName(Parse parse, string dbName, string tableName, string colName, NameContext nc, Expr expr) { int cnt = 0; // Number of matching column names int cntTab = 0; // Number of matching table names int subquerys = 0; // How many levels of subquery Context ctx = parse.Ctx; // The database connection SrcList.SrcListItem item; // Use for looping over pSrcList items SrcList.SrcListItem match = null; // The matching pSrcList item NameContext topNC = nc; // First namecontext in the list Schema schema = null; // Schema of the expression bool isTrigger = false; int i, j; Debug.Assert(nc != null); // the name context cannot be NULL. Debug.Assert(colName != null); // The Z in X.Y.Z cannot be NULL Debug.Assert(!E.ExprHasAnyProperty(expr, EP.TokenOnly | EP.Reduced)); // Initialize the node to no-match expr.TableId = -1; expr.Table = null; E.ExprSetIrreducible(expr); // Translate the schema name in zDb into a pointer to the corresponding schema. If not found, pSchema will remain NULL and nothing will match // resulting in an appropriate error message toward the end of this routine if (dbName != null) { for (i = 0; i < ctx.DBs.length; i++) { Debug.Assert(ctx.DBs[i].Name != null); if (string.Compare(ctx.DBs[i].Name, dbName) == 0) { schema = ctx.DBs[i].Schema; break; } } } // Start at the inner-most context and move outward until a match is found while (nc != null && cnt == 0) { ExprList list; SrcList srcList = nc.SrcList; if (srcList != null) { for (i = 0; i < srcList.Srcs; i++) { item = srcList.Ids[i]; Table table = item.Table; Debug.Assert(table != null && table.Name != null); Debug.Assert(table.Cols.length > 0); if (item.Select != null && (item.Select.SelFlags & SF.NestedFrom) != 0) { bool hit = false; list = item.Select.EList; for (j = 0; j < list.Exprs; j++) { if (Walker.MatchSpanName(list.Ids[j].Span, colName, tableName, dbName)) { cnt++; cntTab = 2; match = item; expr.ColumnIdx = j; hit = true; } } if (hit || table == null) { continue; } } if (dbName != null && table.Schema != schema) { continue; } if (tableName != null) { string tableName2 = (item.Alias != null ? item.Alias : table.Name); Debug.Assert(tableName2 != null); if (!string.Equals(tableName2, tableName, StringComparison.OrdinalIgnoreCase)) { continue; } } if (cntTab++ == 0) { match = item; } Column col; for (j = 0; j < table.Cols.length; j++) { col = table.Cols[j]; if (string.Equals(col.Name, colName, StringComparison.InvariantCultureIgnoreCase)) { // If there has been exactly one prior match and this match is for the right-hand table of a NATURAL JOIN or is in a // USING clause, then skip this match. if (cnt == 1) { if ((item.Jointype & JT.NATURAL) != 0) { continue; } if (NameInUsingClause(item.Using, colName)) { continue; } } cnt++; match = item; // Substitute the rowid (column -1) for the INTEGER PRIMARY KEY expr.ColumnIdx = (j == table.PKey ? -1 : (short)j); break; } } } if (match != null) { expr.TableId = match.Cursor; expr.Table = match.Table; schema = expr.Table.Schema; } } #if !OMIT_TRIGGER // If we have not already resolved the name, then maybe it is a new.* or old.* trigger argument reference if (dbName == null && tableName != null && cnt == 0 && parse.TriggerTab != null) { TK op = parse.TriggerOp; Table table = null; Debug.Assert(op == TK.DELETE || op == TK.UPDATE || op == TK.INSERT); if (op != TK.DELETE && string.Equals("new", tableName, StringComparison.InvariantCultureIgnoreCase)) { expr.TableId = 1; table = parse.TriggerTab; } else if (op != TK.INSERT && string.Equals("old", tableName, StringComparison.InvariantCultureIgnoreCase)) { expr.TableId = 0; table = parse.TriggerTab; } if (table != null) { int colId; schema = table.Schema; cntTab++; for (colId = 0; colId < table.Cols.length; colId++) { Column col = table.Cols[colId]; if (string.Equals(col.Name, colName, StringComparison.InvariantCultureIgnoreCase)) { if (colId == table.PKey) { colId = -1; } break; } } if (colId >= table.Cols.length && Expr.IsRowid(colName)) { colId = -1; // IMP: R-44911-55124 } if (colId < table.Cols.length) { cnt++; if (colId < 0) { expr.Aff = AFF.INTEGER; } else if (expr.TableId == 0) { C.ASSERTCOVERAGE(colId == 31); C.ASSERTCOVERAGE(colId == 32); parse.Oldmask |= (colId >= 32 ? 0xffffffff : (((uint)1) << colId)); } else { C.ASSERTCOVERAGE(colId == 31); C.ASSERTCOVERAGE(colId == 32); parse.Newmask |= (colId >= 32 ? 0xffffffff : (((uint)1) << colId)); } expr.ColumnIdx = (short)colId; expr.Table = table; isTrigger = true; } } } #endif // Perhaps the name is a reference to the ROWID if (cnt == 0 && cntTab == 1 && Expr.IsRowid(colName)) { cnt = 1; expr.ColumnIdx = -1; // IMP: R-44911-55124 expr.Aff = AFF.INTEGER; } // If the input is of the form Z (not Y.Z or X.Y.Z) then the name Z might refer to an result-set alias. This happens, for example, when // we are resolving names in the WHERE clause of the following command: // // SELECT a+b AS x FROM table WHERE x<10; // // In cases like this, replace pExpr with a copy of the expression that forms the result set entry ("a+b" in the example) and return immediately. // Note that the expression in the result set should have already been resolved by the time the WHERE clause is resolved. if (cnt == 0 && (list = nc.EList) != null && tableName == null) { for (j = 0; j < list.Exprs; j++) { string asName = list.Ids[j].Name; if (asName != null && string.Equals(asName, colName, StringComparison.InvariantCultureIgnoreCase)) { Debug.Assert(expr.Left == null && expr.Right == null); Debug.Assert(expr.x.List == null); Debug.Assert(expr.x.Select == null); Expr orig = list.Ids[j].Expr; if ((nc.NCFlags & NC.AllowAgg) == 0 && E.ExprHasProperty(orig, EP.Agg)) { parse.ErrorMsg("misuse of aliased aggregate %s", asName); return(WRC.Abort); } ResolveAlias(parse, list, j, expr, "", subquerys); cnt = 1; match = null; Debug.Assert(tableName == null && dbName == null); goto lookupname_end; } } } // Advance to the next name context. The loop will exit when either we have a match (cnt>0) or when we run out of name contexts. if (cnt == 0) { nc = nc.Next; subquerys++; } } // If X and Y are NULL (in other words if only the column name Z is supplied) and the value of Z is enclosed in double-quotes, then // Z is a string literal if it doesn't match any column names. In that case, we need to return right away and not make any changes to // pExpr. // // Because no reference was made to outer contexts, the pNC->nRef fields are not changed in any context. if (cnt == 0 && tableName == null && E.ExprHasProperty(expr, EP.DblQuoted)) { expr.OP = TK.STRING; expr.Table = null; return(WRC.Prune); } // cnt==0 means there was not match. cnt>1 means there were two or more matches. Either way, we have an error. if (cnt != 1) { string err = (cnt == 0 ? "no such column" : "ambiguous column name"); if (dbName != null) { parse.ErrorMsg("%s: %s.%s.%s", err, dbName, tableName, colName); } else if (tableName != null) { parse.ErrorMsg("%s: %s.%s", err, tableName, colName); } else { parse.ErrorMsg("%s: %s", err, colName); } parse.CheckSchema = 1; topNC.Errs++; } // If a column from a table in pSrcList is referenced, then record this fact in the pSrcList.a[].colUsed bitmask. Column 0 causes // bit 0 to be set. Column 1 sets bit 1. And so forth. If the column number is greater than the number of bits in the bitmask // then set the high-order bit of the bitmask. if (expr.ColumnIdx >= 0 && match != null) { int n = expr.ColumnIdx; C.ASSERTCOVERAGE(n == BMS - 1); if (n >= BMS) { n = BMS - 1; } Debug.Assert(match.Cursor == expr.TableId); match.ColUsed |= ((Bitmask)1) << n; } // Clean up and return Expr.Delete(ctx, ref expr.Left); expr.Left = null; Expr.Delete(ctx, ref expr.Right); expr.Right = null; expr.OP = (isTrigger ? TK.TRIGGER : TK.COLUMN); lookupname_end: if (cnt == 1) { Debug.Assert(nc != null); Auth.Read(parse, expr, schema, nc.SrcList); // Increment the nRef value on all name contexts from TopNC up to the point where the name matched. for (; ;) { Debug.Assert(topNC != null); topNC.Refs++; if (topNC == nc) { break; } topNC = topNC.Next; } return(WRC.Prune); } return(WRC.Abort); }
static WRC ResolveExprStep(Walker walker, Expr expr) { NameContext nc = walker.u.NC; Debug.Assert(nc != null); Parse parse = nc.Parse; Debug.Assert(parse == walker.Parse); if (E.ExprHasAnyProperty(expr, EP.Resolved)) { return(WRC.Prune); } E.ExprSetProperty(expr, EP.Resolved); #if !NDEBUG if (nc.SrcList != null && nc.SrcList.Allocs > 0) { SrcList srcList = nc.SrcList; for (int i = 0; i < nc.SrcList.Srcs; i++) { Debug.Assert(srcList.Ids[i].Cursor >= 0 && srcList.Ids[i].Cursor < parse.Tabs); } } #endif switch (expr.OP) { #if ENABLE_UPDATE_DELETE_LIMIT && !OMIT_SUBQUERY // The special operator TK_ROW means use the rowid for the first column in the FROM clause. This is used by the LIMIT and ORDER BY // clause processing on UPDATE and DELETE statements. case TK.ROW: { SrcList srcList = nc.SrcList; Debug.Assert(srcList != null && srcList.Srcs == 1); SrcList.SrcListItem item = srcList.Ids[0]; expr.OP = TK.COLUMN; expr.Table = item.Table; expr.TableId = item.Cursor; expr.ColumnIdx = -1; expr.Aff = AFF.INTEGER; break; } #endif case TK.ID: // A lone identifier is the name of a column. { return(LookupName(parse, null, null, expr.u.Token, nc, expr)); } case TK.DOT: // A table name and column name: ID.ID Or a database, table and column: ID.ID.ID { string columnName; string tableName; string dbName; // if (srcList == nullptr) break; Expr right = expr.Right; if (right.OP == TK.ID) { dbName = null; tableName = expr.Left.u.Token; columnName = right.u.Token; } else { Debug.Assert(right.OP == TK.DOT); dbName = expr.Left.u.Token; tableName = right.Left.u.Token; columnName = right.Right.u.Token; } return(LookupName(parse, dbName, tableName, columnName, nc, expr)); } case TK.CONST_FUNC: case TK.FUNCTION: // Resolve function names { ExprList list = expr.x.List; // The argument list int n = (list != null ? list.Exprs : 0); // Number of arguments bool noSuchFunc = false; // True if no such function exists bool wrongNumArgs = false; // True if wrong number of arguments bool isAgg = false; // True if is an aggregate function TEXTENCODE encode = E.CTXENCODE(parse.Ctx); // The database encoding C.ASSERTCOVERAGE(expr.OP == TK.CONST_FUNC); Debug.Assert(!E.ExprHasProperty(expr, EP.xIsSelect)); string id = expr.u.Token; // The function name. int idLength = id.Length; // Number of characters in function name FuncDef def = Callback.FindFunction(parse.Ctx, id, idLength, n, encode, false); // Information about the function if (def == null) { def = Callback.FindFunction(parse.Ctx, id, idLength, -2, encode, false); if (def == null) { noSuchFunc = true; } else { wrongNumArgs = true; } } else { isAgg = (def.Func == null); } #if !OMIT_AUTHORIZATION if (def != null) { ARC auth = Auth.Check(parse, AUTH.FUNCTION, null, def.Name, null); // Authorization to use the function if (auth != ARC.OK) { if (auth == ARC.DENY) { parse.ErrorMsg("not authorized to use function: %s", def.Name); nc.Errs++; } expr.OP = TK.NULL; return(WRC.Prune); } } #endif if (isAgg && (nc.NCFlags & NC.AllowAgg) == 0) { parse.ErrorMsg("misuse of aggregate function %.*s()", idLength, id); nc.Errs++; isAgg = false; } else if (noSuchFunc && !ctx.Init.Busy) { parse.ErrorMsg("no such function: %.*s", idLength, id); nc.Errs++; } else if (wrongNumArgs) { parse.ErrorMsg("wrong number of arguments to function %.*s()", idLength, id); nc.Errs++; } if (isAgg) { nc.NCFlags &= ~NC.AllowAgg; } walker.WalkExprList(list); if (isAgg) { NameContext nc2 = nc; expr.OP = TK.AGG_FUNCTION; expr.OP2 = 0; while (nc2 != null && !expr.FunctionUsesThisSrc(nc2.SrcList)) { expr.OP2++; nc2 = nc2.Next; } if (nc2 != null) { nc2.NCFlags |= NC.HasAgg; } nc.NCFlags |= NC.AllowAgg; } // FIX ME: Compute pExpr->affinity based on the expected return type of the function return(WRC.Prune); } #if !OMIT_SUBQUERY case TK.SELECT: case TK.EXISTS: { C.ASSERTCOVERAGE(expr.OP == TK.EXISTS); goto case TK.IN; } #endif case TK.IN: { C.ASSERTCOVERAGE(expr.OP == TK.IN); if (E.ExprHasProperty(expr, EP.xIsSelect)) { int refs = nc.Refs; #if !OMIT_CHECK if ((nc.NCFlags & NC.IsCheck) != 0) { parse.ErrorMsg("subqueries prohibited in CHECK constraints"); } #endif walker.WalkSelect(expr.x.Select); Debug.Assert(nc.Refs >= refs); if (refs != nc.Refs) { E.ExprSetProperty(expr, EP.VarSelect); } } break; } #if !OMIT_CHECK case TK.VARIABLE: { if ((nc.NCFlags & NC.IsCheck) != 0) { parse.ErrorMsg("parameters prohibited in CHECK constraints"); } break; } #endif } return(parse.Errs != 0 || parse.Ctx.MallocFailed ? WRC.Abort : WRC.Continue); }
static WRC LookupName(Parse parse, string dbName, string tableName, string colName, NameContext nc, Expr expr) { int cnt = 0; // Number of matching column names int cntTab = 0; // Number of matching table names int subquerys = 0; // How many levels of subquery Context ctx = parse.Ctx; // The database connection SrcList.SrcListItem item; // Use for looping over pSrcList items SrcList.SrcListItem match = null; // The matching pSrcList item NameContext topNC = nc; // First namecontext in the list Schema schema = null; // Schema of the expression bool isTrigger = false; int i, j; Debug.Assert(nc != null); // the name context cannot be NULL. Debug.Assert(colName != null); // The Z in X.Y.Z cannot be NULL Debug.Assert(!E.ExprHasAnyProperty(expr, EP.TokenOnly | EP.Reduced)); // Initialize the node to no-match expr.TableId = -1; expr.Table = null; E.ExprSetIrreducible(expr); // Translate the schema name in zDb into a pointer to the corresponding schema. If not found, pSchema will remain NULL and nothing will match // resulting in an appropriate error message toward the end of this routine if (dbName != null) { for (i = 0; i < ctx.DBs.length; i++) { Debug.Assert(ctx.DBs[i].Name != null); if (string.Compare(ctx.DBs[i].Name, dbName) == 0) { schema = ctx.DBs[i].Schema; break; } } } // Start at the inner-most context and move outward until a match is found while (nc != null && cnt == 0) { ExprList list; SrcList srcList = nc.SrcList; if (srcList != null) { for (i = 0; i < srcList.Srcs; i++) { item = srcList.Ids[i]; Table table = item.Table; Debug.Assert(table != null && table.Name != null); Debug.Assert(table.Cols.length > 0); if (item.Select != null && (item.Select.SelFlags & SF.NestedFrom) != 0) { bool hit = false; list = item.Select.EList; for (j = 0; j < list.Exprs; j++) { if (Walker.MatchSpanName(list.Ids[j].Span, colName, tableName, dbName)) { cnt++; cntTab = 2; match = item; expr.ColumnIdx = j; hit = true; } } if (hit || table == null) continue; } if (dbName != null && table.Schema != schema) continue; if (tableName != null) { string tableName2 = (item.Alias != null ? item.Alias : table.Name); Debug.Assert(tableName2 != null); if (!string.Equals(tableName2, tableName, StringComparison.OrdinalIgnoreCase)) continue; } if (cntTab++ == 0) match = item; Column col; for (j = 0; j < table.Cols.length; j++) { col = table.Cols[j]; if (string.Equals(col.Name, colName, StringComparison.InvariantCultureIgnoreCase)) { // If there has been exactly one prior match and this match is for the right-hand table of a NATURAL JOIN or is in a // USING clause, then skip this match. if (cnt == 1) { if ((item.Jointype & JT.NATURAL) != 0) continue; if (NameInUsingClause(item.Using, colName)) continue; } cnt++; match = item; // Substitute the rowid (column -1) for the INTEGER PRIMARY KEY expr.ColumnIdx = (j == table.PKey ? -1 : (short)j); break; } } } if (match != null) { expr.TableId = match.Cursor; expr.Table = match.Table; schema = expr.Table.Schema; } } #if !OMIT_TRIGGER // If we have not already resolved the name, then maybe it is a new.* or old.* trigger argument reference if (dbName == null && tableName != null && cnt == 0 && parse.TriggerTab != null) { TK op = parse.TriggerOp; Table table = null; Debug.Assert(op == TK.DELETE || op == TK.UPDATE || op == TK.INSERT); if (op != TK.DELETE && string.Equals("new", tableName, StringComparison.InvariantCultureIgnoreCase)) { expr.TableId = 1; table = parse.TriggerTab; } else if (op != TK.INSERT && string.Equals("old", tableName, StringComparison.InvariantCultureIgnoreCase)) { expr.TableId = 0; table = parse.TriggerTab; } if (table != null) { int colId; schema = table.Schema; cntTab++; for (colId = 0; colId < table.Cols.length; colId++) { Column col = table.Cols[colId]; if (string.Equals(col.Name, colName, StringComparison.InvariantCultureIgnoreCase)) { if (colId == table.PKey) colId = -1; break; } } if (colId >= table.Cols.length && Expr.IsRowid(colName)) colId = -1; // IMP: R-44911-55124 if (colId < table.Cols.length) { cnt++; if (colId < 0) expr.Aff = AFF.INTEGER; else if (expr.TableId == 0) { C.ASSERTCOVERAGE(colId == 31); C.ASSERTCOVERAGE(colId == 32); parse.Oldmask |= (colId >= 32 ? 0xffffffff : (((uint)1) << colId)); } else { C.ASSERTCOVERAGE(colId == 31); C.ASSERTCOVERAGE(colId == 32); parse.Newmask |= (colId >= 32 ? 0xffffffff : (((uint)1) << colId)); } expr.ColumnIdx = (short)colId; expr.Table = table; isTrigger = true; } } } #endif // Perhaps the name is a reference to the ROWID if (cnt == 0 && cntTab == 1 && Expr.IsRowid(colName)) { cnt = 1; expr.ColumnIdx = -1; // IMP: R-44911-55124 expr.Aff = AFF.INTEGER; } // If the input is of the form Z (not Y.Z or X.Y.Z) then the name Z might refer to an result-set alias. This happens, for example, when // we are resolving names in the WHERE clause of the following command: // // SELECT a+b AS x FROM table WHERE x<10; // // In cases like this, replace pExpr with a copy of the expression that forms the result set entry ("a+b" in the example) and return immediately. // Note that the expression in the result set should have already been resolved by the time the WHERE clause is resolved. if (cnt == 0 && (list = nc.EList) != null && tableName == null) { for (j = 0; j < list.Exprs; j++) { string asName = list.Ids[j].Name; if (asName != null && string.Equals(asName, colName, StringComparison.InvariantCultureIgnoreCase)) { Debug.Assert(expr.Left == null && expr.Right == null); Debug.Assert(expr.x.List == null); Debug.Assert(expr.x.Select == null); Expr orig = list.Ids[j].Expr; if ((nc.NCFlags & NC.AllowAgg) == 0 && E.ExprHasProperty(orig, EP.Agg)) { parse.ErrorMsg("misuse of aliased aggregate %s", asName); return WRC.Abort; } ResolveAlias(parse, list, j, expr, "", subquerys); cnt = 1; match = null; Debug.Assert(tableName == null && dbName == null); goto lookupname_end; } } } // Advance to the next name context. The loop will exit when either we have a match (cnt>0) or when we run out of name contexts. if (cnt == 0) { nc = nc.Next; subquerys++; } } // If X and Y are NULL (in other words if only the column name Z is supplied) and the value of Z is enclosed in double-quotes, then // Z is a string literal if it doesn't match any column names. In that case, we need to return right away and not make any changes to // pExpr. // // Because no reference was made to outer contexts, the pNC->nRef fields are not changed in any context. if (cnt == 0 && tableName == null && E.ExprHasProperty(expr, EP.DblQuoted)) { expr.OP = TK.STRING; expr.Table = null; return WRC.Prune; } // cnt==0 means there was not match. cnt>1 means there were two or more matches. Either way, we have an error. if (cnt != 1) { string err = (cnt == 0 ? "no such column" : "ambiguous column name"); if (dbName != null) parse.ErrorMsg("%s: %s.%s.%s", err, dbName, tableName, colName); else if (tableName != null) parse.ErrorMsg("%s: %s.%s", err, tableName, colName); else parse.ErrorMsg("%s: %s", err, colName); parse.CheckSchema = 1; topNC.Errs++; } // If a column from a table in pSrcList is referenced, then record this fact in the pSrcList.a[].colUsed bitmask. Column 0 causes // bit 0 to be set. Column 1 sets bit 1. And so forth. If the column number is greater than the number of bits in the bitmask // then set the high-order bit of the bitmask. if (expr.ColumnIdx >= 0 && match != null) { int n = expr.ColumnIdx; C.ASSERTCOVERAGE(n == BMS - 1); if (n >= BMS) n = BMS - 1; Debug.Assert(match.Cursor == expr.TableId); match.ColUsed |= ((Bitmask)1) << n; } // Clean up and return Expr.Delete(ctx, ref expr.Left); expr.Left = null; Expr.Delete(ctx, ref expr.Right); expr.Right = null; expr.OP = (isTrigger ? TK.TRIGGER : TK.COLUMN); lookupname_end: if (cnt == 1) { Debug.Assert(nc != null); Auth.Read(parse, expr, schema, nc.SrcList); // Increment the nRef value on all name contexts from TopNC up to the point where the name matched. for (; ; ) { Debug.Assert(topNC != null); topNC.Refs++; if (topNC == nc) break; topNC = topNC.Next; } return WRC.Prune; } return WRC.Abort; }
public static bool ResolveOrderGroupBy(Parse parse, Select select, ExprList orderBy, string type) { Context ctx = parse.Ctx; if (orderBy == null || parse.Ctx.MallocFailed) return 0; #if !MAX_COLUMN if (orderBy.Exprs > ctx.Limits[(int)LIMIT.COLUMN]) { parse.ErrorMsg("too many terms in %s BY clause", type); return true; } #endif ExprList list = select.EList; Debug.Assert(list != null); // sqlite3SelectNew() guarantees this int i; ExprList.ExprListItem item; for (i = 0; i < orderBy.Exprs; i++) { item = orderBy.Ids[i]; if (item.OrderByCol != null) { if (item.OrderByCol > list.Exprs) { ResolveOutOfRangeError(parse, type, i + 1, list.Exprs); return true; } ResolveAlias(parse, list, item.OrderByCol - 1, item.Expr, type); } } return false; }
public bool ProcessJoin(Parse parse) { SrcList src = Src; // All tables in the FROM clause SrcList.SrcListItem left; // Left table being joined SrcList.SrcListItem right; // Right table being joined int j; for (int i = 0; i < src.Srcs - 1; i++) { left = src.Ids[i]; right = src.Ids[i + 1]; Table leftTable = left.Table; Table rightTable = right.Table; if (C._NEVER(leftTable == null || rightTable == null)) continue; bool isOuter = ((right.Jointype & JT.OUTER) != 0); // When the NATURAL keyword is present, add WHERE clause terms for every column that the two tables have in common. if ((right.Jointype & JT.NATURAL) != 0) { if (right.On != null || right.Using != null) { parse.ErrorMsg("a NATURAL join may not have an ON or USING clause"); return true; } for (j = 0; j < rightTable.Cols.length; j++) { string name = rightTable.Cols[j].Name; // Name of column in the right table int leftId = 0; // Matching left table int leftColId = 0; // Matching column in the left table if (TableAndColumnIndex(src, i + 1, name, ref leftId, ref leftColId)) AddWhereTerm(parse, src, leftId, leftColId, i + 1, j, isOuter, ref Where); } } // Disallow both ON and USING clauses in the same join if (right.On != null && right.Using != null) { parse.ErrorMsg("cannot have both ON and USING clauses in the same join"); return true; } // Add the ON clause to the end of the WHERE clause, connected by if (right.On != null) { if (isOuter) SetJoinExpr(right.On, right.Cursor); Where = Expr.And(parse.Ctx, Where, right.On); right.On = null; } // Create extra terms on the WHERE clause for each column named in the USING clause. Example: If the two tables to be joined are // A and B and the USING clause names X, Y, and Z, then add this to the WHERE clause: A.X=B.X AND A.Y=B.Y AND A.Z=B.Z // Report an error if any column mentioned in the USING clause is not contained in both tables to be joined. if (right.Using != null) { IdList list = right.Using; for (j = 0; j < list.Ids.length; j++) { string name = list.Ids[j].Name; // Name of the term in the USING clause int leftId = 0; // Table on the left with matching column name int leftColId = 0; // Column number of matching column on the left int rightColId = ColumnIndex(rightTable, name); // Column number of matching column on the right if (rightColId < 0 || TableAndColumnIndex(src, i + 1, name, ref leftId, ref leftColId)) { parse.ErrorMsg("cannot join using column %s - column not present in both tables", name); return true; } AddWhereTerm(parse, src, leftId, leftColId, i + 1, rightColId, isOuter, ref Where); } } } return true; }
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 DropTrigger(Parse parse, SrcList name, int noErr) { Context ctx = parse.Ctx; if (ctx.MallocFailed || parse.ReadSchema(parse) != RC.OK) goto drop_trigger_cleanup; Debug.Assert(name.Srcs == 1); string dbName = name.Ids[0].Database; string nameAsString = name.Ids[0].Name; int nameLength = nameAsString.Length; Debug.Assert(dbName != null || Btree.HoldsAllMutexes(ctx)); Trigger trigger = null; for (int i = E.OMIT_TEMPDB; i < ctx.DBs.length; i++) { int j = (i < 2 ? i ^ 1 : i); // Search TEMP before MAIN if (dbName != null && !string.Equals(ctx.DBs[j].Name, dbName, StringComparison.InvariantCultureIgnoreCase)) continue; Debug.Assert(Btree.SchemaMutexHeld(ctx, j, null)); trigger = ctx.DBs[j].Schema.TriggerHash.Find(nameAsString, nameLength, (Trigger)null); if (trigger != null) break; } if (trigger == null) { if (noErr == 0) parse.ErrorMsg("no such trigger: %S", name, 0); else parse.CodeVerifyNamedSchema(dbName); parse.CheckSchema = true; goto drop_trigger_cleanup; } DropTriggerPtr(parse, trigger); drop_trigger_cleanup: SrcListDelete(ctx, ref name); }
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); }
static bool CheckForMultiColumnSelectError(Parse parse, SelectDest dest, int exprs) { SRT dest2 = dest.Dest; if (exprs > 1 && (dest2 == SRT.Mem || dest2 == SRT.Set)) { parse.ErrorMsg("only a single result allowed for a SELECT that is part of an expression"); return true; } return false; }
public static void BadReturnCode(Parse parse) { parse.ErrorMsg("authorizer malfunction"); parse.RC = RC.ERROR; }