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---语义分析的主要内容,如果未能解决你的问题,请参考以下文章