]> granicus.if.org Git - postgresql/commitdiff
Make operator precedence follow the SQL standard more closely.
authorTom Lane <tgl@sss.pgh.pa.us>
Wed, 11 Mar 2015 17:22:52 +0000 (13:22 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Wed, 11 Mar 2015 17:22:52 +0000 (13:22 -0400)
While the SQL standard is pretty vague on the overall topic of operator
precedence (because it never presents a unified BNF for all expressions),
it does seem reasonable to conclude from the spec for <boolean value
expression> that OR has the lowest precedence, then AND, then NOT, then IS
tests, then the six standard comparison operators, then everything else
(since any non-boolean operator in a WHERE clause would need to be an
argument of one of these).

We were only sort of on board with that: most notably, while "<" ">" and
"=" had properly low precedence, "<=" ">=" and "<>" were treated as generic
operators and so had significantly higher precedence.  And "IS" tests were
even higher precedence than those, which is very clearly wrong per spec.

Another problem was that "foo NOT SOMETHING bar" constructs, such as
"x NOT LIKE y", were treated inconsistently because of a bison
implementation artifact: they had the documented precedence with respect
to operators to their right, but behaved like NOT (i.e., very low priority)
with respect to operators to their left.

Fixing the precedence issues is just a small matter of rearranging the
precedence declarations in gram.y, except for the NOT problem, which
requires adding an additional lookahead case in base_yylex() so that we
can attach a different token precedence to NOT LIKE and allied two-word
operators.

The bulk of this patch is not the bug fix per se, but adding logic to
parse_expr.c to allow giving warnings if an expression has changed meaning
because of these precedence changes.  These warnings are off by default
and are enabled by the new GUC operator_precedence_warning.  It's believed
that very few applications will be affected by these changes, but it was
agreed that a warning mechanism is essential to help debug any that are.

18 files changed:
doc/src/sgml/config.sgml
doc/src/sgml/syntax.sgml
src/backend/nodes/outfuncs.c
src/backend/parser/gram.y
src/backend/parser/parse_expr.c
src/backend/parser/parse_target.c
src/backend/parser/parser.c
src/backend/parser/scan.l
src/backend/utils/misc/guc.c
src/backend/utils/misc/postgresql.conf.sample
src/bin/psql/psqlscan.l
src/include/nodes/parsenodes.h
src/include/parser/parse_expr.h
src/include/parser/scanner.h
src/interfaces/ecpg/preproc/parse.pl
src/interfaces/ecpg/preproc/parser.c
src/interfaces/ecpg/preproc/pgc.l
src/pl/plpgsql/src/pl_gram.y

index 07214bfd76a77e2889e0f4d92fbdecc68423c828..d0f43c64af3e7430e28c4c236d0a0a8c1c5c23eb 100644 (file)
@@ -6816,6 +6816,29 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </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>
index 8283e252582b39aa8708fb1cf3c63dacef3a4ef6..ff2c3e2b9a371bab67594b89ac3d462503551627 100644 (file)
@@ -984,10 +984,11 @@ CAST ( '<replaceable>string</replaceable>' AS <replaceable>type</replaceable> )
     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>&lt;</> and
-    <literal>&gt;</> have a different precedence than the Boolean
-    operators <literal>&lt;=</> and <literal>&gt;=</>.  Also, you will
+    into the parser.
+   </para>
+
+   <para>
+    You will
     sometimes need to add parentheses when using combinations of
     binary and unary operators.  For instance:
 <programlisting>
@@ -1008,7 +1009,7 @@ SELECT (5 !) - 6;
    </para>
 
    <table id="sql-precedence-table">
-    <title>Operator Precedence (decreasing)</title>
+    <title>Operator Precedence (highest to lowest)</title>
 
     <tgroup cols="3">
      <thead>
@@ -1063,41 +1064,11 @@ SELECT (5 !) - 6;
       </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>(any other operator)</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>
@@ -1105,21 +1076,23 @@ SELECT (5 !) - 6;
       </row>
 
       <row>
-       <entry><token>LIKE</token> <token>ILIKE</token> <token>SIMILAR</token></entry>
+       <entry><token>BETWEEN</token> <token>IN</token> <token>LIKE</token> <token>ILIKE</token> <token>SIMILAR</token></entry>
        <entry></entry>
-       <entry>string pattern matching</entry>
+       <entry>range containment, set membership, string matching</entry>
       </row>
 
       <row>
-       <entry><token>&lt;</token> <token>&gt;</token></entry>
+       <entry><token>&lt;</token> <token>&gt;</token> <token>=</token> <token>&lt;=</token> <token>&gt;=</token> <token>&lt;&gt;</token>
+</entry>
        <entry></entry>
-       <entry>less than, greater than</entry>
+       <entry>comparison operators</entry>
       </row>
 
       <row>
-       <entry><token>=</token></entry>
-       <entry>right</entry>
-       <entry>equality, assignment</entry>
+       <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>
@@ -1159,9 +1132,32 @@ SELECT (5 !) - 6;
 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
+    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>&lt;=</token>
+     <token>&gt;=</token> and <token>&lt;&gt;</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>
 
index 775f482abda73baad1d98dedf3febc8f765bdf13..03f8adaae3ee010a508beb6a5c91cb981ba8cb67 100644 (file)
@@ -2546,6 +2546,9 @@ _outAExpr(StringInfo str, const A_Expr *node)
                        appendStringInfoString(str, " NOT_BETWEEN_SYM ");
                        WRITE_NODE_FIELD(name);
                        break;
+               case AEXPR_PAREN:
+                       appendStringInfoString(str, " PAREN");
+                       break;
                default:
                        appendStringInfoString(str, " ??");
                        break;
index 21b8897038f2602d09bce5f9e5df175f82a3c985..cf0d31744e15a571a0082cb7972ea59715d4c860 100644 (file)
@@ -58,6 +58,7 @@
 #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"
@@ -534,6 +535,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %token <str>   IDENT FCONST SCONST BCONST XCONST Op
 %token <ival>  ICONST PARAM
 %token                 TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
+%token                 LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 
 /*
  * If you want to make any keyword changes, update the keyword table in
@@ -636,8 +638,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
  * 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                 NULLS_LA WITH_LA
+%token         NOT_LA NULLS_LA WITH_LA
 
 
 /* Precedence: lowest to highest */
@@ -647,13 +654,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %left          OR
 %left          AND
 %right         NOT
-%right         '='
-%nonassoc      '<' '>'
-%nonassoc      LIKE ILIKE SIMILAR
-%nonassoc      ESCAPE
+%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
-%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
@@ -678,9 +683,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
 %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          '^'
@@ -11147,6 +11149,12 @@ interval_second:
  *
  * 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
@@ -11196,6 +11204,12 @@ a_expr:                c_expr                                                                  { $$ = $1; }
                                { $$ = (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); }
@@ -11210,13 +11224,15 @@ a_expr:               c_expr                                                                  { $$ = $1; }
                                { $$ = 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
+                       | a_expr LIKE a_expr ESCAPE a_expr                                      %prec LIKE
                                {
                                        FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                                                           list_make2($3, $5),
@@ -11224,12 +11240,12 @@ a_expr:               c_expr                                                                  { $$ = $1; }
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "~~",
                                                                                                   $1, (Node *) n, @2);
                                }
-                       | a_expr NOT LIKE a_expr
+                       | a_expr NOT_LA LIKE a_expr                                                     %prec NOT_LA
                                {
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "!~~",
                                                                                                   $1, $4, @2);
                                }
