PostgreSql——语义分析2——From子句的语义分析处理

2021SC@SDUSC

概述

我负责的PostgreSQL代码部分:查询的编译与执行
此篇博客的分析内容:From子句的语义分析处理


From子句的语义分析主要由TransformFromClause函数处理,并生成范围表。而TransformFromClause函数主要调用transformFromClauseItem对from clause(一个list)中的每一个node处理。处理完每一个node后都会检查该Node表示的范围表是否已经存在(重名检查-- select语句中同一个表不能定义两次)。TransformFromClause函数主要用于对结构体各自段进行赋值,存储语义分析的相关信息。

transformFromClauseItem函数的输入参数——RangeTblEntry结构体

typedef struct RangeTblEntry
{
	NodeTag		type;//节点类型
	RTEKind		rtekind;	//RTE类型(具体类型见上)

typedef enum //RTEKind RTE类型
{
RTE_RELATION, //表示普通表
RTE_SUBQUERY, //表示子查询
RTE_JOIN, //表示连接
RTE_FUNCTION, //表示from里的函数
RTE_TABLEFUNC, //表示表函数类型

RTE_TABLEFUNC,表函数是生成一个行集合的函数,这些行可以是由基本(标量)数据类型, 也可以是由复合(多字段)数据类型组成。它们的使用类似 FROM 中的一个表,视图, 或者是子查询。表函数返回的字段可以象表,视图,或者子查询字段一样的 形式包含在 SELECT,JOIN, 或者 WHERE 子句里。

RTE_VALUES, //表示values表达式
RTE_CTE, //表示公共表达式
RTE_NAMEDTUPLESTORE, //tuplestore,比如AFTER触发器
RTE_RESULT //表示空的FROM语句.通过规划器添加,在解析和重写阶段不会出现
} RTEKind;

	Oid			relid;			//表的Oid,不是RTE_RELATION类型时relid=0
	char		relkind;		//表的类型(具体见上)

relkind表的类型:
#define RELKIND_RELATION ‘r’ //原始普通表
#define RELKIND_INDEX ‘i’ //次要索引
#define RELKIND_SEQUENCE ‘S’ //序列
#define RELKIND_TOASTVALUE ‘t’ //线外存储的值(表太大,放到另一个表中存储)
#define RELKIND_VIEW ‘v’ //视图
#define RELKIND_MATVIEW ‘m’ //物化视图
#define RELKIND_COMPOSITE_TYPE ‘c’ /* composite type /
#define RELKIND_FOREIGN_TABLE ‘f’ //外表
#define RELKIND_PARTITIONED_TABLE ‘p’ //分区的表
#define RELKIND_PARTITIONED_INDEX ‘I’ /
分区的索引

	
	int			rellockmode;	//在表里查询所需要的锁的等级
	struct TableSampleClause *tablesample;	//存储tablesample子句信息
	 //RTEKind=RTE_SUBQUERY时使用的结构
	Query	   *subquery;		//子查询的查询树,RTE_SUBQUERY类型有效
	bool		security_barrier;	/* is from security_barrier view? */
	 //RTEKind=RTE_JOIN时使用的结构
	JoinType	jointype;		//连接类型,后续字段对RTE_JOIN类型有效
	List	   *joinaliasvars;	//连接结果中属性的别名

	//RTEKind=RTE_FUNCTION时使用的结构
	List	   *functions;		//用于RangeTblFunction的节点
	bool		funcordinality; //是否给返回结果加上序列号
	
	//RTEKind=RTE_TABLEFUNC时使用的结构
	TableFunc  *tablefunc;//表函数指针
   
    //RTEKind=RTE_VALUES时使用的结构
	List	   *values_lists;	//VALUES表达式列表,RTE_VALUES有效

	//RTEKind=RTE_CTE时使用的结构
	char	   *ctename;		//公共表表达式名称,后续字段对RTE_CTE类型有效
	Index		ctelevelsup;	//公共表表达式的级别
	bool		self_reference; //是否是递归地自我引用
	//* OID list of column type OIDs */
	List	   *coltypmods;		
	/* integer list of column typmods */
	List	   *colcollations;	/* OID list of column collation OIDs */

	/*
	 * Fields valid for ENR RTEs (else NULL/zero):
	 */
	char	   *enrname;		//临时命名关系,识别关系
	double		enrtuples;		//元组数量
   //所有RTE类型都会使用的结构
	Alias	   *alias;			//用户定义的别名,后续字段对于所有RTE类型都有效
	Alias	   *eref;			//扩展的引用名称
	bool		lateral;		//from里是否有关联引用
	bool		inh;			//是否需要继承
	bool		inFromCl;		//是否出现在from子句中
	AclMode		requiredPerms;	//该RTE所需的访问权限
	Oid			checkAsUser;	//如果是有效值,则以该OID对应的用户身份进行授权检查
	Bitmapset  *selectedCols;	//需要select权限的属性集合
	Bitmapset  *insertedCols;	//需要insert权限的属性集合
	Bitmapset  *updatedCols;	//需要update权限的属性集合
	Bitmapset  *extraUpdatedCols;	//将要被更新的属性集合
	List	   *securityQuals;	//是否是安全栏视图
} RangeTblEntry;

