Portal(门户),也称为策略选择模块,根据sql语句类型选择不同的执行模块(ProcessUtility、Executor)。
SQL语句类型包括:可优化语句、数据定义语句。

QD执行会从exec_simple_query进入,QE执行从exec_mpp_query进入。
首先初识Portal内部数据结构:

策略
只包含一个SELECT查询。
select * from t1;
包含一个INSERT/UPDATE/DELETE查询,且带RETURNING条件。
INSERT INTO ret_tbl (id) VALUES (3) RETURNING id INTO tableId;
包含一个SELECT查询并且有修改的CTE。
WITH ins AS (
INSERT INTO t1 (t1_id) VALUES(1) RETURNING t1_id
)
SELECT * from ins;
例如:下面这个就不是PORTAL_ONE_MOD_WITH,而是PORTAL_MULTI_QUERY。
WITH ins AS (
SELECT * from t1
) INSERT INTO t2
(t2_id, col2)
SELECT * from ins;
包含一个utility语句,且该语句执行会返回像SELECT那样有输出结果。
postgres=# explain select * from t1;
QUERY PLAN
-------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (cost=0.00..431.00 rows=1 width=12)
-> Seq Scan on t1 (cost=0.00..431.00 rows=1 width=12)
Optimizer: Pivotal Optimizer (GPORCA)
(3 rows)
postgres=# EXECUTE t1_fn_ret(2, 'helloworld');
t1_id | col1
-------+------------
2 | helloworld
(1 row)
INSERT 0 1
其他情况,例如:PORTAL_MULTI_QUERY + PORTAL_MULTI_QUERY
PREPARE t1_fn (int, text) AS INSERT INTO t1 VALUES($1, $2);
状态
这几个状态会在下面依次引入。
创建一个新的Portal,传入参数均为: CreatePortal("", true, true),表示创建一个匿名的Portal,允许重复且重复后保持沉默。
CreatePortal逻辑:
Portal
CreatePortal(const char *name, bool allowDup, bool dupSilent)
执行完毕后,便创建好了一个状态为PORTAL_NEW的Portal。
定义portal数据,包含了:查询语句sourceText、PlannedStmts、查询完成标记qc。
注意:QD上根据传递进来的stmt来设置nodeTag,但是QE上为T_Query,因为QE上不是parsed statement,所以不是 T_SelectStmt。
最终设置Portal状态为PORTAL_DEFINED。
准备好portal,主要有如下几步:
1. 设置ddesc,该信息为QD到QE上的额外信息,QD上为NULL,QE上不为NULL。
2. 设置全局参数,例如:当前活跃的portal、resourceOwner、context。
3. 设置portal参数字段:portalParams,同样QD上为NULL,QE上不为NULL。
4. 设置portal策略(ChoosePortalStrategy)。
如下图所示:输入为查询计划链表,针对PORTAL_ONE_SELECT、PORTAL_ONE_MOD_WITH、PORTAL_UTIL_SELECT、PORTAL_ONE_RETURNING都是要求一个计划,首先判断是Query还是PlannedStmt,一般情况下的查询语句基本都是PlannedStmt,对于像PREPARE st(int) as select * from t1之类utility语句第一次调用ChoosePortalStrategy返回PORTAL_MULTI_QUERY(命中PlannedStmt),第二次调用返回PORTAL_ONE_SELECT(命中Query)。

choose
5. 根据portal策略初始化portal,最重要的是初始化tupDesc与cursor postion。

例如:"QUERY PLAN"、"t1_id、col1"就是tupDesc。
postgres=# explain select * from t1;
QUERY PLAN
-------------------------------------------------------------------------------
Gather Motion 3:1 (slice1; segments: 3) (cost=0.00..431.00 rows=1 width=12)
-> Seq Scan on t1 (cost=0.00..431.00 rows=1 width=12)
Optimizer: Pivotal Optimizer (GPORCA)
(3 rows)
postgres=# EXECUTE t1_fn_ret(2, 'helloworld');
t1_id | col1
-------+------------
2 | helloworld
(1 row)
INSERT 0 1
6. 在执行Portal过程中发生异常,设置portal的状态为PORTAL_FAILED;否则,下一步。
7. 设置Portal状态为PORTAL_READY。
根据sql的语句类型选择不同的执行路径,获取元组数据,完成portal工作,运行完之后要么Done要么下一轮(READY,而非ACTIVE)。
portal策略执行路径如下:

此外,上述图中填充holdStore逻辑如下:

如果不想一次执行整个命令,可以设置一个封装该命令的游标(cursor), 然后每次读取几行命令结果。
name [ [ NO ] SCROLL ] CURSOR [ ( arguments ) ] FOR query;
例如:
DECLARE liahona SCROLL CURSOR FOR SELECT * FROM t1;
该命令运行机制为:首先识别到是一个数据定义语句,便会调用ProcessUtility,随后解析从PlannedStmt中的utilityStmt识别出是一个T_DeclareCursorStmt节点,调用PerformCursorOpen执行Declare cursor命令。
PerformCursorOpen处理逻辑如下:
关闭游标,实际就是关闭Portal,调用PerformPortalClose。
如下两个操作:
CLOSE cursor_name;
CLOSE ALL;
如果传入的名字为空,则是CLOSE ALL关闭所有非活跃portal,否则,只关闭指定的portal(cursor)。
FETCH与MOVE语法分别如下:
FETCH [ direction { FROM | IN } ] cursor INTO target;
MOVE [ direction { FROM | IN } ] cursor;
FETCH从游标中检索n行到目标中, 目标可以是一个行变量、记录变量、逗号分隔的普通变量列表, 就像SELECT INTO一样, 如果没有获取到数据,目标会设为NULL。
MOVE重新定位一个游标,而不需要检索任何数据,例如:一旦游标位置确定,则可以删除或更新行。
MOVE cursor_variable;
UPDATE table_name
SET column = value, ...
WHERE CURRENT OF cursor_variable;
从实现层面两者都会进入到PerformPortalFetch,都被解析为FetchStmt,内部有个成员ismove决定是MOVE还是FETCH。
不管是哪个,都会指定cursor名,有了这个名字,便知道了portal,随后调用PortalRunFetch来获取结果。
PortalRunFetch内部会像PortalRun运行一样,首先设置portal状态为Active,随后根据策略选择不同的调用链。
DoPortalRunFetch内部实现,会考虑传入的direction,决定是前向还是后向等不同方向的扫描,最后调用PortalRunSelect获取数据,注意:gpdb不支持backward scan,但是pg支持。

本节完