PostgreSQL---语义分析

Posted weixin_47373497

tags:

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

2021SC@SDUSC

我负责的PostgreSQL代码部分:查询的编译与执行
此篇博客分析内容:语义分析
上篇博客我分析了查询分析的词法分析和语法分析,我们可以知道经过词法分析和语法分析我们可以得到一个用SelectStmt结构体存储的分析树,由流程分析图可以得知我们还需经过语法分析才可以得到查询树。

语义分析:语义分析阶段会检查命令中是否有不符合语义规定的成分,检查命令是否可以正确执行。
语义分析阶段主要负责语义分析的是analyze.c文件中的parse_analyze函数。parse_analyze函数会根据分析树生成一个对应的查询树,然后再由后面的查询重写模块对这一查询树进行进一步的修改,将查询树改写成查询树链表。在parse_analyze函数中会根据命令类型分成七种情况处理:




语义分析中涉及的重要结构体:

Query:用于存储查询树,是查询分析的最终输出结果
ParseState:ParseState:用于记录语义分析的中间信息

struct ParseState
{
	struct ParseState *parentParseState;	//如果当前是一个子查询的话,这个字段指向其外层查询
	const char *p_sourcetext;	//原始sql命令,只用于报告语法分析出错的位置
	List	   *p_rtable;		//查询涉及的表--范围表
	List	   *p_joinexprs;	//连接表达式
	List	   *p_joinlist;	//连接项	
	List	   *p_namespace;	//表名字集合,用于检查表名冲突
	bool		p_lateral_active;	//是否有关联引用
	List	   *p_ctenamespace;//公共表达式的名字集合
	List	   *p_future_ctes;	//不在p_ctenamespace中的公共表达式
	CommonTableExpr *p_parent_cte;	/* this query's containing CTE */
	Relation	p_target_relation;	//目标表
	RangeTblEntry *p_target_rangetblentry;	/* target rel's RTE */
	bool		p_is_insert;	//是否为insert语句
	List	   *p_windowdefs;	//window子句的原始定义形式
	ParseExprKind p_expr_kind;	//参数的类型
	int			p_next_resno;	
	List	   *p_multiassign_exprs;	
	List	   *p_locking_clause;	//locking子句
	bool		p_locked_from_parent;	
	bool		p_resolve_unknowns; //还未解决的类型
	QueryEnvironment *p_queryEnv;	

	bool		p_hasAggs;//是否有聚集函数
	bool		p_hasWindowFuncs;//是否有窗口函数
	bool		p_hasTargetSRFs;//是否有SRF
	bool		p_hasSubLinks;//是否有子链接
	bool		p_hasModifyingCTE;//是否修改CTE
	Node	   *p_last_srf;		
	/*
	 * Optional hook functions for parser callbacks.  These are null unless
	 * set up by the caller of make_parsestate.
	 */
	PreParseColumnRefHook p_pre_columnref_hook;
	PostParseColumnRefHook p_post_columnref_hook;
	ParseParamRefHook p_paramref_hook;
	CoerceParamHook p_coerce_param_hook;
	void	   *p_ref_hook_state;	/* common passthrough link for above */
};
typedef struct Query
{
	NodeTag		type;//节点类型,T_Query

	CmdType		commandType;	//命令类型
	QuerySource querySource;	//是原始查询还是来自规则的查询
	uint64		queryId;		/* query identifier (can be set by plugins) */

	bool		canSetTag;		
	//查询重写时用到,如果该query是由原始查询转换而来则此字段为假;如果query是由查询重写或查询规划时新增加的则此字段为真
	Node	   *utilityStmt;//定义游标或者不可优化的查询语句
	int			resultRelation; //结果关系
	bool		hasAggs;//目标子句或者having子句中是否有聚集函数
	bool		hasWindowFuncs; //目标属性中是否有窗口函数
	bool		hasTargetSRFs;	/* has set-returning functions in tlist */
	bool		hasSubLinks;	//是否有子查询
	bool		hasDistinctOn;	//distinct子句
	bool		hasRecursive;	//公共表达式中是否允许递归
	bool		hasModifyingCTE;	/* has INSERT/UPDATE/DELETE in WITH */
	bool		hasForUpdate;	//是否有更新
	bool		hasRowSecurity; /* rewriter has applied some RLS policy */

	List	   *cteList;		//with子句用于公共表达式
	List	   *rtable;			//范围表
	FromExpr   *jointree;		//连接树,描述from和where出现的连接
	List	   *targetList;		//目标属性
	OverridingKind override;	/* OVERRIDING clause */
	OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
	List	   *returningList;	//returning子句
	List	   *groupClause;	//group子句

	List	   *groupingSets;	//groupsets子句

	Node	   *havingQual;		//having子句

	List	   *windowClause;	//窗口函数子句
	List	   *distinctClause; //distinct子句
	List	   *sortClause;		//order子句
	Node	   *limitOffset;	//offset子句
	Node	   *limitCount;		//limit子句
	List	   *rowMarks;		//行标记链表,用于for update,for share子句
	Node	   *setOperations;	//集合操作(union,intersect,except)
	List	   *constraintDeps; 
	List	   *withCheckOptions;	
	int			stmt_location;	//记录开始的地方
	int			stmt_len;		//记录字符串的长度
} Query;