-                       | a_expr NOT LIKE a_expr ESCAPE a_expr
+                       | a_expr NOT_LA LIKE a_expr ESCAPE a_expr                       %prec NOT_LA
                                {
                                        FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                                                           list_make2($4, $6),
@@ -11242,7 +11258,7 @@ a_expr:         c_expr                                                                  { $$ = $1; }
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "~~*",
                                                                                                   $1, $3, @2);
                                }
-                       | a_expr ILIKE a_expr ESCAPE a_expr
+                       | a_expr ILIKE a_expr ESCAPE a_expr                                     %prec ILIKE
                                {
                                        FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                                                           list_make2($3, $5),
@@ -11250,12 +11266,12 @@ a_expr:               c_expr                                                                  { $$ = $1; }
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "~~*",
                                                                                                   $1, (Node *) n, @2);
                                }
-                       | a_expr NOT ILIKE a_expr
+                       | a_expr NOT_LA ILIKE a_expr                                            %prec NOT_LA
                                {
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "!~~*",
                                                                                                   $1, $4, @2);
                                }
-                       | a_expr NOT ILIKE a_expr ESCAPE a_expr
+                       | a_expr NOT_LA ILIKE a_expr ESCAPE a_expr                      %prec NOT_LA
                                {
                                        FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
                                                                                           list_make2($4, $6),
@@ -11264,7 +11280,7 @@ a_expr:         c_expr                                                                  { $$ = $1; }
                                                                                                   $1, (Node *) n, @2);
                                }
 
