在PostgreSQL中,Parser将SQL语句解析成ParseTree,ParseTree只是简单记录SQL语句中的信息,并不保证信息的正确性。因此,在进行查询优化之前先要检验SQL语义的正确性。
Analyzer负责对ParseTree进行语义分析,并将ParseTree转换成QueryTree,QueryTree是优化器的输入参数。
这里我们只关注查询语句的QueryTree。对于查询语句,它的ParseTree实际是通过SelectStmt的数据结构来表示的。QueryTree则通过Query的数据结构来表示。我们将通过分析从SelectStmt到Query的转换过程来揭示Query的组成结构。
Query
/*
* Query -
* Parse analysis turns all statements into a Query tree
* for further processing by the rewriter and planner.
*
* Utility statements (i.e. non-optimizable statements) have the
* utilityStmt field set, and the rest of the Query is mostly dummy.
*
* Planning converts a Query tree into a Plan tree headed by a PlannedStmt
* node --- the Query structure is not used by the executor.
*/
typedef struct Query
{
NodeTag type;
CmdType commandType; /* select|insert|update|delete|utility */
QuerySource querySource; /* where did I come from? */
uint64 queryId; /* query identifier (can be set by plugins) */
bool canSetTag; /* do I set the command result tag? */
Node *utilityStmt; /* non-null if commandType == CMD_UTILITY */
int resultRelation; /* rtable index of target relation for
* INSERT/UPDATE/DELETE; 0 for SELECT */
bool hasAggs; /* has aggregates in tlist or havingQual */
bool hasWindowFuncs; /* has window functions in tlist */
bool hasTargetSRFs; /* has set-returning functions in tlist */
bool hasSubLinks; /* has subquery SubLink */
bool hasDistinctOn; /* distinctClause is from DISTINCT ON */
bool hasRecursive; /* WITH RECURSIVE was specified */
bool hasModifyingCTE; /* has INSERT/UPDATE/DELETE in WITH */
bool hasForUpdate; /* FOR [KEY] UPDATE/SHARE was specified */
bool hasRowSecurity; /* rewriter has applied some RLS policy */
List *cteList; /* WITH list (of CommonTableExpr's) */
List *rtable; /* list of range table entries */
FromExpr *jointree; /* table join tree (FROM and WHERE clauses) */
List *targetList; /* target list (of TargetEntry) */
OverridingKind override; /* OVERRIDING clause */
OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
List *returningList; /* return-values list (of TargetEntry) */
List *groupClause; /* a list of SortGroupClause's */
List *groupingSets; /* a list of GroupingSet's if present */
Node *havingQual; /* qualifications applied to groups */
List *windowClause; /* a list of WindowClause's */
List *distinctClause; /* a list of SortGroupClause's */
List *sortClause; /* a list of SortGroupClause's */
Node *limitOffset; /* # of result tuples to skip (int8 expr) */
Node *limitCount; /* # of result tuples to return (int8 expr) */
List *rowMarks; /* a list of RowMarkClause's */
Node *setOperations; /* set-operation tree if this is top level of
* a UNION/INTERSECT/EXCEPT query */
List *constraintDeps; /* a list of pg_constraint OIDs that the query
* depends on to be semantically valid */
List *withCheckOptions; /* a list of WithCheckOption's, which are
* only added during rewrite and therefore
* are not written out as part of Query. */
/*
* The following two fields identify the portion of the source text string
* containing this query. They are typically only populated in top-level
* Queries, not in sub-queries. When not set, they might both be zero, or
* both be -1 meaning "unknown".
*/
int stmt_location; /* start location, or -1 if unknown */
int stmt_len; /* length in bytes; 0 means "rest of string" */
} Query;
ParseState
ParseState数据结构用于保存在分析阶段使用的状态信息。
当存在子查询时,ParseState通过parentParseState
指针指向外层查询的ParseState。
struct ParseState
{
struct ParseState *parentParseState; /* stack link */
const char *p_sourcetext; /* source text, or NULL if not available */
List *p_rtable; /* range table so far */
List *p_joinexprs; /* JoinExprs for RTE_JOIN p_rtable entries */
List *p_joinlist; /* join items so far (will become FromExpr
* node's fromlist) */
List *p_namespace; /* currently-referenceable RTEs (List of
* ParseNamespaceItem) */
bool p_lateral_active; /* p_lateral_only items visible? */
List *p_ctenamespace; /* current namespace for common table exprs */
List *p_future_ctes; /* common table exprs not yet in namespace */
CommonTableExpr *p_parent_cte; /* this query's containing CTE */
Relation p_target_relation; /* INSERT/UPDATE/DELETE target rel */
RangeTblEntry *p_target_rangetblentry; /* target rel's RTE */
bool p_is_insert; /* process assignment like INSERT not UPDATE */
List *p_windowdefs; /* raw representations of window clauses */
ParseExprKind p_expr_kind; /* what kind of expression we're parsing */
int p_next_resno; /* next targetlist resno to assign */
List *p_multiassign_exprs; /* junk tlist entries for multiassign */
List *p_locking_clause; /* raw FOR UPDATE/FOR SHARE info */
bool p_locked_from_parent; /* parent has marked this subquery
* with FOR UPDATE/FOR SHARE */
bool p_resolve_unknowns; /* resolve unknown-type SELECT outputs as
* type text */
QueryEnvironment *p_queryEnv; /* curr env, incl refs to enclosing env */
/* Flags telling about things found in the query: */
bool p_hasAggs;
bool p_hasWindowFuncs;
bool p_hasTargetSRFs;
bool p_hasSubLinks;
bool p_hasModifyingCTE;
Node *p_last_srf; /* most recent set-returning func/op found */
/*
* 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 */
};
parse_analyze
SelectStmt转换为Query的过程通过函数parse_analyze
实现,位于src/backend/parser/analyze.c
中。
函数parse_analyze
首先生成一个ParseState用于记录分析时的中间信息,然后调用transformTopLevelStmt
函数来完成语义分析。
transformTopLevelStmt
函数调用transformOptionalSelectInto
函数对SELECT ... INTO
进行处理。transformOptionalSelectInto
先将SELECT ... INTO
语句转换为CREATE TABLE AS
语句,然后调用transformStmt
函数对语句进行处理。
transformStmt
函数根据不同的查询类型调用相应的函数进行处理,其主要作用是将分析树转换为查询树。这里我们主要关注对SELECT语句进行转换的函数transformSelectStmt
函数,不考虑集合操作。
transformValuesClause
VALUES子句可以通过值表达式生成一个记录集,通常用于生成一个“常量表”。
关于VALUES的具体说明可参考https://www.postgresql.org/docs/current/sql-values.html,其语法如下所示:
VALUES ( expression [, ...] ) [, ...]
[ ORDER BY sort_expression [ ASC | DESC | USING operator ] [, ...] ]
[ LIMIT { count | ALL } ]
[ OFFSET start [ ROW | ROWS ] ]
[ FETCH { FIRST | NEXT } [ count ] { ROW | ROWS } ONLY ]
Parser会将VALUES子句封装成一个单独的子查询SelectStmt,此时SelectStmt::valuesLists
保存的是值表达式的链表,链表中每一个节点是一个常量表达式。
transformValuesClause
函数用于转换VALUES子句,将VALUES子句所在的SelectStmt转换成一个Query。值表达式链表被转换成常量节点链表,通过addRangeTableEntryForValues
函数向子查询的ParseState::p_rtable
增加RTE_VALUES类型的RTE。
transformValuesClause
函数对with子句、sort子句、limit子句的处理与transformSelectStmt
函数一致,不再赘叙。
在外层,VALUES子句转换成的Query被包装成RET_SUBQUERY类型的RTE添加到外层的ParseState::p_rtable
中。
transformSelectStmt
transformSelectStmt
函数根据SelectStmt数据结构生成一个查询树,其主要处理操作如下:
- With clause
- From clause
- Target list
- Where clause
- Having clause
- Sort clause
- Group clause
- Distinct clause
- Limit clause
- Window clause
- Locking clause
With clause
With子句包含的是CTE(Common Table Expressions)的链表,链表中的每个CTE是一个CommonTableExpr
数据结构。
transformWithClause
函数对with子句进行处理,处理流程如下:
- 检查CTE名称有没有重复;
- 检查CTE是否有数据修改语句(INSERT/UPDATE/DELETE);
- 如果CTE是递归的,检查CTE之间的依赖关系,根据依赖关系重新排序;
- 分析每一个CTE:
- 调用
transformStmt
函数对CTE中的子查询进行转换 - 计算输出列的名称和类型
- 调用
From clause
transformFromClause
函数负责处理From子句,并且向Query的range table、joinlist和namespace中增加项。
SelectStmt的fromClause
是一个链表,链表中的节点可能是RangeVar
、RangeSubselect
、RangeFunction
、RangeTableFunc
、RangeTableSample
、JoinExpr
。
transformFromClause
函数主要调用transformFromClauseItem
函数对fromClause
链表中的每一个节点进行处理。处理完节点之后会检查该节点表示的范围表是否有重名。
transformFromClauseItem
函数转换fromClause
链表中的节点,向ParseState
的p_rtable
链表字段添加生成的RangeTableEntry
结构,每一个RangeTableEntry
结构表示一个范围表,最终生成的Query的rtable字段也指向该链表。
transformFromClause
函数将transformFromClauseItem
函数返回的Node
添加到ParseState::p_joinlist
中,将ParseNamespaceItem
添加到pstate->p_namespace
中。
RangeTblRef
实际保存的是RTE在ParseState::p_rtable
链表中的位置。
在transformSelectStmt
函数的最后会将ParseState::p_rtable
指向的链表赋值给QUery::rtable
。
下面依次分析transformFromClauseItem
函数对不同节点类型的处理。
RangeVar
首先检查节点是不是一个CTE或者EphemeralNamedRelation,如果是则通过
addRangeTableEntryForCTE
函数或addRangeTableEntryForENR
函数向ParseState::p_rtable
添加RTE,RTE的类型为RTE_CTE或RTE_NAMEDTUPLESTORE。如果是普通表,则通过
addRangeTableEntry
函数向ParseState::p_rtable
添加RTE_RELATION类型的RTE。
RangeSubselect
transformRangeSubselect
函数用来转换FROM子句中的子查询。
首先通过parse_sub_analyze
函数解析子查询,生成子查询的Query。parse_sub_analyze
函数递归调用transformStmt
函数实现对子查询的分析。
最后通过addRangeTableEntryForSubquery
函数将子查询作为RTE_SUBQUERY类型的RTE加入到ParseState::p_rtable
中。
返回RangeTblRef
。
RangeFunction
transformRangeFunction
函数用来转换FROM子句中的函数。
RangeFunction
中保存的是函数表达式的链表,需要对每个函数表达式进行分析。通过transformExpr
对函数表达式进行分析转换。
最后通过addRangeTableEntryForFunction
函数向ParseState::p_rtable
添加RTE_FUNCTION类型的RTE。
返回RangeTblRef
。
RangeTableFunc
表函数是能够产生记录集的函数,可以在FROM子句中像表、视图或子查询一样使用。
transformRangeTableFunc
函数用来转换FROM子句的表函数。目前只支持XMLTABLE。
最后通过addRangeTableEntryForTableFunc
函数向ParseState::p_rtable
添加RTE_TABLEFUNC类型的RTE。
返回RangeTblRef
。
RangeTableSample
表采样对表使用采样方法进行采样。
首先递归调用transformFromClauseItem
函数对RangeTableSample中的relation进行转换。
然后调用transformRangeTableSample
函数将RangeTableSample转换为TableSampleClause。这里会分析采样方法是否有效,转换参数表达式和REPEATABLE表达式。
返回RangeTblRef
。
JoinExpr
JoinExpr是连接表达式,有左右子树并且有不同的连接类型。主要处理流程如下:
- 递归调用
transformFromClauseItem
函数对JoinExpr的左右子树进行分析。 - 检查命名空间是否冲突(重名检查)。
- 从左右子树中抽取出列。
- 如果是自然连接,则转换成using子句。
- 如果存在using子句,则检查其中的连接条件,并通过
transformJoinUsingClause
函数生成“=”表达式加入到JoinExpr的quals字段中。 - 如果不存在using子句,但是quals不为空,则通过
transformJoinOnClause
函数处理ON给出的连接条件。 - 通过
addRangeTableEntryForJoin
函数向ParseState::p_rtable
添加RTE_JOIN类型的RTE。
Target list
SelectStmt::targetList
是由ResTarget
组成的链表,由函数transformTargetList
进行转换。通过transformTargetEntry
函数将每一个ResTarget
转换成TargetEntry
。如果ResTarget::val
会通过transformExpr
函数转换成表达式。例如,ColumnRef结构会转换成Var结构。
最终,转换后的TargetEntry
链表会保存到Query::targetList
字段上。
Where clause
通过transformWhereClause
函数处理where子句,在内部通过transformExpr
函数转换成条件表达式树。
在transformSelectStmt
函数的后面将生成的条件表达式树和ParseState::p_joinlist
包装成FromExpr结构保存到Query::jointree
字段上。
Having clause
通过transformWhereClause
函数处理having子句,并将生成的条件表达式树保存到Query::havingQual
字段上。
Sort clause
通过transformSortClause
函数处理sort子句。
首先对排序链表中的每一个元素分别到targetList中查找是否由匹配的TLE。如果不存在,则将其加入到Query::targetList
中,并标记为"resjunk"节点。
然后根据排序顺序和排序字段类型确定排序操作符、相等比较操作符,并生成SortGroupClause
结构加入到sortList中,并为对应的TLE的TargetEntry::ressortgroupref
字段分配值,并将该值赋给SortGroupClause::tleSortGroupRef
。
最后将sortList保存到Query::sortClause
字段上。
Group clause
通过transformGroupClause
函数处理group子句。
首先将groupList中的grouping set扁平化,然后对groupList中的每一个元素进行转换。与sort子句类似,转换过程中会到targetList中查找是否由匹配的TLE。如果不存在,则将其加入到Query::targetList
中,并标记为"resjunk"节点。
如果group字段已经在sortList中,则直接复制对应的SortGroupClause
结构并将其加入到groupList中。
如果group字段不在sortList中,则需要生成SortGroupClause
结构并为其确定排序操作符、相等比较操作符,并为对应的TLE的TargetEntry::ressortgroupref
字段分配值,并将该值赋给SortGroupClause::tleSortGroupRef
,最后将SortGroupClause
加入到groupList中。
最后将groupList保存到Query::groupClause
字段上。
Distinct clause
对于SELECT DISTINCT,通过transformDistinctClause
函数处理distinct子句。此时,如果由sort子句,则要求sort字段必需出现在targetList中且不能是resjunk。
对于SELECT DISTINCT ON,通过transformDistinctOnClause
函数处理distinct on子句。
对于上面两种情况的处理是相似的。如果sort子句中由distinct子句中的字段,则直接复制sort子句中的SortGroupClause结构并加入到distinctList中。最后通过addTargetToGroupList
将所有未出现在sort子句中的字段添加到distinctList中。
最后将distinctList保存到Query::distinctClause
字段上。
Limit clause
通过transformLimitClause
函数处理offset和count。
首先通过transformExpr
函数转换表达式,然后确保表达式是bigint类型,最后检查不能是当前查询中的变量(可以是子查询)。
最后将offset和count表达式保存到Query::limitOffset
和Query::limitCount
上。
Window clause
通过transformWindowDefinitions
函数处理将WindowDef转换为WindowClause。
对WindowDef中的orderClause调用transformSortClause
函数进行处理(会将涉及的TLE添加到Query的targetList中)。
对WindowDef中的partitionClause调用transformGroupClause
函数进行处理(会将涉及的TLE添加到Query的targetList中)。
对WindowDef中的startOffset和endOffset调用transformFrameOffset
函数进行处理。
最后将WindowClause的链表保存到Query::windowClause
字段上。
Locking clause
通过transformLockingClause
函数处理locking子句链表中的每一项,只支持表和子查询两种类型。
对每一个需要加锁的表生成RowMarkClause
并增加到Query::rowMarks
中。对于子查询需要递归处理。