PostgreSQL——查询重写——定义规则

Posted weixin_47373497

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PostgreSQL——查询重写——定义规则相关的知识,希望对你有一定的参考价值。

2021SC@SDUSC

概述

我负责的PostgreSQL代码部分:查询的编译与执行
此篇博客分析内容:查询重写
之前的博客分析完了语义分析,Postgresql经过语义分析步骤得到了查询树。在获取查询树之后会立刻对查询树进行查询重写处理。所以接下来我会分析查询处理流程中的查询重写

查询重写

查询重写:对语义分析后的查询树重写并生成新的查询树,以提供对规则和视图的支持
查询重写模块使用规则系统判断来进行查询树的重写,如果查询树中某个目标被定义了转换规则,则转换规则就会被用来重写查询树。
查询重写的源代码位于:src\\Backend\\rewrite文件夹中
查询重写的入口函数是:pg_rewrite_query

查询重写的核心----规则系统

查询重写的核心是规则系统,规则系统是由一系列的规则组成。由系统表pg_rewrite存储重写规则。
我去查了一下关于系统表pg_rewrite的资料,然后又整理了一下。pg_rewrite表的内容大致如下:

名字类型引用描述
rulenamename规则名称
ev_classoidpg_class.oid使用该规则的表名称
ev_attrint2规则适用的属性
ev_typechar规则适用的命令类型:1=SELECT,2=UPDATE,3=INSERT,4=DELETE
is_insteadbool如果是INSTEAD规则,则为真;ALSO为假
ev_qualtext规则的条件表达式(WHERE子句)
ev_actiontext规则动作的查询树(DO子句)

pg_rewrite表中一个元组代表一条规则,对于该表中的某条规则(即一条记录),在该条记录的ev_class属性表示该规则适用的表(ev_class)上执行特定的命令(ev_type)且满足规则的条件表达式(ev_equal),那么就用规则的动作(ev_action)来重写原始的查询树。

INSTEAD规则和ALSO规则
INSTEAD规则和ALSO规则通过pg_rewrite表的is_instead属性区分:为真则是 INSTEAD规则,反之则为ALSO规则。
INSTEAD:用规则中定义的动作替代原始的查询树中的对规则所在表的引用。
ALSO:原始查询和规则动作都会执行

为了能解释清楚规则系统,我用视图的创建作为例子
先给出下面规则,此规则的作用是——将对视图的查询改写成对基表的查询。

create rule view_rule
as on select
to test_view
do instead
select s.sname, p.pname
from supplier s, sells se, part p
where s.sno = se.sno and
p.pno = se.pno;

当检测到对关系 test_view 的 select 时,就会触发此规则。那么,用户写的对视图的查询(见下面引用)就会被转换成执行do里面的规则,而不是从 test_view 里选择元组。

用户写的对视图的查询
select sname
from test_view
where sname <> ‘Smith’;

实际上当检测到对关系 test_view 的 select 时,就会触发规则,将查询重写为规则里的动作(即do后面的语句)即:首先,获取在规则的动作部分给出的语句——do后(此时需要判断是INSTEAD还是ALSO)因为举例的规则是INSTEAD,所以在此我们用INSTEAD。其次,调整目标列表以便与用户查询给出的字段的数目和顺序相匹配。 最后,把用户查询的 where 子句里的条件部分追加到规则动作部分的查询的条件上。所以用户给出的对test_view的查询将会被重写为

select s.sname
from supplier s, sells se, part p
where s.sno = se.sno and
p.pno = se.pno and
s.sname <> ‘Smith’;

从视图查询的例子可以知道,PostgreSQL的视图实际上是用规则实现的,在对视图进行查询时,则会用相应的规则将对视图的查询改写成对基表的查询。

查询重写的处理操作

查询重写部分的处理操作主要包括定义规则,删除规则,利用规则进行查询重写。我将按照这三个部分逐一对每个操作进行分析

查询重写的处理操作——定义规则

在使用规则系统进行查询重写前,需要定义规则即遇到什么条件时执行什么样的规则动作。规则的定义通过CREATE RULE命令完成。