transformFromClauseItem函数分析

transformFromClauseItem的主要工作是根据来自from Clause子句的的node产生的一个或者多个RangeTableEntry结构(结构体见上)加入到ParseState的P_rtable字段指向的链表中,其中每一个RangeTableEntry表示一个范围表,最终生成的查询树的rtable字段也将指向该链表,所以该函数实际完成了范围表链表的构造。
transformFromClauseItem函数根据要处理的节点的类型不同,做不同的处理

static Node *
transformFromClauseItem(ParseState *pstate, Node *n,
						RangeTblEntry **top_rte, int *top_rti,
						List **namespace)
{
  //如果要处理的Node是一个RangeVar结构(表示一个普通表)
	if (IsA(n, RangeVar))//检查是否是一个RangeVar结构(表示一个普通表)
	{
		/* Plain relation reference, or perhaps a CTE reference */
		RangeVar   *rv = (RangeVar *) n;
		RangeTblRef *rtr;//RangeTblRef类似于是RangeTblEntry的引用。主要用在query结构体的jointree中,引用具体的RangeTblEntry

typedef struct RangeTblRef{ NodeTag type; int rtindex; // 在rtable中的位置} RangeTblRef;

		RangeTblEntry *rte;//RangeTblEntry结构的指针(具体RangeTblEntry结构体见上)
		int			rtindex;  // 在rtable中的位置
		//检查是否是一个CTE或者一个元组引用
		rte = getRTEForSpecialRelationTypes(pstate, rv);

		//如果rte为空,则不是一个CTE,则调用transformTableEntry将表作为一个RTE_RELATION类型的RTE加入到p_rtable
		if (!rte)
			rte = transformTableEntry(pstate, rv);

		/* assume new rte is at end */
		rtindex = list_length(pstate->p_rtable);//默认把新加入的rte加到范围链表的末尾,并记录该节点在范围表链表中的位置
		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));//检查上面两步是否有异常
		*top_rte = rte;//存储指向rte的指针,并作为函数的一个返回结果返回给上层函数TransformFromClause
		*top_rti = rtindex;//存储该节点在范围表中的位置,并作为函数的一个返回结果返回给上层函数TransformFromClause
		*namespace = list_make1(makeDefaultNSItem(rte));//利用参数namespace 将生成的RTE传出
		rtr = makeNode(RangeTblRef);//创建一个引用该RTE的RangeTblRef结构
		rtr->rtindex = rtindex;//记录位置
		return (Node *) rtr;//返回引用
	}
	else if (IsA(n, RangeSubselect))//检查是否是RangeSubselect结构(表示一个子查询)
	{
		/* sub-SELECT is like a plain relation */
		RangeTblRef *rtr;//RangeTblRef类似于是RangeTblEntry的引用。主要用在query结构体的jointree中,引用具体的RangeTblEntry
		RangeTblEntry *rte;//RangeTblEntry结构的指针(具体RangeTblEntry结构体见上)
		int			rtindex;// 在rtable中的位置
      //调用transformRangeSubselect将子查询作为一个RTE_SUBQUERY类型的RTE加入到p_rtable中,此外还会递归调用transform SelectStmt(上篇博客分析过的语义分析的主要函数)生成子查询的查询树用于填充该RTE的subquery字段
		rte = transformRangeSubselect(pstate, (RangeSubselect *) n);
		/* assume new rte is at end */
		rtindex = list_length(pstate->p_rtable);//默认把新加入的rte加到范围链表的末尾,并记录该节点在范围表链表中的位置
		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));//检查上面两步是否有异常
		*top_rte = rte;//存储指向rte的指针,并作为函数的一个返回结果返回给上层函数TransformFromClause
		*top_rti = rtindex;//存储该节点在范围表中的位置,并作为函数的一个返回结果返回给上层函数TransformFromClause
		*namespace = list_make1(makeDefaultNSItem(rte));//利用参数namespace 将生成的RTE传出
		rtr = makeNode(RangeTblRef);//创建一个引用该RTE的RangeTblRef结构
		rtr->rtindex = rtindex;//记录位置
		return (Node *) rtr;//返回引用
	}
	else if (IsA(n, RangeFunction))//检查是否是RangeFunction结构(表示一个函数)
	{
		/* function is like a plain relation */
		RangeTblRef *rtr;//RangeTblRef类似于是RangeTblEntry的引用。主要用在query结构体的jointree中,引用具体的RangeTblEntry
		RangeTblEntry *rte;//RangeTblEntry结构的指针(具体RangeTblEntry结构体见上)
		int			rtindex;// 在rtable中的位置
//调用transformRangeFunction生成RTE_FUNCTION类型的RTE加入到p_rtable中,除填充该RTE中与函数相关相关的字段之外,还会根据该函数是聚集函数或者窗口函数来设置ParseState中相应标志字段
		rte = transformRangeFunction(pstate, (RangeFunction *) n);
		rtindex = list_length(pstate->p_rtable);//默认把新加入的rte加到范围链表的末尾,并记录该节点在范围表链表中的位置
		Assert(rte == rt_fetch(rtindex, pstate->p_rtable));//检查上面两步是否有异常
		*top_rte = rte;//存储指向rte的指针,并作为函数的一个返回结果返回给上层函数TransformFromClause
		*top_rti = rtindex;//存储该节点在范围表中的位置,并作为函数的一个返回结果返回给上层函数TransformFromClause
		*namespace = list_make1(makeDefaultNSItem(rte));//利用参数namespace 将生成的RTE传出
		rtr = makeNode(RangeTblRef);//创建一个引用该RTE的RangeTblRef结构
		rtr->rtindex = rtindex;//记录位置
		return (Node *) rtr;//返回引用
	}

