调试 PostgreSQL 语法 (Bison)

来自 PostgreSQL wiki
跳转到导航跳转到搜索

移进/归约冲突

Postgres 开发规则禁止在主语法中出现移进/归约冲突(以及 Bison 生成的其他冲突 - 归约/归约冲突 - 甚至更糟)。通常,如果您正在更改语法,您可能会在语法中引入移进/归约冲突,需要修复。正如 Tom Lane 在这里解释的那样,移进/归约冲突通常可以通过对语法进行一些“分解”来解决。(有关更多上下文,请参阅原始的 讨论 在 -hackers 邮件列表中。)

通常,在调试移进/归约冲突时,查看 gram.output 文件非常有用,默认情况下,构建解析器会保留该文件。正如这里所述,此报告可以指示歧义是如何产生的。

对于那些对解析器理论有更多了解的人来说,大多数编译器构造书籍都在一定程度上涵盖了它们,包括久负盛名的,虽然有点过时的 龙书

> > bison -v doesn't show anything useful beyond saying that there is one
> > shift/reduce conflict. The gram.output is 10MB, which doesn't help me
> > much (I'm still trying to make sense of it).

Well, you need to learn a bit more about bison I think.  The complaint
is

State 1135 conflicts: 1 shift/reduce

so we look at state 1135, which says:

state 1135

  241 alter_table_cmd: ADD_P . opt_column columnDef
  251                | ADD_P . TableConstraint

    CHECK       shift, and go to state 1698
    COLUMN      shift, and go to state 1742
    CONSTRAINT  shift, and go to state 1699
    EXCLUSION   shift, and go to state 1700
    FOREIGN     shift, and go to state 1701
    PRIMARY     shift, and go to state 1702
    UNIQUE      shift, and go to state 1703

    EXCLUSION  [reduce using rule 887 (opt_column)]
    $default   reduce using rule 887 (opt_column)

    TableConstraint  go to state 1743
    ConstraintElem   go to state 1705
    opt_column       go to state 1744

This is the state immediately after scanning "ADD" in an ALTER TABLE
command, and what it's unhappy about is that it has two different things
to do if the next token is EXCLUSION.  (The dot in the productions
indicates "where we are", and the square brackets mark the unreachable
action.)  If you check the other mentioned states it becomes clear that
the state-1700 path leads to deciding that EXCLUSION begins a
TableConstraint, while rule 887 is the "empty" alternative for
opt_column, and is what would have to be done next if EXCLUSION is a
column name beginning a ColumnDef.  So the difficulty is that it can't
be sure whether EXCLUSION is a column name without looking one token
past EXCLUSION, but it has to decide whether to eliminate COLUMN before
it can look ahead past EXCLUSION.

This is a pretty common difficulty with empty-producing productions.
The usual way around it is to eliminate the empty production by making
the calling production a bit more redundant.  In this case, we can fix
it by replacing

alter_table_cmd:
            ADD_P opt_column columnDef

with two productions

alter_table_cmd:
            ADD_P columnDef
            | ADD_P COLUMN columnDef

The reason this fixes it is that now the parser does not have to make
a shift-reduce decision while EXCLUSION is the next token: it's just
going to shift all the time, and it only has to reduce once EXCLUSION
is the current token and it can see the next one as lookahead.  (In
which case, it will reduce EXCLUSION to ColId and proceed with the
its-a-ColumnDef path, only if the next token isn't "(" or "USING".)

Another way to think about it is that we are forcing bison to split
this one state into two, but I find it easier to understand how to
fix the problem by looking for ways to postpone the reduce decision.

调试 PostgreSQL 解析器

当语法产生意外结果时,可能需要更复杂的分析(即,解析器已构建,但解析分析接收到的原始查询树在某种程度上不符合预期)。Bison 文档描述了如何启用跟踪

https://www.gnu.org/software/bison/manual/html_node/Enabling-Traces.html

在 PostgreSQL 中让它工作的最佳方法似乎是在 parser.c 的顶部声明一个“extern int base_yydebug”变量。还需要使用 %debug 指令。以下补丁实现了这一点(调试输出将显示在 stderr 中)

diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 0bc8815..fa21a5a 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -635,6 +635,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 
 
 /* Precedence: lowest to highest */
+%debug
 %nonassoc      SET                             /* see relation_expr_opt_alias */
 %left          UNION EXCEPT
 %left          INTERSECT
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index 6632966..84d401c 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -24,6 +24,7 @@
 #include "parser/gramparse.h"
 #include "parser/parser.h"
 
+extern int base_yydebug;
 
 /*
  * raw_parser
@@ -48,6 +49,8 @@ raw_parser(const char *str)
        /* initialize the bison parser */
        parser_init(&yyextra);
 
+       base_yydebug = 1;
+
        /* Parse! */
        yyresult = base_yyparse(yyscanner);
 

这将允许您查看解析器在解析语句时进入的精确状态,这与 Bison 报告相结合,将极大地帮助调试。

其他问题

当无法确定解析器为何产生意外结果时,暂时在 pg_config_manual.h 中 #define COPY_PARSE_PLAN_TREES 也可能有用。