/**PageBeginHtml Block Begin **/ /***自定义返回顶部小火箭***/ /*生成博客目录的JS 开始*/ /*生成博客目录的JS 结束*/

PostgreSQL子查询别名改造(兼容Oracle)


原文:传送门
在PostgreSQL中子查询必须得加上别名,即使我们在其它地方不会再引用到这个别名。
否则便会报以下错误:

  1 postgres=# select * from (select * from t1 limit 5);
  2 ERROR:  subquery in FROM must have an alias
  3  LINE 1: select * from (select * from t1 limit 5);
  4                        ^
  5  HINT:  For example, FROM (SELECT ...) [AS] foo.


而在Oracle中是可以不加别名的,例如:

  1 SQL> select * from (select * from t1);
  2 
  3         ID
  4 ----------
  5          1
  6 


当然并不是说这样不好,因为PG中的这种语法是SQL标准语法,但对于某些从Oracle迁移的用户而言,可能会存在一些困扰,那么我们来看看如何从内核层面去实现兼容呢?

首先需要知道,我们输入的SQL语句是作为字符串传递给查询分析器,然后数据库对其进行词法分析和语法分析生成分析树。
而在PG中,词法分析和语法分析依赖的文件是scan.l和gram.y中。通过scan.l进行词法分析,将sql中的关键字识别,作为token传递给语法分析器,而gram.y对传入的token定义语法,并进行语法分析,从而生成parsetree。