CREATE RULE 命令:
CREATE RULE 规则名 AS
ON {SELECT | INSERT | UPDATE | DELETE}
TO 表名 [WHERE 规则条件]
DO [INSTEAD] {NOTHING | 命令 | (命令, 命令…)}

定义重写规则的操作主要是由函数DefineRule实现。而CREATE RULE 命令被之前分析过的词法分析和语法分析部分首先处理,然后将分离出的相关信息存储在RuleStmt结构中,然后由查询执行模块把RuleStmt结构输入到函数DefineRule中完成规则的创建。
RuleStmt结构体

typedef struct RuleStmt
{
	NodeTag		type;//节点类型
	RangeVar   *relation;//规则关系
	char	   *rulename;//规则名
	Node	   *whereClause;//条件字句
	CmdType		event;	//动作类型 SELECT,UPDATE,INSERT,DELETE
	bool		instead;//是否是instead,为真则是instead,否则为also
	List	   *actions;//查询重写规则的替换的动作
	bool		replace;//创建语句中是否有OR REPLACE
} RuleStmt;

DefineRule函数

DefineRule(RuleStmt *stmt, const char *queryString)
{
	List	   *actions;//保存动作的链表
	Node	   *whereClause;//存放where子句对应的表达式树
	Oid			relId;//表的oid
	//首先调用transformRuleStmt对RuleStmt结构体进行处理
	transformRuleStmt(stmt, queryString, &actions, &whereClause);

	//RangeVarGetRelid函数通过调用RangeVarGetRelidExtended函数选择正确的命名空间并找到表的OID
	relId = RangeVarGetRelid(stmt->relation, AccessExclusiveLock, false);
	//调用DefineQueryRewrite函数,将已经处理好的规则,作为一个元组,插入到系统表pg_rewrite中,DefineQueryRewrite会把处理好的where子句的表达式树以及规则的动作作为其参数之一
	return DefineQueryRewrite(stmt->rulename,
							  relId,
							  whereClause,
							  stmt->event,
							  stmt->instead,
							  stmt->replace,
							  actions);
}

DefineRule调用的transformRuleStmt函数

transformRuleStmt(RuleStmt *stmt, const char *queryString,
				  List **actions, Node **whereClause)
{
	Relation	rel;
	ParseState *pstate;
	RangeTblEntry *oldrte;//存放old的RTE结构体
	RangeTblEntry *newrte;//存放new的RTE结构体
	//把要定义规则的以别名old封装成RTE,加入到Parsestate结构体的p_rtable中
	oldrte = addRangeTableEntryForRelation(pstate, rel,									   AccessShareLock,makeAlias("old", NIL),false, false);
	//把要定义规则的以别名new封装成RTE,加入到Parsestate结构体的p_rtable中
		newrte=addRangeTableEntryForRelation(pstate,rel,AccessShareLock,makeAlias("new", NIL),false, false);
//根据规则适用的命令不同,将old或者new对应的RTE加入到Parsestate结构体的joinlist中
	switch (stmt->event)
	{
		//如果是select,只加入old
		case CMD_SELECT:
			addRTEtoQuery(pstate, oldrte, false, true, true);
			break;
			//如果是update则将old和new都加入
		case CMD_UPDATE:
			addRTEtoQuery(pstate, oldrte, false, true, true);
			addRTEtoQuery(pstate, newrte, false, true, true);
			break;
			//如果是insert,则只加入new
		case CMD_INSERT:
			addRTEtoQuery(pstate, newrte, false, true, true);
			break;
			//如果是delete,则只加入old
		case CMD_DELETE:
			addRTEtoQuery(pstate, oldrte, false, true, true);
			break;
			//如果都不是就报错
		default:
			elog(ERROR, "unrecognized event type: %d",
				 (int) stmt->event);
			break;
	}

	//调用transformWhereClause函数,将ruleStmt结构体中的where子句转换成一个表达式树,放入参数whereClause中
	*whereClause = transformWhereClause(pstate,(Node *) copyObject(stmt->whereClause),EXPR_KIND_WHERE,"WHERE");
//判断该规则是否没有动作,如果没有action,则创建一个命令类型为CMD_NOTHING且没有连接树的Query,并将此Query包装成一个list,通过参数actions将list的指针传出
	if (stmt->actions == NIL)
	{
		Query	   *nothing_qry = makeNode(Query);
		//创建一个命令类型为CMD_NOTHING
		nothing_qry->commandType = CMD_NOTHING;
		nothing_qry->rtable = pstate->p_rtable;
		//创建没有连接树的Query
		nothing_qry->jointree = makeFromExpr(NIL, NULL);	
		//将此Query包装成一个list,通过参数actions将list的指针传出
		*actions = list_make1(nothing_qry);
	}
	//如果该规则有action,将ruleStmt结构体的actions字段中的每一个节点,通过transformStmt函数,转换成一个Query结构,每一个Query结构的范围表中,都要增加old和new,最后将这些Query放在一个链表中,并由参数actions传出。
	else
	{
		ListCell   *l;
		List	   *newactions = NIL;
		 //取出ruleStmt结构体的actions字段
		foreach(l, stmt->actions)
		{
			Node	   *action = (Node *) lfirst(l);
			ParseState *sub_pstate = make_parsestate(NULL);
			Query	   *sub_qry,
					   *top_subqry;
			bool		has_old,
						has_new;

		//在每个Query结构的范围表中,添加old
			addRTEtoQuery(sub_pstate, oldrte, false, true, false);
		//在每个Query结构的范围表中,添加new
			addRTEtoQuery(sub_pstate, newrte, false, true, false);

		//将ruleStmt结构体的actions字段中的每一个节点,通过transformStmt函数,转换成一个Query结构
			top_subqry = transformStmt(sub_pstate,
									 (Node*)copyObject(action));
			//将Query放在一个链表中
			newactions = lappend(newactions, top_subqry);

			free_parsestate(sub_pstate);
		}
			//由actions传出
		*actions = newactions;
	}
}