-                       | a_expr SIMILAR TO a_expr                              %prec SIMILAR
+                       | a_expr SIMILAR TO a_expr                                                      %prec SIMILAR
                                {
                                        FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
                                                                                           list_make2($4, makeNullAConst(-1)),
@@ -11272,7 +11288,7 @@ a_expr:         c_expr                                                                  { $$ = $1; }
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
                                                                                                   $1, (Node *) n, @2);
                                }
-                       | a_expr SIMILAR TO a_expr ESCAPE a_expr
+                       | a_expr SIMILAR TO a_expr ESCAPE a_expr                        %prec SIMILAR
                                {
                                        FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
                                                                                           list_make2($4, $6),
@@ -11280,7 +11296,7 @@ a_expr:         c_expr                                                                  { $$ = $1; }
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
                                                                                                   $1, (Node *) n, @2);
                                }
-                       | a_expr NOT SIMILAR TO a_expr                  %prec SIMILAR
+                       | a_expr NOT_LA SIMILAR TO a_expr                                       %prec NOT_LA
                                {
                                        FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
                                                                                           list_make2($5, makeNullAConst(-1)),
@@ -11288,7 +11304,7 @@ a_expr:         c_expr                                                                  { $$ = $1; }
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~",
                                                                                                   $1, (Node *) n, @2);
                                }
-                       | a_expr NOT SIMILAR TO a_expr ESCAPE a_expr
+                       | a_expr NOT_LA SIMILAR TO a_expr ESCAPE a_expr         %prec NOT_LA
                                {
                                        FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
                                                                                           list_make2($5, $7),
@@ -11420,7 +11436,7 @@ a_expr:         c_expr                                                                  { $$ = $1; }
                                {
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "<>", $1, (Node *) $6, @2);
                                }
-                       | a_expr BETWEEN opt_asymmetric b_expr AND b_expr               %prec BETWEEN
+                       | a_expr BETWEEN opt_asymmetric b_expr AND a_expr               %prec BETWEEN
                                {
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_BETWEEN,
                                                                                                   "BETWEEN",
@@ -11428,7 +11444,7 @@ a_expr:         c_expr                                                                  { $$ = $1; }
                                                                                                   (Node *) list_make2($4, $6),
                                                                                                   @2);
                                }
-                       | a_expr NOT BETWEEN opt_asymmetric b_expr AND b_expr   %prec BETWEEN
+                       | a_expr NOT_LA BETWEEN opt_asymmetric b_expr AND a_expr %prec NOT_LA
                                {
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_NOT_BETWEEN,
                                                                                                   "NOT BETWEEN",
@@ -11436,7 +11452,7 @@ a_expr:         c_expr                                                                  { $$ = $1; }
                                                                                                   (Node *) list_make2($5, $7),
                                                                                                   @2);
                                }
-                       | a_expr BETWEEN SYMMETRIC b_expr AND b_expr                    %prec BETWEEN
+                       | a_expr BETWEEN SYMMETRIC b_expr AND a_expr                    %prec BETWEEN
                                {
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_BETWEEN_SYM,
                                                                                                   "BETWEEN SYMMETRIC",
@@ -11444,7 +11460,7 @@ a_expr:         c_expr                                                                  { $$ = $1; }
                                                                                                   (Node *) list_make2($4, $6),
                                                                                                   @2);
                                }
