注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:
1、Oracle中ROWNUM的使用技巧,点击前往
2、oracle中rownum和rowid的区别,点击前往
3、参考书籍:《涂抹Oracle 三思笔记之一步一步学Oracle》
4、参考书籍:《Oracle Database 11g数据库管理艺术》
5、PostgreSQL Oracle 兼容性之 - rownum,点击前往
6、EDB Postgres Advanced Server User Guides,点击前往
7、AntDB 开源仓库,点击前往 或者 AntDB 本人gitee仓库,点击前往
8、PostgreSQL数据库仓库链接,点击前往
1、本文内容全部来源于开源社区 GitHub和以上博主的贡献,本文也免费开源(可能会存在问题,评论区等待大佬们的指正)
2、本文目的:开源共享 抛砖引玉 一起学习
3、本文不提供任何资源 不存在任何交易 与任何组织和机构无关
4、大家可以根据需要自行 复制粘贴以及作为其他个人用途,但是不允许转载 不允许商用 (写作不易,还请见谅 )
5、本文仅适于从事于PostgreSQL数据库内核开发者和数据库爱好者,对普通读者而言难度较大 但对于希望从事于数据库内核开发的初学者来说,是一次机会十分难得的学习案例 (不过还是非常非常烧脑的)
6、有一个自称是粉丝的人私信我,上来对我一顿臭骂 说文章很乱内容很差很臃肿看不明白不想看之类的,唉 我真的不知道该说什么 这是一个十分复杂逻辑性非常强的新功能,从前期了解 准备 编码 调试 优化 测试到文章汇总 我花费了巨大的时间 每天几乎都是凌晨才睡觉的 而你凭什么不想花费一点点精力和时间就能够弄明白搞清楚?简直搞笑 我也送你一句:有你这样的粉丝 是我的悲哀!
7、本文内容基于PostgreSQL13.0源码开发而成
学习目标:
在PostgreSQL上实现Oracle数据库的 rownum伪列 功能,这里的实现不是说使用某些limit的功能来 曲线救国 地实现类似功能。而是将执行与在Oracle上一模一样的SQL语句!
学习内容:(详见目录)
1、Oracle数据库的 rownum说明
2、PostgreSQL数据库的limit方式原理
3、PostgreSQL数据库新增语法的实现
学习时间:
2020年12月19日 14:58:00
学习产出:
1、PostgreSQL数据库内核新增语法 个
2、CSDN 技术博客 1篇
3、PostgreSQL数据库内核深入学习
注:下面我们所有的学习环境是Centos7+PostgreSQL13.0+Oracle11g+MySQL5.7
postgres=# select version();
version
-----------------------------------------------------------------------------
PostgreSQL 13.0 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 7.1.0, 64-bit
(1 row)
postgres=#
#-----------------------------------------------------------------------------#
SQL> select * from v$version;
BANNER
--------------------------------------------------------------------------------
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Production
PL/SQL Release 11.2.0.1.0 - Production
CORE 11.2.0.1.0 Production
TNS for Linux: Version 11.2.0.1.0 - Production
NLSRTL Version 11.2.0.1.0 - Production
SQL>
#-----------------------------------------------------------------------------#
mysql> select version();
+-----------+
| version() |
+-----------+
| 5.7.19 |
+-----------+
1 row in set (0.06 sec)
mysql>
演示背景如下:
SQL> select rownum ,id,time,name from test_rownum;
ROWNUM ID TIME NAME
---------- ---------- --------------------------------------------------------------------------- ----------------
1 1 18-AUG-20 10.01.38.918770 AM song
2 2 18-AUG-20 10.02.09.671042 AM zhang
3 3 18-AUG-20 10.02.27.206742 AM li
4 4 18-AUG-20 10.02.53.959601 AM zhao
5 5 18-AUG-20 10.03.10.639620 AM sun
SQL> select * from test_rownum;
ID TIME NAME
---------- --------------------------------------------------------------------------- ----------------
1 18-AUG-20 10.01.38.918770 AM song
2 18-AUG-20 10.02.09.671042 AM zhang
3 18-AUG-20 10.02.27.206742 AM li
4 18-AUG-20 10.02.53.959601 AM zhao
5 18-AUG-20 10.03.10.639620 AM sun
SQL> desc test_rownum;
Name Null? Type
----------------------------------------------------------------------------------- -------- --------------------------------------------------------
ID NUMBER(38)
TIME TIMESTAMP(6)
NAME VARCHAR2(16)
SQL>
在Oracle数据库中,伪列rownum就像表中的列一样,但是在表中并不存储。伪列只能查询,不能进行增删改操作。
功能: 在查询的结果集中,ROWNUM为结果集中每一行标识一个行号,第一行返回1,第二行返回2,以此类推。通过ROWNUM伪列可以限制查询结果集中返回的行数。
注:这里不要与rowid混淆。ROWNUM与ROWID不同,ROWID是插入记录时生成,ROWNUM是查询数据时生成。ROWID标识的是行的物理地址;而ROWNUM标识的是查询结果中的行的次序。 后者可以说是物理存在的,表示记录在表空间中的唯一位置ID,在数据库中唯一。只要记录没被搬动过,rowid是不变的。rowid 相对于表来说又像表中的一般列,所以以 rowid 为条件就不会有 rownum那些情况发生(如下)。
SQL> select rowid,rownum ,id,time,name from test_rownum;
ROWID ROWNUM ID TIME NAME
------------------ ---------- ---------- --------------------------------------------------------------------------- ----------------
AAASPBAABAAAU5xAAA 1 1 18-AUG-20 10.01.38.918770 AM song
AAASPBAABAAAU5xAAB 2 2 18-AUG-20 10.02.09.671042 AM zhang
AAASPBAABAAAU5xAAC 3 3 18-AUG-20 10.02.27.206742 AM li
AAASPBAABAAAU5xAAD 4 4 18-AUG-20 10.02.53.959601 AM zhao
AAASPBAABAAAU5xAAE 5 5 18-AUG-20 10.03.10.639620 AM sun
SQL>
下面展示一些rownum的常用功能点:
实例1:查询表中前3行数据的全部信息
SQL> select rowid,rownum as rn,id,time,name from test_rownum where rownum<=3;
ROWID RN ID TIME NAME
------------------ ---------- ---------- --------------------------------------------------------------------------- ----------------
AAASPBAABAAAU5xAAA 1 1 18-AUG-20 10.01.38.918770 AM song
AAASPBAABAAAU5xAAB 2 2 18-AUG-20 10.02.09.671042 AM zhang
AAASPBAABAAAU5xAAC 3 3 18-AUG-20 10.02.27.206742 AM li
SQL>
实例2:查询出表中的ID最大的3行的全部信息,并按照id逆序
SQL> select rowid,rownum as rn,id,time,name from test_rownum where rownum<=3;
ROWID RN ID TIME NAME
------------------ ---------- ---------- --------------------------------------------------------------------------- ----------------
AAASPBAABAAAU5xAAA 1 1 18-AUG-20 10.01.38.918770 AM song
AAASPBAABAAAU5xAAB 2 2 18-AUG-20 10.02.09.671042 AM zhang
AAASPBAABAAAU5xAAC 3 3 18-AUG-20 10.02.27.206742 AM li
SQL> select rowid,rownum as rn,id,time,name from test_rownum where rownum<=3 ORDER BY id desc;
ROWID RN ID TIME NAME
------------------ ---------- ---------- --------------------------------------------------------------------------- ----------------
AAASPBAABAAAU5xAAC 3 3 18-AUG-20 10.02.27.206742 AM li
AAASPBAABAAAU5xAAB 2 2 18-AUG-20 10.02.09.671042 AM zhang
AAASPBAABAAAU5xAAA 1 1 18-AUG-20 10.01.38.918770 AM song
SQL> select rownum as rn,t.* from (select * from test_rownum ORDER BY id desc) t where rownum<=3;
RN ID TIME NAME
---------- ---------- --------------------------------------------------------------------------- ----------------
1 5 18-AUG-20 10.03.10.639620 AM sun
2 4 18-AUG-20 10.02.53.959601 AM zhao
3 3 18-AUG-20 10.02.27.206742 AM li
SQL>
注:"ID最大的3行"需要先降序排序,再取前3名。因为生成ROWNUM操作比排序要早,排序时已经连同ROWNUM一起排序了,因此不能直接在实例1的语句中直接加上Order by就行,而是需要对排序的结果重新做二次查询,产生新的ROWNUM才能作为查询的条件依据。(如上,此时的ROWNUM是第二次查询后的ROWNUM)
实例3:rownum分页
# 合理地使用子查询很重要
SQL> select * from (select rownum as rn,id,time,name from test_rownum where rownum<=4) where rn>1;
RN ID TIME NAME
---------- ---------- --------------------------------------------------------------------------- ----------------
2 2 18-AUG-20 10.02.09.671042 AM zhang
3 3 18-AUG-20 10.02.27.206742 AM li
4 4 18-AUG-20 10.02.53.959601 AM zhao
SQL>
1、内部查询中得到rownum并且用别名rn记录,供外层条件使用
2、内部查询的rownum,与外面的rownum列是平等的两列
3、使用的rn是内层产生的rownum,在外层看来,内层查询的rownum是正常的一列
4、查找到第二行以后的记录 可使用以下的 子查询方法 来解决。注意子查询中的 rownum必须要有别名,否则还是不会查出记录来,这是因为rownum不是某个表的列,如果不起别名的话,无法知道rownum是子查询的列还是主查询的列
5、查询rownum在 某区间的数据,必须使用 子查询方法 。例如要查询rownum在第二行到第三行之间的数据,包括第二行和第三行数据,那么我们只能写以下操作:先让它返回小于等于三的记录行,然后在主查询中判断新的rownum的别名列大于等于二的记录行。但是这样的操作会在大数据集中影响速度
下面展示一些rownum的常见的"坑"(理解伪列的意义很重要):
实例4:先要有结果集才有意义!
SQL> select rownum ,id,time,name from test_rownum where rownum <3;
ROWNUM ID TIME NAME
---------- ---------- ------------------------------------------------------------ ----
1 1 18-AUG-20 10.01.38.918770 AM song
2 2 18-AUG-20 10.02.09.671042 AM zhang
SQL> select rownum ,id,time,name from test_rownum where rownum >3;
no rows selected
SQL>
我们都知道这个表里面是有5行数据的,第一条SQL正常执行 可第二条怎么就没数据呢?
OK,我们再来理解一下所谓的伪列rownum:ROWNUM是对结果集加的一个伪列,即先查到结果集之后再加上去的一个列 (强调:先要有结果集)。简而言之: rownum 是对符合条件结果的序列号。它总是从1开始排起的。所以你选出的结果不可能没有1,而有其他大于1的值。
从另外一个角度理解:rownum >3 之所以没有记录,是因为第一条不满足去掉的话,第二条的rownum又成了1,所以永远没有满足条件的记录。或者可以这样理解:它是一个序列,是oracle数据库从数据文件或缓冲区中读取数据的顺序。它取得第一条记录则rownum值为1,第二条为2,依次类推。如果你用>,>=,=,between...and
这些条件去做判断,因为从缓冲区或数据文件中得到的第一条记录的rownum为1,则被删除, 接着取下条,可是它的rownum还是1,又被删除,照此往复,便没有了数据。
实例5:!= 和 <
的效果怎么一样?
SQL> select rownum ,id,time,name from test_rownum;
ROWNUM ID TIME NAME
---------- ---------- --------------------------------------------------------------------------- ----------------
1 1 18-AUG-20 10.01.38.918770 AM song
2 2 18-AUG-20 10.02.09.671042 AM zhang
3 3 18-AUG-20 10.02.27.206742 AM li
4 4 18-AUG-20 10.02.53.959601 AM zhao
5 5 18-AUG-20 10.03.10.639620 AM sun
SQL> select rownum ,id,time,name from test_rownum where rownum <3;
ROWNUM ID TIME NAME
---------- ---------- --------------------------------------------------------------------------- ----------------
1 1 18-AUG-20 10.01.38.918770 AM song
2 2 18-AUG-20 10.02.09.671042 AM zhang
SQL> select rownum ,id,time,name from test_rownum where rownum !=3;
ROWNUM ID TIME NAME
---------- ---------- --------------------------------------------------------------------------- ----------------
1 1 18-AUG-20 10.01.38.918770 AM song
2 2 18-AUG-20 10.02.09.671042 AM zhang
SQL>
这上下两条返回的结果是一致的,道理和上面实例一样。因为是在查询到结果集后,显示完第 2 条记录后,之后的记录也都是 != 3 or >=3
,所以只显示前面2条记录。也可以这样理解:rownum 为2后的记录的 rownum为3,因条件为 !=3,所以去掉,其后记录补上,rownum又是3,也去掉,如果下去也就只会显示前面2条记录了。
实例6: rownum >1
时查不到一条记录,而 rownum >0 或 rownum >=1
却总显示所有的记录
SQL> select rownum ,id,time,name from test_rownum where rownum > 1;
no rows selected
SQL> select rownum ,id,time,name from test_rownum where rownum >0;
ROWNUM ID TIME NAME
---------- ---------- --------------------------------------------------------------------------- ----------------
1 1 18-AUG-20 10.01.38.918770 AM song
2 2 18-AUG-20 10.02.09.671042 AM zhang
3 3 18-AUG-20 10.02.27.206742 AM li
4 4 18-AUG-20 10.02.53.959601 AM zhao
5 5 18-AUG-20 10.03.10.639620 AM sun
SQL> select rownum ,id,time,name from test_rownum where rownum >=1;
ROWNUM ID TIME NAME
---------- ---------- --------------------------------------------------------------------------- ----------------
1 1 18-AUG-20 10.01.38.918770 AM song
2 2 18-AUG-20 10.02.09.671042 AM zhang
3 3 18-AUG-20 10.02.27.206742 AM li
4 4 18-AUG-20 10.02.53.959601 AM zhao
5 5 18-AUG-20 10.03.10.639620 AM sun
SQL>
原因:因为 rownum 是在查询到的结果集后加上去的,它总是从1开始。下面这个例子亦是这个原因!
实例7:between 1 and 10
或者 between 0 and 10
能查到结果,而用 between 2 and 10
却得不到结果
SQL> select rownum ,id,time,name from test_rownum where rownum between 0 and 6;
ROWNUM ID TIME NAME
---------- ---------- --------------------------------------------------------------------------- ----------------
1 1 18-AUG-20 10.01.38.918770 AM song
2 2 18-AUG-20 10.02.09.671042 AM zhang
3 3 18-AUG-20 10.02.27.206742 AM li
4 4 18-AUG-20 10.02.53.959601 AM zhao
5 5 18-AUG-20 10.03.10.639620 AM sun
SQL> select rownum ,id,time,name from test_rownum where rownum between 1 and 7;
ROWNUM ID TIME NAME
---------- ---------- --------------------------------------------------------------------------- ----------------
1 1 18-AUG-20 10.01.38.918770 AM song
2 2 18-AUG-20 10.02.09.671042 AM zhang
3 3 18-AUG-20 10.02.27.206742 AM li
4 4 18-AUG-20 10.02.53.959601 AM zhao
5 5 18-AUG-20 10.03.10.639620 AM sun
SQL> select rownum ,id,time,name from test_rownum where rownum between 2 and 5;
no rows selected
SQL>
至理名言:从上可以看出,任何时候想把 rownum = 1
这条记录抛弃都是不对的,它在结果集中是不可或缺的,少了rownum=1
就像空中楼阁一般不能存在,所以任何一个 rownum
条件要包含到 1。但如果就是想要用 rownum > 3 这种条件的话就要用嵌套语句,把 rownum 先生成,然后对它进行查询。(就像实例3那样!) 因此需要时刻谨记:rownum都是从1开始,但是1以上的自然数在rownum做 等于判断时 是认为都是false条件,所以永远无法查到rownum = n(n>1的自然数)这种类似的条件判断。
实例8:rownum
不能以任何基表的名称作为前缀
SQL> select rownum ,t.id,t.name from test_rownum t;
ROWNUM ID NAME
---------- ---------- ----------------
1 1 song
2 2 zhang
3 3 li
4 4 zhao
5 5 sun
SQL> select t.rownum ,t.id,t.name from test_rownum t;
select t.rownum ,t.id,t.name from test_rownum t
*
ERROR at line 1:
ORA-01747: invalid user.table.column, table.column, or column specification
SQL> select test_rownum.rownum ,id,name from test_rownum;
select test_rownum.rownum ,id,name from test_rownum
*
ERROR at line 1:
ORA-01747: invalid user.table.column, table.column, or column specification
SQL>
实例9:rownum
分页的效率考虑
上面第3点的时候,只是简单的介绍一下子查询的强大功效。而接下来我们主要来看一下分页查询的实现及对应效率问题:
SQL> select rownum ,test.* from test;
ROWNUM ID NAME
---------- ---------- ----------
1 1 PostgreSQL
2 2 Oracle
3 3 MySQL
4 4 SQL server
SQL> SELECT * FROM ( SELECT test.*, ROWNUM rn FROM test) a WHERE a.rn BETWEEN 2 AND 5;
ID NAME RN
---------- ---------- ----------
2 Oracle 2
3 MySQL 3
4 SQL server 4
SQL> SELECT test.*, ROWNUM rn FROM test WHERE ROWNUM <=5 MINUS SELECT test.*, ROWNUM rn FROM test WHERE ROWNUM <=1;
ID NAME RN
---------- ---------- ----------
2 Oracle 2
3 MySQL 3
4 SQL server 4
SQL> SELECT * FROM ( SELECT test.*, ROWNUM rn FROM test WHERE ROWNUM <=5) a WHERE a.rn>=2;
ID NAME RN
---------- ---------- ----------
2 Oracle 2
3 MySQL 3
4 SQL server 4
SQL>
# 如上三种方式的效率依次:最低 较高 最高
注:PostgreSQL的商用发行版本 EDB公司的 EPAS产品(EDB Postgres Advanced Server版本:13
)完全实现了与Oracle一样的rownum功能(至目前为止13.0 PostgreSQL官方尚未开发出对应功能),其相关文档可以参见:
1、Database Compatibility for Oracle® Developer’s Guide,点击前往
2、EDB Postgres Advanced Server User Guides,点击前往
在PostgreSQL中,目前没有rownum虚拟列,但是实现同样的功能且语法上支持的 可以考虑limit:
# 背景如下:
postgres=# select * from test;
id | name
----+------------
1 | PostgreSQL
2 | Oracle
3 | MySQL
4 | SQL server
(4 rows)
postgres=#
第一种:使用窗口函数
row_number() over ()
或row_number() over (ORDER BY id)
来输出行号
postgres=# select row_number() OVER (ORDER BY id) as rownum ,* from test;
rownum | id | name
--------+----+------------
1 | 1 | PostgreSQL
2 | 2 | Oracle
3 | 3 | MySQL
4 | 4 | SQL server
(4 rows)
postgres=#
# 下面来看一下这个函数:
postgres=# \df row_number
List of functions
Schema | Name | Result data type | Argument data types | Type
------------+------------+------------------+---------------------+--------
pg_catalog | row_number | bigint | | window
(1 row)
postgres=# \sf row_number
CREATE OR REPLACE FUNCTION pg_catalog.row_number()
RETURNS bigint
LANGUAGE internal
WINDOW IMMUTABLE PARALLEL SAFE
AS $function$window_row_number$function$
postgres=#
// postgres\src\backend\utils\adt\windowfuncs.c
// 源码C函数如下:
/*
* row_number
* just increment up from 1 until current partition finishes.
*/
Datum
window_row_number(PG_FUNCTION_ARGS)
{
WindowObject winobj = PG_WINDOW_OBJECT();
int64 curpos = WinGetCurrentPosition(winobj);
WinSetMarkPosition(winobj, curpos);
PG_RETURN_INT64(curpos + 1);
}
第二种:
limit
语法支持
postgres=# select * from test limit 3;
id | name
----+------------
1 | PostgreSQL
2 | Oracle
3 | MySQL
(3 rows)
postgres=# select * from test limit 2 offset 1;
id | name
----+--------
2 | Oracle
3 | MySQL
(2 rows)
postgres=# select * from test limit 2 offset 2;
id | name
----+------------
3 | MySQL
4 | SQL server
(2 rows)
postgres=# select * from test limit 3 offset 2;
id | name
----+------------
3 | MySQL
4 | SQL server
(2 rows)
postgres=# select * from test limit 2 offset 3;
id | name
----+------------
4 | SQL server
(1 row)
postgres=#
第三种:临时序列 来输出行号
postgres=# select * from test;
id | name
----+------------
1 | PostgreSQL
2 | Oracle
3 | MySQL
4 | SQL server
(4 rows)
postgres=# create temp sequence if not exists tmp_seq;
CREATE SEQUENCE
postgres=# alter sequence tmp_seq restart with 1;
ALTER SEQUENCE
postgres=# select nextval('tmp_seq') as rownum, * from test;
rownum | id | name
--------+----+------------
1 | 1 | PostgreSQL
2 | 2 | Oracle
3 | 3 | MySQL
4 | 4 | SQL server
(4 rows)
postgres=#
# 然而如下:
postgres=# select nextval('tmp_seq') as rownum, * from test limit 2;
rownum | id | name
--------+----+------------
5 | 1 | PostgreSQL
6 | 2 | Oracle
(2 rows)
postgres=#
第四种:使用序列 增加某个字段生成序列值 来输出行号
postgres=# alter sequence tmp_seq restart with 1;
ALTER SEQUENCE
postgres=# alter table test add column rownum int;
ALTER TABLE
postgres=# update test set rownum=nextval('tmp_seq');
UPDATE 4
postgres=# select * from test ;
id | name | rownum
----+------------+--------
1 | PostgreSQL | 1
2 | Oracle | 2
3 | MySQL | 3
4 | SQL server | 4
(4 rows)
postgres=# select * from test where rownum > 2;
id | name | rownum
----+------------+--------
3 | MySQL | 3
4 | SQL server | 4
(2 rows)
postgres=#
看完以上四点 内心没有一点波澜(涟漪也没有),因为都不是我想要的(大家请看上面的方法,跟Oracle的rownum还相差甚远)。那么如何得到你从未拥有过的东西,只有去做你从未做过的事情!开整
下面先来看一下目前PostgreSQL数据库所支持的limit/offset语法,下面是示例:
select * from test limit 3;
select * from test limit 3 offset 2;
下面来看一下其解析过程:
语法层面 |
# postgres\src\backend\parser\gram.y
# 只展示重要的代码细节
SelectStmt
|
select_no_parens
|
select_no_parens
select_no_parens: select_clause opt_sort_clause for_locking_clause opt_select_limit
{
insertSelectOptions((SelectStmt *) $1, $2, $3,
$4,
NULL,
yyscanner);
$$ = $1;
}
| with_clause select_clause opt_sort_clause for_locking_clause opt_select_limit
{
insertSelectOptions((SelectStmt *) $2, $3, $4,
$5,
$1,
yyscanner);
$$ = $2;
}
......
select_clause:
simple_select { $$ = $1; }
| select_with_parens { $$ = $1; }
;
OK,接下来我们的重点将聚焦于opt_select_limit
:
opt_select_limit:
select_limit { $$ = $1; }
| /* EMPTY */ { $$ = NULL; }
;
select_limit:
limit_clause offset_clause
{
$$ = $1;
($$)->limitOffset = $2;
}
| offset_clause limit_clause
{
$$ = $2;
($$)->limitOffset = $1;
}
| limit_clause
{
$$ = $1;
}
| offset_clause
{
SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit));
n->limitOffset = $1;
n->limitCount = NULL;
n->limitOption = LIMIT_OPTION_COUNT;
$$ = n;
}
;
# --------------------------------------------------- #
# 如上可以看出来 offset 和 limit的先后都是可以的,示例如下:
postgres=# select * from test limit 2 offset 1;
id | name
----+--------
3 | MySQL
2 | Oracle
(2 rows)
postgres=# select * from test offset 1 limit 2;
id | name
----+--------
3 | MySQL
2 | Oracle
(2 rows)
postgres=#
# --------------------------------------------------- #
最后就是此次的重中之重:limit_clause
和offset_clause
limit_clause:
LIMIT select_limit_value
{
SelectLimit *n = (SelectLimit *) palloc(sizeof(SelectLimit));
n->limitOffset = NULL;
n->limitCount = $2;
n->limitOption = LIMIT_OPTION_COUNT;
$$ = n;
}
| LIMIT select_limit_value ',' select_offset_value
{
/* Disabled because it was too confusing, bjm 2002-02-18 */
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("LIMIT #,# syntax is not supported"),
errhint("Use separate LIMIT and OFFSET clauses."),
parser_errposition(@1)));
}
......
select_limit_value:
a_expr { $$ = $1; }
| ALL
{
/* LIMIT ALL is represented as a NULL constant */
$$ = makeNullAConst(@1);
}
;
如上limit_clause
就是解析我们的SQL中 limit 3
部分
postgres=# select * from test;
id | name
----+------------
4 | SQL server
3 | MySQL
2 | Oracle
1 | PostgreSQL
(4 rows)
postgres=# select * from test limit 2;
id | name
----+------------
4 | SQL server
3 | MySQL
(2 rows)
postgres=# select * from test limit all;
id | name
----+------------
4 | SQL server
3 | MySQL
2 | Oracle
1 | PostgreSQL
(4 rows)
postgres=#
同理offset_clause
解析offset 2
部分如下:
offset_clause:
OFFSET select_offset_value
{ $$ = $2; }
/* SQL:2008 syntax */
| OFFSET select_fetch_first_value row_or_rows
{ $$ = $2; }
;
select_offset_value:
a_expr { $$ = $1; }
;
select_fetch_first_value:
c_expr { $$ = $1; }
| '+' I_or_F_const
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "+", NULL, $2, @1); }
| '-' I_or_F_const
{ $$ = doNegate($2, @1); }
;
I_or_F_const:
Iconst { $$ = makeIntConst($1,@1); }
| FCONST { $$ = makeFloatConst($1,@1); }
;
/* noise words */
row_or_rows: ROW { $$ = 0; }
| ROWS { $$ = 0; }
;
postgres=# select * from test;
id | name
----+------------
4 | SQL server
3 | MySQL
2 | Oracle
1 | PostgreSQL
(4 rows)
postgres=# select * from test limit 2 offset 1+1 ;
id | name
----+------------
2 | Oracle
1 | PostgreSQL
(2 rows)
postgres=#
解析层面 |
# postgres\src\backend\parser\gram.y
/* Private struct for the result of opt_select_limit production */
typedef struct SelectLimit
{
Node *limitOffset; // 是offset的偏移值
Node *limitCount; // 是limit的限制值
LimitOption limitOption; // 限制选项
} SelectLimit;
/* insertSelectOptions()
* Insert ORDER BY, etc into an already-constructed SelectStmt.
*
* This routine is just to avoid duplicating code in SelectStmt productions.
*/
static void
insertSelectOptions(SelectStmt *stmt,
List *sortClause, List *lockingClause,
SelectLimit *limitClause,
WithClause *withClause,
core_yyscan_t yyscanner)
{
Assert(IsA(stmt, SelectStmt));
/*
* Tests here are to reject constructs like
* (SELECT foo ORDER BY bar) ORDER BY baz
*/
if (sortClause)
{
if (stmt->sortClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple ORDER BY clauses not allowed"),
parser_errposition(exprLocation((Node *) sortClause))));
stmt->sortClause = sortClause;
}
/* We can handle multiple locking clauses, though */
stmt->lockingClause = list_concat(stmt->lockingClause, lockingClause);
if (limitClause && limitClause->limitOffset)
{
if (stmt->limitOffset)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple OFFSET clauses not allowed"),
parser_errposition(exprLocation(limitClause->limitOffset))));
stmt->limitOffset = limitClause->limitOffset;
}
if (limitClause && limitClause->limitCount)
{
if (stmt->limitCount)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple LIMIT clauses not allowed"),
parser_errposition(exprLocation(limitClause->limitCount))));
stmt->limitCount = limitClause->limitCount;
}
if (limitClause && limitClause->limitOption != LIMIT_OPTION_DEFAULT)
{
if (stmt->limitOption)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple limit options not allowed")));
if (!stmt->sortClause && limitClause->limitOption == LIMIT_OPTION_WITH_TIES)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("WITH TIES cannot be specified without ORDER BY clause")));
stmt->limitOption = limitClause->limitOption;
}
if (withClause)
{
if (stmt->withClause)
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("multiple WITH clauses not allowed"),
parser_errposition(exprLocation((Node *) withClause))));
stmt->withClause = withClause;
}
}
解释一下insertSelectOptions
函数:
1、
stmt
参数是接收前面select * from test
的解析结果
2、sortClause
、lockingClause
和withClause
在我们这里面 暂时不做研究 这里都是NULL
3、limitClause
就是负责接收 limit 和 offset两部分的结果。其中上述结构的limitOffset
指向offset部分,limitCount
指向limit 后面的常量表达式
4、就目前的SQL来看,limitClause && limitClause->limitOffset
或limitClause && limitClause->limitCount
成立之后,将把上面的offset 和 limit的限制值 给到前面的stmt
到此处,最终得到的还将是一个SelectStmt
结构的结果(前面含有select部分,后面又得到了 limit/offset部分)!整个结构体如下:(就是下面的limitOffset
和limitCount
)
// postgres\src\include\nodes\parsenodes.h
typedef struct SelectStmt
{
NodeTag type;
/*
* These fields are used only in "leaf" SelectStmts.
* 这些字段仅在“leaf” SelectStmts 中使用。
*/
List *distinctClause; /* NULL, list of DISTINCT ON exprs, or
* lcons(NIL,NIL) for all (SELECT DISTINCT) */
IntoClause *intoClause; /* target for SELECT INTO */
List *targetList; /* the target list (of ResTarget) */
List *fromClause; /* the FROM clause */
Node *whereClause; /* WHERE qualification */
List *groupClause; /* GROUP BY clauses */
Node *havingClause; /* HAVING conditional-expression */
List *windowClause; /* WINDOW window_name AS (...), ... */
/*
* In a "leaf" node representing a VALUES list, the above fields are all
* null, and instead this field is set. Note that the elements of the
* sublists are just expressions, without ResTarget decoration. Also note
* that a list element can be DEFAULT (represented as a SetToDefault
* node), regardless of the context of the VALUES list. It's up to parse
* analysis to reject that where not valid.
*
* 在一个表示值列表的“leaf”节点中,上面的字段都是空的,取而代之的是设置这个字段
* 注意子列表的元素只是表达式,没有ResTarget修饰
* 还要注意,list元素可以是默认的(表示为SetToDefault节点),而与VALUES列表的上下文无关
* 它由解析分析拒绝无效的地方
*/
List *valuesLists; /* 表达式列表的未转换列表 */
/*
* These fields are used in both "leaf" SelectStmts and upper-level
* SelectStmts.
*/
List *sortClause; /* sort clause (a list of SortBy's) */
Node *limitOffset; /* # 要跳过的结果元组 */
Node *limitCount; /* # 返回的结果元组 */
LimitOption limitOption; /* 限制类型 */
List *lockingClause; /* FOR UPDATE (list of LockingClause's) */
WithClause *withClause; /* WITH clause */
/*
* These fields are used only in upper-level SelectStmts.
*/
SetOperation op; /* type of set op */
bool all; /* ALL specified? */
struct SelectStmt *larg; /* left child */
struct SelectStmt *rarg; /* right child */
/* 最终在这里添加相应规范的字段 */
} SelectStmt;
基本思路:
1、rownum作为 表达式节点 的一种
2、在语法解析阶段通过匹配在c_expr表达式规则下的ROWNUM关键字,创建RownumExpr结构体节点
3、在语法分析阶段转换where子句中的rownum条件 为 对应的limit条件
4、没有成功转换的rownum条件 将作为普通的(列)条件进行过滤数据
第一步:增加
rownum
关键字
// postgres\src\include\parser\kwlist.h
// 注意字典顺序 且置为保留关键字
PG_KEYWORD("rownum", ROWNUM, RESERVED_KEYWORD)
第二步:增加
RownumExpr
表达式结构体
// postgres\src\include\nodes\parsenodes.h
/*
* oracle rownum expr for postgres
*/
typedef struct RownumExpr
{
NodeTag type;
// location 代表着将来的出错报错位置
int location; /* token location, or -1 if unknown */
} RownumExpr;
第三步:增加对应
NodeTag
的T_RownumExpr
枚举值
// postgres\src\include\nodes\nodes.h
// 放到 TAGS FOR PRIMITIVE NODES (primnodes.h) T_CurrentOfExpr后面即可 在我这里,其值为146
T_RownumExpr,
第四步:增加匹配
rownum
的语法
// postgres\src\backend\parser\gram.y
// 在keyword 和 reserved_keyword里面添加 关键字ROWNUM 注意顺序
// 在c_expr: 下添加匹配语法,如下:
c_expr:
......
| ROWNUM
{
RownumExpr *n = makeNode(RownumExpr);
n->location = @1;
$$ = (Node*)n;
}
......
第五步:增加
RownumExpr
的相关 节点树的各种通用操作
// postgres\src\backend\nodes\nodeFuncs.c
// 在上面创建得到一个RownumExpr结构体节点,下面是补充该节点的相关get set函数
涉及到的函数如下:
Oid exprType(const Node *expr);
/**
* 返回表达式结果类型的Oid
*
* 于是在这里可以返回代表 int64的 INT8OID
*/
case T_RownumExpr:
type = INT8OID;
break;
int32 exprTypmod(const Node *expr);
/**
* 如果可以确定,则返回表达式结果类型的特定类型修饰符。在很多情况下,它不能,我们返回-1
*
* 于是在这里可以返回 -1
*/
Oid exprCollation(const Node *expr);
/**
* 返回表达式结果排序的Oid
*
* 注意:可以调用函数的表达式节点通常有一个“inputcollid”字段,这是函数应该使用的排序规则
* 这是节点输入的已解析公共排序规则。它通常(但不总是)与结果排序相同;特别是,如果函数从可排序的输入产生不可排序的结果类型,或者相反,这两者是不同的
*
* 这里就不要什么排序规则了 直接返回 InvalidOid
*/
void exprSetCollation(Node *expr, Oid collation);
/**
* 将排序规则信息分配给表达式树节点
*
* 注意:因为这只在解析分析中使用,所以我们不需要担心子计划或预留变量。而这里不需要使用这里的什么排序规则
*/
case T_RownumExpr:
Assert(!OidIsValid(collation));
break;
int exprLocation(const Node *expr);
/**
* 返回表达式树的解析位置,用于错误报告
* 如果无法确定位置,则返回-1
* 对于大于单个令牌的表达式,这里的目的是返回表达式最左边的令牌的位置,而不一定是最顶层节点的位置字段
* 例如:OpExpr的location字段将指向操作符名称,但是如果它不是前缀操作符,那么我们应该返回左操作数的位置
* 原因是我们希望引用整个表达式,而不仅仅是操作符,指向它的开始似乎是最自然的方式
* 位置不是完美的
* 例如 由于语法没有显式地表示解析树中的圆括号,对于写为“(a + b) * c”的内容,我们将指向“a” 而不是“(”
* 但是对于错误报告目的来说,它已经足够好了
* 您可能会认为这段代码过于通用
* 例如,为什么要检查FuncExpr节点的操作数,当函数名可能在它们的左侧的时候? 有以下几个原因:
* 语法有时构建的表达式与用户编写的并不完全一样;例如,x不在……之间会变成一个非表达式,其关键字指针位于其最左边参数的右侧
* 同样,通过解析分析隐式插入的节点(例如用于隐式强制的FuncExprs)将拥有位置-1,因此我们可以在树中拥有已知位置和未知位置的奇数组合
*
*/
// 将上面得到的location进行赋值
case T_RownumExpr:
loc = ((const RownumExpr*)expr)->location;
break;
bool expression_tree_walker(Node *node, bool (*walker) (), void *context);
/**
* 标准表达树遍历支持
* 我们过去在许多不同的例程中都有 接近重复的代码,这些例程理解如何通过表达式节点树递归
* 维护起来很痛苦,而且为了支持特定的节点类型,我们经常忽略一些特定的例程,从而导致错误
* 在大多数情况下,这些例程实际上只关心特定的节点类型,而不关心其他类型,除非它们必须通过非基元节点类型递归
* 因此,我们现在提供了通用的树遍历逻辑来整合冗余的“样板”代码
* 有两个版本:expression_tree_walker()和expression_tree_mutator()。
*/
/**
* expression_tree_walker()旨在支持以只读方式遍历树的例程(尽管它也可以用于就地修改节点但从不添加/删除/替换节点的例程)
* 步行者的日常训练应该是这样的:
bool my_walker (Node *node, my_struct *context)
{
if (node == NULL)
return false;
// check for nodes that special work is required for, eg:
if (IsA(node, Var))
{
... do special actions for Var nodes
}
else if (IsA(node, ...))
{
... do special actions for other node types
}
// for any node type not specially processed, do:
return expression_tree_walker(node, my_walker, (void *) context);
}
* 为空返回false;特定情况特殊处理;处理不掉的调用 expression_tree_walker
* 1. “context”参数指向一个结构,该结构包含walker例程需要的任何上下文信息——它也可以用来返回walker收集的数据
* expression_tree_walker没有触及这个参数,但是它被传递到my_walker的递归子调用
* 树遍历从一个安装例程开始,该例程填充适当的上下文结构,使用树的顶级节点调用my_walker,然后检查结果
* 2. walker例程应返回“false”以继续树遍历,或返回“true”以中止遍历并立即向顶级调用者返回“true”
* 如果步行者找到了它要找的东西,这可以用来短路遍历。如果对walker的调用没有返回“真”,则返回顶层调用者
* 3. expression_tree_walker处理的节点类型包括计划阶段中目标列表和限定词子句中常见的所有节点类型
* 特别是,它处理列表节点,因为cf认证的qual子句将在顶层具有列表结构,并且它处理目标节点,以便不需要额外的代码就可以处理目标列表的扫描
* 此外,还将处理RangeTblRef、FromExpr、JoinExpr和SetOperationStmt节点,这样就可以在不需要额外代码的情况下处理查询jointree和setOperation树
*/
/*
* expression_tree_walker将通过递归到“testexpr”子树(这是一个属于外部计划的表达式)来处理SubLink节点
* 它还将调用子查询节点上的walker
* 但是,当在Query节点上调用expression_tree_walker本身时,它什么也不做,并返回“false”
* 净效果是,除非walker在查询节点上做了一些特殊的操作,否则在表达式树遍历期间将不会访问子选择
* 这正是许多情况下需要的行为——对于那些确实希望递归到子选择中的行走器
* 通常需要在子选择的入口进行特殊的行为(如增加深度计数器)
*/
/* 想要检查子选择的walker应该包括以下代码行: */
if (IsA(node, Query))
{
adjust context for subquery;
result = query_tree_walker((Query *) node, my_walker, context,0); // adjust flags as needed
restore context if needed;
return result;
}
/**
* query_tree_walker是一个方便的例程(参见下面),它调用给定查询节点的所有表达式子树上的walker
*
* expression_tree_walker将通过递归到“testexpr”和“args”列表(它们是属于外部计划的表达式)来处理子计划节点
* 但是,它不会触及已完成的子规划。因为没有到原始查询的链接,所以不可能递归到一个已经规划的表达式树的子选择中
* 这对于当前的使用是可以的,但是在未来可能需要重新访问
*/
// 而rownum 表达式节点,由于它没有表达式子节点的基本节点类型 这里也就直接break了
Node *expression_tree_mutator(Node *node,Node *(*mutator) (),void *context);
/**
* expression_tree_mutator()被设计用来支持对表达式树进行修改副本的例程,其中一些节点被添加、删除或用新的子树替换
* 原始树(通常)不会改变
* 每个递归级别负责返回它所传递的子树的副本(或适当修改的替换)
*
* mutator例程应该像这样:
Node * my_mutator (Node *node, my_struct *context)
{
if (node == NULL)
return NULL;
// check for nodes that special work is required for, eg:
if (IsA(node, Var))
{
... create and return modified copy of Var node
}
else if (IsA(node, ...))
{
... do special transformations of other node types
}
// for any node type not specially processed, do:
return expression_tree_mutator(node, my_mutator, (void *) context);
}
/**
* 如上“context”参数指向一个结构体,该结构体保存了mutator例程需要的任何上下文信息
* 它也可以用来返回mutator收集的额外数据
* expression_tree_mutator不会触及这个参数,但它会被传递到my_mutator的递归子调用
* 树遍历从一个设置例程开始,该例程填充适当的上下文结构,用树的顶级节点调用my_mutator,并进行任何必要的后处理
*
* 每一级递归都必须返回一个经过适当修改的节点
* 如果调用expression_tree_mutator(),它将复制给定节点的精确副本,但是调用my_mutator()复制该节点的子节点
* 通过这种方式,my_mutator()可以完全控制复制过程,但不需要直接处理它不感兴趣的表达式树
*
* 与expression_tree_walker一样,expression_tree_mutator处理的节点类型包括计划阶段中通常可以在目标列表和限定子句中找到的所有节点类型
*
* expression_tree_mutator将通过递归到“testexpr”子树(这是一个属于外部计划的表达式)来处理SubLink节点
* 它还将调用子查询节点上的mutator
* 但是,当在查询节点上调用expression_tree_mutator本身时,它什么也不做,并返回未修改的查询节点
* 净效果是,除非mutator在Query节点上做一些特殊的操作,子选择将不会被访问或修改;原来的子选择将由新的SubLink节点链接到
* 想要下降到子选择中的mutator通常通过识别查询节点并调用query_tree_mutator(下面)来实现
*
* expression_tree_mutator将通过递归到“testexpr”和“args”列表(它们属于外部计划)来处理子计划节点,但它将简单地将链接复制到内部计划,因为这通常是表达式树mutators想要的
* 想要修改子计划的mutator可以通过识别子计划表达式节点并执行正确的操作来强制执行适当的行为
*/
*/
// rownum 这里仅做一个深拷贝返回即可
case T_RownumExpr:
return (Node *) copyObject(node);
bool raw_expression_tree_walker(Node *node, bool (*walker) (), void *context);
/**
* raw_expression_tree_walker 遍历原始解析树
*
* 它具有与expression_tree_walker完全相同的API,但是它没有遍历分析后解析树,而是知道如何遍历原始语法输出中的节点类型
* (目前还不需要组合步行者,所以我们以效率的名义将它们分开使用。)
* 与expression_tree_walker不同的是,对于查询边界没有特殊的规则:我们向下查找可能感兴趣的所有内容
*
* 目前,这里的节点类型覆盖范围仅扩展到DML语句(SELECT/INSERT/UPDATE/DELETE)和可以出现在其中的节点
* 因为这主要是在分析cte时使用的,而且只有DML语句可以出现在cte中
*
*/
// 而rownum 表达式节点,由于它没有子节点的基本节点类型 这里也就直接break了
第六步:在结构体
ExprState
和PlanState
中 增加rownum
相关的变量字段
// postgres\src\include\nodes\execnodes.h
/*
* ExprState是表达式求值的顶级节点。它包含对表达式求值的指令(in ->steps)
*
* 完整结构体如下,新增 hasrownum rownum rownum_marked
*/
/* Bits in ExprState->flags (私有标志位参见execExpr.h):表达式用于ExecQual() */
#define EEO_FLAG_IS_QUAL (1 << 0)
typedef struct ExprState
{
NodeTag tag;
uint8 flags; /* bitmask of EEO_FLAG_* bits, see above */
/*
* 存储标量表达式的结果值,或 ExecBuildProjectionInfo()构建的表达式中的单个列结果
*/
#define FIELDNO_EXPRSTATE_RESNULL 2
bool resnull;
#define FIELDNO_EXPRSTATE_RESVALUE 3
Datum resvalue; // 它将存储这里的结果值(rownum的值)
/*
* 如果投影一个元组结果,这个槽保存结果;其他NULL
*/
#define FIELDNO_EXPRSTATE_RESULTSLOT 4
TupleTableSlot *resultslot;
/*
* 计算表达式返回值的指令
*/
struct ExprEvalStep *steps;
/*
* 实际计算表达式的函数。根据表达式的复杂性,可以将其设置为不同的值
*/
ExprStateEvalFunc evalfunc;
/* original expression tree, for debugging only */
Expr *expr;
/* evalfunc的私有状态 */
void *evalfunc_private;
/*
* XXX: following fields only needed during "compilation" (ExecInitExpr);
* could be thrown away afterwards.
* 以下字段只在“编译”期间需要(ExecInitExpr),之后就会被扔掉
*/
int steps_len; /* 当前的步骤数 */
int steps_alloc; /* allocated length of steps array */
#define FIELDNO_EXPRSTATE_PARENT 11
struct PlanState *parent; /* parent PlanState node, if any */
ParamListInfo ext_params; /* for compiling PARAM_EXTERN nodes */
Datum *innermost_caseval;
bool *innermost_casenull;
Datum *innermost_domainval;
bool *innermost_domainnull;
bool hasrownum; /* for compiling rownum nodes */
int64 rownum;
int64 rownum_marked;
} ExprState;
/* ----------------
* PlanState node
*
* 我们从来没有实例化过任何平面节点;这只是所有planstate类型节点的公共抽象超类
* ----------------
*/
typedef struct PlanState
{
NodeTag type;
Plan *plan; /* 相关的计划节点 */
EState *state; /* 在执行时,单个节点的状态指向整个顶层计划的一个状态 */
ExecProcNodeMtd ExecProcNode; /* function to return next tuple */
ExecProcNodeMtd ExecProcNodeReal; /* 实际函数,如果上面是一个包装器 */
Instrumentation *instrument; /* 此节点的可选运行时统计 */
WorkerInstrumentation *worker_instrument; /* per-worker instrumentation */
/* Per-worker JIT instrumentation */
struct SharedJitInstrumentation *worker_jit_instrument;
/*
* 所有平面图类型的通用结构数据
* 这些指向附属状态树的链接与相关计划树中的链接是并行的(除了在计划树中不存在的子计划列表)
*/
ExprState *qual; /* 布尔试验条件 */
struct PlanState *lefttree; /* input plan tree(s) */
struct PlanState *righttree;
List *initPlan; /* Init SubPlanState nodes (un-correlated expr
* subselects) */
List *subPlan; /* SubPlanState nodes in my expressions */
/*
* State用于参数更改驱动的重新扫描的管理
*/
Bitmapset *chgParam; /* set of IDs of changed Params */
/*
* Other run-time state needed by most if not all node types.
*/
TupleDesc ps_ResultTupleDesc; /* node's return type */
TupleTableSlot *ps_ResultTupleSlot; /* slot for my result tuples */
ExprContext *ps_ExprContext; /* node's expression-evaluation context */
ProjectionInfo *ps_ProjInfo; /* info for doing tuple projection */
/*
* Scanslot's descriptor if known. This is a bit of a hack, but otherwise
* it's hard for expression compilation to optimize based on the
* descriptor, without encoding knowledge about all executor nodes.
*/
TupleDesc scandesc;
/*
* 定义表达式上下文的内部、外部和scanslot类型,并将此状态作为父状态
* 如果设置了*opsset,那么*opsfixed表示是否保证*ops是所使用的槽类型
* 这意味着对应的ExprContext中的每一个slot。当计算表达式时,ecxt_*元组将指向该类型的槽
* 如果*opsfixed是false,但*ops是设置的,这表示最有可能的槽类型。
*
* scan*字段由ExecInitScanTupleSlot()设置。如果没有调用,节点可以自己初始化字段
*
* If outer/inneropsset is false, the information is inferred on-demand
* using ExecGetResultSlotOps() on ->righttree/lefttree, using the
* corresponding node's resultops* fields.
*
* The result* fields are automatically set when ExecInitResultSlot is
* used (be it directly or when the slot is created by
* ExecAssignScanProjectionInfo() /
* ExecConditionalAssignProjectionInfo()). If no projection is necessary
* ExecConditionalAssignProjectionInfo() defaults those fields to the scan
* operations.
*/
const TupleTableSlotOps *scanops;
const TupleTableSlotOps *outerops;
const TupleTableSlotOps *innerops;
const TupleTableSlotOps *resultops;
bool scanopsfixed;
bool outeropsfixed;
bool inneropsfixed;
bool resultopsfixed;
bool scanopsset;
bool outeropsset;
bool inneropsset;
bool resultopsset;
int64 rownum;
int64 rownum_marked;
} PlanState;
/**
* 上面结构体PlanState中的变量 qual 需要关注一下
* ExprState *qual; /* 布尔试验条件 */
*
* 1. 它这里最终存储的是经过重写limit逻辑之后的 where 条件
* 2. where条件为空 qual为空
* 3. rownum < 4 会变成 limit 3,qual为空
* 4. rownum < 4 and (id = 1 or id = 2) 会变成 id = 1 or id = 2 limit 3,qual为 id = 1 or id = 2
* 5. rownum < 4 and id = 1 or id = 2 会保持不变,qual 为 rownum < 4 and id = 1 or id = 2 (没有limit的转换)
*
* 大家先看下 下面这张图有个印象即可:
*
*/
为什么上面第5点没有转换,请看下图和 第十步 的函数rewrite_rownum_query
说明:
第七步:结构体
RownumExpr
功能暂设定比较简单 这里是以后新的扩展
// postgres\src\backend\nodes\equalfuncs.c
// 两个nodes的比较函数:比较是否相等
bool equal(const void *a, const void *b);
// 这里直接返回true 目前的功能暂时用不到该函数:
case T_RownumExpr:
retval = true;
break;
// 于是这里也暂不封装相应的 _equalRownumExpr函数
// postgres\src\backend\nodes\copyfuncs.c
// copyObjectImpl——copyObject()的实现 创建节点树或列表的副本。这是一个“深度”复制:递归地复制所有子结构
void *copyObjectImpl(const void *from);
// 处理为:
case T_RownumExpr:
retval = _copyRownumExpr(from);
break;
// 而_copyRownumExpr函数如下:
static RownumExpr *_copyRownumExpr(const RownumExpr *from)
{
RownumExpr *newnode = makeNode(RownumExpr);
COPY_LOCATION_FIELD(location);
return newnode;
}
第八步:构造显示目标
rownum
列(当显示rownum
时,需要打印)
// postgres\src\backend\parser\parse_target.c
static int FigureColnameInternal(Node *node, char **name);
/**
* FigureColnameInternal - FigureColname的内部工作平台
*
* Return值表示结果的置信度:
*
* 0 -无信息
* 排名第二的名字选择
* 2 -好名字的选择
*
* 返回值实际上只在内部使用。如果结果不为0,则*name被设置为所选的名称。
*/
// 这块 我下面案例四有详细的推演
case T_RownumExpr:
*name = "rownum";
return 2;
看一下函数调用堆栈的信息:
注:此刻就是解析整个select的transformSelectStmt
中的transform targetlist
完成:
// postgres\src\backend\parser\analyze.c
static Query *transformSelectStmt(ParseState *pstate, SelectStmt *stmt)
{
...
/* transform targetlist */
qry->targetList = transformTargetList(pstate, stmt->targetList,EXPR_KIND_SELECT_TARGET);
...
}
接下来就是整个的解析 优化 执行流程,在开始之前需要先看一下下面的这个图:
注:接下来就是要去解析where子句部分:
/* transform WHERE */
qual = transformWhereClause(pstate, stmt->whereClause,EXPR_KIND_WHERE, "WHERE");
第九步:分析和转换表达式中 增加
rownum
相关
在上面创建得到RownumExpr结构体节点之后,进入transformExpr
函数:
// postgres\src\backend\parser\parse_expr.c
函数1: Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKind);
/**
* 函数功能: 分析和转换表达式。类型检查和类型转换在这里完成。此处理将原始语法输出转换为具有完全确定的语义的表达式树
*
* 保存并恢复我们正在解析的表达式类型的标识
*/
// 在里面调用transformExprRecurse
函数2: static Node *transformExprRecurse(ParseState *pstate, Node *expr);
// 1. 根据nodeTag(expr)做进一步的transform
// 2. 因为这里的RownumExpr结构体现在的状态是一个简单的 Node *,我们这里不做任何转换 即:
case T_RownumExpr:
result = (Node *)expr;
break;
看一下这里的解析方式:rownum<3
经过语法解析之后,为一个A_Expr
类型的节点 操作符两边的表达式分别为:RownumExpr
和A_Const
类型的结构体变量。如下左子表达式为T_RownumExpr
,这里不做任何处理。其底层还是一个RownumExpr
结构体变量:
其函数调用堆栈如下图所示:
解析这个常量3
的过程如下:
在左右表达式处理完成之后,接下来封装整个表达式:
/* Ordinary scalar operator */
Node *last_srf = pstate->p_last_srf;
lexpr = transformExprRecurse(pstate, lexpr);
rexpr = transformExprRecurse(pstate, rexpr);
result = (Node *) make_op(pstate,a->name,lexpr,rexpr,last_srf,a->location);
下面来看一下这个make_op
函数:
Expr *make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,Node *last_srf, int location);
/**
* make_op () 操作符表达式建设
*
* 转换运算符表达式,确保类型兼容性。这是一些类型转换发生的地方
*
* last_srf应该是pstate->p_last_srf的副本,就在我们开始转换操作符的实参之前;这用于巢式srf检
* 如果调用者无论如何都会抛出一个设置返回表达式的错误,可以作弊,只传递pstate->p_last_srf
*/
// 下面是函数内的重点说明:
/* otherwise, binary operator */
ltypeId = exprType(ltree); // 其值为20 就是上面第五步的 INT8OID
rtypeId = exprType(rtree); // 其值为23
// 最终该函数会生成一个OpExpr的表达式节点
注:函数调用堆栈返回,直到query = transformTopLevelStmt(pstate, parseTree);
,我们将得到一个从Parse tree
转变而来的 Query tree
,接下来我们重写rownum
部分到limit
逻辑。
在解析完成where子句之后,得到的是一个qualification
节点:
/* transform WHERE */
qual = transformWhereClause(pstate, stmt->whereClause,EXPR_KIND_WHERE, "WHERE");
...
// 并将该SQL语句的 from部分 和 where的条件部分都封装到一个 FromExpr结构体中,最终赋值给 Query的jointree字段
qry->jointree = makeFromExpr(pstate->p_joinlist, qual);
...
第十步:将上述得到的节点
Query node
里面的rownum
部分重写到limit
逻辑
/*----------
* FromExpr - represents a FROM ... WHERE ... construct
*
* This is both more flexible than a JoinExpr (it can have any number of
* children, including zero) and less so --- we don't need to deal with
* aliases and so on. The output column set is implicitly just the union
* of the outputs of the children.
*
* 这比JoinExpr更灵活(它可以有任意数量的子节点,包括0),也更灵活——我们不需要处理别名等
* 输出列集隐式地只是子输出的并集
*----------
*/
typedef struct FromExpr
{
NodeTag type;
List *fromlist; /* List of join subtrees */
Node *quals; /* qualifiers on join, if any */
} FromExpr;
经过上面第九步函数处理之后,我们重写得到的Query node
,如下:
// postgres\src\backend\parser\analyze.c
// 在parse_analyze函数中新增重写逻辑:
...
query = transformTopLevelStmt(pstate, parseTree);
// 下面就是新增接口:
rewrite_rownum_query_enum((Node*)query, NULL);
...
下面是rewrite_rownum_query_enum
的实现:
static bool rewrite_rownum_query_enum(Node *node, void *context)
{
if (node == NULL)
return false;
/*
if (node_tree_walker(node, rewrite_rownum_query_enum, context))
return true; */
if (IsA(node, Query))
{
rewrite_rownum_query((Query *)node);
}
return false;
}
OK,下面进入最重要的函数rewrite_rownum_query
中:
/*
* let "rownum <[=] CONST" or "CONST >[=] rownum"
* to "limit N"
* TODO: fix when "Const::consttypmod != -1"
* TODO: fix when "rownum < 1 and rownum < 2" to "limit CASE WHEN 1<2 THEN 1 ELSE 2"
*/
static void rewrite_rownum_query(Query *query)
{
List *qual_list, *args;
ListCell *lc;
Node *expr, *l, *r;
Node *limitCount;
Bitmapset *hints;
char opname[4];
Oid opno;
Oid funcid;
int i;
int64 v64;
Assert(query);
if (query->jointree == NULL || query->limitOffset != NULL || query->limitCount != NULL || contain_rownum(query->jointree->quals) == false)
return;
query->jointree->quals = expr = (Node*)canonicalize_qual((Expr*)(query->jointree->quals), false);
if (is_andclause((Node *)expr))
qual_list = ((BoolExpr *)expr)->args;
else
qual_list = list_make1(expr);
/* find expr */
limitCount = NULL;
hints = NULL;
for (i = 0, lc = list_head(qual_list); lc; lc = lnext(qual_list, lc), ++i)
{
expr = lfirst(lc);
if (contain_rownum((Node *)expr) == false)
continue;
if (IsA(expr, OpExpr))
{
args = ((OpExpr *)expr)->args;
opno = ((OpExpr *)expr)->opno;
funcid = ((OpExpr *)expr)->opfuncid;
}
else if (IsA(expr, FuncExpr))
{
funcid = ((FuncExpr *)expr)->funcid;
args = ((FuncExpr *)expr)->args;
opno = InvalidOid;
}
else
{
return;
}
if (list_length(args) != 2)
return;
l = linitial(args);
r = llast(args);
Assert(l != NULL && r != NULL);
if (!IsA(l, RownumExpr) && !IsA(r, RownumExpr))
return;
if (opno == InvalidOid)
{
/* get operator */
Assert(OidIsValid(funcid));
opno = get_operator_for_function(funcid);
if (opno == InvalidOid)
return;
}
if (IsA(r, RownumExpr))
{
/* exchange operator, like "10>rownum" to "rownum<10" */
Node *tmp;
opno = get_commutator(opno);
if (opno == InvalidOid)
return;
tmp = l;
l = r;
r = tmp;
}
if (!IsA(l, RownumExpr))
return;
/* get operator name */
{
char *tmp = get_opname(opno);
if (tmp == NULL)
return;
strncpy(opname, tmp, lengthof(opname));
pfree(tmp);
}
if (opname[0] == '<')
{
if (contain_mutable_functions((Node *)r))
return;
if (const_get_int64((Expr *)r, &v64) == false)
return;
/* 这里是处理 <= 的情况 */
if (opname[1] == '=' && opname[2] == '\0')
{
/* rownum <= expr */
/* 下面是处理 <= 0的情形,即limit 0,一行结果也没有 */
if (v64 <= (int64)0)
{
/* rownum <= n, and (n<=0) */
limitCount = (Node *)make_int8_const(Int64GetDatum(0));
qual_list = NIL;
break;
}
if (limitCount != NULL)
return; /* has other operator */
/* 下面是处理 <= n的情形,即limit n,n行结果 */
limitCount = r;
}
else if (opname[1] == '\0')
{
if (v64 <= (int64)1)
{
/* rownum < n, and (n<=1) */
limitCount = (Node *)make_int8_const(Int64GetDatum(0));
qual_list = NIL;
break;
}
if (limitCount != NULL)
return; /* has other operator */
limitCount = (Node *)make_op(NULL, SystemFuncName("-"), (Node *)r, (Node *)make_int8_const(Int64GetDatum(1)), NULL, -1);
if (limitCount == NULL)
return;
}
else if (opname[1] == '>' && opname[2] == '\0')
{
/* rownum <> expr */
if (v64 <= (int64)0)
{
/* rownum <> n, and (n <= 0) ignore */
}
else if (limitCount != NULL)
{
return; /* has other operator */
}
else
{
/* for now, rownum <> n equal limit n-1 */
limitCount = (Node *)make_op(NULL, SystemFuncName("-"), (Node *)r, (Node *)make_int8_const(Int64GetDatum(1)), NULL, -1);
if (limitCount == NULL)
return;
}
}
else
{
return; /* unknown operator */
}
}
else if (opname[0] == '>')
{
if (const_get_int64((Expr *)r, &v64) == false)
return;
if (opname[1] == '=' && opname[2] == '\0')
{
/* rownum >= expr
* only support rownum >= 1 rownum >= 0
*/
if (v64 != (int64)1 && v64 != (int64)0)
return;
}
else if (opname[1] == '\0')
{
/* rownum > expr
* only support rownum > 0
*/
if (v64 != (int64)0)
return;
}
else
{
return;
}
}
else if (opname[0] == '=' && opname[1] == '\0')
{
if (!IsA(r, RownumExpr))
return;
/* rownum = rownum ignore */
}
else
{
return;
}
hints = bms_add_member(hints, i);
}
query->limitCount = limitCount;
if (qual_list != NIL)
{
/* whe use args for get new quals */
args = NIL;
for (i = 0, lc = list_head(qual_list); lc; lc = lnext(qual_list,lc), ++i)
{
if (bms_is_member(i, hints))
continue;
Assert(contain_rownum(lfirst(lc)) == false);
args = lappend(args, lfirst(lc));
}
if (args == NIL)
{
query->jointree->quals = NULL;
}
else if (list_length(args) == 1)
{
query->jointree->quals = linitial(args);
}
else
{
query->jointree->quals = (Node *)makeBoolExpr(AND_EXPR, args, -1);
}
}
return;
}
解释一下这个函数:
1、功能目的:将
rownum <[=] CONST
或者CONST >[=] rownum
都转化为limit N
。例如:rownum <= 3
将转化为limit 3
2、rownum < 3
将转化为limit 2
3、注:原因是前面被转换,到后面被转换的时候发现了 结果2个都不转换rownum <= 3 and rownum < 2
将转化为limit 1
4、rownum <= 3 or rownum < 5
将转化为limit 4
,注:因为这个好像是T_BoolExpr
,然后作为普通的条件处理
5、rownum <0
或rownum <1
或rownum = 2
或rownum > 2
将转化为limit 0
6、rownum >=1
或rownum >=0
或rownum >0
转化为limit all
或者说无limit
详细来看一下这个函数:
1、这个是在原PostgreSQL数据库的 limit/offset 的基础之上完成的,因此
rewrite_rownum_query
函数在下面的情形是不做事情的:①. query->jointree 部分为空,说明 没有from 和 where 子句
②. 有offset模块
③. 有limit模块
④. 没有rownum的node节点2、在上面四种都不满足(有 from 和 where子句 && 没有offset && 没有limit && 含有rownum条件节点)
①.
contain_rownum
函数的功能:就是遍历query->jointree->quals
就是看一个SQL的where条件部分 是否有rownum节点
②. 解析得到where的条件部分:qual_list
。若是含有and,则匹配上半部分 若是单条件或者含有or的这种将走下半部分
③. 首先设置limitCount
,就是准备赋值的limit
部分
④. 遍历上面的where条件,若是rownum 节点,然后交换左右 目的:让左节点为RownumExpr
3、解析得到 这个rownum的条件 操作符的名字:
< <= <> > >= =
这6种,const_get_int64
函数就是为了得到右表达式的 这个数字,并放到v64变量里面;make_int8_const
函数将构造limitCount
所需要对应的这个数字Const
类型的节点①. 是
<=
的情况:若是v64 <= (int64)0
是处理 <= 0的情形,即limit 0,一行结果也没有
②. 是<=
的情况:若是v64 <= n
是处理 <= n的情形,即limit n,n行结果
③. 是<
的情况:若是v64 <= (int64)1
是处理 < 0的情形,即limit 0,一行结果也没有
④. 是<
的情况:若是v64 <= (int64)1
是处理 < n的情形,即limit n-1,n-1行结果
⑤. 是<>
的情况:若是v64 <= (int64)0
是处理rownum <> n, and (n <= 0)
的情形 不处理
⑥. 是<>
的情况:若是v64 >= 1
是处理rownum <> n equal limit n-1
的情形,即limit n-1,n-1行结果
⑦. 是
>=
的情况:rownum >= expr
只支持rownum >= 1
和rownum >= 0
,不过这种都相当于没有limit
⑧. 是>
的情况:rownum > expr
只支持rownum > 0
不过这种也都相当于没有limit。第七种和第八种 最后相当于没有使用where条件 因为query->limitCount
和query->jointree->quals
都是NULL
⑨. 是
=
的情况:暂不支持rownum = rownum
4、考虑特殊的情况:
select * from t1 where rownum >= 2;
select * from t1 where rownum = 2;
select rownum, * from t1 where rownum < 3 and id = 2;
select rownum, * from t1 where rownum < 3 or id = 2;
select * from t1 where rownum < 4 limit 2;
案例一、这里我们来结合一个实际的案例,来详细分析一下上面的这个函数:
postgres=# select rownum, * from t1 where rownum < 3 and id = 2;
rownum | id | name
--------+----+------
1 | 2 | h
(1 row)
postgres=#
简单分析一下这个SQL:
1、这个怎么理解呢?首先rownum < 3,其次这两行中 id = 2
这个从转换的角度可以这么理解:
postgres=# select rownum, * from t1 where id = 2 limit 2;
rownum | id | name
--------+----+------
1 | 2 | h
(1 row)
postgres=#
在进入rewrite_rownum_query
的时候,经过剪枝的expr
里面含有and
且类型为T_BoolExpr
如下:
上面得到的where条件部分含有两部分:rownum < 3 和 id = 2
- 第一部分成功匹配到
OpExpr
,实质上就是rownum < 3
。紧接着opname[0]
为<
,且v64
是 3,这样就将limitCount
设置为limit 3-1
最后limit 2
的limitCount
赋值给了query->limitCount
hints = bms_add_member(hints, i);
这一步解释一下:将第i个这样的rownum条件 给记录下来 这里就把i=0
给保存到集合里面了qual_list
保存着 where的条件,(在这里就有上面两部分)接下来的for循环做的事情:看一下第2步记录的条件,如果是(指的是 rownum的条件) 则跳过,不是则记录到args
里面 于是这里的id = 2
就被记录下来 并最后赋值给query->jointree->quals
- 意思就是说:经过上面三步 这个SQL已经被成功的修改为:where条件是
id = 2
并且 其limit部分是limit 2
如下:
第十一步:查询优化模块增加
rownum
遍历查询逻辑
// postgres\src\backend\optimizer\util\clauses.c
// 在查询优化阶段 增加的内容不是很多,上面也提到的 增加`contain_rownum`函数
// 功能:判断给定的 node 节点中是否含有 RownumExpr节点
bool contain_rownum(Node *clause)
{
return contain_rownum_walker(clause, NULL);
}
static bool contain_rownum_walker(Node *node, void *context)
{
if (node == NULL)
return false;
if (IsA(node, RownumExpr))
return true;
return expression_tree_walker(node, contain_rownum_walker, context);
查询执行模块:执行生成的计划。查询编译器将用户提交的 SQL 查询语句转变成执行计划之后,由查询执行器继续执行查询的处理过程。
在查询执行阶段,将根据执行计划进行数据提取、处理、存储等一系列活动,以完成整个查询执行过程。查询执行过程更像个结构良好的裸机:执行计划为输入,执行相应的功能。查询执行器的框架结构如下图所示:
同查询编译器一样,查询执行器也是被函数exec_simple_query
调用, 只是调用的顺序上查询编译器在前,查询执行器在后。从总体上看,查询执行器实际就是按照执行计划的安排,有机地调用存储、索引、并发等模块,按照各种执行计划中各种计划节点的实现算法来完成数据的读取或者修改的过程。
(在exec_simple_query
内部)经过上面两个函数之后:
...
querytree_list = pg_analyze_and_rewrite(parsetree, query_string,NULL, 0, NULL);
// 为已经重写的查询列表生成计划
// 对于普通的可优化语句,请调用规划器。对于实用程序语句,只需制作一个包装器PlannedStmt节点
// 结果是一个PlannedStmt节点列表
plantree_list = pg_plan_queries(querytree_list, query_string,CURSOR_OPT_PARALLEL_OK, NULL);
...
下面是函数查询执行器的核心模块:Portal,其启动函数PortalStart
如下:
// postgres\src\backend\tcop\pquery.c
void
PortalStart(Portal portal, ParamListInfo params,int eflags, Snapshot snapshot);
/**
* PortalStart为执行准备一个门户
* 调用者必须已经创建门户,执行PortalDefineQuery(),并在需要时调整门户选项
*
* 如果查询需要参数,则它们必须以“params”的形式传递(调用方负责给它们适当的生存期)
* 调用者还可以提供传递给ExecutorStart的初始“eflags”集合
* (但请注意,这些可以在内部修改,而且它们目前仅适用于PORTAL_ONE_SELECT门户)
* 大多数调用者应该简单地传递0
*
* 调用者可以选择传递要使用的快照
* pass InvalidSnapshot用于设置新快照的正常行为
* 对于非portal_one_select门户,该参数将被忽略(它仅用于游标)
*
* 返回时,portal准备接受PortalRun()调用,并且知道结果tupdesc(如果有的话)
*/
...
/*
* 调用ExecutorStart来准备执行计划
*/
ExecutorStart(queryDesc, myeflags);
...
案例二、这里我们来结合一个实际的案例,来详细分析一下上面的这个过程:
select rownum,* from t1 where rownum < 4 and (id = 2 or id = 1);
# 在最上面的函数中,我们将上述SQL将转换为 `select rownum,* from t1 where id = 2 or id = 1 limit 3;`
postgres=# select * from t2;
id | name
----+------
6 | a
9 | b
8 | c
5 | d
7 | e
3 | f
7 | g
1 | h
2 | h
1 | k
2 | m
(11 rows)
# 如下这个 就是没有做 limit 转换的,相当于把 rownum < 4 当成了一个普通的条件来对待
postgres=# select rownum,* from t1 where rownum < 4 or id = 5 ;
rownum | id | name
--------+----+------
1 | 6 | a
2 | 9 | b
3 | 8 | c
4 | 5 | d
(4 rows)
# 而如下这个 就是做了 limit 转换的,相当于把 rownum < 4 变成了 limit 3
postgres=# select rownum,* from t1 where rownum < 4 and id = 5 ;
rownum | id | name
--------+----+------
1 | 5 | d
(1 row)
postgres=#
接下来在portal
准备阶段,主要看一下 where新条件qual(若是转换成功,就是新的qual;不成功就是含有rownum的条件)到最后每一个tuple的判断(ExecQual函数)中判断操作的转化 的ExecInitQual
函数:
注:这里的转换成功 决定因素在于 上面第十步的 rewrite_rownum_query
函数
// postgres\src\backend\executor\execExpr.c
/*
* ExecInitQual: prepare a qual for execution by ExecQual
*
* 准备计算一个连接布尔表达式(具有隐式和语义的等量列表),如果子表达式都不为false则返回true
*
* 如果列表为空,则必须返回true
* 由于这是一种非常常见的情况,我们进一步优化它,将其转换为空的expstate指针,而不是设置一个计算常量为TRUE的expstate
* (一些特别热点的ExecQual调用程序会检测到这一点,并完全避免调用ExecQual)
*
* 如果任何一个子表达式的结果为NULL,则连词的结果为false
* 这使得ExecQual主要用于计算WHERE子句,因为SQL指定如果结果没有被选中,则元组为null
*/
ExprState *
ExecInitQual(List *qual, PlanState *parent);
第十二步:
ExecInitQual
和ExecInitExprRec
函数中增加rownum
的转换的新条件的处理
上面很重要的一句话就是: where
新条件qual
(若是转换成功,就是新的qual
;不成功就是含有rownum
的条件)到最后每一个tuple
的判断(ExecQual
函数)中判断操作的转化。接下来我来解释一下这句话:
1、我在上面第六步的结尾曾经举过这样的例子,如下图所示:
2、上图的第2 3 4条都是将 rownum 部分转换成了 limit,这样接下来的qual就是不含有 rownum 的条件
3、至于第5条 没有转换成功,所以它的条件qual还是老样子,只是跟PostgreSQL原本的判断 有所不同了,因为这里有个条件叫做 rownum < 4。这也就是我们 第十二步 存在的意义(若是含有rownum的条件,则处理它)
既然我们这一步的目的就是处理掉:rownum 部分被继续当成条件的情况,这次 我们选用的SQL是select rownum,* from t1 where rownum < 4 or id = 5 ;
1、这个SQL上面有它的执行结果 在案例二下面 (可以也看一下其比较的例子)
2、这是一个没有被处理为limit 的语句
3、判断遍历得到的tuple的条件为rownum < 4 or id = 5
两部分
// postgres\src\backend\executor\execExpr.c
ExprState *
ExecInitQual(List *qual, PlanState *parent);
// 在上面函数中 遍历qual的for循环中 添加如下标志
...
foreach(lc, qual)
{
Expr *node = (Expr *) lfirst(lc);
// 它的意思:就是说明现在的where条件 qual里面含有rownum的部分
if (contain_rownum((Node*)node))
state->hasrownum = true;
/* first evaluate expression */
ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
...
接下来看一下 上面代码中 处理每一个qual的函数ExecInitExprRec
:
// postgres\src\backend\executor\execExpr.c
/*
* 将node求值所需的步骤添加到ExprState->steps中,可能递归到node的子表达式中
*
* node 要计算的节点表达式
*
* state 将必要的操作添加到ExprState->steps中
*
* resv / resnull—存储节点结果的位置
*/
static void
ExecInitExprRec(Expr *node, ExprState *state,Datum *resv, bool *resnull);
// 在其的nodeTag(node)中 增加是rownum节点的 case
...
/* 注意在添加的时候 T_RownumExpr的顺序就是按照在nodes.h中枚举值的顺序 */
case T_RownumExpr:
{
scratch.opcode = EEOP_ROWNUMEXPR;
ExprEvalPushStep(state, &scratch);
break;
}
// 这里标注 条件中含有rownum 条件的比较操作 其操作码是 EEOP_ROWNUMEXPR 这个是和第十五步对应
...
下图是rownum < 4
被初始化解析为判定操作的逻辑:
此时的函数调用堆栈如下:
(gdb) bt
#0 ExecInitFunc (scratch=0x7ffdcff46c60, node=0x2bf8460, args=0x2bf83c0, funcid=476, inputcollid=0, state=0x2c92220) at execExpr.c:2233
#1 0x00000000006b4713 in ExecInitExprRec (node=0x2bf8460, state=0x2c92220, resv=0x2c92228, resnull=0x2c92225) at execExpr.c:899
#2 0x00000000006b4af0 in ExecInitExprRec (node=0x2bf8760, state=0x2c92220, resv=0x2c92228, resnull=0x2c92225) at execExpr.c:1034
#3 0x00000000006b354e in ExecInitQual (qual=0x2ca15a8, parent=0x2c91710) at execExpr.c:256
#4 0x0000000000706402 in ExecInitSeqScan (node=0x2ca15f8, estate=0x2c914e8, eflags=16) at nodeSeqscan.c:172
#5 0x00000000006ceb5f in ExecInitNode (node=0x2ca15f8, estate=0x2c914e8, eflags=16) at execProcnode.c:207
#6 0x00000000006c58a2 in InitPlan (queryDesc=0x2c8cdf8, eflags=16) at execMain.c:1020
#7 0x00000000006c480c in standard_ExecutorStart (queryDesc=0x2c8cdf8, eflags=16) at execMain.c:266
#8 0x00000000006c45d8 in ExecutorStart (queryDesc=0x2c8cdf8, eflags=0) at execMain.c:148
#9 0x00000000008c2dfd in PortalStart (portal=0x2c359d8, params=0x0, eflags=0, snapshot=0x0) at pquery.c:505
#10 0x00000000008bd834 in exec_simple_query (query_string=0x2bd2b78 "select rownum,* from t1 where rownum < 4 or id = 5 ;") at postgres.c:1200
#11 0x00000000008c17ba in PostgresMain (argc=1, argv=0x2bfdaa0, dbname=0x2bfd9c8 "postgres", username=0x2bcf588 "uxdb") at postgres.c:4315
#12 0x00000000008229d1 in BackendRun (port=0x2bf59c0) at postmaster.c:4536
#13 0x00000000008221d8 in BackendStartup (port=0x2bf59c0) at postmaster.c:4220
#14 0x000000000081ead2 in ServerLoop () at postmaster.c:1739
#15 0x000000000081e3ae in PostmasterMain (argc=3, argv=0x2bcd4e0) at postmaster.c:1412
#16 0x00000000007333cf in main (argc=3, argv=0x2bcd4e0) at main.c:210
(gdb)
然后接下来就是处理第二部分条件 id=5
的部分,OK 案例二不再往下展开(后续案例有后续的介绍)
Portal模块 其初始执行函数PortalRun
如下:
// postgres\src\backend\tcop\pquery.c
/**
* PortalRun 运行门户的查询
*
* count <= 0被解释为no-op:启动和关闭目标,但不发生其他事情
* 同样,count == FETCH_ALL被解释为“所有行”
* 注意,在多查询的情况下,我们总是运行门户到完成,计数将被忽略
*
* isTopLevel:如果查询在后端“顶级”执行(即直接从客户端命令消息执行),则为true
*
* dest:发送主(canSetTag)查询输出的位置
*
* altdest:发送非主查询输出的位置
*
* qc: 在哪里存储命令完成状态数据。如果调用者不想要状态数据,则可能为NULL
* 如果门户执行完成,则返回true;如果由于count参数用尽而挂起门户,则返回false
*/
bool PortalRun(Portal portal, long count, bool isTopLevel, bool run_once,
DestReceiver *dest, DestReceiver *altdest,
QueryCompletion *qc)
{
bool result;
uint64 nprocessed;
ResourceOwner saveTopTransactionResourceOwner;
MemoryContext saveTopTransactionContext;
Portal saveActivePortal;
ResourceOwner saveResourceOwner;
MemoryContext savePortalContext;
MemoryContext saveMemoryContext;
AssertArg(PortalIsValid(portal));
TRACE_POSTGRESQL_QUERY_EXECUTE_START();
/* 初始化空的完成数据 */
if (qc)
InitializeQueryCompletion(qc);
if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY)
{
elog(DEBUG3, "PortalRun");
/* PORTAL_MULTI_QUERY记录它自己的每个查询的统计信息 */
ResetUsage();
}
/*
* 检查门户使用是否不当,并将门户标记为活动状态
*/
MarkPortalActive(portal);
/* 设置run_once标志。不应该清楚是否预先设置 */
Assert(!portal->run_once || run_once);
portal->run_once = run_once;
/*
* 设置全局门户上下文指针
*
* 我们必须玩一个特殊的游戏来支持像VACUUM和CLUSTER这样的实用命令,它们在内部启动和提交事务
* 当我们被调用来执行这样一个命令时,CurrentResourceOwner将指向TopTransactionResourceOwner——它将在内部提交和重启过程中被销毁和替换
* 因此,我们需要准备将它恢复为指向退出时的TopTransactionResourceOwner。(不是丑吗?这种从内部启动全新事务的想法是不好的。)
* CurrentMemoryContext也有类似的问题,但是我们保存在这里的其他指针将为NULL,或者指向寿命较长的对象。
*/
saveTopTransactionResourceOwner = TopTransactionResourceOwner;
saveTopTransactionContext = TopTransactionContext;
saveActivePortal = ActivePortal;
saveResourceOwner = CurrentResourceOwner;
savePortalContext = PortalContext;
saveMemoryContext = CurrentMemoryContext;
PG_TRY();
{
ActivePortal = portal;
if (portal->resowner)
CurrentResourceOwner = portal->resowner;
PortalContext = portal->portalContext;
MemoryContextSwitchTo(PortalContext);
switch (portal->strategy)
{
case PORTAL_ONE_SELECT:
case PORTAL_ONE_RETURNING:
case PORTAL_ONE_MOD_WITH:
case PORTAL_UTIL_SELECT:
/*
* 如果我们还没有运行该命令,那么就运行它,并将其结果存储在门户的tuplestore中
* 但是对于PORTAL_ONE_SELECT情况我们不这样做
*/
if (portal->strategy != PORTAL_ONE_SELECT && !portal->holdStore)
FillPortalStore(portal, isTopLevel);
/*
* 现在获取所需的结果部分
*/
nprocessed = PortalRunSelect(portal, true, count, dest);
/*
* 如果门户结果包含一个命令标记,而调用者给了我们一个用于存储该标记的指针,则复制该标记并更新行数
*/
if (qc && portal->qc.commandTag != CMDTAG_UNKNOWN)
{
CopyQueryCompletion(qc, &portal->qc);
qc->nprocessed = nprocessed;
}
/* Mark portal not active */
portal->status = PORTAL_READY;
/*
* 因为这是一个前向取回,所以说DONE iff atEnd现在为真
*/
result = portal->atEnd;
break;
case PORTAL_MULTI_QUERY:
PortalRunMulti(portal, isTopLevel, false,
dest, altdest, qc);
/* Prevent portal's commands from being re-executed */
MarkPortalDone(portal);
/* Always complete at end of RunMulti */
result = true;
break;
default:
elog(ERROR, "unrecognized portal strategy: %d",
(int) portal->strategy);
result = false; /* keep compiler quiet */
break;
}
}
PG_CATCH();
{
/* Uncaught error while executing portal: mark it dead */
MarkPortalFailed(portal);
/* Restore global vars and propagate error */
if (saveMemoryContext == saveTopTransactionContext)
MemoryContextSwitchTo(TopTransactionContext);
else
MemoryContextSwitchTo(saveMemoryContext);
ActivePortal = saveActivePortal;
if (saveResourceOwner == saveTopTransactionResourceOwner)
CurrentResourceOwner = TopTransactionResourceOwner;
else
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
PG_RE_THROW();
}
PG_END_TRY();
if (saveMemoryContext == saveTopTransactionContext)
MemoryContextSwitchTo(TopTransactionContext);
else
MemoryContextSwitchTo(saveMemoryContext);
ActivePortal = saveActivePortal;
if (saveResourceOwner == saveTopTransactionResourceOwner)
CurrentResourceOwner = TopTransactionResourceOwner;
else
CurrentResourceOwner = saveResourceOwner;
PortalContext = savePortalContext;
if (log_executor_stats && portal->strategy != PORTAL_MULTI_QUERY)
ShowUsage("EXECUTOR STATISTICS");
TRACE_POSTGRESQL_QUERY_EXECUTE_DONE();
return result;
}
案例三、下面我们来结合一个实际的案例,来详细分析一下执行模块中的逻辑:
select * from t1 where rownum < 3 ;
# 这个相当于 limit 2
如上,我们调试的SQL语句 在上面PortalRun
里面,最重要的函数:
...
/*
* Execute the plan and obtain a tuple
*/
slot = ExecProcNode(planstate);
...
// 执行计划 从其中得到一个记录
第十三步:
ExecProcNode
函数中增加rownum
的+1
及相关赋值
这里每次执行计划要得到一个tuple的时候,其中的rownum便会自增!
// postgres\src\include\executor\executor.h
// 在该函数中,新增每次的rownum++
// 并将新的值(PlanState中的) 赋到node->ps_ProjInfo->pi_state.rownum 和 node->qual->rownum中
/* ----------------------------------------------------------------
* ExecProcNode
*
* Execute the given node to return a(nother) tuple.
* ----------------------------------------------------------------
*/
#ifndef FRONTEND
static inline TupleTableSlot *
ExecProcNode(PlanState *node)
{
if (node->chgParam != NULL) /* something changed? */
ExecReScan(node); /* let ReScan handle this */
++(node->rownum);
if(node->ps_ProjInfo != NULL)
node->ps_ProjInfo->pi_state.rownum = node->rownum;
if(node->qual != NULL && node->qual->hasrownum)
node->qual->rownum = node->rownum;
return node->ExecProcNode(node);
}
#endif
/**
* 解释一下这个函数:
* 1. 首先node->rownum 自增,下面的两个if是负责将值传递到后面有用的地方 后面会说
*
* 2. if(node->ps_ProjInfo != NULL) 这个主要是处理:select rownum,* 的这种前面有rownum显式打印的情况
*
* 3. if(node->qual != NULL && node->qual->hasrownum)
* 这个主要是处理 rownum 没有被转化为limit的时候,该rownum部分也是where条件的一部分了 (详细请看案例二)
*/
(gdb) bt
#0 ExecProcNode (node=0x1554890) at ../../../src/include/executor/executor.h:242
#1 0x00000000006c6b6d in ExecutePlan (estate=0x1554668, planstate=0x1554890, use_parallel_mode=false, operation=CMD_SELECT, sendTuples=true, numberTuples=0, direction=ForwardScanDirection, dest=0x14ba0a8,
execute_once=true) at execMain.c:1646
#2 0x00000000006c49d3 in standard_ExecutorRun (queryDesc=0x14b6838, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:364
#3 0x00000000006c4870 in ExecutorRun (queryDesc=0x14b6838, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:308
#4 0x00000000008c3660 in PortalRunSelect (portal=0x14f6b48, forward=true, count=0, dest=0x14ba0a8) at pquery.c:912
#5 0x00000000008c3354 in PortalRun (portal=0x14f6b48, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x14ba0a8, altdest=0x14ba0a8, qc=0x7ffdc62a12c0) at pquery.c:756
#6 0x00000000008bd920 in exec_simple_query (query_string=0x1494b78 "select * from t1 where rownum < 3 ;") at postgres.c:1239
#7 0x00000000008c17ba in PostgresMain (argc=1, argv=0x14bec10, dbname=0x14beb38 "postgres", username=0x1491598 "uxdb") at postgres.c:4315
#8 0x00000000008229d1 in BackendRun (port=0x14b6b30) at postmaster.c:4536
#9 0x00000000008221d8 in BackendStartup (port=0x14b6b30) at postmaster.c:4220
#10 0x000000000081ead2 in ServerLoop () at postmaster.c:1739
#11 0x000000000081e3ae in PostmasterMain (argc=3, argv=0x148f4f0) at postmaster.c:1412
#12 0x00000000007333cf in main (argc=3, argv=0x148f4f0) at main.c:210
(gdb)
紧接着回调ExecProcNodeFirst
函数,(是由上面函数的return node->ExecProcNode(node);
引发)其作用:ExecProcNode包装器,在调用相关的节点方法(可能通过插装包装器)之前执行一些一次性检查。
接下来再次return node->ExecProcNode(node);
回调ExecLimit
,在其中调用recompute_limits
来 计算限制/偏移表达式——在启动或重新扫描时完成。这也是重置当前位置状态信息的方便位置。此刻案例三 详细的堆栈信息如下:
然后在其中发现:node->limitOffset
为空,当然 这里的offset就为0。然后解析limit
部分,这里前面已经做过转换limit 2
,于是如下:
// postgres\src\backend\executor\nodeLimit.c
// recompute_limits函数中
(gdb) n
360 if (node->limitOffset)
(gdb)
380 node->offset = 0;
(gdb) n
383 if (node->limitCount)
(gdb)
385 val = ExecEvalExprSwitchContext(node->limitCount,
(gdb)
389 if (isNull)
(gdb) p val
$2 = 2
(gdb) n
396 node->count = DatumGetInt64(val);
(gdb)
397 if (node->count < 0)
(gdb) p node->count
$3 = 2
(gdb)
接下来 node->lstate
的状态被设置为LIMIT_RESCAN
;进入case LIMIT_RESCAN:
,然后再次调用ExecProcNode
(此时处理的node是上面node的左子树):
下面来看一下ExecSeqScan
函数
//
/* ----------------------------------------------------------------
* ExecSeqScan(node)
*
* 按顺序扫描关系并返回下一个符合条件的元组
* 我们调用ExecScan()例程,并将适当的访问方法函数传递给它
* ----------------------------------------------------------------
*/
static TupleTableSlot *
ExecSeqScan(PlanState *pstate)
{
SeqScanState *node = castNode(SeqScanState, pstate);
return ExecScan(&node->ss,
(ExecScanAccessMtd) SeqNext,
(ExecScanRecheckMtd) SeqRecheck);
}
下面进入ExecScan
函数:
// postgres\src\backend\executor\execScan.c
/**
* ExecScan
* 使用指定的“访问方法”扫描表,并返回下一个符合条件的元组
* access方法返回下一个元组,ExecScan()负责检查根据qual子句返回的元组
* 还必须提供一个“recheck方法”,它可以根据access方法内部实现的任何限定条件来检查关系的任意元组
*
* AMI维护的“游标”定位在先前返回的元组上
* 初始状态:——指定的关系打开用于扫描,因此“游标”位于第一个符合条件的元组之前
*/
TupleTableSlot *
ExecScan(ScanState *node,
ExecScanAccessMtd accessMtd, /* function returning a tuple */
ExecScanRecheckMtd recheckMtd);
注:PostgreSQL数据库果然是全世界最强大的数据库,没有之一! 我特么不知道调试走到哪了 ! 中间的一些步骤这里暂不追究其细节(其实是我说不太清楚 pg的一些底层细节),接着这个案例三 调试函数调用堆栈目前如下:
当我看到这个8的时候,非常高兴 因为目前我现在的这个表里面有8行数据。那么这个8是否就是表内记录的条数呢?
OK 于是在得到一个满足条件的tuple之后,函数调用堆栈返回:
紧接着退回到ExecutePlan
函数中,计算此刻的current_tuple_count
然后for (;;)
继续执行:
/*
* Execute the plan and obtain a tuple
*/
slot = ExecProcNode(planstate);
去得到下一条tuple,这里的限制 就是limit的作用如下 在ExecLimit函数中
:
然后接着上面逻辑再走一遍,直到table_scan_getnextslot
又得到了一条之后。导致node->position++
,当如下:
// 继续:
ExecProcNode
ExecLimit // (在这里将会让上面的limit条件 满足,将停止整个循环)
毕竟这里只是处理了一个:select * from t1 limit 2;
,接下来就是这两行的后续打印,案例三到此结束!
案例四:打印rownum
列,以及使用其他条件:
postgres=# select * from t1;
id | name
----+------
6 | a
9 | b
8 | c
5 | d
7 | e
3 | f
1 | h
2 | h
(8 rows)
postgres=#
postgres=# select rownum,* from t1 where rownum < 4 and (id = 2 or id = 1);
rownum | id | name
--------+----+------
1 | 1 | h
2 | 2 | h
(2 rows)
# 这个相当于 如下:
postgres=# select rownum,* from t1 where id = 2 or id = 1 limit 3;
rownum | id | name
--------+----+------
1 | 1 | h
2 | 2 | h
(2 rows)
postgres=#
最后函数transformTargetList
解析得到将要显示的列有3行:
// postgres\src\backend\parser\parse_target.c
// transformTargetList
(gdb)
214 return p_target;
(gdb) p *p_target
$6 = {
type = T_List, length = 3, max_length = 5, elements = 0x219c598, initial_elements = 0x219c598}
(gdb)
接下来,我们直接到达 如下:
// E:\postgresql 12\postgres\src\backend\parser\analyze.c
/* transform WHERE */
qual = transformWhereClause(pstate, stmt->whereClause,EXPR_KIND_WHERE, "WHERE");
|
transformExpr
|
transformExprRecurse
// 解析where条件部分
现在的条件rownum < 4 and (id = 2 or id = 1)
整体是一个bool节点,其解析结果和函数调用堆栈 如下:
然后由 and
开始切割左右,左边就是 rownum < 4
这是一个T_A_Expr
节点,其左右分别为T_RownumExpr
和 T_A_Const
如下 作为第一部分:
而(id = 2 or id = 1)
的解析,也是一个T_BoolExpr
,其由 or
分割,其左右都是 T_OpExpr
如下:
由于这里的分割a->boolop
是or
那么这两个条件最后就会被拼接为 一个整体 作为第二部分:
id = 2 or id = 1
最后经过return (Node *) makeBoolExpr(a->boolop, args, a->location);
这两部分就归于一体:
于是在下面的堆栈返回的过程中,对where条件(rownum<4 and (id = 2 or id = 1)
)的解析 将赋值给 qual
:
(gdb)
transformExpr (pstate=0x219bf20, expr=0x219bd60, exprKind=EXPR_KIND_WHERE) at parse_expr.c:159
159 pstate->p_expr_kind = sv_expr_kind;
(gdb)
161 return result;
(gdb)
162 }
(gdb)
transformWhereClause (pstate=0x219bf20, clause=0x219bd60, exprKind=EXPR_KIND_WHERE, constructName=0xabd7a2 "WHERE") at parse_clause.c:1730
1730 qual = coerce_to_boolean(pstate, qual, constructName);
(gdb)
1732 return qual;
(gdb)
1733 }
(gdb)
transformSelectStmt (pstate=0x219bf20, stmt=0x219bd90) at analyze.c:1565
1565 qry->havingQual = transformWhereClause(pstate, stmt->havingClause,
(gdb) p *qual
$26 = {
type = T_BoolExpr}
(gdb) bt
#0 transformSelectStmt (pstate=0x219bf20, stmt=0x219bd90) at analyze.c:1565
#1 0x000000000059d72d in transformStmt (pstate=0x219bf20, parseTree=0x219bd90) at analyze.c:631
#2 0x000000000059d602 in transformOptionalSelectInto (pstate=0x219bf20, parseTree=0x219bd90) at analyze.c:576
#3 0x000000000059d4fa in transformTopLevelStmt (pstate=0x219bf20, parseTree=0x219bea0) at analyze.c:526
#4 0x000000000059d35f in parse_analyze (parseTree=0x219bea0, sourceText=0x219ab78 "select rownum,* from t1 where rownum < 4 and (id = 2 or id = 1);", paramTypes=0x0, numParams=0, queryEnv=0x0) at analyze.c:444
#5 0x00000000008bd113 in pg_analyze_and_rewrite (parsetree=0x219bea0, query_string=0x219ab78 "select rownum,* from t1 where rownum < 4 and (id = 2 or id = 1);", paramTypes=0x0, numParams=0, queryEnv=0x0) at postgres.c:691
#6 0x00000000008bd786 in exec_simple_query (query_string=0x219ab78 "select rownum,* from t1 where rownum < 4 and (id = 2 or id = 1);") at postgres.c:1155
#7 0x00000000008c17ba in PostgresMain (argc=1, argv=0x21c5aa0, dbname=0x21c59c8 "postgres", username=0x2197588 "uxdb") at postgres.c:4315
#8 0x00000000008229d1 in BackendRun (port=0x21bd9c0) at postmaster.c:4536
#9 0x00000000008221d8 in BackendStartup (port=0x21bd9c0) at postmaster.c:4220
#10 0x000000000081ead2 in ServerLoop () at postmaster.c:1739
#11 0x000000000081e3ae in PostmasterMain (argc=3, argv=0x21954e0) at postmaster.c:1412
#12 0x00000000007333cf in main (argc=3, argv=0x21954e0) at main.c:210
(gdb)
OK,在上面 targetlist 和 where条件的解析完成之后,接下来就好做多了 (建议回头再阅读一下 第八步 第九步)
然后 第十步(重写到limit逻辑) 就好理解多了(这里因为时间和篇幅的关系 我就不详细再复述一遍),在第十步中:我们会把上面得到的where条件:rownum<4 and (id = 2 or id = 1)
变成 如下状态:
1、
rownum<4
会被 变成limit 3
2、query->jointree->quals
就是where语句的条件会被转变成id = 2 or id = 1
这也就是 案例一 的全部内容,但是该案例将继续向下展开(重复 案例三 的操作),我们到达 8行数据的位置:
OK,上面 案例三 是没有where条件的,在得到一个tuple之后 就直接返回了 这也就是我们 案例四 存在的意义,又说了这么多废话 终于开始案例四的核心讨论点 ExecQual
:
那么下面看一下ExecQual
函数:
// postgres\src\include\executor\executor.h
/*
* ExecQual - 用ExecInitQual(可能通过ExecPrepareQual)评估一个准备好的qual
* 如果满足qual则返回true,否则返回false
*
* 注意:ExecQual过去有第三个参数"resultForNull"
* 这个函数的行为现在对应于resultForNull == false
* 如果你想要resultForNull == true的行为,请参阅ExecCheck
*/
#ifndef FRONTEND
static inline bool
ExecQual(ExprState *state, ExprContext *econtext)
{
Datum ret;
bool isnull;
/* 短路(这里和在ExecInitQual中)空的限制列表 */
if (state == NULL)
return true;
/* 验证表达式是使用ExecInitQual编译的 */
Assert(state->flags & EEO_FLAG_IS_QUAL);
ret = ExecEvalExprSwitchContext(state, econtext, &isnull);
/* EEOP_QUAL should never return NULL */
Assert(!isnull);
return DatumGetBool(ret);
}
#endif
接下来的函数调用为:ExecEvalExprSwitchContext
-->回调ExecInterpExprStillValid
-->回调 ExecInterpExpr
,下面是ExecInterpExpr
函数的说明:
// postgres\src\backend\executor\execExprInterp.c
/*
* 在由“econtext”给出的执行上下文中,由“state”标识的表达式求值
* *isnull被设置为结果的is-null标志
* Datum值是函数结果
*
* 作为特例,如果state为NULL,则返回调度表的地址
*
* ExecInitInterpreter使用它来设置dispatch_table全局表(仅适用于定义了EEO_USE_COMPUTED_GOTO的情况。)
*/
static Datum
ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull);
注:这一块 相当相当相当 复杂,具体说一下:
我们已知上面ExecScanFetch
将会依次将表中的数据 一行一行的得到,然后下面ExecQual
模块的功能:
1、检查这个slot是否满足 条件
qual
不满足就跳过这一行ExecQual
返回值为假 只能执行下面的 else(然后开启下一次循环)
2、满足的话 在ExecInterpExpr
内部返回的值 就是true 这样,最外面的ExecQual
返回值为真 于是就可以第3步
3、进行如下return 操作(意思就是说 这个tuple是一个满足条件可以返回的):
if (qual == NULL || ExecQual(qual, econtext))
{
/*
* Found a satisfactory scan tuple.
*/
if (projInfo)
{
/*
* Form a projection tuple, store it in the result tuple slot
* and return it.
*/
return ExecProject(projInfo);
}
else
{
/*
* Here, we aren't projecting, so just return scan tuple.
*/
return slot;
}
}
else
InstrCountFiltered1(node, 1);
那么上面的每一个tuple都是怎么被判定为满足 或者 不满足条件的呢?如下:(学会 调试 这个强大的武器,真的会所向披靡 无往不利)
postgres=# select * from t1;
id | name
----+------
6 | a
9 | b
8 | c
5 | d
7 | e
3 | f
1 | h
2 | h
(8 rows)
postgres=# select rownum,* from t1 where rownum < 4 and (id = 2 or id = 1);
比如 我们这里的条件是 id = 2 or id = 1
,那么在得到每一行数据之后 都将会去判定 所要判断的字段的值 是否满足条件(这里需要执行的判断条件的注册 详细请看案例二):
1、一行行的取出来 上面8行数据会被依次取出,然后经由
ExecQual
函数的判断
2、该条件被分为两部分:id = 2
和id = 1
依次判断这两个条件
3、在ExecInterpExpr
函数中,会去要比较的值 和 上面的条件 进行比较,其函数调用堆栈如下(此时是 已经到达id = 1
的这一行):
4、于是满足条件的 和 不满足条件的,最终会导致p *op->resvalue
的不同,满足的结果为1 不满足的为0
5、而上面这个值的不同 会直接导致在EEO_CASE(EEOP_QUAL)
中的返回值不同 不满足的将返回false 满足的返回为true
6、而这个bool值 将会最终作为ExecQual
函数的返回值,我们在上面紧接着的代码块中 可以看到ExecQual
函数的返回值为真,才有下一步的 return 操作;为假 说明这一行数据不满足条件 被舍弃
OK,到了这一步 我们的 案例四 的第一个目标(使用其他条件,进行的过滤)已经达到,接下来看一下后续操作:
1、在上面
ExecQual
函数的返回值为真,于是就得到了一个满足的tuple 下面就是保存返回
2、构造一个投影tuple(前面需要得到rownum的值),由ExecProject
完成 取值过程和堆栈如下:
(gdb) bt
#0 ExecInterpExpr (state=0x225bfb8, econtext=0x225bb20, isnull=0x7ffefb9ebd17) at execExprInterp.c:1787
#1 0x00000000006bc39d in ExecInterpExprStillValid (state=0x225bfb8, econtext=0x225bb20, isNull=0x7ffefb9ebd17) at execExprInterp.c:1809
#2 0x00000000006d2533 in ExecEvalExprSwitchContext (state=0x225bfb8, econtext=0x225bb20, isNull=0x7ffefb9ebd17) at ../../../src/include/executor/executor.h:320
#3 0x00000000006d259f in ExecProject (projInfo=0x225bfb0) at ../../../src/include/executor/executor.h:354
#4 0x00000000006d298f in ExecScan (node=0x225ba10, accessMtd=0x706220 <SeqNext>, recheckMtd=0x7062c5 <SeqRecheck>) at execScan.c:238
#5 0x0000000000706302 in ExecSeqScan (pstate=0x225ba10) at nodeSeqscan.c:112
#6 0x00000000006cf0d5 in ExecProcNodeFirst (node=0x225ba10) at execProcnode.c:450
#7 0x00000000006fa1fe in ExecProcNode (node=0x225ba10) at ../../../src/include/executor/executor.h:252
#8 0x00000000006fa3ac in ExecLimit (pstate=0x225b720) at nodeLimit.c:96
#9 0x00000000006cf0d5 in ExecProcNodeFirst (node=0x225b720) at execProcnode.c:450
#10 0x00000000006c44ad in ExecProcNode (node=0x225b720) at ../../../src/include/executor/executor.h:252
#11 0x00000000006c6b6d in ExecutePlan (estate=0x225b4f8, planstate=0x225b720, use_parallel_mode=false, operation=CMD_SELECT, sendTuples=true, numberTuples=0, direction=ForwardScanDirection, dest=0x2267198, execute_once=true) at execMain.c:1646
#12 0x00000000006c49d3 in standard_ExecutorRun (queryDesc=0x21be078, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:364
#13 0x00000000006c4870 in ExecutorRun (queryDesc=0x21be078, direction=ForwardScanDirection, count=0, execute_once=true) at execMain.c:308
#14 0x00000000008c3660 in PortalRunSelect (portal=0x21fd9d8, forward=true, count=0, dest=0x2267198) at pquery.c:912
#15 0x00000000008c3354 in PortalRun (portal=0x21fd9d8, count=9223372036854775807, isTopLevel=true, run_once=true, dest=0x2267198, altdest=0x2267198, qc=0x7ffefb9ec170) at pquery.c:756
#16 0x00000000008bd920 in exec_simple_query (query_string=0x219ab78 "select rownum,* from t1 where rownum < 4 and (id = 2 or id = 1);") at postgres.c:1239
#17 0x00000000008c17ba in PostgresMain (argc=1, argv=0x21c5aa0, dbname=0x21c59c8 "postgres", username=0x2197588 "uxdb") at postgres.c:4315
#18 0x00000000008229d1 in BackendRun (port=0x21bd9c0) at postmaster.c:4536
#19 0x00000000008221d8 in BackendStartup (port=0x21bd9c0) at postmaster.c:4220
#20 0x000000000081ead2 in ServerLoop () at postmaster.c:1739
#21 0x000000000081e3ae in PostmasterMain (argc=3, argv=0x21954e0) at postmaster.c:1412
#22 0x00000000007333cf in main (argc=3, argv=0x21954e0) at main.c:210
(gdb)
到此含有rownum打印的元组也被构建出来了!案例四到此结束
第十四步:
ExecInterpExpr
函数中增加rownum
的取值,以支持条件判断 和 构造投影tuple
如上图所示,就是增加了在该tuple中 获取其rownum的值,以构造最终的显示结果 投影tuple的:
// postgres\src\backend\executor\execExprInterp.c
static Datum
ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull);
// 增加的内容 第一块如下,在dispatch_table里面 增加 获取rownum的 case:
&&CASE_EEOP_ROWNUMEXPR, /* 放到 &&CASE_EEOP_LAST 即可 */
// 在下面增加对应的case:
EEO_CASE(EEOP_ROWNUMEXPR)
{
*op->resvalue = Int64GetDatum(state->rownum);
EEO_NEXT();
}
上面这个(第十四步)的存在 就是在处理被重写的where条件中 还具有 rownum 部分的条件的 (这一部分详见案例二),取出此刻的rownum值,然后跟这个rownum条件进行匹配 看是否满足。接下来若是满足 则该值 *op->resvalue
将可以用于构造 目标列表中 需要显式打印rownum的投影tuple
第十五步:
ExprEvalOp
枚举中增加EEOP_ROWNUMEXPR
,以支持构造投影tuple
// postgres\src\include\executor\execExpr.h
/**
* ExprEvalSteps鉴别器
*
* 标识要执行的操作以及expprevalstep ->d联合中的哪个成员是有效的
*
* 条目的顺序需要与execExprInterp.c:ExecInterpExpr()中的dispatch_table[]数组保持同步
*/
// 注:在上面的dispatch_table头上 有这么一句话:这个数组必须与enum ExprEvalOp的顺序相同,所以在里面添加:
EEOP_ROWNUMEXPR, // 注意顺序,这里还是放在 EEOP_LAST 头上
至此 在PostgreSQL数据库中支持 rownum 伪列的功能全部完成,下面是现在支持的测试用例:
psql (13.0)
Type "help" for help.
postgres=# create table t1(id int,name character varying(20));
CREATE TABLE
postgres=# insert into t1 values(6,'a');
INSERT 0 1
postgres=# insert into t1 values(9,'b');
INSERT 0 1
postgres=# insert into t1 values(8,'c');
INSERT 0 1
postgres=# insert into t1 values(5,'d');
INSERT 0 1
postgres=# insert into t1 values(7,'e');
INSERT 0 1
postgres=# insert into t1 values(3,'f');
INSERT 0 1
postgres=# insert into t1 values(7,'g');
INSERT 0 1
postgres=# insert into t1 values(1,'h');
INSERT 0 1
postgres=# insert into t1 values(2,'h');
INSERT 0 1
postgres=#
postgres=# create table test(id int,name character varying(20));
CREATE TABLE
postgres=# insert into test values(1,'a1');
INSERT 0 1
postgres=# insert into test values(2,'a2');
INSERT 0 1
postgres=# insert into test values(3,'a3');
INSERT 0 1
postgres=# insert into test values(5,'a5');
INSERT 0 1
postgres=#
postgres=# select rownum, * from t1;
rownum | id | name
--------+----+------
1 | 6 | a
2 | 9 | b
3 | 8 | c
4 | 5 | d
5 | 7 | e
6 | 3 | f
7 | 7 | g
8 | 1 | h
9 | 2 | h
(9 rows)
postgres=# select rownum, * from t1 where rownum = 1;
rownum | id | name
--------+----+------
1 | 6 | a
(1 row)
postgres=# select rownum, * from t1 where rownum > 1;
rownum | id | name
--------+----+------
(0 rows)
postgres=# select rownum, * from t1 where rownum = id;
rownum | id | name
--------+----+------
1 | 1 | h
2 | 2 | h
(2 rows)
postgres=# select * from t1 where rownum =id;
id | name
----+------
1 | h
2 | h
(2 rows)
postgres=# select rownum, * from t1 where rownum < id;
rownum | id | name
--------+----+------
1 | 6 | a
2 | 9 | b
3 | 8 | c
4 | 5 | d
5 | 7 | e
6 | 7 | g
(6 rows)
postgres=# select * from t1 where rownum < id;
id | name
----+------
6 | a
9 | b
8 | c
5 | d
7 | e
7 | g
(6 rows)
postgres=# select rownum, * from t1 where rownum < id and id < 5;
rownum | id | name
--------+----+------
1 | 3 | f
(1 row)
postgres=# select rownum, * from t1 where rownum < 3 and rownum < 4;
rownum | id | name
--------+----+------
1 | 6 | a
2 | 9 | b
(2 rows)
postgres=# select * from t1 where rownum != 10;
id | name
----+------
6 | a
9 | b
8 | c
5 | d
7 | e
3 | f
7 | g
1 | h
2 | h
(9 rows)
postgres=# select rownum, * from t1 order by id;
rownum | id | name
--------+----+------
1 | 1 | h
2 | 2 | h
3 | 3 | f
4 | 5 | d
5 | 6 | a
6 | 7 | e
7 | 7 | g
8 | 8 | c
9 | 9 | b
(9 rows)
postgres=# select rownum, * from t1 where rownum < 6 order by name DESC;
rownum | id | name
--------+----+------
1 | 2 | h
2 | 1 | h
3 | 7 | g
4 | 3 | f
5 | 7 | e
(5 rows)
postgres=# select rownum,id from t1 where rownum < 6 group by t1.id;
rownum | id
--------+----
1 | 9
2 | 3
3 | 5
4 | 6
5 | 2
(5 rows)
postgres=# select rownum,id from t1 where rownum < 6 group by t1.id having t1.id > 6;
rownum | id
--------+----
1 | 9
2 | 7
3 | 8
(3 rows)
postgres=# select * from(select rownum ,id,name from t1) t where id>2;
rownum | id | name
--------+----+------
1 | 6 | a
2 | 9 | b
3 | 8 | c
4 | 5 | d
5 | 7 | e
6 | 3 | f
7 | 7 | g
(7 rows)
postgres=# select rownum, * from (select rownum, * from t1 right join test on t1.id=test.id) t where rownum =1;
rownum | rownum | id | name | id | name
--------+--------+----+------+----+------
1 | 1 | 1 | h | 1 | a1
(1 row)
postgres=# select rownum, * from (select rownum, * from t1 right join test on t1.id=test.id) t where rownum =2;
rownum | rownum | id | name | id | name
--------+--------+----+------+----+------
(0 rows)
postgres=# select rownum , * from (select rownum,* from t1 where rownum < 6 group by t1.id,t1.name having t1.id > 6) t where rownum < 4;
rownum | rownum | id | name
--------+--------+----+------
1 | 1 | 7 | e
2 | 2 | 8 | c
3 | 3 | 7 | g
(3 rows)
postgres=# select * from (select rownum id,name from t1 where rownum <= 10 order by name ) t where id > 5;
id | name
----+------
6 | f
7 | g
8 | h
9 | h
(4 rows)
postgres=# select rownum, * from (select rownum, * from t1 right join test on t1.id=test.id) t where rownum < 3;
rownum | rownum | id | name | id | name
--------+--------+----+------+----+------
1 | 1 | 1 | h | 1 | a1
2 | 2 | 2 | h | 2 | a2
(2 rows)
postgres=#