SELECT * FROM shoelace
查询主函数 exec_simple_query
后台服务器收到查询后,在PostgresMain()函数中根据输入的查询语句"select * from shoelace"读取首字符firstchar,并将其分为"Q"(simple query),然后执行exec_simple_query()函数
static void exec_simple_query(const char *query_string){
/*
该该函数用于执行”simple query“协议信息
在该函数中调用函数pg_parse_query进行词法解析并产生解析树,返回给变量parsetree_list
然后调用pg_analyze_and_rewrite函数逐个对paarsetree_list中的元素进行语义分析和重写
参数是查询语句的地址
*/
}
查询的执行流程为:
- 经过词法分析将SQL命令转成解析树
- 语义分析将解析树转成查询树
- 为查询树生成plan
1.1 exec_simple_query中调用pg_parse_query
词法分析函数
List* pg_parse_query(const char* query_string){
/*
该函数处理SQL命令生成分析树的List
返回值类型是List的指针类型
*/
}
// List数据结构
typedef struct List
{
NodeTag type; /* T_List, T_IntList, or T_OidList */
int length; /* number of elements currently present */
int max_length; /* allocated length of elements[] */
ListCell *elements; /* re-allocatable array of cells */
/* We may allocate some cells along with the List header: */
ListCell initial_elements[FLEXIBLE_ARRAY_MEMBER];
/* If elements == initial_elements, it's not a separate allocation */
} List;
1.1.1 pg_parse_query中调用raw_parser
在pg_parse_query函数中中调用raw_parser生成分析树,并将返回值给变量raw_parse_tree_list
List* raw_parser(const char* str, RowParseMode mode){
/*
给定字符串形式的查询,进行词法和语法分析。
返回原始(未分析)解析树列表。 该列表的内容具有指定的 RawParseMode 所要求的形式。
在简单查询中,mode为RAW_PARSE_DEFAULT
*/
}
不理解该函数的执行过程,它返回一个List* 类型的指针
对于该SQL它打印出来为
(gdb) print *raw_parsetree_list
{type = T_List, length = 1, max_length = 5, elements = 0x157e070, initial_elements = 0x157e070}
(gdb) p*(Node *)(raw_parsetree_list->elements->ptr_value)
$9 = {type = T_RawStmt}
(gdb) p*(RawStmt *)(parsetree_list->elements->ptr_value)
No symbol "parsetree_list" in current context.
(gdb) p*(RawStmt *)(raw_parsetree_list->elements->ptr_value)
$10 = {type = T_RawStmt, stmt = 0x157df18, stmt_location = 0, stmt_len = 22}
(gdb) p *(((RawStmt *)(raw_parsetree_list->elements->ptr_value))->stmt)
$11 = {type = T_SelectStmt}
数据结构
typedef struct SelectStmt
{
NodeTag type;
/*
* 这些字段仅在"叶子" SelectStmt 中使用。
*/
List *distinctClause; /* NULL,DISTINCT ON 表达式列表,或者
* lcons(NIL,NIL) 表示所有 (SELECT DISTINCT) */
IntoClause *intoClause; /* SELECT INTO 的目标 */
List *targetList; /* 目标列表(ResTarget 列表) */
List *fromClause; /* FROM 子句 */
Node *whereClause; /* WHERE 条件 */
List *groupClause; /* GROUP BY 子句 */
bool groupDistinct; /* 是否为 GROUP BY DISTINCT? */
Node *havingClause; /* HAVING 条件表达式 */
List *windowClause; /* WINDOW window_name AS (...), ... */
/*
* 在表示 VALUES 列表的"叶子"节点中,上述字段都为空,取而代之的是这个字段。
* 需要注意的是,子列表的元素仅为表达式,没有 ResTarget 的修饰。
* 还需要注意,列表元素可以是 DEFAULT(表示为 SetToDefault 节点),
* 无论 VALUES 列表的上下文如何。在解析分析中,需要在不合法的情况下拒绝这种情况。
*/
List *valuesLists; /* 未转换的表达式列表 */
/*
* 这些字段在"叶子" SelectStmt 和上层 SelectStmt 中都使用。
*/
List *sortClause; /* 排序子句(SortBy 列表) */
Node *limitOffset; /* 跳过的结果元组数目 */
Node *limitCount; /* 返回的结果元组数目 */
LimitOption limitOption; /* 限制类型 */
List *lockingClause; /* FOR UPDATE(LockingClause 列表) */
WithClause *withClause; /* WITH 子句 */
/*
* 这些字段仅在上层 SelectStmt 中使用。
*/
SetOperation op; /* 集合操作的类型 */
bool all; /* 是否指定了 ALL? */
struct SelectStmt *larg; /* 左子树 */
struct SelectStmt *rarg; /* 右子树 */
/* 最终在这里添加对应规范的字段 */
} SelectStmt;
SQL语句经过raw_parser函数解析后,存储在raw_parsetree_list中,其中List存储SelectStmt结构体,数据结构为链表的形式。每一条SQL语句被解析后存储在结构体SelectStmt中
当词法解析完成后,将变量raw_parsetree_list返回给pg_parse_query函数中的变量parsetree_list
1.2 exec_simple_query中调用pg_analyze_and_rewrite_fixedparams
然后再对得到的变量paarsetree_list中的元素逐个进行语义分析和重写
使用的函数是pg_analyze_and_rewrite_fixedparams
List* pg_analyze_and_rewrite_fixedparams(RawStmt *parsetree,
const char *query_string,
const Oid *paramTypes,
int numParams,
QueryEnvironment *queryEnv){
/*
对原始解析树和执行分析和重写
由于分析器或重写器可能会将一个查询扩展为多个查询,因此会返回一个查询节点列表。
*/
}
1.2.1 pg_analyze_and_rewrite_fixedparams中调用parse_analyze_fixedparams
执行分析的函数为parse_analyze_fixedparams
Query *parse_analyze_fixedparams(RawStmt *parseTree, const char *sourceText,
const Oid *paramTypes, int numParams,
QueryEnvironment *queryEnv){
/*
分析原始解析树并将其转换为查询形式。
返回值为Query节点指针类型
*/
}
Query结构体:
typedef struct Query
{
NodeTag type;
CmdType commandType; /* select|insert|update|delete|merge|utility */
QuerySource querySource; /* 查询来源 */
uint64 queryId; /* 查询标识符(可以由插件设置) */
bool canSetTag; /* 是否设置命令结果标记? */
Node *utilityStmt; /* 若 commandType == CMD_UTILITY,则为非空 */
int resultRelation; /* INSERT/UPDATE/DELETE/MERGE 的目标关系的 rtable 索引;SELECT 为 0 */
bool hasAggs; /* 目标列表或 havingQual 中是否有聚合函数 */
bool hasWindowFuncs; /* 目标列表中是否有窗口函数 */
bool hasTargetSRFs; /* 目标列表中是否有返回集函数 */
bool hasSubLinks; /* 是否有子查询 SubLink */
bool hasDistinctOn; /* distinctClause 是否来自 DISTINCT ON */
bool hasRecursive; /* 是否指定了 WITH RECURSIVE */
bool hasModifyingCTE; /* WITH 中是否有 INSERT/UPDATE/DELETE */
bool hasForUpdate; /* 是否指定了 FOR [KEY] UPDATE/SHARE */
bool hasRowSecurity; /* 重写器是否应用了某些 RLS 策略 */
bool isReturn; /* 是否为 RETURN 语句 */
List *cteList; /* WITH 列表(CommonTableExpr 列表) */
List *rtable; /* 范围表条目列表 */
FromExpr *jointree; /* 表联接树(FROM 和 WHERE 子句);MERGE 中的 USING 子句 */
List *mergeActionList; /* MERGE 的操作列表 */
bool mergeUseOuterJoin; /* 是否使用外连接 */
List *targetList; /* 目标列表(TargetEntry 列表) */
OverridingKind override; /* OVERRIDING 子句 */
OnConflictExpr *onConflict; /* ON CONFLICT DO [NOTHING | UPDATE] */
List *returningList; /* 返回值列表(TargetEntry 列表) */
List *groupClause; /* 排序分组子句(SortGroupClause 列表) */
bool groupDistinct; /* 分组是否使用 DISTINCT? */
List *groupingSets; /* 若存在,表示 GroupingSet 列表 */
Node *havingQual; /* 应用于分组的限制条件 */
List *windowClause; /* 窗口子句列表(WindowClause 列表) */
List *distinctClause; /* DISTINCT 子句列表(SortGroupClause 列表) */
List *sortClause; /* 排序子句列表(SortGroupClause 列表) */
Node *limitOffset; /* 跳过的结果元组数目(int8 表达式) */
Node *limitCount; /* 返回的结果元组数目(int8 表达式) */
LimitOption limitOption; /* 限制类型 */
List *rowMarks; /* RowMarkClause 列表 */
Node *setOperations; /* 若是 UNION/INTERSECT/EXCEPT 查询的顶层,则为集合操作树 */
List *constraintDeps; /* 语句依赖的 pg_constraint OID 列表 */
List *withCheckOptions; /* WithCheckOption 列表(在重写期间添加) */
/*
* 下面两个字段标识包含此查询的源文本字符串的部分。通常只在顶层查询中填充,
* 而不是在子查询中填充。当未设置时,它们可能都为零,或者都为 -1,表示"未知"。
*/
int stmt_location; /* 起始位置,若未知则为 -1 */
int stmt_len; /* 字节长度;0 表示"剩余部分" */
} Query;
在函数parse_analyze_fixedparams中调用transformTopLevelStmt函数将parse tree 转换成 query tree
通过transformStmt函数选择合适的处理模式
在该语句中使用的是transformSelectStmt函数来执行
transformSelectStmt函数中根据结构体SelectStmt对Query结构体进行赋值
最终将Query类型的结构体返回给pg_analyze_and_rewrite_fixedparams中的参数变量query,完成parse tree 到 query tree的转换
1.2.2 pg_analyze_and_rewrite_fixedparams中调用pg_rewrite_query
pg_analyze_and_rewrite_fixedparams函数还需要完成对query的重写
调用pg_rewrite_query函数,将值返回给变量querytree_list
List* pg_rewrite_query(Query* query){
/*
对解析分析产生的查询执行重写。
参数是parse_analyze_fixedparams函数的返回值
返回值是List类型的指针
*/
}
在该函数中调用QueryRewrite函数做重写常规查询,QueryRewrite的参数是pg_rewrite_query的形参,返回值又作为pg_rewrite_query的返回值返回给pg_analyze_and_rewrite_fixedparams函数,最终返回给exec_simple_query函数中的变量querytree_list
List* QueryRewrite(Query* parsetree){
/*
通过该函数重写一个查询,可能返回 0 个或多个查询。
参数:解析树,是pg_rewrite_query的形参
返回值:List类型的指针
*/
}
主要步骤:
step 1 : 应用所有非 SELECT 规则,可能得到 0 个或多个查询
调用RewriteQuery函数
static List* RewriteQuery(Query* parsetree, List* rewrite_events, int orig_rt_length){
/*
应用所有非 SELECT 规则,可能得到 0 个或多个查询
参数: parsetree:分析树,QueryRewrite的传参
rewrit_events: 空的List
orig_rt_length : 0
返回值:rewritten : List类型的指针,是重写后的查询
*/
}
对于SELECT查询语句,在该函数中调用lappend函数,返回值是rewritten
List* lappend(List* list, void* datum){
/*
向列表添加一个指针。返回指向修改后列表的指针。
*/
}
最终lappend的返回值传递给RewriteQuery的返回值,作为QueryRewrite函数中的变量querylist。
step 2 : 对每个查询应用所有 RIR 规则
遍历querylist,对每个query都执行RIR规则(在该查询语句中querylist只有一个query)
通过调用firRIRrules函数来实现RIR规则
static Query* fireRIRrules(Query* parsetree, List* activateRIRs){
/*
对parsetree应用RIR规则
参数:parsetree : RewriteQuery返回List中的元素
activateRIRs:已经使用RIR处理后的views的OID列表,这里默认为空List
*/
}
fireRIRrules调用rewriteRuleAction函数将RIR规则应用到查询树中,得到一个查询树的链表返回给函数RewriteQuery,RewriteQuery的带重写后的查询列表之后还会对每一个查询树进行重写,最后返回给QueryRewrite函数的变量querylist
step 3 : 将step 2 得到的变量作为查询结果返回
最终将该结果返回给exec_simple_query函数的变量querytree_list
1.3 exec_simple_query中调用pg_plan_queries
在exec_simple_query函数中,执行完查询重写后,执行函数pg_plan_queries为重写后的querytree_list生成plan
List* pg_plan_queries(List* querytrees, const char *query_string, int cursorOptions, ParamListInfo boundParams){
/*
为重写后的querytree_list生成plan
参数: querytrees : 重写后的解析树
query_string : 输入的命令
cursorOptions : 这里为CURSOR_OPT_PARALLEL_OK 0x0800 并行
boundParams : NULL
返回值 : 生成的plan列表 为List类型的指针
*/
}
函数pg_plan_queries是为所有query递归生成plans
在其内部使用pg_plan_query为每个query生成plan
最后在pg_plan_queries中将plan添加到List中
PlannedStmt* pg_plan_query(Query *querytree, const char *query_string, int cursorOptions,
ParamListInfo boundParams){
/*
为一个已经重写的查询生成一个计划。
参数: querytrees : 重写后的解析树
query_string : 输入的命令
cursorOptions : 这里为CURSOR_OPT_PARALLEL_OK 0x0800 并行
boundParams : NULL
返回值 : 生成的plan 是PlannedStmt类型的指针,为List中的元素
*/
}
在pg_plan_query函数中调用planner再调用standard_planner函数生成具体的plan
PlannedStmt* standard_planner(Query *parse, const char *query_string, int cursorOptions,
ParamListInfo boundParams){
/*
为一个已经重写的查询生成一个计划。
参数: querytrees : 重写后的解析树
query_string : 输入的命令
cursorOptions : 这里为CURSOR_OPT_PARALLEL_OK 0x0800 并行
boundParams : NULL
返回值 : 生成的plan 是PlannedStmt类型的指针,为List中的元素
*/
}
最终返回值返回给pg_plan_queries函数中的stmt变量
并在pg_plan_queries函数中添加到List中,返回给exec_simple_query函数中的变量plantree_list
1.4 执行阶段 exec_simple_query中调用PortalRun
查询执行一个sQL语句时,用一个Portal作为输入数据,它包含着SQL语句相关的的所有信息:如查询树、计划树、执行状态等。Portal结构体如下
typedef struct PortalData
{
/* 书面数据 */
const char *name; /* Portal的名称 */
const char *prepStmtName; /* 源预处理语句(如果没有则为NULL) */
MemoryContext portalContext; /* Portal的辅助内存 */
ResourceOwner resowner; /* Portal拥有的资源 */
void (*cleanup) (Portal portal); /* 清理hook */
/*
* 记录门户被创建或使用的子事务状态数据。如果门户是从前一个事务中保留下来的,
* 则两个 subxids 都是 InvalidSubTransactionId。否则,createSubid 是创建子事务,
* activeSubid 是我们运行门户的最后一个子事务。
*/
SubTransactionId createSubid; /* 创建子事务 */
SubTransactionId activeSubid; /* 最后一个有活动的子事务 */
int createLevel; /* 创建子事务的嵌套层级 */
/* 门户将执行的查询或查询 */
const char *sourceText; /* SQL语句(从8.4开始,永不为NULL) */
CommandTag commandTag; /* 原始查询的命令标记 */
QueryCompletion qc; /* 执行查询的命令完成数据 */
List *stmts; /* PlannedStmts 列表 */
CachedPlan *cplan; /* CachedPlan,如果 stmts 来自其中之一 */
ParamListInfo portalParams; /* 传递给查询的参数 */
QueryEnvironment *queryEnv; /* 查询环境 */
/* 特性/选项 */
PortalStrategy strategy; /* 见上面 */
int cursorOptions; /* DECLARE CURSOR 选项位 */
bool run_once; /* Portal只会运行一次 */
/* 状态数据 */
PortalStatus status; /* 见上面 */
bool portalPinned; /* 固定的Portal无法被删除 */
bool autoHeld; /* 自动从固定转换为持有(参见 HoldPinnedPortals()) */
/* 如果不为 NULL,则 Executor 正在活动中;最终需要调用 ExecutorEnd: */
QueryDesc *queryDesc; /* 执行器调用所需的信息 */
/* 如果门户返回元组,则这是它们的 tupdesc: */
TupleDesc tupDesc; /* 结果元组的描述符 */
/* 这些是用于列的格式代码: */
int16 *formats; /* 每列的格式代码 */
/*
* 用于门户查询执行的最外层 ActiveSnapshot。除了一些实用程序命令外,
* 我们需要这样一个快照来存在。这确保查询结果中的 TOAST 引用可以被解压,
* 并有助于减少进程的暴露的 xmin。
*/
Snapshot portalSnapshot; /* 活跃快照,如果没有则为 NULL */
/*
* 为持有的游标或 PORTAL_ONE_RETURNING 或 PORTAL_UTIL_SELECT 查询存储元组的位置。
* (超过其事务结束的游标不再有任何活动的执行状态。)
*/
Tuplestorestate *holdStore; /* 用于可持有游标的存储 */
MemoryContext holdContext; /* 包含 holdStore 的内存 */
/*
* 用于从 holdStore 读取元组的快照。如果元组可能包含 TOAST 引用,我们必须
* 保持对此快照的引用,因为释放快照可能允许最近死亡的行被清理,
* 以及属于它们的任何 TOAST 数据。对于一个持有的游标,我们避免需要
* 保持这样一个快照,通过强制解压数据。
*/
Snapshot holdSnapshot; /* 注册的快照,如果没有则为 NULL */
/*
* atStart、atEnd 和 portalPos 表示当前游标位置。在第一行之前,portalPos 为零,
* 在获取查询的第 N 行后,portalPos 为 N。在我们执行结束之后,
* portalPos = 查询中的行数,atEnd 为 true。注意,atStart 意味着 portalPos == 0,
* 但反之不成立:我们可能只回退到第一行,而不是到开头。另请注意,
* 各种代码检查 atStart 和 atEnd,但只有门户移动例程应该触摸 portalPos。
*/
bool atStart;
bool atEnd;
uint64 portalPos;
/* 演示数据,主要用于 pg_cursors 系统视图 */
TimestampTz creation_time; /* 定义此Portal的时间 */
bool visible; /* 是否包括此Portal在 pg_cursors 中? */
} PortalData;
执行过程
- PortalStart:初始化
- PortalRun:执行
- PortalDrop:清理
主要跟踪执行过程中的函数
在exec_simply_query中通过调用PortalRun来执行语句
PortalRun函数运行一个或多个Port查询
bool PortalRun(Portal portal, long count, bool isTopLevel, bool run_once,
DestReceiver *dest, DestReceiver *altdest,
QueryCompletion *qc){
/*
函数运行一个或多个Port查询
参数:
portal:要执行的数据结构
count : <= 0 解释为无操作:执行启动或者关闭,不会发生额外的操作
FETCH_ALL, 解释为对查询所有记录,是该语句所传递的参数
isTopLevel:如果查询是在后台 "顶层 "执行,则为 true。传入为true
run_once:是否一次运行,传入为true
dest:主查询发送的位置
altdest: 非主查询发送位置
qc:存储命令完成状态数据的位置
返回 :完成执行返回true,否则false
*/
}
在PortalRun函数中,根据portal->strategy == PORTAL_ONE_SELECT执行查询及换,对于本语句调用函数PortalRunSelect
1.4.1 执行查询计划 ExecutePlan函数
查询计划的实际执行由函数execute完成,
PortalRun调用PortalRunSelect,
PortalRunSelect函数调用ExecutorRun,
ExecutorRun再次调用standard_ExecutorRun,
standard_ExecutorRun最后调用ExecutePlan完成
static void ExecutePlan(EState *estate,
PlanState *planstate,
bool use_parallel_mode,
CmdType operation,
bool sendTuples,
uint64 numberTuples,
ScanDirection direction,
DestReceiver *dest,
bool execute_once)
{
/*
该函数是一个循环,每一次循环都通过ExecProcNode函数从计划节点树中获取一个元组,并对该进行相应的处理,然后返回处理结果。当ExecProcNode从计划节点状态数中取不到有效元组,结束循环
*/
}
static inline TupleTableSlot *ExecProcNode(PlanState *node){
/*
从node中获取元组
*/
}
1.5 结果输出 ReadyForQuery函数
该函数在PostgresMain中调用,查询执行完毕,告诉 dest 可以进行新的查询了。并将之前的查询结果输出到client上
在该函数中调用socket_flush函数,将查询结果刷到client中
socket_flush调用internal_flush 来 flush待处理的输出
函数中利用send来发送数据。