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

2021/10/28 2:10:00

本文主要是介绍PostgreSQL子查询别名改造(兼容Oracle),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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

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

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

SQL> select * from (select * from t1);

        ID
----------
         1

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

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

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

| select_with_parens opt_alias_clause
	{
		RangeSubselect *n = makeNode(RangeSubselect);
		n->lateral = false;
		n->subquery = $1;
		n->alias = $2;
		/*
		 * The SQL spec does not permit a subselect
		 * (<derived_table>) without an alias clause,
		 * so we don't either.  This avoids the problem
		 * of needing to invent a unique refname for it.
		 * That could be surmounted if there's sufficient
		 * popular demand, but for now let's just implement
		 * the spec and see if anyone complains.
		 * However, it does seem like a good idea to emit
		 * an error message that's better than "syntax error".
		 */
		
		if ($2 == NULL)
		{
			if (IsA($1, SelectStmt) &&
				((SelectStmt *) $1)->valuesLists)
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("VALUES in FROM must have an alias"),
						 errhint("For example, FROM (VALUES ...) [AS] foo."),
						 parser_errposition(@1)));
			else
				ereport(ERROR,
						(errcode(ERRCODE_SYNTAX_ERROR),
						 errmsg("subquery in FROM must have an alias"),
						 errhint("For example, FROM (SELECT ...) [AS] foo."),
						 parser_errposition(@1)));
		}

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

接下来开始我们的改造:

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

| select_with_parens
	{
		...
    }

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

这里简单说明下:

  • 移入–归约冲突:某一产生式的右部是另一产生式的前缀
  • 归约–归约冲突:不同产生式有相同的右部 或者 产生式的右部是另一产生式的后缀

既然这条路行不通,那我们换个思路,当$2 == NULL时,我们不抛出错误,而是给自己定义一个别名,只是对于用户而言看不到就好了,于是我们可以修改成这样:

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

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

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

if ($2 == NULL)
					{
						Alias *a = makeNode(Alias);
						StringInfoData newAliasBuf;

						initStringInfo(&newAliasBuf);
						appendStringInfo(&newAliasBuf, "__new_alias__%d", ++emptyAliasCounts);
						a->aliasname = newAliasBuf.data;
						n->alias = a;
 					}

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

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

完整patch如下:

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 5fa322d8d4..bec1826865 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -66,6 +66,7 @@
 #include "utils/numeric.h"
 #include "utils/xml.h"
 
+int emptyAliasCounts = 0;
 
 /*
  * Location tracking support --- simpler than bison's default, since we only
@@ -12097,6 +12098,8 @@ table_ref:	relation_expr opt_alias_clause
 					 * However, it does seem like a good idea to emit
 					 * an error message that's better than "syntax error".
 					 */
+
+					 /*
 					if ($2 == NULL)
 					{
 						if (IsA($1, SelectStmt) &&
@@ -12112,7 +12115,19 @@ table_ref:	relation_expr opt_alias_clause
 									 errmsg("subquery in FROM must have an alias"),
 									 errhint("For example, FROM (SELECT ...) [AS] foo."),
 									 parser_errposition(@1)));
+					} */
+
+					if ($2 == NULL)
+					{
+						Alias *a = makeNode(Alias);
+						StringInfoData newAliasBuf;
+
+						initStringInfo(&newAliasBuf);
+						appendStringInfo(&newAliasBuf, "__new_alias__%d", ++emptyAliasCounts);
+						a->aliasname = newAliasBuf.data;
+						n->alias = a;
 					}
+
 					$$ = (Node *) n;
 				}
 			| LATERAL_P select_with_parens opt_alias_clause
@@ -12122,6 +12137,8 @@ table_ref:	relation_expr opt_alias_clause
 					n->subquery = $2;
 					n->alias = $3;
 					/* same comment as above */
+
+					/*
 					if ($3 == NULL)
 					{
 						if (IsA($2, SelectStmt) &&
@@ -12137,7 +12154,19 @@ table_ref:	relation_expr opt_alias_clause
 									 errmsg("subquery in FROM must have an alias"),
 									 errhint("For example, FROM (SELECT ...) [AS] foo."),
 									 parser_errposition(@2)));
+					} */
+
+					if ($3 == NULL)
+					{
+						Alias *a = makeNode(Alias);
+						StringInfoData newAliasBuf;
+
+						initStringInfo(&newAliasBuf);
+						appendStringInfo(&newAliasBuf, "__new_alias__%d", ++emptyAliasCounts);
+						a->aliasname = newAliasBuf.data;
+						n->alias = a;
 					}
+
 					$$ = (Node *) n;
 				}
 			| joined_table
diff --git a/src/include/parser/parser.h b/src/include/parser/parser.h
index 3bdeeb8b0b..0e1b418b99 100644
--- a/src/include/parser/parser.h
+++ b/src/include/parser/parser.h
@@ -38,4 +38,6 @@ extern List *raw_parser(const char *str);
 extern List *SystemFuncName(char *name);
 extern TypeName *SystemTypeName(char *name);
 
+extern int emptyAliasCounts;
+
 #endif							/* PARSER_H */

参考链接:
https://rng-songbaobao.blog.csdn.net/article/details/111152337
https://www.modb.pro/db/11343



这篇关于PostgreSQL子查询别名改造(兼容Oracle)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程