PostgreSQL——语义分析3——目标属性及Where子句

Posted weixin_47373497

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PostgreSQL——语义分析3——目标属性及Where子句相关的知识,希望对你有一定的参考价值。

2021SC@SDUSC

概述

我负责的PostgreSQL代码部分:查询的编译与执行
此篇博客分析内容:目标属性的语义分析
上篇博客,我分析了from子句的语义分析,大体了解了PostgreSQL是如何处理范围表以及表与表之间的连接,此片博客接着向下分析,在确定了范围表后,又该如何取得目标属性呢?

目标属性的语义分析

处理目标属性的入口函数是TransformTargetList函数,TransformTargetList函数通过调用函数TransformTargetEntry来处理分析树的目标属性中的每一项。对于目标属性中任何一项都会调用函数makeTargetentry创建TargetEntry结构体用来存储和组织。TransformTargetList函数返回p_target,而再通过查询语义分析的函数transformSelectStmt将直接把transformTargetList函数的返回值作为目标属性赋值给targetList字段

TransformTargetList函数

ResTarget结构体

typedef struct ResTarget
{
	NodeTag		type;           //节点类型
	char	   *name;			//列名或者null
	List	   *indirection;	//目标列存储下标,域名,*或者NIL
	Node	   *val;			//要计算的表达式
	int			location;		//token的位置,-1表示未知
} ResTarget;

transformTargetList函数是处理目标属性的入口函数,通过调用transformTargetEntry来处理目标属性。transformTargetList函数的参数由两个ParseState(在上上篇博客PostgreSQL–语义分析-ParseState分析过)和分析树的targetList字段指向的链表(在上上篇博客PostgreSQL–语义分析-Query分析过)

List *
transformTargetList(ParseState *pstate, List *targetlist,
					ParseExprKind exprKind)
{
	List	   *p_target = NIL;//准备空的链表p_target ,为了以后存储TargetEntry结构体
	bool		expand_star;//是否要展开查询表达式里包含的*(即是否把*对应的目标属性全部列出)
	ListCell   *o_target;//临时变量存储targetlist里面的每一个节点
	//逐一取出targetlist里面的每一个节点
	foreach(o_target, targetlist)
	{
		ResTarget  *res = (ResTarget *) lfirst(o_target);//取出每个节点的RESTarget结构体
		if (expand_star)//判断有无展开的*
		{
		//val字段:要计算的表达式
			if (IsA(res->val, ColumnRef))//判断val字段中是否存储列名
			{
				ColumnRef  *cref = (ColumnRef *) res->val;//从val字段中获取列名

				if (IsA(llast(cref->fields), A_Star))//判断ResTarget的Val字段中是否是一个*
				{
					//如果是一个*,则调用 ExpandColumnRefStar函数,生成TargetEntry结构体,然后加入p_target中
					p_target = list_concat(p_target,
										   ExpandColumnRefStar(pstate,
															   cref,
															   true));
					continue;
						}
			}

ExpandColumnRefStar函数的作用:用来处理select中包含的情况,将展开成目标表的具体每一个列信息

A_Indirection结构体
typedef struct A_Indirection
{
NodeTag type;//节点类型
Node *arg; //存储被选择的目标列名
List *indirection;//存储的是例如A_Indices节点(具体A_Indices结构体见下), A_Star节点等。用于select语句包含数组的情况
} A_Indirection;

PostgreSQL允许将字段定义成变长的多维数组。 数组类型可以是任何基本类型或用户定义类型,枚举类型或复合类型。 目前还不支持域的数组。
SELECT pay_by_quarter[3] FROM sal_emp; //select语句中包含数组的情况
SELECT schedule[1:2][2] FROM sal_emp WHERE name = ‘Bill’;//select语句中包含切片的情况

			else if (IsA(res->val, A_Indirection))//判断val字段有无数组类型
			{
				A_Indirection *ind = (A_Indirection *) res->val;//如果有数组的话,则会把val字段的数组类型取出并暂存

				if (IsA(llast(ind->indirection), A_Star))//因为A_Indirection结构体中存储有多个不同数据类型的节点,其中包含A_Star类型,判断 A_Indirection是否包含有A_Star数组类型
				{
					//ExpandIndirectionStar函数展开代指数组类型的*
					p_target = list_concat(p_target,
										   ExpandIndirectionStar(pstate,
																 ind,
																 true,
																 exprKind));
					continue;
				}
			}
		}

A_Indices结构体
typedef struct A_Indices
{
NodeTag type;//节点类型
bool is_slice; //判断是否是切片
Node *lidx; //如果是切片,则存储切片的下边界
Node *uidx; //如果是切片,则存储切片的下标或者上边界
} A_Indices;

		//如果ResTarget的val字段不是一个*则调用transformTargetEntry函数为它生成Target Entry结构体并加入到p_target链表中。 transformTargetEntry函数会调用transformExpr函数为val字段中的数据类型生成表达式结构
		p_target = lappend(p_target,
						   transformTargetEntry(pstate,
												res->val,
												NULL,
												exprKind,
												res->name,
												false));
	}
//TransformTargetList函数返回p_target,而再通过查询语义分析的函数transformSelectStmt将直接把transformTargetList函数的返回值作为目标属性赋值给targetList字段
return p_target;
}

TransformTargetEntry函数

对于每一个目标属性生成TargetEntry结构体

