Re: Precedence of standard comparison operators - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Precedence of standard comparison operators
Date
Msg-id 16153.1424843790@sss.pgh.pa.us
Whole thread Raw
In response to Re: Precedence of standard comparison operators  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-hackers
Here's a completed patch for this.  This includes fixing the NOT LIKE
problem as discussed in the other thread.

I've done more-or-less-exhaustive testing on this to verify that it
produces warnings whenever necessary.  It generates a few false-positive
warnings in corner cases that seem too complicated to model more precisely.
An example is that the production for "= ANY (sub-select)" clauses looks
like

            a_expr subquery_Op sub_type select_with_parens    %prec Op

but in point of fact its precedence against operators to its left is
not necessarily Op, but whatever actual operator appears --- for
example "= ANY" has the precedence of "=".  This is because of the same
point noted in the other thread that Bison really implements that by
comparison to the lookahead token's precedence, not the rule's declared
precedence.  The patch treats this as having precedence Op, which is
the highest possibility, so it will sometimes warn unnecessarily.

            regards, tom lane

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 9261e7f..cd89819 100644
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** dynamic_library_path = 'C:\tools\postgre
*** 6792,6797 ****
--- 6792,6820 ----
        </listitem>
       </varlistentry>

+      <varlistentry id="guc-operator-precedence-warning" xreflabel="operator_precedence_warning">
+       <term><varname>operator_precedence_warning</varname> (<type>boolean</type>)
+       <indexterm>
+        <primary><varname>operator_precedence_warning</> configuration parameter</primary>
+       </indexterm>
+       </term>
+       <listitem>
+        <para>
+         When on, the parser will emit a warning for any construct that might
+         have changed meanings since <productname>PostgreSQL</> 9.4 as a result
+         of changes in operator precedence.  This is useful for auditing
+         applications to see if precedence changes have broken anything; but it
+         is not meant to be kept turned on in production, since it will warn
+         about some perfectly valid, standard-compliant SQL code.
+         The default is <literal>off</>.
+        </para>
+
+        <para>
+         See <xref linkend="sql-precedence"> for more information.
+        </para>
+       </listitem>
+      </varlistentry>
+
      <varlistentry id="guc-quote-all-identifiers" xreflabel="quote-all-identifiers">
        <term><varname>quote_all_identifiers</varname> (<type>boolean</type>)
        <indexterm>
diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml
index 4b81b08..e492684 100644
*** a/doc/src/sgml/syntax.sgml
--- b/doc/src/sgml/syntax.sgml
*************** CAST ( '<replaceable>string</replaceable
*** 984,993 ****
      associativity of the operators in <productname>PostgreSQL</>.
      Most operators have the same precedence and are left-associative.
      The precedence and associativity of the operators is hard-wired
!     into the parser.  This can lead to non-intuitive behavior; for
!     example the Boolean operators <literal><</> and
!     <literal>></> have a different precedence than the Boolean
!     operators <literal><=</> and <literal>>=</>.  Also, you will
      sometimes need to add parentheses when using combinations of
      binary and unary operators.  For instance:
  <programlisting>
--- 984,994 ----
      associativity of the operators in <productname>PostgreSQL</>.
      Most operators have the same precedence and are left-associative.
      The precedence and associativity of the operators is hard-wired
!     into the parser.
!    </para>
!
!    <para>
!     You will
      sometimes need to add parentheses when using combinations of
      binary and unary operators.  For instance:
  <programlisting>
*************** SELECT (5 !) - 6;
*** 1008,1014 ****
     </para>

     <table id="sql-precedence-table">
!     <title>Operator Precedence (decreasing)</title>

      <tgroup cols="3">
       <thead>
--- 1009,1015 ----
     </para>

     <table id="sql-precedence-table">
!     <title>Operator Precedence (highest to lowest)</title>

      <tgroup cols="3">
       <thead>
*************** SELECT (5 !) - 6;
*** 1063,1125 ****
        </row>

        <row>
!        <entry><token>IS</token></entry>
!        <entry></entry>
!        <entry><literal>IS TRUE</>, <literal>IS FALSE</>, <literal>IS NULL</>, etc</entry>
!       </row>
!
!       <row>
!        <entry><token>ISNULL</token></entry>
!        <entry></entry>
!        <entry>test for null</entry>
!       </row>
!
!       <row>
!        <entry><token>NOTNULL</token></entry>
!        <entry></entry>
!        <entry>test for not null</entry>
!       </row>
!
!       <row>
!        <entry>(any other)</entry>
         <entry>left</entry>
         <entry>all other native and user-defined operators</entry>
        </row>

        <row>
-        <entry><token>IN</token></entry>
-        <entry></entry>
-        <entry>set membership</entry>
-       </row>
-
-       <row>
-        <entry><token>BETWEEN</token></entry>
-        <entry></entry>
-        <entry>range containment</entry>
-       </row>
-
-       <row>
         <entry><token>OVERLAPS</token></entry>
         <entry></entry>
         <entry>time interval overlap</entry>
        </row>

        <row>
!        <entry><token>LIKE</token> <token>ILIKE</token> <token>SIMILAR</token></entry>
         <entry></entry>
!        <entry>string pattern matching</entry>
        </row>

        <row>
!        <entry><token><</token> <token>></token></entry>
         <entry></entry>
!        <entry>less than, greater than</entry>
        </row>

        <row>
!        <entry><token>=</token></entry>
!        <entry>right</entry>
!        <entry>equality, assignment</entry>
        </row>

        <row>
--- 1064,1098 ----
        </row>

        <row>
!        <entry>(any other operator)</entry>
         <entry>left</entry>
         <entry>all other native and user-defined operators</entry>
        </row>

        <row>
         <entry><token>OVERLAPS</token></entry>
         <entry></entry>
         <entry>time interval overlap</entry>
        </row>

        <row>
!        <entry><token>BETWEEN</token> <token>IN</token> <token>LIKE</token> <token>ILIKE</token>
<token>SIMILAR</token></entry>
         <entry></entry>
!        <entry>range containment, set membership, string matching</entry>
        </row>

        <row>
!        <entry><token><</token> <token>></token> <token>=</token> <token><=</token> <token>>=</token>
<token><></token>
! </entry>
         <entry></entry>
!        <entry>comparison operators</entry>
        </row>

        <row>
!        <entry><token>IS</token> <token>ISNULL</token> <token>NOTNULL</token></entry>
!        <entry></entry>
!        <entry><literal>IS TRUE</>, <literal>IS FALSE</>, <literal>IS
!        NULL</>, <literal>IS DISTINCT FROM</>, etc</entry>
        </row>

        <row>
*************** SELECT (5 !) - 6;
*** 1159,1167 ****
  SELECT 3 OPERATOR(pg_catalog.+) 4;
  </programlisting>
      the <literal>OPERATOR</> construct is taken to have the default precedence
!     shown in <xref linkend="sql-precedence-table"> for <quote>any other</> operator.  This is true no matter
      which specific operator appears inside <literal>OPERATOR()</>.
     </para>
    </sect2>
   </sect1>

--- 1132,1163 ----
  SELECT 3 OPERATOR(pg_catalog.+) 4;
  </programlisting>
      the <literal>OPERATOR</> construct is taken to have the default precedence
!     shown in <xref linkend="sql-precedence-table"> for
!     <quote>any other operator</>.  This is true no matter
      which specific operator appears inside <literal>OPERATOR()</>.
     </para>
+
+    <note>
+     <para>
+      <productname>PostgreSQL</> versions before 9.5 used slightly different
+      operator precedence rules.  In particular, <token><=</token>
+      <token>>=</token> and <token><></token> used to be treated as
+      generic operators; <literal>IS</> tests used to have higher priority;
+      and <literal>NOT BETWEEN</> and related constructs acted inconsistently,
+      being taken in some cases as having the precedence of <literal>NOT</>
+      rather than <literal>BETWEEN</>.  These rules were changed for better
+      compliance with the SQL standard and to reduce confusion from
+      inconsistent treatment of logically equivalent constructs.  In most
+      cases, these changes will result in no behavioral change, or perhaps
+      in <quote>no such operator</> failures which can be resolved by adding
+      parentheses.  However there are corner cases in which a query might
+      change behavior without any parsing error being reported.  If you are
+      concerned about whether these changes have silently broken something,
+      you can test your application with the configuration
+      parameter <xref linkend="guc-operator-precedence-warning"> turned on
+      to see if any warnings are logged.
+     </para>
+    </note>
    </sect2>
   </sect1>

diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 2f417fe..f5e9f48 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outAExpr(StringInfo str, const A_Expr *
*** 2544,2549 ****
--- 2544,2552 ----
              appendStringInfoString(str, " NOT_BETWEEN_SYM ");
              WRITE_NODE_FIELD(name);
              break;
+         case AEXPR_PAREN:
+             appendStringInfoString(str, " PAREN");
+             break;
          default:
              appendStringInfoString(str, " ??");
              break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 581f7a1..4e82ef5 100644
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 58,63 ****
--- 58,64 ----
  #include "nodes/nodeFuncs.h"
  #include "parser/gramparse.h"
  #include "parser/parser.h"
+ #include "parser/parse_expr.h"
  #include "storage/lmgr.h"
  #include "utils/date.h"
  #include "utils/datetime.h"
*************** static Node *makeRecursiveViewSelect(cha
*** 532,537 ****
--- 533,539 ----
  %token <str>    IDENT FCONST SCONST BCONST XCONST Op
  %token <ival>    ICONST PARAM
  %token            TYPECAST DOT_DOT COLON_EQUALS
+ %token            LESS_EQUALS GREATER_EQUALS NOT_EQUALS

  /*
   * If you want to make any keyword changes, update the keyword table in
*************** static Node *makeRecursiveViewSelect(cha
*** 634,641 ****
   * The grammar thinks these are keywords, but they are not in the kwlist.h
   * list and so can never be entered directly.  The filter in parser.c
   * creates these tokens when required (based on looking one token ahead).
   */
! %token            NULLS_LA WITH_LA


  /* Precedence: lowest to highest */
--- 636,648 ----
   * The grammar thinks these are keywords, but they are not in the kwlist.h
   * list and so can never be entered directly.  The filter in parser.c
   * creates these tokens when required (based on looking one token ahead).
+  *
+  * NOT_LA exists so that productions such as NOT LIKE can be given the same
+  * precedence as LIKE; otherwise they'd effectively have the same precedence
+  * as NOT, at least with respect to their left-hand subexpression.
+  * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
   */
! %token        NOT_LA NULLS_LA WITH_LA


  /* Precedence: lowest to highest */
*************** static Node *makeRecursiveViewSelect(cha
*** 645,657 ****
  %left        OR
  %left        AND
  %right        NOT
! %right        '='
! %nonassoc    '<' '>'
! %nonassoc    LIKE ILIKE SIMILAR
! %nonassoc    ESCAPE
  %nonassoc    OVERLAPS
- %nonassoc    BETWEEN
- %nonassoc    IN_P
  %left        POSTFIXOP        /* dummy for postfix Op rules */
  /*
   * To support target_el without AS, we must give IDENT an explicit priority
--- 652,662 ----
  %left        OR
  %left        AND
  %right        NOT
! %nonassoc    IS ISNULL NOTNULL    /* IS sets precedence for IS NULL, etc */
! %nonassoc    '<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
! %nonassoc    BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
! %nonassoc    ESCAPE            /* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
  %nonassoc    OVERLAPS
  %left        POSTFIXOP        /* dummy for postfix Op rules */
  /*
   * To support target_el without AS, we must give IDENT an explicit priority
*************** static Node *makeRecursiveViewSelect(cha
*** 676,684 ****
  %nonassoc    UNBOUNDED        /* ideally should have same precedence as IDENT */
  %nonassoc    IDENT NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING
  %left        Op OPERATOR        /* multi-character ops and user-defined operators */
- %nonassoc    NOTNULL
- %nonassoc    ISNULL
- %nonassoc    IS                /* sets precedence for IS NULL, etc */
  %left        '+' '-'
  %left        '*' '/' '%'
  %left        '^'
--- 681,686 ----
*************** interval_second:
*** 11170,11175 ****
--- 11172,11183 ----
   *
   * c_expr is all the productions that are common to a_expr and b_expr;
   * it's factored out just to eliminate redundant coding.
+  *
+  * Be careful of productions involving more than one terminal token.
+  * By default, bison will assign such productions the precedence of their
+  * last terminal, but in nearly all cases you want it to be the precedence
+  * of the first terminal instead; otherwise you will not get the behavior
+  * you expect!  So we use %prec annotations freely to set precedences.
   */
  a_expr:        c_expr                                    { $$ = $1; }
              | a_expr TYPECAST Typename
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11219,11224 ****
--- 11227,11238 ----
                  { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", $1, $3, @2); }
              | a_expr '=' a_expr
                  { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", $1, $3, @2); }
+             | a_expr LESS_EQUALS a_expr
+                 { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", $1, $3, @2); }
+             | a_expr GREATER_EQUALS a_expr
+                 { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); }
+             | a_expr NOT_EQUALS a_expr
+                 { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); }

              | a_expr qual_Op a_expr                %prec Op
                  { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11233,11245 ****
                  { $$ = makeOrExpr($1, $3, @2); }
              | NOT a_expr
                  { $$ = makeNotExpr($2, @1); }

              | a_expr LIKE a_expr
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "~~",
                                                     $1, $3, @2);
                  }
!             | a_expr LIKE a_expr ESCAPE a_expr
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                 list_make2($3, $5),
--- 11247,11261 ----
                  { $$ = makeOrExpr($1, $3, @2); }
              | NOT a_expr
                  { $$ = makeNotExpr($2, @1); }
+             | NOT_LA a_expr                        %prec NOT
+                 { $$ = makeNotExpr($2, @1); }

              | a_expr LIKE a_expr
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "~~",
                                                     $1, $3, @2);
                  }
!             | a_expr LIKE a_expr ESCAPE a_expr                    %prec LIKE
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                 list_make2($3, $5),
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11247,11258 ****
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "~~",
                                                     $1, (Node *) n, @2);
                  }
!             | a_expr NOT LIKE a_expr
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "!~~",
                                                     $1, $4, @2);
                  }
!             | a_expr NOT LIKE a_expr ESCAPE a_expr
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                 list_make2($4, $6),
--- 11263,11274 ----
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "~~",
                                                     $1, (Node *) n, @2);
                  }
!             | a_expr NOT_LA LIKE a_expr                            %prec NOT_LA
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "!~~",
                                                     $1, $4, @2);
                  }
!             | a_expr NOT_LA LIKE a_expr ESCAPE a_expr            %prec NOT_LA
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                 list_make2($4, $6),
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11265,11271 ****
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "~~*",
                                                     $1, $3, @2);
                  }
!             | a_expr ILIKE a_expr ESCAPE a_expr
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                 list_make2($3, $5),
--- 11281,11287 ----
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "~~*",
                                                     $1, $3, @2);
                  }
!             | a_expr ILIKE a_expr ESCAPE a_expr                    %prec ILIKE
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                 list_make2($3, $5),
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11273,11284 ****
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "~~*",
                                                     $1, (Node *) n, @2);
                  }
!             | a_expr NOT ILIKE a_expr
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "!~~*",
                                                     $1, $4, @2);
                  }
