PostgreSQL的学习心得和知识总结(二十二) 您所在的位置:网站首页 ora-00976此处不允许指定的伪列或运算符 PostgreSQL的学习心得和知识总结(二十二)

PostgreSQL的学习心得和知识总结(二十二)

2023-01-24 11:51| 来源: 网络整理| 查看: 265

目录结构

注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:

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真正的 伪列rownum 文章快速说明索引 Oracle中的伪列rownum PostgreSQL里面的 limit 在"形式"上实现 rownum limit/offset功能语法解析 PostgreSQL rownum实现 新增 rownum 的语法相关 查询分析阶段 parser模块 查询优化阶段 optimizer模块 查询执行模块 executor模块 PostgreSQL rownum测试

文章快速说明索引

学习目标:

在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> Oracle中的伪列rownum

演示背景如下:

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

实例2:查询出表中的ID最大的3行的全部信息,并按照id逆序

SQL> select rowid,rownum as rn,id,time,name from test_rownum where rownum select rowid,rownum as rn,id,time,name from test_rownum where rownum select rownum as rn,t.* from (select * from test_rownum ORDER BY id desc) t where rownum

注:"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 rownum1; 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 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:!= 和 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 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 # 如上三种方式的效率依次:最低 较高 最高 PostgreSQL里面的 limit

注: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,点击前往

在"形式"上实现 rownum

在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还相差甚远)。那么如何得到你从未拥有过的东西,只有去做你从未做过的事情!开整

limit/offset功能语法解析

下面先来看一下目前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; PostgreSQL rownum实现

基本思路:

1、rownum作为 表达式节点 的一种 2、在语法解析阶段通过匹配在c_expr表达式规则下的ROWNUM关键字,创建RownumExpr结构体节点 3、在语法分析阶段转换where子句中的rownum条件 为 对应的limit条件 4、没有成功转换的rownum条件 将作为普通的(列)条件进行过滤数据

新增 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 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; } 查询分析阶段 parser模块

第八步:构造显示目标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;

看一下这里的解析方式:rownump_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 [=] rownum" * to "limit N" * TODO: fix when "Const::consttypmod != -1" * TODO: fix when "rownum < 1 and rownum < 2" to "limit CASE WHEN 1rownum" to "rownum


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有