parse_analyze函数

函数parse_analyze首先生成一个ParseState结构用于记录语义分析的状态,然后通过调用函数transformStmt来完成语义分析的过程。函数transformStmt会根据不同的查询类型调用相应的函数进行行处理。

transformStmt函数包括:
transformInsertStmt函数
transformDeleteStmt函数
transformUpdateStmt函数
transformSelectStmt函数
transformDeclareCursorStmt函数
transformExplainStmt函数
others

Query* parse_analyze(
    Node* parseTree, const char* sourceText, Oid* paramTypes, int numParams, bool isFirstNode, bool isCreateView)
{
     // 
     ParseState* pstate = make_parsestate(NULL);
     ...
     // 转换
     query = transformTopLevelStmt(pstate, parseTree, isFirstNode, isCreateView);
     ...
    pfree_ext(pstate->p_ref_hook_state);
    free_parsestate(pstate);
     ...
     // 返回查询树
     return query;
}

transformSelectStmt函数

由上面可知transformStmt函数有很多,不同的类型对应于不同的sql操作语句,我在此对sql查询中最重要的select子句即transformSelectStmt函数进行分析。

static Query *
transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
//transformSelectStmt该函数对select查询进行语义分析
//SelectStmt *stmt是由词法分析传回的分析树
//ParseState *pstate:用于记录语义分析的状态
{
	Query	   *qry = makeNode(Query);//makeNode链表初始化
	//查询树最后以query的数据结构组织
	Node	   *qual;//postgresql为了方便传递参数,把所有类型的指针都转换成node类型指针
	ListCell   *l;

	qry->commandType = CMD_SELECT;//查询树命令对应的类型,命令类型为select

	//处理with子句
	if (stmt->withClause)//判断有无with子句
	{
		qry->hasRecursive = stmt->withClause->recursive;//获取表达式中with子句是否支持递归
		qry->cteList = transformWithClause(pstate, stmt->withClause);
		qry->hasModifyingCTE = pstate->p_hasModifyingCTE;
	}
	if (stmt->intoClause)//判断有无into,若有into则报错,因为postgresql把into子句作为特例处理
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("SELECT ... INTO is not allowed here"),//提示不允许into
				 parser_errposition(pstate,
									exprLocation((Node *) stmt->intoClause))));

	pstate->p_locking_clause = stmt->lockingClause;

	pstate->p_windowdefs = stmt->windowClause;//以链表形式存储分析树的window子句
	transformFromClause(pstate, stmt->fromClause);//处理from子句
	qry->targetList = transformTargetList(pstate, stmt->targetList,
										  EXPR_KIND_SELECT_TARGET);
	//targetList 存放查询结果属性
	
	markTargetListOrigins(pstate, qry->targetList);//标明查询结果的来源

	//处理select表达式中的where子句
	qual = transformWhereClause(pstate, stmt->whereClause,
								EXPR_KIND_WHERE, "WHERE");

	//处理select表达式中的having子句
	qry->havingQual = transformWhereClause(pstate, stmt->havingClause,
										   EXPR_KIND_HAVING, "HAVING");

	//先进行排序,因为分组group处理和distinct子句的处理都需要排序结果,所以要先排序再筛选分组
	//排序
	qry->sortClause = transformSortClause(pstate,
										  stmt->sortClause,
										  &qry->targetList,
										  EXPR_KIND_ORDER_BY,
										  false);
    //分组
	qry->groupClause = transformGroupClause(pstate,
											stmt->groupClause,
											&qry->groupingSets,
											&qry->targetList,
											qry->sortClause,
											EXPR_KIND_GROUP_BY,false);
    //判断有无distinct即是否允许重复结果
	if (stmt->distinctClause == NIL)//如果分析树中无distinct子句
	{
		qry->distinctClause = NIL;
		qry->hasDistinctOn = false;
	}

	else if (linitial(stmt->distinctClause) == NULL)//linitial函数获取链表中第一个单元的数据
	{
		//判断distinct子句中获取的数据是否为空

		qry->distinctClause = transformDistinctClause(pstate,
													  &qry->targetList,
													  qry->sortClause,
													  false);
		qry->hasDistinctOn = false;
	}
	else
	{
		qry->distinctClause = transformDistinctOnClause(pstate,
														stmt->distinctClause,
														&qry->targetList,
														qry->sortClause);
		qry->hasDistinctOn = true;
	}
	//limit子句:限制结果的条数,避免全表扫描,提高查询效率。
	//offset子句,选择从哪行开始扫描
	qry->limitOffset = transformLimitClause(pstate, stmt->limitOffset,
											EXPR_KIND_OFFSET, "OFFSET");
	qry->limitCount = transformLimitClause(pstate, stmt->limitCount,
										   EXPR_KIND_LIMIT, "LIMIT");