DefineRule返回的DefineQueryRewrite函数

DefineQueryRewrite(const char *rulename,
				   Oid event_relid,
				   Node *event_qual,
				   CmdType event_type,
				   bool is_instead,
				   bool replace,
				   List *action)
{
	ListCell   *l;
	Query	   *query;//查询树
	Oid			ruleId = InvalidOid;//把要插入的规则元组的oid初始化为InvalidOid
	ObjectAddress address;//要返回的的地址
	//判断是否为select规则
	if (event_type == CMD_SELECT)
	{
		//如果是select规则,如果action为null则报错
		if (list_length(action) == 0)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("INSTEAD NOTHING rules on SELECT are not implemented"),
					 errhint("Use views instead.")));
		//如果action数量大于1,也报错
		if (list_length(action) > 1)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("multiple actions for rules on SELECT are not implemented")));
		//如果commandType不是select规则,或者is_instead属性为also,则报错
		query = linitial_node(Query, action);
		if (!is_instead ||
			query->commandType != CMD_SELECT)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("rules on SELECT must have action INSTEAD SELECT")));
	//如果select规则不为空则报错
		if (event_qual != NULL)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("event qualifications are not implemented for rules on SELECT")));

		//保证该动作的目标属性表中每一项都与关系中的属性列项保持一致
		checkRuleResultList(query->targetList,
							RelationGetDescr(event_relation),
							true,
							event_relation->rd_rel->relkind !=
							RELKIND_MATVIEW);

		//确保要创建规则的表上没有其他select规则
		if (!replace && event_relation->rd_rules != NULL)
		{
			int			i;

			for (i = 0; i < event_relation->rd_rules->numLocks; i++)
			{
				RewriteRule *rule;

				rule = event_relation->rd_rules->rules[i];
				if (rule->event == CMD_SELECT)
					ereport(ERROR,
							(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
							 errmsg("\\"%s\\" is already a view",
									RelationGetRelationName(event_relation))));
			}
		}
	}
	else
	{
		//如果不是select规则,会检查动作中的returning列表,确保没有多个returning,在有条件的规则和also规则中不能有returning
		bool		haveReturning = false;
		foreach(l, action)
		{
			query = lfirst_node(Query, l);
//如果动作中的returning列表没有多个returning则会继续
			if (!query->returningList)
				continue;
				
	//如果动作中的returning列表有多个returning则会报错
			if (haveReturning)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("cannot have multiple RETURNING lists in a rule")));
			haveReturning = true;
			//确保有条件的规则中不能有returning
			if (event_qual != NULL)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("RETURNING lists are not supported in conditional rules")));
			//确保also规则中不能有returning
			if (!is_instead)
				ereport(ERROR,
						(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
						 errmsg("RETURNING lists are not supported in non-INSTEAD rules")));
		//保证该动作的目标属性表中,每一项都与关系中的属性列项保持一致	
			checkRuleResultList(query->returningList,
								RelationGetDescr(event_relation),
								false, false);
		}
	}