transformTargetEntry(ParseState *pstate,
					 Node *node,
					 Node *expr,
					 ParseExprKind exprKind,
					 char *colname,
					 bool resjunk)
{
	if (expr == NULL)//判断存储的表达式是否为空
	{
		
		if (exprKind == EXPR_KIND_UPDATE_SOURCE && IsA(node, SetToDefault))//判断表达式是否为SetToDefault类型
			expr = node;//如果是默认类型的话,则把表达式存储
		else
			expr = transformExpr(pstate, node, exprKind);//否则调用 transformExpr函数,进一步在transformExpr函数中报错
	}

	if (colname == NULL && !resjunk)//判断列名是否为空或者是否为垃圾属性
	{
		colname = FigureColname(node);//为它生成一个合适的列名
	}
//调用makeTargetEntry为每一个目标属性生成Target Entry结构体,最后通过TransformTargetList函数加入到p_targetlist
	return makeTargetEntry((Expr *) expr,
						   (AttrNumber) pstate->p_next_resno++,
						   colname,
						   resjunk);
}

makeTargetEntry函数

makeTargetEntry函数由transformTargetEntry调用用来创建结构体:TargetEntry。 makeTargetEntry函数的作用是将一个ResTarget结构体的链表转换成一个TargetEntry结构体的链表,而每一个TargetEntry表示查询树中的一个目标属性

TargetEntry *
makeTargetEntry(Expr *expr,
				AttrNumber resno,
				char *resname,
				bool resjunk)
{
	TargetEntry *tle = makeNode(TargetEntry);//创建并初始化节点

	tle->expr = expr;//把传入makeTargetEntry函数的表达式赋值给节点
	tle->resno = resno;//把传入makeTargetEntry函数的属性编号赋值给节点
	tle->resname = resname;把传入makeTargetEntry函数的列名赋值给节点
	//所有属性的初始值都默认为0,减少发生错误的概率
	tle->ressortgroupref = 0;
	tle->resorigtbl = InvalidOid;
	tle->resorigcol = 0;
	tle->resjunk = resjunk;//与传入的resjunk保持一致
	return tle;//返回构造好的结构体
}

TargetEntry结构体

严格来说TargetEntry并不是一个表达式,因为它不能被表达式计算函数处理,但PostgreSQL仍将它视为一种表达式,因为把目标属性作为一个表达式树进行处理会很方便

typedef struct TargetEntry
{
	Expr		xpr;     //表达式头部
	Expr	   *expr;			//目标属性中需要计算的表达式
	AttrNumber	resno;			//属性编号,在select目标属性中resno对应了属性出现的位置
	char	   *resname;		//属性名
	Index		ressortgroupref;	//被ORDER BY和GROUP BY子句引用时使用,0值表示没有被ORDER BY和GROUP BY子句引用,正值表示被引用
	Oid			resorigtbl;		//目标属性所属于的源表的OID
	AttrNumber	resorigcol;		//属性在源表中的属性号
	bool		resjunk;		//表示该属性是否是一个junk属性,如果为真表示这个属性是一个工作属性,在输出结果时应该去除。
} TargetEntry;

TransformExpr函数

当ResTarget val字段没有*则为ResTarget的val字段中的数据类型生成表达式结构

transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKind)
{
	Node	   *result;
	ParseExprKind sv_expr_kind;
	Assert(exprKind != EXPR_KIND_NONE);
	sv_expr_kind = pstate->p_expr_kind;//保存原来的表达式类型
	pstate->p_expr_kind = exprKind;//把传入的解析后的表达式类型传入pstate,用于生成对应结构的表达式节点
//transformExprRecurse函数根据传入的不同类型的表达式,生成不同类型的表达式节点
	result = transformExprRecurse(pstate, expr);
//还原原有的pstate表达式类型
	pstate->p_expr_kind = sv_expr_kind;
//返回生成的表达式
	return result;
}

Var结构体

typedef struct Var
{
	Expr		xpr;
	Index		varno;			//变量属性所在的表在范围表中的编号
	AttrNumber	varattno;		//属性编号
	Oid			vartype;		//该属性数据类型在pg_type中的OID
	int32		vartypmod;		//该属性对应的pg_attribute元组的typmod值
	Oid			varcollid;		//用于校对的OID
	Index		varlevelsup;	//用于子查询中引用外层表的变量,表示子查询的层数
	Index		varnoold;		//varno的原始值,用于调试信息
	AttrNumber	varoattno;		//varoattno的原始值,用于调试
	int			location;	    //符号出现的位置
} Var;

Where子句的语义分析

TransformWhereClause函数

在函数 transformWhereClause中,其会调用 transformExpr 来处理该where子句,并对该子句进行递归处理,由transformExprRecurse 函数完成此递归处理,并将其处理的结果作为 ParseState中 jointree的结果。

transformWhereClause(ParseState *pstate, Node *clause,
					 ParseExprKind exprKind, const char *constructName)
{
	Node	   *qual;
	if (clause == NULL)//判断where子句是否为空
		return NULL;
//调用 transformExpr函数将分析树的whereClause字段表示的where子句转换为一棵表达式树,然后将ParseState的P_joinlist字段所指向的链表以及从where子句得到的表达式树包装成一个fromExpr结构体存入查询树的jointree中用以表示连接树
	qual = transformExpr(pstate, clause, exprKind);
	return qual;//返回表达式
}

语义分析部分总结

截止此篇博客,我们已经分析完了PostgreSQL中select语句中三个主要子句:select,from ,where的语义分析过程

select子句解析分析树中的目标属性表达式并生成TargetEntry结构体存储目标属性
from子句解析分析树中的from表达式,根据from子句中出现的表,视图,子查询,函数或者连接表达式生成范围表
where子句解析分析树中的where Clause字段,转换为一棵表达式树然后包装成FromExpr结构存入查询树的jointree

以上是关于PostgreSQL——语义分析3——目标属性及Where子句的主要内容,如果未能解决你的问题,请参考以下文章

PostgreSQL---语义分析

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

编译技术图示(第一章 编译概述)

编译原理复习总结-耗子尾汁

编译原理复习总结-耗子尾汁

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