-                       | a_expr NOT BETWEEN SYMMETRIC b_expr AND b_expr                %prec BETWEEN
+                       | a_expr NOT_LA BETWEEN SYMMETRIC b_expr AND a_expr             %prec NOT_LA
                                {
                                        $$ = (Node *) makeSimpleA_Expr(AEXPR_NOT_BETWEEN_SYM,
                                                                                                   "NOT BETWEEN SYMMETRIC",
@@ -11472,7 +11488,7 @@ a_expr:         c_expr                                                                  { $$ = $1; }
                                                $$ = (Node *) makeSimpleA_Expr(AEXPR_IN, "=", $1, $3, @2);
                                        }
                                }
-                       | a_expr NOT IN_P in_expr
+                       | 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))
@@ -11576,6 +11592,12 @@ b_expr:                c_expr
                                { $$ = (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
@@ -11647,6 +11669,24 @@ c_expr:                columnref                                                               { $$ = $1; }
                                                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;
                                }
@@ -12495,6 +12535,9 @@ MathOp:          '+'                                                                    { $$ = "+"; }
                        | '<'                                                                   { $$ = "<"; }
                        | '>'                                                                   { $$ = ">"; }
                        | '='                                                                   { $$ = "="; }
+                       | LESS_EQUALS                                                   { $$ = "<="; }
+                       | GREATER_EQUALS                                                { $$ = ">="; }
+                       | NOT_EQUALS                                                    { $$ = "<>"; }
                ;
 
 qual_Op:       Op
@@ -12517,11 +12560,11 @@ subquery_Op:
                                        { $$ = $3; }
                        | LIKE
                                        { $$ = list_make1(makeString("~~")); }
-                       | NOT LIKE
+                       | NOT_LA LIKE
                                        { $$ = list_make1(makeString("!~~")); }
                        | ILIKE
                                        { $$ = list_make1(makeString("~~*")); }
-                       | NOT ILIKE
+                       | 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),
index 130e52b26c4f20fe909811baa70d5a854f12792c..f759606f88bc9c53a5527c60cdcb3d75fbc5cc10 100644 (file)
 #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);
@@ -76,6 +123,11 @@ static Node *make_row_distinct_op(ParseState *pstate, List *opname,
                                         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);
 
 
 /*
@@ -194,6 +246,9 @@ transformExprRecurse(ParseState *pstate, Node *expr)
                                        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 */
@@ -255,6 +310,11 @@ transformExprRecurse(ParseState *pstate, Node *expr)
                        {
                                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));
@@ -782,6 +842,18 @@ transformAExprOp(ParseState *pstate, A_Expr *a)
        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
@@ -858,8 +930,17 @@ transformAExprOp(ParseState *pstate, A_Expr *a)
 static Node *
 transformAExprOpAny(ParseState *pstate, A_Expr *a)
 {
-       Node       *lexpr = transformExprRecurse(pstate, a->lexpr);
-       Node       *rexpr = transformExprRecurse(pstate, a->rexpr);
+       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,
@@ -872,8 +953,17 @@ transformAExprOpAny(ParseState *pstate, A_Expr *a)
 static Node *
 transformAExprOpAll(ParseState *pstate, A_Expr *a)
 {
-       Node       *lexpr = transformExprRecurse(pstate, a->lexpr);
-       Node       *rexpr = transformExprRecurse(pstate, a->rexpr);
+       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,
@@ -886,8 +976,16 @@ transformAExprOpAll(ParseState *pstate, A_Expr *a)
 static Node *
 transformAExprDistinct(ParseState *pstate, A_Expr *a)
 {
-       Node       *lexpr = transformExprRecurse(pstate, a->lexpr);
-       Node       *rexpr = transformExprRecurse(pstate, a->rexpr);
+       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))
@@ -944,20 +1042,27 @@ transformAExprNullIf(ParseState *pstate, A_Expr *a)
        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)
 {
-       /*
-        * Checking an expression for match to a list of type names. Will result
-        * in a boolean constant node.
-        */
-       Node       *lexpr = transformExprRecurse(pstate, a->lexpr);
+       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)
        {
@@ -1001,6 +1106,13 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
        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
@@ -1153,6 +1265,22 @@ transformAExprBetween(ParseState *pstate, A_Expr *a)
        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
@@ -1657,6 +1785,19 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
                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)".
                 */
