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 + -
显示快捷键?