!             | a_expr NOT ILIKE a_expr ESCAPE a_expr
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                 list_make2($4, $6),
--- 11289,11300 ----
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "~~*",
                                                     $1, (Node *) n, @2);
                  }
!             | a_expr NOT_LA ILIKE a_expr                        %prec NOT_LA
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "!~~*",
                                                     $1, $4, @2);
                  }
!             | a_expr NOT_LA ILIKE a_expr ESCAPE a_expr            %prec NOT_LA
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                 list_make2($4, $6),
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11287,11293 ****
                                                     $1, (Node *) n, @2);
                  }

!             | a_expr SIMILAR TO a_expr                %prec SIMILAR
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
                                                 list_make2($4, makeNullAConst(-1)),
--- 11303,11309 ----
                                                     $1, (Node *) n, @2);
                  }

!             | a_expr SIMILAR TO a_expr                            %prec SIMILAR
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
                                                 list_make2($4, makeNullAConst(-1)),
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11295,11301 ****
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
                                                     $1, (Node *) n, @2);
                  }
!             | a_expr SIMILAR TO a_expr ESCAPE a_expr
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
                                                 list_make2($4, $6),
--- 11311,11317 ----
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
                                                     $1, (Node *) n, @2);
                  }
!             | a_expr SIMILAR TO a_expr ESCAPE a_expr            %prec SIMILAR
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
                                                 list_make2($4, $6),
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11303,11309 ****
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
                                                     $1, (Node *) n, @2);
                  }
!             | a_expr NOT SIMILAR TO a_expr            %prec SIMILAR
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
                                                 list_make2($5, makeNullAConst(-1)),
--- 11319,11325 ----
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
                                                     $1, (Node *) n, @2);
                  }
!             | a_expr NOT_LA SIMILAR TO a_expr                    %prec NOT_LA
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
                                                 list_make2($5, makeNullAConst(-1)),
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11311,11317 ****
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~",
                                                     $1, (Node *) n, @2);
                  }
!             | a_expr NOT SIMILAR TO a_expr ESCAPE a_expr
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
                                                 list_make2($5, $7),
--- 11327,11333 ----
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~",
                                                     $1, (Node *) n, @2);
                  }
!             | a_expr NOT_LA SIMILAR TO a_expr ESCAPE a_expr        %prec NOT_LA
                  {
                      FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
                                                 list_make2($5, $7),
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11443,11449 ****
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "<>", $1, (Node *) $6, @2);
                  }
!             | a_expr BETWEEN opt_asymmetric b_expr AND b_expr        %prec BETWEEN
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_BETWEEN,
                                                     "BETWEEN",
--- 11459,11465 ----
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "<>", $1, (Node *) $6, @2);
                  }
!             | a_expr BETWEEN opt_asymmetric b_expr AND a_expr        %prec BETWEEN
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_BETWEEN,
                                                     "BETWEEN",
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11451,11457 ****
                                                     (Node *) list_make2($4, $6),
                                                     @2);
                  }
!             | a_expr NOT BETWEEN opt_asymmetric b_expr AND b_expr    %prec BETWEEN
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_NOT_BETWEEN,
                                                     "NOT BETWEEN",
--- 11467,11473 ----
                                                     (Node *) list_make2($4, $6),
                                                     @2);
                  }
!             | a_expr NOT_LA BETWEEN opt_asymmetric b_expr AND a_expr %prec NOT_LA
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_NOT_BETWEEN,
                                                     "NOT BETWEEN",
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11459,11465 ****
                                                     (Node *) list_make2($5, $7),
                                                     @2);
                  }
!             | a_expr BETWEEN SYMMETRIC b_expr AND b_expr            %prec BETWEEN
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_BETWEEN_SYM,
                                                     "BETWEEN SYMMETRIC",
--- 11475,11481 ----
                                                     (Node *) list_make2($5, $7),
                                                     @2);
                  }
!             | a_expr BETWEEN SYMMETRIC b_expr AND a_expr            %prec BETWEEN
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_BETWEEN_SYM,
                                                     "BETWEEN SYMMETRIC",
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11467,11473 ****
                                                     (Node *) list_make2($4, $6),
                                                     @2);
                  }
!             | a_expr NOT BETWEEN SYMMETRIC b_expr AND b_expr        %prec BETWEEN
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_NOT_BETWEEN_SYM,
                                                     "NOT BETWEEN SYMMETRIC",
--- 11483,11489 ----
                                                     (Node *) list_make2($4, $6),
                                                     @2);
                  }
!             | a_expr NOT_LA BETWEEN SYMMETRIC b_expr AND a_expr        %prec NOT_LA
                  {
                      $$ = (Node *) makeSimpleA_Expr(AEXPR_NOT_BETWEEN_SYM,
                                                     "NOT BETWEEN SYMMETRIC",
*************** a_expr:        c_expr                                    { $$ = $1; }
*** 11495,11501 ****
                          $$ = (Node *) makeSimpleA_Expr(AEXPR_IN, "=", $1, $3, @2);
                      }
                  }
!             | a_expr NOT IN_P in_expr
                  {
                      /* in_expr returns a SubLink or a list of a_exprs */
                      if (IsA($4, SubLink))
--- 11511,11517 ----
                          $$ = (Node *) makeSimpleA_Expr(AEXPR_IN, "=", $1, $3, @2);
                      }
                  }