typedef struct JoinExpr
{
NodeTag type;
JoinType jointype; //连接类型
bool isNatural; //是否自然连接
Node *larg; //左子树
Node *rarg; //右子树
List *usingClause; //using子句
Node *quals; //连接条件
Alias *alias; //别名
int rtindex; //连接表达式在范围表中的索引号,如果没有则为0
} JoinExpr;

  else if (IsA(n, JoinExpr))//检查是否一个JoinExpr结构(表示一个连接表达式)
	{
		/* A newfangled join expression */
		JoinExpr   *j = (JoinExpr *) n;
		RangeTblEntry *l_rte;
		RangeTblEntry *r_rte;
		int			l_rtindex;//记录在左子树中的位置
		int			r_rtindex;//记录在右子树中的位置
		List	   *l_namespace,//左子树的命名空间链表
				   *r_namespace,//右子树的命名空间链表
				   *my_namespace,//左右子树合并后的命名空间
				   *l_colnames,//左子树列名
				   *r_colnames,//右子树列名
				   *res_colnames,//合并后的列名
				   *l_colvars,//左子树列变量
				   *r_colvars,//右子树列变量
				   *res_colvars;//合并后的列变量
		bool		lateral_ok;//是否可以使用关联连接
		int			sv_namespace_length;//命名空间的长度
		RangeTblEntry *rte;
		int			k;

		 //处理左子树,对左子树递归调用 transformFromClauseItem进行语义分析
		j->larg = transformFromClauseItem(pstate, j->larg,
										  &l_rte,
										  &l_rtindex,
										  &l_namespace);

		
		lateral_ok = (j->jointype == JOIN_INNER || j->jointype == JOIN_LEFT);//如果是内连接或者左连接则允许和外表实现关联连接

lateral:
可以在出现于FROM中的子查询前放置关键词LATERAL。这允许它们引用前面的FROM项提供的列(如果没有 LATERAL,每一个子查询将被独立计算,并且因此不能被其他FROM项交叉引用)。
出现在FROM中的表函数的前面也可以被放上关键词LATERAL,但对于关键词的不可选的,在任何情况下函 数的参数都可以包含前面FROM项提供额列的引用。
一个LATERAL项可以出现在FROM列表项层,或者出现在一个JOIN树中。在后者如果出现在JOIN的右部, 那么可以引用在JOIN左部分的任何项。
如果一个FROM项包含LATERAL交叉引用,计算过程中:对于提供交叉引用列的FROM项的每一行,或者 多个提供这些列的多个FROM项进行集合,LATERAL项将被使用该行或者行集中的列值进行计算。得到结 果行将和它们被计算出来的行进行正常的连接。对于来自这些列的源表的每一行或行集,该过程将重复。

		
		setNamespaceLateralState(l_namespace, true, lateral_ok);//进行局部重名检查
		//因为JoinExpr结构体中可能涉及多个表,因此需要进行重名检查,只有两个RTE的表名称及其别名都相同才被认为是重名,

		sv_namespace_length = list_length(pstate->p_namespace);
		pstate->p_namespace = list_concat(pstate->p_namespace, l_namespace);//将检查完后的左子树对应的RTE加入到参数relnamespace指向的链表中以便传给transformFromClause函数进行重名检查

	   //处理右子树,对右子树递归调用 transformFromClauseItem进行语义分析
		j->rarg = transformFromClauseItem(pstate, j->rarg,
										  &r_rte,
										  &r_rtindex,
										  &r_namespace);

		//把命名空间链表中的左边的RTE删除
		pstate->p_namespace = list_truncate(pstate->p_namespace,
											sv_namespace_length);

		//检查左右子树中是否有冲突的引用名
		checkNameSpaceConflicts(pstate, l_namespace, r_namespace);

		//检查完成后,连接左子树和右子树的命名空间
		my_namespace = list_concat(l_namespace, r_namespace);

		//expandRTE函数返回新的可以修改的安全链表
		expandRTE(l_rte, l_rtindex, 0, -1, false,
				  &l_colnames, &l_colvars);
		expandRTE(r_rte, r_rtindex, 0, -1, false,
				  &r_colnames, &r_colvars);

		//判断是否是自然连接
		if (j->isNatural)
		{
			List	   *rlist = NIL;
			ListCell   *lx,//节点
					   *rx;

			Assert(j->usingClause == NIL);//如果有using子句则会报错。因为自然连接下不需要通过using关键字来指定连接属性
      //使用嵌套循环函数,来查找左右子树即左右关系自然连接的公共属性,并把自然连接转换成using子句
			foreach(lx, l_colnames)
			{
				char	   *l_colname = strVal(lfirst(lx));
				Value	   *m_name = NULL;

				foreach(rx, r_colnames)
				{
					char	   *r_colname = strVal(lfirst(rx));

					if (strcmp(l_colname, r_colname) == 0)
					{
						m_name = makeString(l_colname);
						break;
					}
				}

				if (m_name != NULL)//如果找到了共同属性
					rlist = lappend(rlist, m_name);//把m_name记录在rlist链表中
			}
           
			j->usingClause = rlist;//把公共属性放在JoinExpr的using字段中(在自然连接条件下using原本为空)
		}
  
    //判断是否有using子句,如果有using子句则处理由using给出的连接条件
		if (j->usingClause)
		{
			List	   *ucols = j->usingClause;//存储using子句
			List	   *l_usingvars = NIL;//把左子树的using置为空
			List	   *r_usingvars = NIL;//把右子树的using置为空
			ListCell   *ucol;//链表节点

			Assert(j->quals == NULL);	//不应该有on,因为在连接中using和on是互斥的
			
     //将从using字段中逐一取出属性名,然后检查它是否在左右两个RTE的属性中存在,只要在某一方不存在,则会报错,并终止整个sql命令的执行
     
			foreach(ucol, ucols)
			{
				char	   *u_colname = strVal(lfirst(ucol));
				ListCell   *col;
				int			ndx;
				int			l_index = -1;
				int			r_index = -1;
				Var		   *l_colvar,
						   *r_colvar;

				foreach(col, res_colnames)//从合并后的列名集合中遍历,查找using子句提供的目标列名
				{
					char	   *res_colname = strVal(lfirst(col));
         //看合并后的列名集合中是否含有using子句中提供的目标列名,如果没有则会报错,并终止整个sql命令的执行
					if (strcmp(res_colname, u_colname) == 0)
						ereport(ERROR,
								(errcode(ERRCODE_DUPLICATE_COLUMN),
								 errmsg("column name \"%s\" appears more than once in USING clause",
										u_colname)));
				}

		//看左子树的列名集合中是否含有using子句中提供的目标列名,如果没有则会报错,并终止整个sql命令的执行
				ndx = 0;
				foreach(col, l_colnames)
				{
					char	   *l_colname = strVal(lfirst(col));

					if (strcmp(l_colname, u_colname) == 0)
					{
						if (l_index >= 0)
							ereport(ERROR,
									(errcode(ERRCODE_AMBIGUOUS_COLUMN),
									 errmsg("common column name \"%s\" appears more than once in left table",
											u_colname)));
						l_index = ndx;
					}
					ndx++;
				}
				if (l_index < 0)//左子树中没有找到目标列名
					ereport(ERROR,
							(errcode(ERRCODE_UNDEFINED_COLUMN),
							 errmsg("column \"%s\" specified in USING clause does not exist in left table",
									u_colname)));

	 //看右子树的列名集合中是否含有using子句中提供的目标列名,如果没有则会报错,并终止整个sql命令的执行
				ndx = 0;
				foreach(col, r_colnames)
				{
					char	   *r_colname = strVal(lfirst(col));

					if (strcmp(r_colname, u_colname) == 0)
					{
						if (r_index >= 0)
							ereport(ERROR,
									(errcode(ERRCODE_AMBIGUOUS_COLUMN),
									 errmsg("common column name \"%s\" appears more than once in right table",
											u_colname)));
						r_index = ndx;
					}
					ndx++;
				}
				if (r_index < 0)//右子树中没有找到目标列名
					ereport(ERROR,
							(errcode(ERRCODE_UNDEFINED_COLUMN),
							 errmsg("column \"%s\" specified in USING clause does not exist in right table",
									u_colname)));

				l_colvar = list_nth(l_colvars, l_index);
				l_usingvars = lappend(l_usingvars, l_colvar);
				r_colvar = list_nth(r_colvars, r_index);
				r_usingvars = lappend(r_usingvars, r_colvar);

				res_colnames = lappend(res_colnames, lfirst(ucol));
				res_colvars = lappend(res_colvars,
									  buildMergedJoinVar(pstate,
														 j->jointype,
														 l_colvar,
														 r_colvar));
			}
     //在检查过程中将分别为左右RTE生成参加连接的属性的列表,完成这一检查后,根据这些属性列表,调用函数 transformJoinUsingClause生成
     //多个=表达式,将它们作为一个list放入Joinexpr的quals字段中
			j->quals = transformJoinUsingClause(pstate,
												l_rte,
												r_rte,
												l_usingvars,
												r_usingvars);
		}
		//如果j->quals字段不为空,则需要处理由ON给出的连接条件
		else if (j->quals)
		{
			//调用transformJoinOnClause来处理quals中的内容,实际上具体分析,transformJoinOnClause会调用TransformWhereClause函数完成工作
			j->quals = transformJoinOnClause(pstate, j, my_namespace);
		}
		else
		{
			/* CROSS JOIN: no quals */
		}

		//将左右子树的剩余列添加到输出列中
		extractRemainingColumns(res_colnames,
								l_colnames, l_colvars,
								&l_colnames, &l_colvars);
		extractRemainingColumns(res_colnames,
								r_colnames, r_colvars,
								&r_colnames, &r_colvars);
		res_colnames = list_concat(res_colnames, l_colnames);
		res_colvars = list_concat(res_colvars, l_colvars);
		res_colnames = list_concat(res_colnames, r_colnames);
		res_colvars = list_concat(res_colvars, r_colvars);

		//检查是否有as子句
		if (j->alias)
		{
			if (j->alias->colnames != NIL)//判断as中是否有列名
			{
				if (list_length(j->alias->colnames) > list_length(res_colnames))//
					ereport(ERROR,
							(errcode(ERRCODE_SYNTAX_ERROR),
							 errmsg("column alias list for \"%s\" has too many entries",
									j->alias->aliasname)));
			}
		}

		//调用addRangeTableEntryForJoin函数从完成的joinexpr生成一个表达该连接的RTE加入p_rtable中,并返回joinexpr
		rte = addRangeTableEntryForJoin(pstate,
										res_colnames,
										j->jointype,
										res_colvars,
										j->alias,
										true);

		
		j->rtindex = list_length(pstate->p_rtable);//默认把新加入的rte加到范围链表的末尾,并记录该节点在范围表链表中的位置
		Assert(rte == rt_fetch(j->rtindex, pstate->p_rtable));//检查上面是否有异常

		*top_rte = rte;//存储指向rte的指针,并作为函数的一个返回结果返回给上层函数TransformFromClause
		*top_rti = j->rtindex;//存储该节点在范围表中的位置,并作为函数的一个返回结果返回给上层函数TransformFromClause

		//创建一个到JoinExpr的匹配链接,以便以后使用
		for (k = list_length(pstate->p_joinexprs) + 1; k < j->rtindex; k++)
			pstate->p_joinexprs = lappend(pstate->p_joinexprs, NULL);
		pstate->p_joinexprs = lappend(pstate->p_joinexprs, j);
		Assert(list_length(pstate->p_joinexprs) == j->rtindex);

		
		 //如果JOIN有别名,那么它完全隐藏包含的RTE;否则,所包含的RTE仍然作为表名可见
		if (j->alias != NULL)
			my_namespace = NIL;
		else
			setNamespaceColumnVisibility(my_namespace, false);
	}
	else
		elog(ERROR, "unrecognized node type: %d", (int) nodeTag(n));//无法识别的节点类型
	return NULL;				/* can't get here, keep compiler quiet */
}

总结

通过源码分析,大致了解了PostgreSQL from子句语法分析的流程。

感谢批评指正

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

weixin_47373497

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值