rewritehandler.c
来自「PostgreSQL7.4.6 for Linux」· C语言 代码 · 共 1,349 行 · 第 1/3 页
C
1,349 行
} /* * Recurse into sublink subqueries, too. But we already did the ones * in the rtable. */ if (parsetree->hasSubLinks) query_tree_walker(parsetree, fireRIRonSubLink, (void *) activeRIRs, QTW_IGNORE_RT_SUBQUERIES); /* * If the query was marked having aggregates, check if this is still * true after rewriting. Ditto for sublinks. Note there should be no * aggs in the qual at this point. (Does this code still do anything * useful? The view-becomes-subselect-in-FROM approach doesn't look * like it could remove aggs or sublinks...) */ if (parsetree->hasAggs) { parsetree->hasAggs = checkExprHasAggs((Node *) parsetree); if (parsetree->hasAggs) if (checkExprHasAggs((Node *) parsetree->jointree)) elog(ERROR, "failed to remove aggregates from qual"); } if (parsetree->hasSubLinks) parsetree->hasSubLinks = checkExprHasSubLink((Node *) parsetree); return parsetree;}/* * Modify the given query by adding 'AND rule_qual IS NOT TRUE' to its * qualification. This is used to generate suitable "else clauses" for * conditional INSTEAD rules. (Unfortunately we must use "x IS NOT TRUE", * not just "NOT x" which the planner is much smarter about, else we will * do the wrong thing when the qual evaluates to NULL.) * * The rule_qual may contain references to OLD or NEW. OLD references are * replaced by references to the specified rt_index (the relation that the * rule applies to). NEW references are only possible for INSERT and UPDATE * queries on the relation itself, and so they should be replaced by copies * of the related entries in the query's own targetlist. */static Query *CopyAndAddInvertedQual(Query *parsetree, Node *rule_qual, int rt_index, CmdType event){ Query *new_tree = (Query *) copyObject(parsetree); Node *new_qual = (Node *) copyObject(rule_qual); /* Fix references to OLD */ ChangeVarNodes(new_qual, PRS2_OLD_VARNO, rt_index, 0); /* Fix references to NEW */ if (event == CMD_INSERT || event == CMD_UPDATE) new_qual = ResolveNew(new_qual, PRS2_NEW_VARNO, 0, parsetree->targetList, event, rt_index); /* And attach the fixed qual */ AddInvertedQual(new_tree, new_qual); return new_tree;}/* * fireRules - * Iterate through rule locks applying rules. * * Input arguments: * parsetree - original query * rt_index - RT index of result relation in original query * event - type of rule event * locks - list of rules to fire * Output arguments: * *instead_flag - set TRUE if any unqualified INSTEAD rule is found * (must be initialized to FALSE) * *qual_product - filled with modified original query if any qualified * INSTEAD rule is found (must be initialized to NULL) * Return value: * list of rule actions adjusted for use with this query * * Qualified INSTEAD rules generate their action with the qualification * condition added. They also generate a modified version of the original * query with the negated qualification added, so that it will run only for * rows that the qualified action doesn't act on. (If there are multiple * qualified INSTEAD rules, we AND all the negated quals onto a single * modified original query.) We won't execute the original, unmodified * query if we find either qualified or unqualified INSTEAD rules. If * we find both, the modified original query is discarded too. */static List *fireRules(Query *parsetree, int rt_index, CmdType event, List *locks, bool *instead_flag, Query **qual_product){ List *results = NIL; List *i; foreach(i, locks) { RewriteRule *rule_lock = (RewriteRule *) lfirst(i); Node *event_qual = rule_lock->qual; List *actions = rule_lock->actions; QuerySource qsrc; List *r; /* Determine correct QuerySource value for actions */ if (rule_lock->isInstead) { if (event_qual != NULL) qsrc = QSRC_QUAL_INSTEAD_RULE; else { qsrc = QSRC_INSTEAD_RULE; *instead_flag = true; /* report unqualified INSTEAD */ } } else qsrc = QSRC_NON_INSTEAD_RULE; if (qsrc == QSRC_QUAL_INSTEAD_RULE) { /* * If there are INSTEAD rules with qualifications, the * original query is still performed. But all the negated rule * qualifications of the INSTEAD rules are added so it does * its actions only in cases where the rule quals of all * INSTEAD rules are false. Think of it as the default action * in a case. We save this in *qual_product so RewriteQuery() * can add it to the query list after we mangled it up enough. * * If we have already found an unqualified INSTEAD rule, then * *qual_product won't be used, so don't bother building it. */ if (!*instead_flag) { if (*qual_product == NULL) *qual_product = parsetree; *qual_product = CopyAndAddInvertedQual(*qual_product, event_qual, rt_index, event); } } /* Now process the rule's actions and add them to the result list */ foreach(r, actions) { Query *rule_action = lfirst(r); if (rule_action->commandType == CMD_NOTHING) continue; rule_action = rewriteRuleAction(parsetree, rule_action, event_qual, rt_index, event); rule_action->querySource = qsrc; rule_action->canSetTag = false; /* might change later */ results = lappend(results, rule_action); } } return results;}/* * RewriteQuery - * rewrites the query and apply the rules again on the queries rewritten * * rewrite_events is a list of open query-rewrite actions, so we can detect * infinite recursion. */static List *RewriteQuery(Query *parsetree, List *rewrite_events){ CmdType event = parsetree->commandType; bool instead = false; Query *qual_product = NULL; List *rewritten = NIL; /* * If the statement is an update, insert or delete - fire rules on it. * * SELECT rules are handled later when we have all the queries that * should get executed. Also, utilities aren't rewritten at all (do * we still need that check?) */ if (event != CMD_SELECT && event != CMD_UTILITY) { int result_relation; RangeTblEntry *rt_entry; Relation rt_entry_relation; List *locks; result_relation = parsetree->resultRelation; Assert(result_relation != 0); rt_entry = rt_fetch(result_relation, parsetree->rtable); Assert(rt_entry->rtekind == RTE_RELATION); /* * This may well be the first access to the result relation during * the current statement (it will be, if this Query was extracted * from a rule or somehow got here other than via the parser). * Therefore, grab the appropriate lock type for a result * relation, and do not release it until end of transaction. This * protects the rewriter and planner against schema changes * mid-query. */ rt_entry_relation = heap_open(rt_entry->relid, RowExclusiveLock); /* * If it's an INSERT or UPDATE, rewrite the targetlist into * standard form. This will be needed by the planner anyway, and * doing it now ensures that any references to NEW.field will * behave sanely. */ if (event == CMD_INSERT || event == CMD_UPDATE) rewriteTargetList(parsetree, rt_entry_relation); /* * Collect and apply the appropriate rules. */ locks = matchLocks(event, rt_entry_relation->rd_rules, result_relation, parsetree); if (locks != NIL) { List *product_queries; product_queries = fireRules(parsetree, result_relation, event, locks, &instead, &qual_product); /* * If we got any product queries, recursively rewrite them --- * but first check for recursion! */ if (product_queries != NIL) { List *n; rewrite_event *rev; foreach(n, rewrite_events) { rev = (rewrite_event *) lfirst(n); if (rev->relation == RelationGetRelid(rt_entry_relation) && rev->event == event) ereport(ERROR, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), errmsg("infinite recursion detected in rules for relation \"%s\"", RelationGetRelationName(rt_entry_relation)))); } rev = (rewrite_event *) palloc(sizeof(rewrite_event)); rev->relation = RelationGetRelid(rt_entry_relation); rev->event = event; rewrite_events = lcons(rev, rewrite_events); foreach(n, product_queries) { Query *pt = (Query *) lfirst(n); List *newstuff; newstuff = RewriteQuery(pt, rewrite_events); rewritten = nconc(rewritten, newstuff); } } } heap_close(rt_entry_relation, NoLock); /* keep lock! */ } /* * For INSERTs, the original query is done first; for UPDATE/DELETE, * it is done last. This is needed because update and delete rule * actions might not do anything if they are invoked after the update * or delete is performed. The command counter increment between the * query executions makes the deleted (and maybe the updated) tuples * disappear so the scans for them in the rule actions cannot find * them. * * If we found any unqualified INSTEAD, the original query is not done at * all, in any form. Otherwise, we add the modified form if qualified * INSTEADs were found, else the unmodified form. */ if (!instead) { if (parsetree->commandType == CMD_INSERT) { if (qual_product != NULL) rewritten = lcons(qual_product, rewritten); else rewritten = lcons(parsetree, rewritten); } else { if (qual_product != NULL) rewritten = lappend(rewritten, qual_product); else rewritten = lappend(rewritten, parsetree); } } return rewritten;}/* * QueryRewrite - * Primary entry point to the query rewriter. * Rewrite one query via query rewrite system, possibly returning 0 * or many queries. * * NOTE: The code in QueryRewrite was formerly in pg_parse_and_plan(), and was * moved here so that it would be invoked during EXPLAIN. */List *QueryRewrite(Query *parsetree){ List *querylist; List *results = NIL; List *l; CmdType origCmdType; bool foundOriginalQuery; Query *lastInstead; /* * Step 1 * * Apply all non-SELECT rules possibly getting 0 or many queries */ querylist = RewriteQuery(parsetree, NIL); /* * Step 2 * * Apply all the RIR rules on each query */ foreach(l, querylist) { Query *query = (Query *) lfirst(l); query = fireRIRrules(query, NIL); /* * If the query target was rewritten as a view, complain. */ if (query->resultRelation) { RangeTblEntry *rte = rt_fetch(query->resultRelation, query->rtable); if (rte->rtekind == RTE_SUBQUERY) { switch (query->commandType) { case CMD_INSERT: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot insert into a view"), errhint("You need an unconditional ON INSERT DO INSTEAD rule."))); break; case CMD_UPDATE: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot update a view"), errhint("You need an unconditional ON UPDATE DO INSTEAD rule."))); break; case CMD_DELETE: ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot delete from a view"), errhint("You need an unconditional ON DELETE DO INSTEAD rule."))); break; default: elog(ERROR, "unrecognized commandType: %d", (int) query->commandType); break; } } } results = lappend(results, query); } /* * Step 3 * * Determine which, if any, of the resulting queries is supposed to set * the command-result tag; and update the canSetTag fields * accordingly. * * If the original query is still in the list, it sets the command tag. * Otherwise, the last INSTEAD query of the same kind as the original * is allowed to set the tag. (Note these rules can leave us with no * query setting the tag. The tcop code has to cope with this by * setting up a default tag based on the original un-rewritten query.) * * The Asserts verify that at most one query in the result list is marked * canSetTag. If we aren't checking asserts, we can fall out of the * loop as soon as we find the original query. */ origCmdType = parsetree->commandType; foundOriginalQuery = false; lastInstead = NULL; foreach(l, results) { Query *query = (Query *) lfirst(l); if (query->querySource == QSRC_ORIGINAL) { Assert(query->canSetTag); Assert(!foundOriginalQuery); foundOriginalQuery = true;#ifndef USE_ASSERT_CHECKING break;#endif } else { Assert(!query->canSetTag); if (query->commandType == origCmdType && (query->querySource == QSRC_INSTEAD_RULE || query->querySource == QSRC_QUAL_INSTEAD_RULE)) lastInstead = query; } } if (!foundOriginalQuery && lastInstead != NULL) lastInstead->canSetTag = true; return results;}
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?