+ to see if any warnings are logged.
#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 IDENT FCONST SCONST BCONST XCONST Op
%token 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.
*