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>&lt;</> and
!     <literal>&gt;</> have a different precedence than the Boolean
!     operators <literal>&lt;=</> and <literal>&gt;=</>.  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>&lt;</token> <token>&gt;</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>&lt;</token> <token>&gt;</token> <token>=</token> <token>&lt;=</token> <token>&gt;=</token> <token>&lt;&gt;</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>&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>
  
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).