@@ -2000,6 +2141,11 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
        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)
@@ -2172,6 +2318,11 @@ transformBooleanTest(ParseState *pstate, BooleanTest *b)
 {
        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:
@@ -2688,6 +2839,309 @@ make_distinct_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
        return result;
 }
 
+/*
+ * 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.
  *
index 3724330dc842561a1162a10c9f365ee99120b5ac..2d85cf08e706b374fcd835347d162fd41bc809f5 100644 (file)
@@ -1654,12 +1654,17 @@ FigureColnameInternal(Node *node, char **name)
                        *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)
                        {
+                               /* 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,
index b17771d4cca2ee532e6b519dab7ba92324b9e6ea..fdf5a6a1cafc6f5559a9ce2438cbc45a36ec0e60 100644 (file)
@@ -107,6 +107,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
         */
        switch (cur_token)
        {
+               case NOT:
+                       cur_token_length = 3;
+                       break;
                case NULLS_P:
                        cur_token_length = 5;
                        break;
@@ -151,6 +154,20 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
        /* 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)
index a0e20434ef392c1fd68b3926c1eb42186e8854a4..82b20c6e5f695054acbebf7e6f40a169fd7c5597 100644 (file)
@@ -331,10 +331,15 @@ ident_cont                [A-Za-z\200-\377_0-9\$]
 
 identifier             {ident_start}{ident_cont}*
 
+/* Assorted special-case operators and operator-like tokens */
 typecast               "::"
 dot_dot                        \.\.
 colon_equals   ":="
 equals_greater "=>"
+less_equals            "<="
+greater_equals ">="
+less_greater   "<>"
+not_equals             "!="
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -814,6 +819,28 @@ other                      .
                                        return EQUALS_GREATER;
                                }
 
+{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];
@@ -891,11 +918,7 @@ other                      .
                                        if (nchars >= NAMEDATALEN)
                                                yyerror("operator too long");
 
-                                       /* Convert "!=" operator to "<>" for compatibility */
-                                       if (strcmp(yytext, "!=") == 0)
-                                               yylval->str = pstrdup("<>");
-                                       else
-                                               yylval->str = pstrdup(yytext);
+                                       yylval->str = pstrdup(yytext);
                                        return Op;
                                }
 
index 6eaab4352aa53febf7b813bdc12af2c5b1b08951..7196b0b2157b2bcf8de50e5e1196effd94bf46de 100644 (file)
@@ -1599,6 +1599,16 @@ static struct config_bool ConfigureNamesBool[] =
                NULL, NULL, NULL
        },
 
+       {
+               {"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."),
index 7590a6f056d9ae7b76266925ec313a4e108fe731..d7a61f1fc4b8da58e424cd9baa0838f93e7ba0cc 100644 (file)
 #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
index 2b2dec95be72a7222230e37f7b2ee14900a76bd9..bb134a42d8a983e40a6a2e929703c6aba90aea71 100644 (file)
@@ -355,10 +355,15 @@ ident_cont                [A-Za-z\200-\377_0-9\$]
 
 identifier             {ident_start}{ident_cont}*
 
+/* Assorted special-case operators and operator-like tokens */
 typecast               "::"
 dot_dot                        \.\.
 colon_equals   ":="
 equals_greater "=>"
+less_equals            "<="
+greater_equals ">="
+less_greater   "<>"
+not_equals             "!="
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -674,6 +679,22 @@ other                      .
                                        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
index 497559df5887982b12386d37881ed3b6045e0bff..c226b039cf1c023ebec09d87e083833b13b80726 100644 (file)
@@ -239,7 +239,8 @@ typedef enum A_Expr_Kind
        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_NOT_BETWEEN_SYM,          /* name must be "NOT BETWEEN SYMMETRIC" */
+       AEXPR_PAREN                                     /* nameless dummy node for parentheses */
 } A_Expr_Kind;
 
 typedef struct A_Expr
