Attached is an improved patch that includes optional warnings for
constructs that changed parsing.  It's not quite 100% but I think it's
about 90% correct; the difference in size between this and the previous
patch should be a pretty fair indication of what it's going to cost us
to have a warning capability.

What's missing from this version is that it can't tell the difference
between LIKE/ILIKE/SIMILAR TO and the underlying operators, that is,
it sees "a LIKE b" as "a ~~ b" because that's what the grammar emits.
However, those inputs have different operator-precedence behavior.

Likewise, we can't tell the difference between
        xmlexpr IS NOT DOCUMENT
        NOT (xmlexpr IS DOCUMENT)
because the grammar converts the former into the latter --- and again,
those two things have different precedence behavior.

It wouldn't take very much additional code to fix these things by changing
what the grammar emits; but I'm running out of energy for today.  In any
case, I thought I should put this up and see if this general approach is
going to satisfy people's concerns about making such a change.

                        regards, tom lane

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 6bcb106..22a2d32 100644
*** a/doc/src/sgml/config.sgml
--- b/doc/src/sgml/config.sgml
*************** dynamic_library_path = 'C:\tools\postgre
*** 6752,6757 ****
--- 6752,6780 ----
        </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 left 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..e7484c4 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,1087 ****
        </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>
--- 1064,1070 ----
        </row>
  
        <row>
!        <entry>(any other operator)</entry>
         <entry>left</entry>
         <entry>all other native and user-defined operators</entry>
        </row>
*************** SELECT (5 !) - 6;
*** 1111,1125 ****
        </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>
--- 1094,1110 ----
        </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>
  
--- 1144,1172 ----
  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 prior to 9.5 used a slightly