Limit可以被用于强制 SELECT 语句返回指定的记录数
Limit的执行效率高,是对于一种特定条件下来说的:即数据库的数量很大,但是只需要查询一部分数据的情况。
高效率的原理是:避免全表扫描,提高查询效率。

	//处理窗口函数,针对一组行计算值
qry->windowClause = transformWindowDefinitions(pstate,								   pstate->p_windowdefs, &qry->targetList);

窗口函数:开窗函数也叫分析函数,针对一组行计算值,并为每行返回一个结果。这与聚合函数不同;聚合函数会为一组行返回一个结果。开窗函数包含一个 OVER 子句,该子句定义了涵盖所要计算行的行窗口。对于每一行,系统会使用选定的行窗口作为输入来计算分析函数结果,并可能进行聚合。


	//将还未解析的输出列解析为类型文本
	if (pstate->p_resolve_unknowns)//判断有无未解析的输出列
		resolveTargetListUnknowns(pstate, qry->targetList);

	qry->rtable = pstate->p_rtable;//处理select要查询的目标表的范围
	qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
	//连接树处理from和where之间的连接,显示from子句中有无表连接

	qry->hasSubLinks = pstate->p_hasSubLinks;//检查from中是否有子查询
	qry->hasWindowFuncs = pstate->p_hasWindowFuncs;//检查目标函数中是否有窗口函数
	qry->hasTargetSRFs = pstate->p_hasTargetSRFs;//检查有无自己定义的SRF函数
	qry->hasAggs = pstate->p_hasAggs;//检查目标子句或者having子句中是否有聚集函数

	foreach(l, stmt->lockingClause)//检查是否有锁定子句:for update, for share子句,如果有的话就要上锁
	//保持一致性
	{
		transformLockingClause(pstate, qry,
							   (LockingClause *) lfirst(l), false);
	}

	assign_query_collations(pstate, qry);//在给定的查询中用整理信息标记所有表达式

	if (pstate->p_hasAggs || qry->groupClause || qry->groupingSets || qry->havingQual)
	    //判断聚集函数是否使用错误,或者分组出现错误
		parseCheckAggregates(pstate, qry);
	return qry;
}

GROUPING SETS的每一个子列表可以指定一个或者多个列或者表达式, 它们将按照直接出现在GROUP BY子句中同样的方式被解释。

SRF:set return fountain。PostgreSQL中支持SRF函数,即返回多行数据的函数,我们也可以自己定义,只需要returns setof type即可。

transformSelectStmt函数中涉及的结构体定义及解释

struct ListCell//存储分析树的一个节点
{
	union//共用体类型,共用体的所有成员占用同一段内存,修改一个成员会影响其余所有成员。
	{
		void	   *ptr_value;
		int			int_value;
		Oid			oid_value;//postgresql里用一个一个无符号整数来标识所有对象和文件
	}			data;
	//以上3个变量共用同一个内存
	ListCell   *next;
};

#define linitial(l)				list_lfirst(head(l))
#define lfirst(lc)				((lc)->data.ptr_value)
//linitial函数获取链表中第一个单元的数据

总结

通过上一篇博客的词法语法分析以及这一篇的语法分析,我基本了解了查询分析的执行流程以及相应的功能和实现。
感谢批评指正!

以上是关于PostgreSQL---语义分析的主要内容,如果未能解决你的问题,请参考以下文章

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

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

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

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

PostgreSQL 是不是支持表(片段)的透明压缩?

编译原理系列 实验四语义分析与中间代码生成