//如果规则的动作不为空或者是instead类型就调用insert函数,向系统表pg_rewrite中插入规则
	if (action != NIL || is_instead)
	{
	
		ruleId = InsertRule(rulename,
							event_type,
							event_relid,
							is_instead,
							event_qual,
							action,
							replace);

		SetRelationRuleStatus(event_relid, true);
	}

	ObjectAddressSet(address, RewriteRelationId, ruleId);
	return address;
}

DefineQueryRewrite函数调用的InsertRule函数
InsertRule函数将处理好的规则信息,组成一个pg_rewrite表的一行,将该行插入到系统表pg_rewrite中,将规则成功插入后,该函数返回规则对应的oid

static Oid
InsertRule(const char *rulname,
		   int evtype,//规则适用的命令类型:1=SELECT,2=UPDATE,3=INSERT,4=DELETE
		   Oid eventrel_oid,//系统表的oid
		   bool evinstead,//是否为instead规则
		   Node *event_qual,//规则的条件表达式
		   List *action,//动作链表
		 )
{ //将规则条件转换成文本格式
	char	   *evqual = nodeToString(event_qual);
	//将规则动作转换成文本格式
	char	   *actiontree = nodeToString((Node *) action);
	Relation	pg_rewrite_desc;//关系描述符
	HeapTuple	tup,//系统表新元组
				oldtup;//系统表旧元组
	//首先检查系统中是否有同名规则,如果有则报错
	namestrcpy(&rname, rulname);
	//打开系统表pg_rewrite获取其元组描述符
	pg_rewrite_desc = table_open(RewriteRelationId, RowExclusiveLock);
	//检查是否有重复元组即同名的规则
	oldtup = SearchSysCache2(RULERELNAME,
							 ObjectIdGetDatum(eventrel_oid),
							 PointerGetDatum(rulname));

	//如果有同名规则则报错
	if (HeapTupleIsValid(oldtup))
	{
		if (!replace)
			ereport(ERROR,
					(errcode(ERRCODE_DUPLICATE_OBJECT),
					 errmsg("rule \\"%s\\" for relation \\"%s\\" already exists",
							rulname, get_rel_name(eventrel_oid))));
	}
	//如果没有同名规则,则新建一个pg_rewrite元组,并对其每一个属性赋值,其中规则条件和规则动作都来自于transformRuleStmt传出的where子句的表达式树和规则动作列表并且会将他们转换成文本格式存放在元组的对应属性中
	else
	{
		//如果没有同名规则,则新建一个pg_rewrite元组
		rewriteObjectId = GetNewOidWithIndex(pg_rewrite_desc,							 RewriteOidIndexId,Anum_pg_rewrite_oid);
		values[Anum_pg_rewrite_oid - 1] = ObjectIdGetDatum(rewriteObjectId);

//对pg_rewrite中新元组的每一个属性赋值
tup = heap_form_tuple(pg_rewrite_desc->rd_att, values, nulls);
	//插入元组到系统表中
		CatalogTupleInsert(pg_rewrite_desc, tup);
	}
//释放元组
	heap_freetuple(tup);
//更新索引
	InvokeObjectPostCreateHook(RewriteRelationId, rewriteObjectId, 0);
//返回规则的oid
	return rewriteObjectId;
}

总结

通过源码分析,了解到了PostgreSQL查询重写中的核心部分——规则系统,以及查询重写操作中的定义规则操作。

感谢批评指正

以上是关于PostgreSQL——查询重写——定义规则的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL——查询重写——删除重写规则以及对查询树进行重写

PostgreSQL——查询重写——删除重写规则以及对查询树进行重写

postgresql的规则系统

50.4. The PostgreSQL Rule System

50.4. The PostgreSQL Rule System

PostgreSQL——查询分析