index 66391dfc74d1eb58c653f09e2324e763932a51a3..fbc3f17c688e93ce1bc467d59566cf49f4a0388f 100644 (file)
@@ -16,6 +16,7 @@
 #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);
index 0e22d031f140d6a7472d33c8b03a17b02de82423..f941977865ae4c9bef86037427915cc19198f2c9 100644 (file)
@@ -51,6 +51,7 @@ typedef union core_YYSTYPE
  *     %token <str>    IDENT FCONST SCONST BCONST XCONST Op
  *     %token <ival>   ICONST PARAM
  *     %token                  TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
+ *     %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).
index 7ae7acc6130193604a4794ff3f4b017c70f7f60a..588bb63e53ff1f914cee5658d0963b7d494c3471 100644 (file)
@@ -42,12 +42,17 @@ my %replace_token = (
 
 # or in the block
 my %replace_string = (
+       'NOT_LA'          => 'not',
        'NULLS_LA'        => 'nulls',
        'WITH_LA'         => 'with',
        'TYPECAST'        => '::',
        'DOT_DOT'         => '..',
        'COLON_EQUALS'    => ':=',
-       'EQUALS_GREATER'  => '=>',);
+       'EQUALS_GREATER'  => '=>',
+       '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
index 099a213d11872f01796d1f83621996c2dc2f4ea6..662a90a3f6bd311b3b8fe996e12401f4dc40b58b 100644 (file)
@@ -75,6 +75,9 @@ filtered_base_yylex(void)
         */
        switch (cur_token)
        {
+               case NOT:
+                       cur_token_length = 3;
+                       break;
                case NULLS_P:
                        cur_token_length = 5;
                        break;
@@ -119,6 +122,20 @@ filtered_base_yylex(void)
        /* 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)
index a8cc3d877c091f33bc14624a3ad7190f4b59692b..c70f2986962d6323b8ae197e8a21f1985649cacc 100644 (file)
@@ -233,10 +233,16 @@ ident_cont                [A-Za-z\200-\377_0-9\$]
 identifier             {ident_start}{ident_cont}*
 
 array                  ({ident_cont}|{whitespace}|[\[\]\+\-\*\%\/\(\)\>\.])*
+
+/* Assorted special-case operators and operator-like tokens */
 typecast               "::"
 dot_dot                        \.\.
 colon_equals   ":="
 equals_greater "=>"
+less_equals            "<="
+greater_equals ">="
+less_greater   "<>"
+not_equals             "!="
 
 /*
  * "self" is the set of chars that should be returned as single-character
@@ -622,6 +628,10 @@ cppline                    {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})(.*\\{space})*.
 <SQL>{dot_dot}         { return DOT_DOT; }
 <SQL>{colon_equals}    { return COLON_EQUALS; }
 <SQL>{equals_greater} { return EQUALS_GREATER; }
+<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)
@@ -701,11 +711,7 @@ cppline                    {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})(.*\\{space})*.
                                                                return yytext[0];
                                                }
 
-                                               /* Convert "!=" operator to "<>" for compatibility */
-                                               if (strcmp(yytext, "!=") == 0)
-                                                       yylval.str = mm_strdup("<>");
-                                               else
-                                                       yylval.str = mm_strdup(yytext);
+                                               yylval.str = mm_strdup(yytext);
                                                return Op;
                                        }
 <SQL>{param}           {
index a1758e03c3f0398dcae8d7e38ddc4bef9c6e234f..46217fd64bd7a2c109d7190152e53b69ad90b2b5 100644 (file)
@@ -227,6 +227,7 @@ static      void                    check_raise_parameters(PLpgSQL_stmt_raise *stmt);
 %token <str>   IDENT FCONST SCONST BCONST XCONST Op
 %token <ival>  ICONST PARAM
 %token                 TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
+%token                 LESS_EQUALS GREATER_EQUALS NOT_EQUALS
 
 /*
  * Other tokens recognized by plpgsql's lexer interface layer (pl_scanner.c).