+      different operator precedence rule; in particular, <token>&lt;=</token>
+      <token>&gt;=</token> and <token>&lt;&gt;</token> used to be treated as
+      generic operators, and <literal>IS</> tests used to have higher priority.
+      This was changed for better compliance with the SQL standard, and because
+      treating these operators differently from the other comparison operators 
+      <token>&lt;</token> <token>&gt;</token> and <token>=</token> was
+      confusing.  In most cases, this change will result in no behavioral
+      change or obvious <quote>no such operator</> failures; however there are
+      corner cases in which a query might change behavior without any parsing
+      error being reported.  If you are concerned about whether this change has
+      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 98aa5f0..a6d0c5e 100644
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
*************** _outAExpr(StringInfo str, const A_Expr *
*** 2532,2537 ****
--- 2532,2540 ----
  			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 76b0aff..3c28349 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
*** 645,652 ****
  %left		OR
  %left		AND
  %right		NOT
! %right		'='
! %nonassoc	'<' '>'
  %nonassoc	LIKE ILIKE SIMILAR
  %nonassoc	ESCAPE
  %nonassoc	OVERLAPS
--- 647,654 ----
  %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	LIKE ILIKE SIMILAR
  %nonassoc	ESCAPE
  %nonassoc	OVERLAPS
*************** 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		'^'
--- 678,683 ----
*************** a_expr:		c_expr									{ $$ = $1; }
*** 11204,11209 ****
--- 11203,11214 ----
  				{ $$ = (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); }
*************** b_expr:		c_expr
*** 11564,11569 ****
--- 11569,11580 ----
  				{ $$ = (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; }
*** 11635,11640 ****
--- 11646,11669 ----
  						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:		 '+'									{ $$ = "+"; }
*** 12483,12488 ****
--- 12512,12520 ----
  			| '<'									{ $$ = "<"; }
  			| '>'									{ $$ = ">"; }
  			| '='									{ $$ = "="; }
+ 			| LESS_EQUALS							{ $$ = "<="; }
+ 			| GREATER_EQUALS						{ $$ = ">="; }
+ 			| NOT_EQUALS							{ $$ = "<>"; }
  		;
  
  qual_Op:	Op
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index f314745..de19502 100644
*** a/src/backend/parser/parse_expr.c
--- b/src/backend/parser/parse_expr.c
***************
*** 37,42 ****
--- 37,44 ----
  #include "utils/xml.h"
  
  
+ /* GUC parameters */
+ bool		operator_precedence_warning = false;
  bool		Transform_null_equals = false;
  
  static Node *transformExprRecurse(ParseState *pstate, Node *expr);
*************** static Node *make_row_distinct_op(ParseS
*** 76,81 ****
--- 78,88 ----
  					 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,
*** 188,193 ****
--- 195,203 ----
  					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,
*** 249,254 ****
--- 259,269 ----
  			{
  				NullTest   *n = (NullTest *) expr;
  
+ 				if (operator_precedence_warning)
+ 					emit_precedence_warnings(pstate, 1, "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
*** 773,778 ****
--- 788,805 ----
  	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
*************** transformAExprOpAll(ParseState *pstate, 
*** 877,884 ****
  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))
--- 904,919 ----
  static Node *
  transformAExprDistinct(ParseState *pstate, A_Expr *a)
  {
! 	Node	   *lexpr = a->lexpr;
! 	Node	   *rexpr = a->rexpr;
! 
! 	if (operator_precedence_warning)
! 		emit_precedence_warnings(pstate, 1, "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,
*** 935,954 ****
  	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)
  	{
--- 970,996 ----
  	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, 1, "IS",
+ 								 lexpr, NULL,
+ 								 a->location);
+ 
+ 	lexpr = transformExprRecurse(pstate, lexpr);
+ 
  	ltype = exprType(lexpr);
  	foreach(telem, (List *) a->rexpr)
  	{
*************** transformBooleanTest(ParseState *pstate,
*** 2157,2162 ****
--- 2199,2209 ----
  {
  	const char *clausename;
  
+ 	if (operator_precedence_warning)
+ 		emit_precedence_warnings(pstate, 1, "IS",
+ 								 (Node *) b->arg, NULL,
+ 								 b->location);
+ 
  	switch (b->booltesttype)
  	{
  		case IS_TRUE:
*************** make_distinct_op(ParseState *pstate, Lis
*** 2674,2679 ****
--- 2721,2904 ----
  }
  
  /*
+  * Identify node's group for operator precedence warnings
+  *
+  * Groups are:
+  * 0: everything not classified below
+  * 1: IS tests (NullTest, BooleanTest, etc)
+  * 2: < > =
+  * 3: <= => <>
+  * 4: LIKE ILIKE SIMILAR BETWEEN IN
+  * 5: generic Op
+  *
+  * 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.  Also, nodes in group 4 do have precedences
+  * relative to each other, but we don't care since those did not change.
+  */
+ 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 ||
+ 						 strcmp(*nodename, "~~") == 0 ||
+ 						 strcmp(*nodename, "!~~") == 0 ||
+ 						 strcmp(*nodename, "~~*") == 0 ||
+ 						 strcmp(*nodename, "!~~*") == 0)
+ 					group = 4;	/* LIKE, ILIKE, SIMILAR */
+ 				else if (strcmp(*nodename, "<=") == 0 ||
+ 						 strcmp(*nodename, ">=") == 0 ||
+ 						 strcmp(*nodename, "<>") == 0)
+ 					group = 3;
+ 				else if (strcmp(*nodename, "<") == 0 ||
+ 						 strcmp(*nodename, ">") == 0 ||
+ 						 strcmp(*nodename, "=") == 0)
+ 					group = 2;
+ 				else
+ 					group = 5;
+ 			}
+ 			else
+ 			{
+ 				/* schema-qualified operator syntax */
+ 				*nodename = "OPERATOR()";
+ 				group = 5;
+ 			}
+ 		}
+ 		else if (aexpr->kind == AEXPR_DISTINCT ||
+ 				 aexpr->kind == AEXPR_OF)
+ 		{
+ 			*nodename = "IS";
+ 			group = 1;
+ 		}
+ 		else if (aexpr->kind == AEXPR_IN)
+ 		{
+ 			*nodename = "IN";
+ 			group = 4;
+ 		}
+ 		else if (aexpr->kind == AEXPR_BETWEEN ||
+ 				 aexpr->kind == AEXPR_NOT_BETWEEN ||
+ 				 aexpr->kind == AEXPR_BETWEEN_SYM ||
+ 				 aexpr->kind == AEXPR_NOT_BETWEEN_SYM)
+ 		{
+ 			Assert(list_length(aexpr->name) == 1);
+ 			*nodename = strVal(linitial(aexpr->name));
+ 			group = 4;
+ 		}
+ 	}
+ 	else if (IsA(node, NullTest) ||IsA(node, BooleanTest))
+ 	{
+ 		*nodename = "IS";
+ 		group = 1;
+ 	}
+ 	else if (IsA(node, XmlExpr))
+ 	{
+ 		XmlExpr    *x = (XmlExpr *) node;
+ 
+ 		if (x->op == IS_DOCUMENT)
+ 		{
+ 			*nodename = "IS";
+ 			group = 1;
+ 		}
+ 		/* XXX what of IS NOT DOCUMENT? */
+ 	}
+ 
+ 	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)
+ {
+ 	/*----------
+ 	 * Map precedence groupings to old precedence ordering
+ 	 *
+ 	 * Old precedence order:
+ 	 * 2: < > =
+ 	 * 4: LIKE ILIKE SIMILAR BETWEEN IN
+ 	 * 5: generic Op, inclding 3: <= => <>
+ 	 * 1: IS tests (NullTest, BooleanTest, etc)
+ 	 *----------
+ 	 */
+ 	static const int oldprecedence[] = {0, 4, 1, 3, 2, 3};
+ 	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.
+ 	 */
+ 	cgroup = operator_precedence_group(lchild, &copname);
+ 	if (cgroup > 0)
+ 	{
+ 		Assert(opgroup <= cgroup);
+ 		if (oldprecedence[cgroup] < oldprecedence[opgroup])
+ 			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.
+ 	 */
+ 	cgroup = operator_precedence_group(rchild, &copname);
+ 	if (cgroup > 0)
+ 	{
+ 		Assert(opgroup < cgroup);
+ 		if (oldprecedence[cgroup] <= oldprecedence[opgroup])
+ 			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/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 9572777..360dad9 100644
*** a/src/backend/utils/misc/guc.c
--- b/src/backend/utils/misc/guc.c
*************** static struct config_bool ConfigureNames
*** 1514,1519 ****
--- 1514,1529 ----
  	},
  
  	{
+ 		{"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 b053659..9b2ca28 100644
*** a/src/backend/utils/misc/postgresql.conf.sample
--- b/src/backend/utils/misc/postgresql.conf.sample
***************
*** 582,587 ****
--- 582,588 ----
  #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 d7b6148..1987313 100644
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef enum A_Expr_Kind
*** 236,242 ****
  	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
--- 236,243 ----
  	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/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).
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to