!             | a_expr NOT_LA IN_P in_expr                        %prec NOT_LA
                  {
                      /* in_expr returns a SubLink or a list of a_exprs */
                      if (IsA($4, SubLink))
*************** b_expr:        c_expr
*** 11599,11604 ****
--- 11615,11626 ----
                  { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", $1, $3, @2); }
              | b_expr '=' b_expr
                  { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", $1, $3, @2); }
+             | b_expr LESS_EQUALS b_expr
+                 { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", $1, $3, @2); }
+             | b_expr GREATER_EQUALS b_expr
+                 { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); }
+             | b_expr NOT_EQUALS b_expr
+                 { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); }
              | b_expr qual_Op b_expr                %prec Op
                  { $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
              | qual_Op b_expr                    %prec Op
*************** c_expr:        columnref                                { $$ = $1; }
*** 11670,11675 ****
--- 11692,11715 ----
                          n->indirection = check_indirection($4, yyscanner);
                          $$ = (Node *)n;
                      }
+                     else if (operator_precedence_warning)
+                     {
+                         /*
+                          * If precedence warnings are enabled, insert
+                          * AEXPR_PAREN nodes wrapping all explicitly
+                          * parenthesized subexpressions; this prevents bogus
+                          * warnings from being issued when the ordering has
+                          * been forced by parentheses.
+                          *
+                          * In principle we should not be relying on a GUC to
+                          * decide whether to insert AEXPR_PAREN nodes.
+                          * However, since they have no effect except to
+                          * suppress warnings, it's probably safe enough; and
+                          * we'd just as soon not waste cycles on dummy parse
+                          * nodes if we don't have to.
+                          */
+                         $$ = (Node *) makeA_Expr(AEXPR_PAREN, NIL, $2, NULL, @1);
+                     }
                      else
                          $$ = $2;
                  }
*************** MathOp:         '+'                                    { $$ = "+"; }
*** 12518,12523 ****
--- 12558,12566 ----
              | '<'                                    { $$ = "<"; }
              | '>'                                    { $$ = ">"; }
              | '='                                    { $$ = "="; }
+             | LESS_EQUALS                            { $$ = "<="; }
+             | GREATER_EQUALS                        { $$ = ">="; }
+             | NOT_EQUALS                            { $$ = "<>"; }
          ;

  qual_Op:    Op
*************** subquery_Op:
*** 12540,12550 ****
                      { $$ = $3; }
              | LIKE
                      { $$ = list_make1(makeString("~~")); }
!             | NOT LIKE
                      { $$ = list_make1(makeString("!~~")); }
              | ILIKE
                      { $$ = list_make1(makeString("~~*")); }
!             | NOT ILIKE
                      { $$ = list_make1(makeString("!~~*")); }
  /* cannot put SIMILAR TO here, because SIMILAR TO is a hack.
   * the regular expression is preprocessed by a function (similar_escape),
--- 12583,12593 ----
                      { $$ = $3; }
              | LIKE
                      { $$ = list_make1(makeString("~~")); }
!             | NOT_LA LIKE
                      { $$ = list_make1(makeString("!~~")); }
              | ILIKE
                      { $$ = list_make1(makeString("~~*")); }
!             | NOT_LA ILIKE
                      { $$ = list_make1(makeString("!~~*")); }
  /* cannot put SIMILAR TO here, because SIMILAR TO is a hack.
   * the regular expression is preprocessed by a function (similar_escape),
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 7829bcb..cf09d65 100644
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 37,44 ****
--- 37,91 ----
  #include "utils/xml.h"


+ /* GUC parameters */
+ bool        operator_precedence_warning = false;
  bool        Transform_null_equals = false;

+ /*
+  * Node-type groups for operator precedence warnings
+  * We use zero for everything not otherwise classified
+  */
+ #define PREC_GROUP_POSTFIX_IS    1        /* postfix IS tests (NullTest, etc) */
+ #define PREC_GROUP_INFIX_IS        2        /* infix IS (IS DISTINCT FROM, etc) */
+ #define PREC_GROUP_LESS            3        /* < > */
+ #define PREC_GROUP_EQUAL        4        /* = */
+ #define PREC_GROUP_LESS_EQUAL    5        /* <= >= <> */
+ #define PREC_GROUP_LIKE            6        /* LIKE ILIKE SIMILAR */
+ #define PREC_GROUP_BETWEEN        7        /* BETWEEN */
+ #define PREC_GROUP_IN            8        /* IN */
+ #define PREC_GROUP_NOT_LIKE        9        /* NOT LIKE/ILIKE/SIMILAR */
+ #define PREC_GROUP_NOT_BETWEEN    10        /* NOT BETWEEN */
+ #define PREC_GROUP_NOT_IN        11        /* NOT IN */
+ #define PREC_GROUP_POSTFIX_OP    12        /* generic postfix operators */
+ #define PREC_GROUP_INFIX_OP        13        /* generic infix operators */
+ #define PREC_GROUP_PREFIX_OP    14        /* generic prefix operators */
+
+ /*
+  * Map precedence groupings to old precedence ordering
+  *
+  * Old precedence order:
+  * 1. NOT
+  * 2. =
+  * 3. < >
+  * 4. LIKE ILIKE SIMILAR
+  * 5. BETWEEN
+  * 6. IN
+  * 7. generic postfix Op
+  * 8. generic Op, including <= => <>
+  * 9. generic prefix Op
+  * 10. IS tests (NullTest, BooleanTest, etc)
+  *
+  * NOT BETWEEN etc map to BETWEEN etc when considered as being on the left,
+  * but to NOT when considered as being on the right, because of the buggy
+  * precedence handling of those productions in the old grammar.
+  */
+ static const int oldprecedence_l[] = {
+     0, 10, 10, 3, 2, 8, 4, 5, 6, 4, 5, 6, 7, 8, 9
+ };
+ static const int oldprecedence_r[] = {
+     0, 10, 10, 3, 2, 8, 4, 5, 6, 1, 1, 1, 7, 8, 9
+ };
+
  static Node *transformExprRecurse(ParseState *pstate, Node *expr);
  static Node *transformParamRef(ParseState *pstate, ParamRef *pref);
  static Node *transformAExprOp(ParseState *pstate, A_Expr *a);
*************** static Node *make_row_distinct_op(ParseS
*** 76,81 ****
--- 123,133 ----
                       RowExpr *lrow, RowExpr *rrow, int location);
  static Expr *make_distinct_op(ParseState *pstate, List *opname,
                   Node *ltree, Node *rtree, int location);
+ static int    operator_precedence_group(Node *node, const char **nodename);
+ static void emit_precedence_warnings(ParseState *pstate,
+                          int opgroup, const char *opname,
+                          Node *lchild, Node *rchild,
+                          int location);


  /*
*************** transformExprRecurse(ParseState *pstate,
*** 194,199 ****
--- 246,254 ----
                      case AEXPR_NOT_BETWEEN_SYM:
                          result = transformAExprBetween(pstate, a);
                          break;
+                     case AEXPR_PAREN:
+                         result = transformExprRecurse(pstate, a->lexpr);
+                         break;
                      default:
                          elog(ERROR, "unrecognized A_Expr kind: %d", a->kind);
                          result = NULL;    /* keep compiler quiet */
*************** transformExprRecurse(ParseState *pstate,
*** 255,260 ****
--- 310,320 ----
              {
                  NullTest   *n = (NullTest *) expr;

+                 if (operator_precedence_warning)
+                     emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_IS, "IS",
+                                              (Node *) n->arg, NULL,
+                                              n->location);
+
                  n->arg = (Expr *) transformExprRecurse(pstate, (Node *) n->arg);
                  /* the argument can be any type, so don't coerce it */
                  n->argisrow = type_is_rowtype(exprType((Node *) n->arg));
*************** transformAExprOp(ParseState *pstate, A_E
*** 779,784 ****
--- 839,856 ----
      Node       *rexpr = a->rexpr;
      Node       *result;

+     if (operator_precedence_warning)
+     {
+         int            opgroup;
+         const char *opname;
+
+         opgroup = operator_precedence_group((Node *) a, &opname);
+         if (opgroup > 0)
+             emit_precedence_warnings(pstate, opgroup, opname,
+                                      lexpr, rexpr,
+                                      a->location);
+     }
+
      /*
       * Special-case "foo = NULL" and "NULL = foo" for compatibility with
       * standards-broken products (like Microsoft's).  Turn these into IS NULL
*************** transformAExprOp(ParseState *pstate, A_E
*** 855,862 ****
  static Node *
  transformAExprOpAny(ParseState *pstate, A_Expr *a)
  {
!     Node       *lexpr = transformExprRecurse(pstate, a->lexpr);
!     Node       *rexpr = transformExprRecurse(pstate, a->rexpr);

      return (Node *) make_scalar_array_op(pstate,
                                           a->name,
--- 927,943 ----
  static Node *
  transformAExprOpAny(ParseState *pstate, A_Expr *a)
  {
!     Node       *lexpr = a->lexpr;
!     Node       *rexpr = a->rexpr;
!
!     if (operator_precedence_warning)
!         emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_OP,
!                                  strVal(llast(a->name)),
!                                  lexpr, NULL,
!                                  a->location);
!
!     lexpr = transformExprRecurse(pstate, lexpr);
!     rexpr = transformExprRecurse(pstate, rexpr);

      return (Node *) make_scalar_array_op(pstate,
                                           a->name,
*************** transformAExprOpAny(ParseState *pstate,
*** 869,876 ****
  static Node *
  transformAExprOpAll(ParseState *pstate, A_Expr *a)
  {
!     Node       *lexpr = transformExprRecurse(pstate, a->lexpr);
!     Node       *rexpr = transformExprRecurse(pstate, a->rexpr);

      return (Node *) make_scalar_array_op(pstate,
                                           a->name,
--- 950,966 ----
  static Node *
  transformAExprOpAll(ParseState *pstate, A_Expr *a)
  {
!     Node       *lexpr = a->lexpr;
!     Node       *rexpr = a->rexpr;
!
!     if (operator_precedence_warning)
!         emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_OP,
!                                  strVal(llast(a->name)),
!                                  lexpr, NULL,
!                                  a->location);
!
!     lexpr = transformExprRecurse(pstate, lexpr);
!     rexpr = transformExprRecurse(pstate, rexpr);

      return (Node *) make_scalar_array_op(pstate,
                                           a->name,
*************** transformAExprOpAll(ParseState *pstate,
*** 883,890 ****
  static Node *
  transformAExprDistinct(ParseState *pstate, A_Expr *a)
  {
!     Node       *lexpr = transformExprRecurse(pstate, a->lexpr);
!     Node       *rexpr = transformExprRecurse(pstate, a->rexpr);

      if (lexpr && IsA(lexpr, RowExpr) &&
          rexpr && IsA(rexpr, RowExpr))
--- 973,988 ----
  static Node *
  transformAExprDistinct(ParseState *pstate, A_Expr *a)
  {
!     Node       *lexpr = a->lexpr;
!     Node       *rexpr = a->rexpr;
!
!     if (operator_precedence_warning)
!         emit_precedence_warnings(pstate, PREC_GROUP_INFIX_IS, "IS",
!                                  lexpr, rexpr,
!                                  a->location);
!
!     lexpr = transformExprRecurse(pstate, lexpr);
!     rexpr = transformExprRecurse(pstate, rexpr);

      if (lexpr && IsA(lexpr, RowExpr) &&
          rexpr && IsA(rexpr, RowExpr))
*************** transformAExprNullIf(ParseState *pstate,
*** 941,960 ****
      return (Node *) result;
  }

  static Node *
  transformAExprOf(ParseState *pstate, A_Expr *a)
  {
!     /*
!      * Checking an expression for match to a list of type names. Will result
!      * in a boolean constant node.
!      */
!     Node       *lexpr = transformExprRecurse(pstate, a->lexpr);
      Const       *result;
      ListCell   *telem;
      Oid            ltype,
                  rtype;
      bool        matched = false;

      ltype = exprType(lexpr);
      foreach(telem, (List *) a->rexpr)
      {
--- 1039,1065 ----
      return (Node *) result;
  }

+ /*
+  * Checking an expression for match to a list of type names. Will result
+  * in a boolean constant node.
+  */
  static Node *
  transformAExprOf(ParseState *pstate, A_Expr *a)
  {
!     Node       *lexpr = a->lexpr;
      Const       *result;
      ListCell   *telem;
      Oid            ltype,
                  rtype;
      bool        matched = false;

+     if (operator_precedence_warning)
+         emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_IS, "IS",
+                                  lexpr, NULL,
+                                  a->location);
+
+     lexpr = transformExprRecurse(pstate, lexpr);
+
      ltype = exprType(lexpr);
      foreach(telem, (List *) a->rexpr)
      {
*************** transformAExprIn(ParseState *pstate, A_E
*** 998,1003 ****
--- 1103,1115 ----
      else
          useOr = true;

+     if (operator_precedence_warning)
+         emit_precedence_warnings(pstate,
+                                  useOr ? PREC_GROUP_IN : PREC_GROUP_NOT_IN,
+                                  "IN",
+                                  a->lexpr, NULL,
+                                  a->location);
+
      /*
       * We try to generate a ScalarArrayOpExpr from IN/NOT IN, but this is only
       * possible if there is a suitable array type available.  If not, we fall
*************** transformAExprBetween(ParseState *pstate
*** 1150,1155 ****
--- 1262,1283 ----
      bexpr = (Node *) linitial(args);
      cexpr = (Node *) lsecond(args);

+     if (operator_precedence_warning)
+     {
+         int            opgroup;
+         const char *opname;
+
+         opgroup = operator_precedence_group((Node *) a, &opname);
+         emit_precedence_warnings(pstate, opgroup, opname,
+                                  aexpr, cexpr,
+                                  a->location);
+         /* We can ignore bexpr thanks to syntactic restrictions */
+         /* Wrap subexpressions to prevent extra warnings */
+         aexpr = (Node *) makeA_Expr(AEXPR_PAREN, NIL, aexpr, NULL, -1);
+         bexpr = (Node *) makeA_Expr(AEXPR_PAREN, NIL, bexpr, NULL, -1);
+         cexpr = (Node *) makeA_Expr(AEXPR_PAREN, NIL, cexpr, NULL, -1);
+     }
+
      /*
       * Build the equivalent comparison expression.  Make copies of
       * multiply-referenced subexpressions for safety.  (XXX this is really
*************** transformSubLink(ParseState *pstate, Sub
*** 1654,1659 ****
--- 1782,1800 ----
          List       *right_list;
          ListCell   *l;

+         if (operator_precedence_warning)
+         {
+             if (sublink->operName == NIL)
+                 emit_precedence_warnings(pstate, PREC_GROUP_IN, "IN",
+                                          sublink->testexpr, NULL,
+                                          sublink->location);
+             else
+                 emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_OP,
+                                          strVal(llast(sublink->operName)),
+                                          sublink->testexpr, NULL,
+                                          sublink->location);
+         }
+
          /*
           * If the source was "x IN (select)", convert to "x = ANY (select)".
           */
*************** transformXmlExpr(ParseState *pstate, Xml
*** 1997,2002 ****
--- 2138,2148 ----
      ListCell   *lc;
      int            i;

+     if (operator_precedence_warning && x->op == IS_DOCUMENT)
+         emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_IS, "IS",
+                                  (Node *) linitial(x->args), NULL,
+                                  x->location);
+
      newx = makeNode(XmlExpr);
      newx->op = x->op;
      if (x->name)
*************** transformBooleanTest(ParseState *pstate,
*** 2169,2174 ****
--- 2315,2325 ----
  {
      const char *clausename;

+     if (operator_precedence_warning)
+         emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_IS, "IS",
+                                  (Node *) b->arg, NULL,
+                                  b->location);
+
      switch (b->booltesttype)
      {
          case IS_TRUE:
*************** make_distinct_op(ParseState *pstate, Lis
*** 2686,2691 ****
--- 2837,3145 ----
  }

  /*
+  * Identify node's group for operator precedence warnings
+  *
+  * For items in nonzero groups, also return a suitable node name into *nodename
+  *
+  * Note: group zero is used for nodes that are higher or lower precedence
+  * than everything that changed precedence; we need never issue warnings
+  * related to such nodes.
+  */
+ static int
+ operator_precedence_group(Node *node, const char **nodename)
+ {
+     int            group = 0;
+
+     *nodename = NULL;
+     if (node == NULL)
+         return 0;
+
+     if (IsA(node, A_Expr))
+     {
+         A_Expr       *aexpr = (A_Expr *) node;
+
+         if (aexpr->kind == AEXPR_OP &&
+             aexpr->lexpr != NULL &&
+             aexpr->rexpr != NULL)
+         {
+             /* binary operator */
+             if (list_length(aexpr->name) == 1)
+             {
+                 *nodename = strVal(linitial(aexpr->name));
+                 /* Ignore if op was always higher priority than IS-tests */
+                 if (strcmp(*nodename, "+") == 0 ||
+                     strcmp(*nodename, "-") == 0 ||
+                     strcmp(*nodename, "*") == 0 ||
+                     strcmp(*nodename, "/") == 0 ||
+                     strcmp(*nodename, "%") == 0 ||
+                     strcmp(*nodename, "^") == 0)
+                     group = 0;
+                 else if (strcmp(*nodename, "<") == 0 ||
+                          strcmp(*nodename, ">") == 0)
+                     group = PREC_GROUP_LESS;
+                 else if (strcmp(*nodename, "=") == 0)
+                     group = PREC_GROUP_EQUAL;
+                 else if (strcmp(*nodename, "<=") == 0 ||
+                          strcmp(*nodename, ">=") == 0 ||
+                          strcmp(*nodename, "<>") == 0)
+                     group = PREC_GROUP_LESS_EQUAL;
+                 else
+                     group = PREC_GROUP_INFIX_OP;
+             }
+             else
+             {
+                 /* schema-qualified operator syntax */
+                 *nodename = "OPERATOR()";
+                 group = PREC_GROUP_INFIX_OP;
+             }
+         }
+         else if (aexpr->kind == AEXPR_OP &&
+                  aexpr->lexpr == NULL &&
+                  aexpr->rexpr != NULL)
+         {
+             /* prefix operator */
+             if (list_length(aexpr->name) == 1)
+             {
+                 *nodename = strVal(linitial(aexpr->name));
+                 /* Ignore if op was always higher priority than IS-tests */
+                 if (strcmp(*nodename, "+") == 0 ||
+                     strcmp(*nodename, "-"))
+                     group = 0;
+                 else
+                     group = PREC_GROUP_PREFIX_OP;
+             }
+             else
+             {
+                 /* schema-qualified operator syntax */
+                 *nodename = "OPERATOR()";
+                 group = PREC_GROUP_PREFIX_OP;
+             }
+         }
+         else if (aexpr->kind == AEXPR_OP &&
+                  aexpr->lexpr != NULL &&
+                  aexpr->rexpr == NULL)
+         {
+             /* postfix operator */
+             if (list_length(aexpr->name) == 1)
+             {
+                 *nodename = strVal(linitial(aexpr->name));
+                 group = PREC_GROUP_POSTFIX_OP;
+             }
+             else
+             {
+                 /* schema-qualified operator syntax */
+                 *nodename = "OPERATOR()";
+                 group = PREC_GROUP_POSTFIX_OP;
+             }
+         }
+         else if (aexpr->kind == AEXPR_OP_ANY ||
+                  aexpr->kind == AEXPR_OP_ALL)
+         {
+             *nodename = strVal(llast(aexpr->name));
+             group = PREC_GROUP_POSTFIX_OP;
+         }
+         else if (aexpr->kind == AEXPR_DISTINCT)
+         {
+             *nodename = "IS";
+             group = PREC_GROUP_INFIX_IS;
+         }
+         else if (aexpr->kind == AEXPR_OF)
+         {
+             *nodename = "IS";
+             group = PREC_GROUP_POSTFIX_IS;
+         }
+         else if (aexpr->kind == AEXPR_IN)
+         {
+             *nodename = "IN";
+             if (strcmp(strVal(linitial(aexpr->name)), "=") == 0)
+                 group = PREC_GROUP_IN;
+             else
+                 group = PREC_GROUP_NOT_IN;
+         }
+         else if (aexpr->kind == AEXPR_LIKE)
+         {
+             *nodename = "LIKE";
+             if (strcmp(strVal(linitial(aexpr->name)), "~~") == 0)
+                 group = PREC_GROUP_LIKE;
+             else
+                 group = PREC_GROUP_NOT_LIKE;
+         }
+         else if (aexpr->kind == AEXPR_ILIKE)
+         {
+             *nodename = "ILIKE";
+             if (strcmp(strVal(linitial(aexpr->name)), "~~*") == 0)
+                 group = PREC_GROUP_LIKE;
+             else
+                 group = PREC_GROUP_NOT_LIKE;
+         }
+         else if (aexpr->kind == AEXPR_SIMILAR)
+         {
+             *nodename = "SIMILAR";
+             if (strcmp(strVal(linitial(aexpr->name)), "~") == 0)
+                 group = PREC_GROUP_LIKE;
+             else
+                 group = PREC_GROUP_NOT_LIKE;
+         }
+         else if (aexpr->kind == AEXPR_BETWEEN ||
+                  aexpr->kind == AEXPR_BETWEEN_SYM)
+         {
+             Assert(list_length(aexpr->name) == 1);
+             *nodename = strVal(linitial(aexpr->name));
+             group = PREC_GROUP_BETWEEN;
+         }
+         else if (aexpr->kind == AEXPR_NOT_BETWEEN ||
+                  aexpr->kind == AEXPR_NOT_BETWEEN_SYM)
+         {
+             Assert(list_length(aexpr->name) == 1);
+             *nodename = strVal(linitial(aexpr->name));
+             group = PREC_GROUP_NOT_BETWEEN;
+         }
+     }
+     else if (IsA(node, NullTest) ||
+              IsA(node, BooleanTest))
+     {
+         *nodename = "IS";
+         group = PREC_GROUP_POSTFIX_IS;
+     }
+     else if (IsA(node, XmlExpr))
+     {
+         XmlExpr    *x = (XmlExpr *) node;
+
+         if (x->op == IS_DOCUMENT)
+         {
+             *nodename = "IS";
+             group = PREC_GROUP_POSTFIX_IS;
+         }
+     }
+     else if (IsA(node, SubLink))
+     {
+         SubLink    *s = (SubLink *) node;
+
+         if (s->subLinkType == ANY_SUBLINK ||
+             s->subLinkType == ALL_SUBLINK)
+         {
+             if (s->operName == NIL)
+             {
+                 *nodename = "IN";
+                 group = PREC_GROUP_IN;
+             }
+             else
+             {
+                 *nodename = strVal(llast(s->operName));
+                 group = PREC_GROUP_POSTFIX_OP;
+             }
+         }
+     }
+     else if (IsA(node, BoolExpr))
+     {
+         /*
+          * Must dig into NOTs to see if it's IS NOT DOCUMENT or NOT IN.  This
+          * opens us to possibly misrecognizing, eg, NOT (x IS DOCUMENT) as a
+          * problematic construct.  We can tell the difference by checking
+          * whether the parse locations of the two nodes are identical.
+          *
+          * Note that when we are comparing the child node to its own children,
+          * we will not know that it was a NOT.  Fortunately, that doesn't
+          * matter for these cases.
+          */
+         BoolExpr   *b = (BoolExpr *) node;
+
+         if (b->boolop == NOT_EXPR)
+         {
+             Node       *child = (Node *) linitial(b->args);
+
+             if (IsA(child, XmlExpr))
+             {
+                 XmlExpr    *x = (XmlExpr *) child;
+
+                 if (x->op == IS_DOCUMENT &&
+                     x->location == b->location)
+                 {
+                     *nodename = "IS";
+                     group = PREC_GROUP_POSTFIX_IS;
+                 }
+             }
+             else if (IsA(child, SubLink))
+             {
+                 SubLink    *s = (SubLink *) child;
+
+                 if (s->subLinkType == ANY_SUBLINK && s->operName == NIL &&
+                     s->location == b->location)
+                 {
+                     *nodename = "IN";
+                     group = PREC_GROUP_NOT_IN;
+                 }
+             }
+         }
+     }
+     return group;
+ }
+
+ /*
+  * helper routine for delivering 9.4-to-9.5 operator precedence warnings
+  *
+  * opgroup/opname/location represent some parent node
+  * lchild, rchild are its left and right children (either could be NULL)
+  *
+  * This should be called before transforming the child nodes, since if a
+  * precedence-driven parsing change has occurred in a query that used to work,
+  * it's quite possible that we'll get a semantic failure while analyzing the
+  * child expression.  We want to produce the warning before that happens.
+  * In any case, operator_precedence_group() expects untransformed input.
+  */
+ static void
+ emit_precedence_warnings(ParseState *pstate,
+                          int opgroup, const char *opname,
+                          Node *lchild, Node *rchild,
+                          int location)
+ {
+     int            cgroup;
+     const char *copname;
+
+     Assert(opgroup > 0);
+
+     /*
+      * Complain if left child, which should be same or higher precedence
+      * according to current rules, used to be lower precedence.
+      *
+      * Exception to precedence rules: if left child is IN or NOT IN or a
+      * postfix operator, the grouping is syntactically forced regardless of
+      * precedence.
+      */
+     cgroup = operator_precedence_group(lchild, &copname);
+     if (cgroup > 0)
+     {
+         if (oldprecedence_l[cgroup] < oldprecedence_r[opgroup] &&
+             cgroup != PREC_GROUP_IN &&
+             cgroup != PREC_GROUP_NOT_IN &&
+             cgroup != PREC_GROUP_POSTFIX_OP &&
+             cgroup != PREC_GROUP_POSTFIX_IS)
+             ereport(WARNING,
+                     (errmsg("operator precedence change: %s is now lower precedence than %s",
+                             opname, copname),
+                      parser_errposition(pstate, location)));
+     }
+
+     /*
+      * Complain if right child, which should be higher precedence according to
+      * current rules, used to be same or lower precedence.
+      *
+      * Exception to precedence rules: if right child is a prefix operator, the
+      * grouping is syntactically forced regardless of precedence.
+      */
+     cgroup = operator_precedence_group(rchild, &copname);
+     if (cgroup > 0)
+     {
+         if (oldprecedence_r[cgroup] <= oldprecedence_l[opgroup] &&
+             cgroup != PREC_GROUP_PREFIX_OP)
+             ereport(WARNING,
+                     (errmsg("operator precedence change: %s is now lower precedence than %s",
+                             opname, copname),
+                      parser_errposition(pstate, location)));
+     }
+ }
+
+ /*
   * Produce a string identifying an expression by kind.
   *
   * Note: when practical, use a simple SQL keyword for the result.  If that
diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c
index 3724330..2d85cf0 100644
*** a/src/backend/parser/parse_target.c
--- b/src/backend/parser/parse_target.c
*************** FigureColnameInternal(Node *node, char *
*** 1654,1665 ****
              *name = strVal(llast(((FuncCall *) node)->funcname));
              return 2;
          case T_A_Expr:
-             /* make nullif() act like a regular function */
              if (((A_Expr *) node)->kind == AEXPR_NULLIF)
              {
                  *name = "nullif";
                  return 2;
              }
              break;
          case T_TypeCast:
              strength = FigureColnameInternal(((TypeCast *) node)->arg,
--- 1654,1670 ----
              *name = strVal(llast(((FuncCall *) node)->funcname));
              return 2;
          case T_A_Expr:
              if (((A_Expr *) node)->kind == AEXPR_NULLIF)
              {
+                 /* make nullif() act like a regular function */
                  *name = "nullif";
                  return 2;
              }
+             if (((A_Expr *) node)->kind == AEXPR_PAREN)
+             {
+                 /* look through dummy parenthesis node */
+                 return FigureColnameInternal(((A_Expr *) node)->lexpr, name);
+             }
              break;
          case T_TypeCast:
              strength = FigureColnameInternal(((TypeCast *) node)->arg,
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index b17771d..fdf5a6a 100644
*** a/src/backend/parser/parser.c
--- b/src/backend/parser/parser.c
*************** base_yylex(YYSTYPE *lvalp, YYLTYPE *lloc
*** 107,112 ****
--- 107,115 ----
       */
      switch (cur_token)
      {
+         case NOT:
+             cur_token_length = 3;
+             break;
          case NULLS_P:
              cur_token_length = 5;
              break;
*************** base_yylex(YYSTYPE *lvalp, YYLTYPE *lloc
*** 151,156 ****
--- 154,173 ----
      /* Replace cur_token if needed, based on lookahead */
      switch (cur_token)
      {
+         case NOT:
+             /* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
+             switch (next_token)
+             {
+                 case BETWEEN:
+                 case IN_P:
+                 case LIKE:
+                 case ILIKE:
+                 case SIMILAR:
+                     cur_token = NOT_LA;
+                     break;
+             }
+             break;
+
          case NULLS_P:
              /* Replace NULLS_P by NULLS_LA if it's followed by FIRST or LAST */
              switch (next_token)
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index a78ce03..7ce7a47 100644
*** a/src/backend/parser/scan.l
--- b/src/backend/parser/scan.l
*************** ident_cont        [A-Za-z\200-\377_0-9\$]
*** 331,339 ****
--- 331,344 ----

  identifier        {ident_start}{ident_cont}*

+ /* Assorted special-case operators and operator-like tokens */
  typecast        "::"
  dot_dot            \.\.
  colon_equals    ":="
+ less_equals        "<="
+ greater_equals    ">="
+ less_greater    "<>"
+ not_equals        "!="

  /*
   * "self" is the set of chars that should be returned as single-character
*************** other            .
*** 808,813 ****
--- 813,840 ----
                      return COLON_EQUALS;
                  }

+ {less_equals}    {
+                     SET_YYLLOC();
+                     return LESS_EQUALS;
+                 }
+
+ {greater_equals} {
+                     SET_YYLLOC();
+                     return GREATER_EQUALS;
+                 }
+
+ {less_greater}    {
+                     /* We accept both "<>" and "!=" as meaning NOT_EQUALS */
+                     SET_YYLLOC();
+                     return NOT_EQUALS;
+                 }
+
+ {not_equals}    {
+                     /* We accept both "<>" and "!=" as meaning NOT_EQUALS */
+                     SET_YYLLOC();
+                     return NOT_EQUALS;
+                 }
+
  {self}            {
                      SET_YYLLOC();
                      return yytext[0];
*************** other            .
*** 885,895 ****
                      if (nchars >= NAMEDATALEN)
                          yyerror("operator too long");

!                     /* Convert "!=" operator to "<>" for compatibility */
!                     if (strcmp(yytext, "!=") == 0)
!                         yylval->str = pstrdup("<>");
!                     else
!                         yylval->str = pstrdup(yytext);
                      return Op;
                  }

--- 912,918 ----
                      if (nchars >= NAMEDATALEN)
                          yyerror("operator too long");

!                     yylval->str = pstrdup(yytext);
                      return Op;
                  }

diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index d84dba7..feaa0c4 100644
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static struct config_bool ConfigureNames
*** 1590,1595 ****
--- 1590,1605 ----
      },

      {
+         {"operator_precedence_warning", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
+             gettext_noop("Emit a warning for constructs that changed meaning since PostgreSQL 9.4."),
+             NULL,
+         },
+         &operator_precedence_warning,
+         false,
+         NULL, NULL, NULL
+     },
+
+     {
          {"quote_all_identifiers", PGC_USERSET, COMPAT_OPTIONS_PREVIOUS,
              gettext_noop("When generating SQL fragments, quote all identifiers."),
              NULL,
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index f8f9ce1..26f3a53 100644
*** a/src/backend/utils/misc/postgresql.conf.sample
--- b/src/backend/utils/misc/postgresql.conf.sample
***************
*** 585,590 ****
--- 585,591 ----
  #default_with_oids = off
  #escape_string_warning = on
  #lo_compat_privileges = off
+ #operator_precedence_warning = off
  #quote_all_identifiers = off
  #sql_inheritance = on
  #standard_conforming_strings = on
diff --git a/src/bin/psql/psqlscan.l b/src/bin/psql/psqlscan.l
index fb3fa11..a37cd2c 100644
*** a/src/bin/psql/psqlscan.l
--- b/src/bin/psql/psqlscan.l
*************** ident_cont        [A-Za-z\200-\377_0-9\$]
*** 355,363 ****
--- 355,368 ----

  identifier        {ident_start}{ident_cont}*

+ /* Assorted special-case operators and operator-like tokens */
  typecast        "::"
  dot_dot            \.\.
  colon_equals    ":="
+ less_equals        "<="
+ greater_equals    ">="
+ less_greater    "<>"
+ not_equals        "!="

  /*
   * "self" is the set of chars that should be returned as single-character
*************** other            .
*** 669,674 ****
--- 674,695 ----
                      ECHO;
                  }

+ {less_equals}    {
+                     ECHO;
+                 }
+
+ {greater_equals} {
+                     ECHO;
+                 }
+
+ {less_greater}    {
+                     ECHO;
+                 }
+
+ {not_equals}    {
+                     ECHO;
+                 }
+
      /*
       * These rules are specific to psql --- they implement parenthesis
       * counting and detection of command-ending semicolon.  These must
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index ac13302..8cda8d6 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef enum A_Expr_Kind
*** 239,245 ****
      AEXPR_BETWEEN,                /* name must be "BETWEEN" */
      AEXPR_NOT_BETWEEN,            /* name must be "NOT BETWEEN" */
      AEXPR_BETWEEN_SYM,            /* name must be "BETWEEN SYMMETRIC" */
!     AEXPR_NOT_BETWEEN_SYM        /* name must be "NOT BETWEEN SYMMETRIC" */
  } A_Expr_Kind;

  typedef struct A_Expr
--- 239,246 ----
      AEXPR_BETWEEN,                /* name must be "BETWEEN" */
      AEXPR_NOT_BETWEEN,            /* name must be "NOT BETWEEN" */
      AEXPR_BETWEEN_SYM,            /* name must be "BETWEEN SYMMETRIC" */
!     AEXPR_NOT_BETWEEN_SYM,        /* name must be "NOT BETWEEN SYMMETRIC" */
!     AEXPR_PAREN                    /* nameless dummy node for parentheses */
  } A_Expr_Kind;

  typedef struct A_Expr
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index 66391df..fbc3f17 100644
*** a/src/include/parser/parse_expr.h
--- b/src/include/parser/parse_expr.h
***************
*** 16,21 ****
--- 16,22 ----
  #include "parser/parse_node.h"

  /* GUC parameters */
+ extern bool operator_precedence_warning;
  extern bool Transform_null_equals;

  extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKind);
diff --git a/src/include/parser/scanner.h b/src/include/parser/scanner.h
index e6724bc..ac645b5 100644
*** a/src/include/parser/scanner.h
--- b/src/include/parser/scanner.h
*************** typedef union core_YYSTYPE
*** 51,56 ****
--- 51,57 ----
   *    %token <str>    IDENT FCONST SCONST BCONST XCONST Op
   *    %token <ival>    ICONST PARAM
   *    %token            TYPECAST DOT_DOT COLON_EQUALS
+  *    %token            LESS_EQUALS GREATER_EQUALS NOT_EQUALS
   * The above token definitions *must* be the first ones declared in any
   * bison parser built atop this scanner, so that they will have consistent
   * numbers assigned to them (specifically, IDENT = 258 and so on).
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 36dce80..d715afd 100644
*** a/src/interfaces/ecpg/preproc/parse.pl
--- b/src/interfaces/ecpg/preproc/parse.pl
*************** my %replace_token = (
*** 42,52 ****

  # or in the block
  my %replace_string = (
      'NULLS_LA'        => 'nulls',
      'WITH_LA'         => 'with',
      'TYPECAST'        => '::',
      'DOT_DOT'         => '..',
!     'COLON_EQUALS'    => ':=',);

  # specific replace_types for specific non-terminals - never include the ':'
  # ECPG-only replace_types are defined in ecpg-replace_types
--- 42,57 ----

  # or in the block
  my %replace_string = (
+     'NOT_LA'          => 'not',
      'NULLS_LA'        => 'nulls',
      'WITH_LA'         => 'with',
      'TYPECAST'        => '::',
      'DOT_DOT'         => '..',
!     'COLON_EQUALS'    => ':=',
!     'LESS_EQUALS'     => '<=',
!     'GREATER_EQUALS'  => '>=',
!     'NOT_EQUALS'      => '<>',
! );

  # specific replace_types for specific non-terminals - never include the ':'
  # ECPG-only replace_types are defined in ecpg-replace_types
diff --git a/src/interfaces/ecpg/preproc/parser.c b/src/interfaces/ecpg/preproc/parser.c
index 099a213..662a90a 100644
*** a/src/interfaces/ecpg/preproc/parser.c
--- b/src/interfaces/ecpg/preproc/parser.c
*************** filtered_base_yylex(void)
*** 75,80 ****
--- 75,83 ----
       */
      switch (cur_token)
      {
+         case NOT:
+             cur_token_length = 3;
+             break;
          case NULLS_P:
              cur_token_length = 5;
              break;
*************** filtered_base_yylex(void)
*** 119,124 ****
--- 122,141 ----
      /* Replace cur_token if needed, based on lookahead */
      switch (cur_token)
      {
+         case NOT:
+             /* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
+             switch (next_token)
+             {
+                 case BETWEEN:
+                 case IN_P:
+                 case LIKE:
+                 case ILIKE:
+                 case SIMILAR:
+                     cur_token = NOT_LA;
+                     break;
+             }
+             break;
+
          case NULLS_P:
              /* Replace NULLS_P by NULLS_LA if it's followed by FIRST or LAST */
              switch (next_token)
diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l
index 530712e..4760ddd 100644
*** a/src/interfaces/ecpg/preproc/pgc.l
--- b/src/interfaces/ecpg/preproc/pgc.l
*************** array            ({ident_cont}|{whitespace}|[\[\]
*** 236,241 ****
--- 236,245 ----
  typecast        "::"
  dot_dot            \.\.
  colon_equals    ":="
+ less_equals        "<="
+ greater_equals    ">="
+ less_greater    "<>"
+ not_equals        "!="

  /*
   * "self" is the set of chars that should be returned as single-character
*************** cppline            {space}*#([^i][A-Za-z]*|{if}|{
*** 620,625 ****
--- 624,633 ----
  <SQL>{typecast}        { return TYPECAST; }
  <SQL>{dot_dot}        { return DOT_DOT; }
  <SQL>{colon_equals}    { return COLON_EQUALS; }
+ <SQL>{less_equals}    { return LESS_EQUALS; }
+ <SQL>{greater_equals} { return GREATER_EQUALS; }
+ <SQL>{less_greater}    { return NOT_EQUALS; }
+ <SQL>{not_equals}    { return NOT_EQUALS; }
  <SQL>{informix_special}    {
                /* are we simulating Informix? */
                  if (INFORMIX_MODE)
*************** cppline            {space}*#([^i][A-Za-z]*|{if}|{
*** 699,709 ****
                                  return yytext[0];
                          }

!                         /* Convert "!=" operator to "<>" for compatibility */
!                         if (strcmp(yytext, "!=") == 0)
!                             yylval.str = mm_strdup("<>");
!                         else
!                             yylval.str = mm_strdup(yytext);
                          return Op;
                      }
  <SQL>{param}        {
--- 707,713 ----
                                  return yytext[0];
                          }

!                         yylval.str = mm_strdup(yytext);
                          return Op;
                      }
  <SQL>{param}        {
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index 506a313..761cfab 100644
*** a/src/pl/plpgsql/src/pl_gram.y
--- b/src/pl/plpgsql/src/pl_gram.y
*************** static    void            check_raise_parameters(PLp
*** 227,232 ****
--- 227,233 ----
  %token <str>    IDENT FCONST SCONST BCONST XCONST Op
  %token <ival>    ICONST PARAM
  %token            TYPECAST DOT_DOT COLON_EQUALS
+ %token            LESS_EQUALS GREATER_EQUALS NOT_EQUALS

  /*
   * Other tokens recognized by plpgsql's lexer interface layer (pl_scanner.c).

pgsql-hackers by date:

Previous
From: Michael Paquier
Date:
Subject: Re: CATUPDATE confusion?
Next
From: Michael Paquier
Date:
Subject: Dereferenced pointer checks in data.c of ECPG