我们根据前面的报错,找到gram.y中对应的部分:

  1 | select_with_parens opt_alias_clause
  2      {
  3          RangeSubselect *n = makeNode(RangeSubselect);
  4          n->lateral = false;
  5          n->subquery = $1;
  6          n->alias = $2;
  7          /*
  8           * The SQL spec does not permit a subselect
  9           * (<derived_table>) without an alias clause,
 10           * so we don't either.  This avoids the problem
 11           * of needing to invent a unique refname for it.
 12           * That could be surmounted if there's sufficient
 13           * popular demand, but for now let's just implement
 14           * the spec and see if anyone complains.
 15           * However, it does seem like a good idea to emit
 16           * an error message that's better than "syntax error".
 17           */
 18 
 19          if ($2 == NULL)
 20          {
 21              if (IsA($1, SelectStmt) &&
 22                  ((SelectStmt *) $1)->valuesLists)
 23                 ereport(ERROR,
 24                          (errcode(ERRCODE_SYNTAX_ERROR),
 25                           errmsg("VALUES in FROM must have an alias"),
 26                           errhint("For example, FROM (VALUES ...) [AS] foo."),
 27                           parser_errposition(@1)));
 28              else
 29                  ereport(ERROR,
 30                          (errcode(ERRCODE_SYNTAX_ERROR),
 31                           errmsg("subquery in FROM must have an alias"),
 32                           errhint("For example, FROM (SELECT ...) [AS] foo."),
 33                           parser_errposition(@1)));
 34          }


可以看到select_with_parens即带括号的select语句,后面紧跟着的便是opt_alias_clause,而当$2 == NULL,即opt_alias_clause为空时,便会抛出前面我们遇到的报错。
从这里我们就可以看出为什么PG不允许子查询后必须得有别名了。

接下来开始我们的改造:

一开始我是想着干脆加一个单独的select_with_parens选项不是就得了,如下:

  1 | select_with_parens
  2     {
  3         ...
  4      }


但是显然这里产生了归约冲突,果不其然编译的时候也报错了。。

这里简单说明下:

移入–归约冲突:某一产生式的右部是另一产生式的前缀
归约–归约冲突:不同产生式有相同的右部 或者 产生式的右部是另一产生式的后缀
既然这条路行不通,那我们换个思路,当$2 == NULL时,我们不抛出错误,而是给自己定义一个别名,只是对于用户而言看不到就好了,于是我们可以修改成这样:

  1 if ($2 == NULL)
  2                     {
  3                          Alias *a = makeNode(Alias);
  4                          a->aliasname = "Alias";
  5                          n->alias = a;
  6                      }


这样虽然可以实现我们想要的功能,但是会存在一个问题,如果我们定义的这个别名常量和数据库中其它对象名产生冲突了那咋办呢?

参考了这篇文章,又进行如下修改:

  1 if ($2 == NULL)
  2                      {
  3                          Alias *a = makeNode(Alias);
  4                          StringInfoData newAliasBuf;
  5 
  6                         initStringInfo(&newAliasBuf);
  7                          appendStringInfo(&newAliasBuf, "__new_alias__%d", ++emptyAliasCounts);
  8                          a->aliasname = newAliasBuf.data;
  9                          n->alias = a;
 10                       }
 11 


于是重新编译,便实现了子查询不用别名的功能了!

  1 bill=# select * from (select * from t1 limit 5);
  2   id |               info
  3 ----+----------------------------------
  4   1 | 9240016a94250b03b5a5c39d01946e3c
  5    2 | dd0ea69b5d9c4fa385c5918d832627c5
  6    3 | 1509dc3c2e147d574cb5cbc64687a132
  7    4 | cb635ead5172046f68e517ba894ae6de
  8    5 | a61265e5b65a243b59f1f920aff300ae
  9  (5 rows)


完整patch如下:

  1 diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
  2  index 5fa322d8d4..bec1826865 100644
  3 --- a/src/backend/parser/gram.y
  4  +++ b/src/backend/parser/gram.y
  5  @@ -66,6 +66,7 @@
  6   #include "utils/numeric.h"
  7   #include "utils/xml.h"
  8 
  9  +int emptyAliasCounts = 0;
 10 
 11   /*
 12    * Location tracking support --- simpler than bison's default, since we only
 13  @@ -12097,6 +12098,8 @@ table_ref:    relation_expr opt_alias_clause
 14                        * However, it does seem like a good idea to emit
 15                        * an error message that's better than "syntax error".
 16                        */
 17  +
 18  +                     /*
 19                       if ($2 == NULL)
 20                       {
 21                           if (IsA($1, SelectStmt) &&
 22  @@ -12112,7 +12115,19 @@ table_ref:    relation_expr opt_alias_clause
 23                                        errmsg("subquery in FROM must have an alias"),
 24                                        errhint("For example, FROM (SELECT ...) [AS] foo."),
 25                                        parser_errposition(@1)));
 26  +                    } */
 27  +
 28  +                    if ($2 == NULL)
 29  +                    {
 30  +                        Alias *a = makeNode(Alias);
 31  +                        StringInfoData newAliasBuf;
 32  +
 33  +                        initStringInfo(&newAliasBuf);
 34  +                        appendStringInfo(&newAliasBuf, "__new_alias__%d", ++emptyAliasCounts);
 35  +                        a->aliasname = newAliasBuf.data;
 36  +                        n->alias = a;
 37                       }
 38  +
 39                       $$ = (Node *) n;
 40                   }
 41               | LATERAL_P select_with_parens opt_alias_clause
 42  @@ -12122,6 +12137,8 @@ table_ref:    relation_expr opt_alias_clause
 43                       n->subquery = $2;
 44                       n->alias = $3;
 45                       /* same comment as above */
 46  +
 47  +                    /*
 48                       if ($3 == NULL)
 49                       {
 50                           if (IsA($2, SelectStmt) &&
 51  @@ -12137,7 +12154,19 @@ table_ref:    relation_expr opt_alias_clause
 52                                        errmsg("subquery in FROM must have an alias"),
 53                                        errhint("For example, FROM (SELECT ...) [AS] foo."),
 54                                        parser_errposition(@2)));
 55  +                    } */
 56  +
 57  +                    if ($3 == NULL)
 58  +                    {
 59  +                        Alias *a = makeNode(Alias);
 60  +                        StringInfoData newAliasBuf;
 61  +
 62  +                        initStringInfo(&newAliasBuf);
 63  +                        appendStringInfo(&newAliasBuf, "__new_alias__%d", ++emptyAliasCounts);
 64  +                        a->aliasname = newAliasBuf.data;
 65  +                        n->alias = a;
 66                       }
 67  +
 68                       $$ = (Node *) n;
 69                   }
 70               | joined_table
 71  diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
 72  index 3bdeeb8b0b..0e1b418b99 100644
 73  --- a/src/include/parser/parser.h
 74  +++ b/src/include/parser/parser.h
 75  @@ -38,4 +38,6 @@ extern List *raw_parser(const char *str);
 76   extern List *SystemFuncName(char *name);
 77   extern TypeName *SystemTypeName(char *name);
 78 
 79  +extern int emptyAliasCounts;
 80  +
 81   #endif                            /* PARSER_H */
 82 

参考链接:
https://rng-songbaobao.blog.csdn.net/article/details/111152337
https://www.modb.pro/db/11343
————————————————
版权声明:本文为CSDN博主「foucus、」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_39540651/article/details/121002478

posted @ 2021-11-02 14:48  一品堂.技术学习笔记  阅读(529)  评论(0编辑  收藏  举报