</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>
associativity of the operators in <productname>PostgreSQL</>.
Most operators have the same precedence and are left-associative.
The precedence and associativity of the operators is hard-wired
- into the parser. This can lead to non-intuitive behavior; for
- example the Boolean operators <literal><</> and
- <literal>></> have a different precedence than the Boolean
- operators <literal><=</> and <literal>>=</>. Also, you will
+ into the parser.
+ </para>
+
+ <para>
+ You will
sometimes need to add parentheses when using combinations of
binary and unary operators. For instance:
<programlisting>
</para>
<table id="sql-precedence-table">
- <title>Operator Precedence (decreasing)</title>
+ <title>Operator Precedence (highest to lowest)</title>
<tgroup cols="3">
<thead>
</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>
</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><</token> <token>></token></entry>
+ <entry><token><</token> <token>></token> <token>=</token> <token><=</token> <token>>=</token> <token><></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>
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><=</token>
+ <token>>=</token> and <token><></token> used to be treated as
+ generic operators; <literal>IS</> tests used to have higher priority;
+ and <literal>NOT BETWEEN</> and related constructs acted inconsistently,
+ being taken in some cases as having the precedence of <literal>NOT</>
+ rather than <literal>BETWEEN</>. These rules were changed for better
+ compliance with the SQL standard and to reduce confusion from
+ inconsistent treatment of logically equivalent constructs. In most
+ cases, these changes will result in no behavioral change, or perhaps
+ in <quote>no such operator</> failures which can be resolved by adding
+ parentheses. However there are corner cases in which a query might
+ change behavior without any parsing error being reported. If you are
+ concerned about whether these changes have silently broken something,
+ you can test your application with the configuration
+ parameter <xref linkend="guc-operator-precedence-warning"> turned on
+ to see if any warnings are logged.
+ </para>
+ </note>
</sect2>
</sect1>
appendStringInfoString(str, " NOT_BETWEEN_SYM ");
WRITE_NODE_FIELD(name);
break;
+ case AEXPR_PAREN:
+ appendStringInfoString(str, " PAREN");
+ break;
default:
appendStringInfoString(str, " ??");
break;
#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"
%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
* 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 */
%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
%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 '^'
*
* 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
{ $$ = (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); }
{ $$ = 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),
$$ = (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),
$$ = (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),
$$ = (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),
$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)),
$$ = (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),
$$ = (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)),
$$ = (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),
{
$$ = (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",
(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",
(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",
(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",
$$ = (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))
{ $$ = (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
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;
}
| '<' { $$ = "<"; }
| '>' { $$ = ">"; }
| '=' { $$ = "="; }
+ | LESS_EQUALS { $$ = "<="; }
+ | GREATER_EQUALS { $$ = ">="; }
+ | NOT_EQUALS { $$ = "<>"; }
;
qual_Op: 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),
#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);
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);
/*
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 */
{
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));
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
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,
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,
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))
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)
{
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
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
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)".
*/
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)
{
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:
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.
*
*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,
*/
switch (cur_token)
{
+ case NOT:
+ cur_token_length = 3;
+ break;
case NULLS_P:
cur_token_length = 5;
break;
/* 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)
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
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];
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;
}
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."),
#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
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
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
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
#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);
* %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).
# 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
*/
switch (cur_token)
{
+ case NOT:
+ cur_token_length = 3;
+ break;
case NULLS_P:
cur_token_length = 5;
break;
/* 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)
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
<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)
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} {
%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).