summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/backend/nodes/outfuncs.c3
-rw-r--r--src/backend/parser/gram.y97
-rw-r--r--src/backend/parser/parse_expr.c476
-rw-r--r--src/backend/parser/parse_target.c7
-rw-r--r--src/backend/parser/parser.c17
-rw-r--r--src/backend/parser/scan.l33
-rw-r--r--src/backend/utils/misc/guc.c10
-rw-r--r--src/backend/utils/misc/postgresql.conf.sample1
-rw-r--r--src/bin/psql/psqlscan.l21
-rw-r--r--src/include/nodes/parsenodes.h3
-rw-r--r--src/include/parser/parse_expr.h1
-rw-r--r--src/include/parser/scanner.h1
-rw-r--r--src/interfaces/ecpg/preproc/parse.pl7
-rw-r--r--src/interfaces/ecpg/preproc/parser.c17
-rw-r--r--src/interfaces/ecpg/preproc/pgc.l16
-rw-r--r--src/pl/plpgsql/src/pl_gram.y1
16 files changed, 660 insertions, 51 deletions
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 775f482abda..03f8adaae3e 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -2546,6 +2546,9 @@ _outAExpr(StringInfo str, const A_Expr *node)
appendStringInfoString(str, " NOT_BETWEEN_SYM ");
WRITE_NODE_FIELD(name);
break;
+ case AEXPR_PAREN:
+ appendStringInfoString(str, " PAREN");
+ break;
default:
appendStringInfoString(str, " ??");
break;
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 21b8897038f..cf0d31744e1 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -58,6 +58,7 @@
#include "nodes/nodeFuncs.h"
#include "parser/gramparse.h"
#include "parser/parser.h"
+#include "parser/parse_expr.h"
#include "storage/lmgr.h"
#include "utils/date.h"
#include "utils/datetime.h"
@@ -534,6 +535,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%token <str> IDENT FCONST SCONST BCONST XCONST Op
%token <ival> ICONST PARAM
%token TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
+%token LESS_EQUALS GREATER_EQUALS NOT_EQUALS
/*
* If you want to make any keyword changes, update the keyword table in
@@ -636,8 +638,13 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
* The grammar thinks these are keywords, but they are not in the kwlist.h
* list and so can never be entered directly. The filter in parser.c
* creates these tokens when required (based on looking one token ahead).
+ *
+ * NOT_LA exists so that productions such as NOT LIKE can be given the same
+ * precedence as LIKE; otherwise they'd effectively have the same precedence
+ * as NOT, at least with respect to their left-hand subexpression.
+ * NULLS_LA and WITH_LA are needed to make the grammar LALR(1).
*/
-%token NULLS_LA WITH_LA
+%token NOT_LA NULLS_LA WITH_LA
/* Precedence: lowest to highest */
@@ -647,13 +654,11 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%left OR
%left AND
%right NOT
-%right '='
-%nonassoc '<' '>'
-%nonassoc LIKE ILIKE SIMILAR
-%nonassoc ESCAPE
+%nonassoc IS ISNULL NOTNULL /* IS sets precedence for IS NULL, etc */
+%nonassoc '<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS
+%nonassoc BETWEEN IN_P LIKE ILIKE SIMILAR NOT_LA
+%nonassoc ESCAPE /* ESCAPE must be just above LIKE/ILIKE/SIMILAR */
%nonassoc OVERLAPS
-%nonassoc BETWEEN
-%nonassoc IN_P
%left POSTFIXOP /* dummy for postfix Op rules */
/*
* To support target_el without AS, we must give IDENT an explicit priority
@@ -678,9 +683,6 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
%nonassoc UNBOUNDED /* ideally should have same precedence as IDENT */
%nonassoc IDENT NULL_P PARTITION RANGE ROWS PRECEDING FOLLOWING
%left Op OPERATOR /* multi-character ops and user-defined operators */
-%nonassoc NOTNULL
-%nonassoc ISNULL
-%nonassoc IS /* sets precedence for IS NULL, etc */
%left '+' '-'
%left '*' '/' '%'
%left '^'
@@ -11147,6 +11149,12 @@ interval_second:
*
* c_expr is all the productions that are common to a_expr and b_expr;
* it's factored out just to eliminate redundant coding.
+ *
+ * Be careful of productions involving more than one terminal token.
+ * By default, bison will assign such productions the precedence of their
+ * last terminal, but in nearly all cases you want it to be the precedence
+ * of the first terminal instead; otherwise you will not get the behavior
+ * you expect! So we use %prec annotations freely to set precedences.
*/
a_expr: c_expr { $$ = $1; }
| a_expr TYPECAST Typename
@@ -11196,6 +11204,12 @@ a_expr: c_expr { $$ = $1; }
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", $1, $3, @2); }
| a_expr '=' a_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", $1, $3, @2); }
+ | a_expr LESS_EQUALS a_expr
+ { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", $1, $3, @2); }
+ | a_expr GREATER_EQUALS a_expr
+ { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); }
+ | a_expr NOT_EQUALS a_expr
+ { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); }
| a_expr qual_Op a_expr %prec Op
{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
@@ -11210,13 +11224,15 @@ a_expr: c_expr { $$ = $1; }
{ $$ = makeOrExpr($1, $3, @2); }
| NOT a_expr
{ $$ = makeNotExpr($2, @1); }
+ | NOT_LA a_expr %prec NOT
+ { $$ = makeNotExpr($2, @1); }
| a_expr LIKE a_expr
{
$$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "~~",
$1, $3, @2);
}
- | a_expr LIKE a_expr ESCAPE a_expr
+ | a_expr LIKE a_expr ESCAPE a_expr %prec LIKE
{
FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
list_make2($3, $5),
@@ -11224,12 +11240,12 @@ a_expr: c_expr { $$ = $1; }
$$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "~~",
$1, (Node *) n, @2);
}
- | a_expr NOT LIKE a_expr
+ | a_expr NOT_LA LIKE a_expr %prec NOT_LA
{
$$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "!~~",
$1, $4, @2);
}
- | a_expr NOT LIKE a_expr ESCAPE a_expr
+ | a_expr NOT_LA LIKE a_expr ESCAPE a_expr %prec NOT_LA
{
FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
list_make2($4, $6),
@@ -11242,7 +11258,7 @@ a_expr: c_expr { $$ = $1; }
$$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "~~*",
$1, $3, @2);
}
- | a_expr ILIKE a_expr ESCAPE a_expr
+ | a_expr ILIKE a_expr ESCAPE a_expr %prec ILIKE
{
FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
list_make2($3, $5),
@@ -11250,12 +11266,12 @@ a_expr: c_expr { $$ = $1; }
$$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "~~*",
$1, (Node *) n, @2);
}
- | a_expr NOT ILIKE a_expr
+ | a_expr NOT_LA ILIKE a_expr %prec NOT_LA
{
$$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "!~~*",
$1, $4, @2);
}
- | a_expr NOT ILIKE a_expr ESCAPE a_expr
+ | a_expr NOT_LA ILIKE a_expr ESCAPE a_expr %prec NOT_LA
{
FuncCall *n = makeFuncCall(SystemFuncName("like_escape"),
list_make2($4, $6),
@@ -11264,7 +11280,7 @@ a_expr: c_expr { $$ = $1; }
$1, (Node *) n, @2);
}
- | a_expr SIMILAR TO a_expr %prec SIMILAR
+ | a_expr SIMILAR TO a_expr %prec SIMILAR
{
FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
list_make2($4, makeNullAConst(-1)),
@@ -11272,7 +11288,7 @@ a_expr: c_expr { $$ = $1; }
$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
$1, (Node *) n, @2);
}
- | a_expr SIMILAR TO a_expr ESCAPE a_expr
+ | a_expr SIMILAR TO a_expr ESCAPE a_expr %prec SIMILAR
{
FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
list_make2($4, $6),
@@ -11280,7 +11296,7 @@ a_expr: c_expr { $$ = $1; }
$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
$1, (Node *) n, @2);
}
- | a_expr NOT SIMILAR TO a_expr %prec SIMILAR
+ | a_expr NOT_LA SIMILAR TO a_expr %prec NOT_LA
{
FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
list_make2($5, makeNullAConst(-1)),
@@ -11288,7 +11304,7 @@ a_expr: c_expr { $$ = $1; }
$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~",
$1, (Node *) n, @2);
}
- | a_expr NOT SIMILAR TO a_expr ESCAPE a_expr
+ | a_expr NOT_LA SIMILAR TO a_expr ESCAPE a_expr %prec NOT_LA
{
FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
list_make2($5, $7),
@@ -11420,7 +11436,7 @@ a_expr: c_expr { $$ = $1; }
{
$$ = (Node *) makeSimpleA_Expr(AEXPR_OF, "<>", $1, (Node *) $6, @2);
}
- | a_expr BETWEEN opt_asymmetric b_expr AND b_expr %prec BETWEEN
+ | a_expr BETWEEN opt_asymmetric b_expr AND a_expr %prec BETWEEN
{
$$ = (Node *) makeSimpleA_Expr(AEXPR_BETWEEN,
"BETWEEN",
@@ -11428,7 +11444,7 @@ a_expr: c_expr { $$ = $1; }
(Node *) list_make2($4, $6),
@2);
}
- | a_expr NOT BETWEEN opt_asymmetric b_expr AND b_expr %prec BETWEEN
+ | a_expr NOT_LA BETWEEN opt_asymmetric b_expr AND a_expr %prec NOT_LA
{
$$ = (Node *) makeSimpleA_Expr(AEXPR_NOT_BETWEEN,
"NOT BETWEEN",
@@ -11436,7 +11452,7 @@ a_expr: c_expr { $$ = $1; }
(Node *) list_make2($5, $7),
@2);
}
- | a_expr BETWEEN SYMMETRIC b_expr AND b_expr %prec BETWEEN
+ | a_expr BETWEEN SYMMETRIC b_expr AND a_expr %prec BETWEEN
{
$$ = (Node *) makeSimpleA_Expr(AEXPR_BETWEEN_SYM,
"BETWEEN SYMMETRIC",
@@ -11444,7 +11460,7 @@ a_expr: c_expr { $$ = $1; }
(Node *) list_make2($4, $6),
@2);
}
- | a_expr NOT BETWEEN SYMMETRIC b_expr AND b_expr %prec BETWEEN
+ | a_expr NOT_LA BETWEEN SYMMETRIC b_expr AND a_expr %prec NOT_LA
{
$$ = (Node *) makeSimpleA_Expr(AEXPR_NOT_BETWEEN_SYM,
"NOT BETWEEN SYMMETRIC",
@@ -11472,7 +11488,7 @@ a_expr: c_expr { $$ = $1; }
$$ = (Node *) makeSimpleA_Expr(AEXPR_IN, "=", $1, $3, @2);
}
}
- | a_expr NOT IN_P in_expr
+ | a_expr NOT_LA IN_P in_expr %prec NOT_LA
{
/* in_expr returns a SubLink or a list of a_exprs */
if (IsA($4, SubLink))
@@ -11576,6 +11592,12 @@ b_expr: c_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">", $1, $3, @2); }
| b_expr '=' b_expr
{ $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "=", $1, $3, @2); }
+ | b_expr LESS_EQUALS b_expr
+ { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<=", $1, $3, @2); }
+ | b_expr GREATER_EQUALS b_expr
+ { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, ">=", $1, $3, @2); }
+ | b_expr NOT_EQUALS b_expr
+ { $$ = (Node *) makeSimpleA_Expr(AEXPR_OP, "<>", $1, $3, @2); }
| b_expr qual_Op b_expr %prec Op
{ $$ = (Node *) makeA_Expr(AEXPR_OP, $2, $1, $3, @2); }
| qual_Op b_expr %prec Op
@@ -11647,6 +11669,24 @@ c_expr: columnref { $$ = $1; }
n->indirection = check_indirection($4, yyscanner);
$$ = (Node *)n;
}
+ else if (operator_precedence_warning)
+ {
+ /*
+ * If precedence warnings are enabled, insert
+ * AEXPR_PAREN nodes wrapping all explicitly
+ * parenthesized subexpressions; this prevents bogus
+ * warnings from being issued when the ordering has
+ * been forced by parentheses.
+ *
+ * In principle we should not be relying on a GUC to
+ * decide whether to insert AEXPR_PAREN nodes.
+ * However, since they have no effect except to
+ * suppress warnings, it's probably safe enough; and
+ * we'd just as soon not waste cycles on dummy parse
+ * nodes if we don't have to.
+ */
+ $$ = (Node *) makeA_Expr(AEXPR_PAREN, NIL, $2, NULL, @1);
+ }
else
$$ = $2;
}
@@ -12495,6 +12535,9 @@ MathOp: '+' { $$ = "+"; }
| '<' { $$ = "<"; }
| '>' { $$ = ">"; }
| '=' { $$ = "="; }
+ | LESS_EQUALS { $$ = "<="; }
+ | GREATER_EQUALS { $$ = ">="; }
+ | NOT_EQUALS { $$ = "<>"; }
;
qual_Op: Op
@@ -12517,11 +12560,11 @@ subquery_Op:
{ $$ = $3; }
| LIKE
{ $$ = list_make1(makeString("~~")); }
- | NOT LIKE
+ | NOT_LA LIKE
{ $$ = list_make1(makeString("!~~")); }
| ILIKE
{ $$ = list_make1(makeString("~~*")); }
- | NOT ILIKE
+ | NOT_LA ILIKE
{ $$ = list_make1(makeString("!~~*")); }
/* cannot put SIMILAR TO here, because SIMILAR TO is a hack.
* the regular expression is preprocessed by a function (similar_escape),
diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c
index 130e52b26c4..f759606f88b 100644
--- a/src/backend/parser/parse_expr.c
+++ b/src/backend/parser/parse_expr.c
@@ -37,8 +37,55 @@
#include "utils/xml.h"
+/* GUC parameters */
+bool operator_precedence_warning = false;
bool Transform_null_equals = false;
+/*
+ * Node-type groups for operator precedence warnings
+ * We use zero for everything not otherwise classified
+ */
+#define PREC_GROUP_POSTFIX_IS 1 /* postfix IS tests (NullTest, etc) */
+#define PREC_GROUP_INFIX_IS 2 /* infix IS (IS DISTINCT FROM, etc) */
+#define PREC_GROUP_LESS 3 /* < > */
+#define PREC_GROUP_EQUAL 4 /* = */
+#define PREC_GROUP_LESS_EQUAL 5 /* <= >= <> */
+#define PREC_GROUP_LIKE 6 /* LIKE ILIKE SIMILAR */
+#define PREC_GROUP_BETWEEN 7 /* BETWEEN */
+#define PREC_GROUP_IN 8 /* IN */
+#define PREC_GROUP_NOT_LIKE 9 /* NOT LIKE/ILIKE/SIMILAR */
+#define PREC_GROUP_NOT_BETWEEN 10 /* NOT BETWEEN */
+#define PREC_GROUP_NOT_IN 11 /* NOT IN */
+#define PREC_GROUP_POSTFIX_OP 12 /* generic postfix operators */
+#define PREC_GROUP_INFIX_OP 13 /* generic infix operators */
+#define PREC_GROUP_PREFIX_OP 14 /* generic prefix operators */
+
+/*
+ * Map precedence groupings to old precedence ordering
+ *
+ * Old precedence order:
+ * 1. NOT
+ * 2. =
+ * 3. < >
+ * 4. LIKE ILIKE SIMILAR
+ * 5. BETWEEN
+ * 6. IN
+ * 7. generic postfix Op
+ * 8. generic Op, including <= => <>
+ * 9. generic prefix Op
+ * 10. IS tests (NullTest, BooleanTest, etc)
+ *
+ * NOT BETWEEN etc map to BETWEEN etc when considered as being on the left,
+ * but to NOT when considered as being on the right, because of the buggy
+ * precedence handling of those productions in the old grammar.
+ */
+static const int oldprecedence_l[] = {
+ 0, 10, 10, 3, 2, 8, 4, 5, 6, 4, 5, 6, 7, 8, 9
+};
+static const int oldprecedence_r[] = {
+ 0, 10, 10, 3, 2, 8, 4, 5, 6, 1, 1, 1, 7, 8, 9
+};
+
static Node *transformExprRecurse(ParseState *pstate, Node *expr);
static Node *transformParamRef(ParseState *pstate, ParamRef *pref);
static Node *transformAExprOp(ParseState *pstate, A_Expr *a);
@@ -76,6 +123,11 @@ static Node *make_row_distinct_op(ParseState *pstate, List *opname,
RowExpr *lrow, RowExpr *rrow, int location);
static Expr *make_distinct_op(ParseState *pstate, List *opname,
Node *ltree, Node *rtree, int location);
+static int operator_precedence_group(Node *node, const char **nodename);
+static void emit_precedence_warnings(ParseState *pstate,
+ int opgroup, const char *opname,
+ Node *lchild, Node *rchild,
+ int location);
/*
@@ -194,6 +246,9 @@ transformExprRecurse(ParseState *pstate, Node *expr)
case AEXPR_NOT_BETWEEN_SYM:
result = transformAExprBetween(pstate, a);
break;
+ case AEXPR_PAREN:
+ result = transformExprRecurse(pstate, a->lexpr);
+ break;
default:
elog(ERROR, "unrecognized A_Expr kind: %d", a->kind);
result = NULL; /* keep compiler quiet */
@@ -255,6 +310,11 @@ transformExprRecurse(ParseState *pstate, Node *expr)
{
NullTest *n = (NullTest *) expr;
+ if (operator_precedence_warning)
+ emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_IS, "IS",
+ (Node *) n->arg, NULL,
+ n->location);
+
n->arg = (Expr *) transformExprRecurse(pstate, (Node *) n->arg);
/* the argument can be any type, so don't coerce it */
n->argisrow = type_is_rowtype(exprType((Node *) n->arg));
@@ -782,6 +842,18 @@ transformAExprOp(ParseState *pstate, A_Expr *a)
Node *rexpr = a->rexpr;
Node *result;
+ if (operator_precedence_warning)
+ {
+ int opgroup;
+ const char *opname;
+
+ opgroup = operator_precedence_group((Node *) a, &opname);
+ if (opgroup > 0)
+ emit_precedence_warnings(pstate, opgroup, opname,
+ lexpr, rexpr,
+ a->location);
+ }
+
/*
* Special-case "foo = NULL" and "NULL = foo" for compatibility with
* standards-broken products (like Microsoft's). Turn these into IS NULL
@@ -858,8 +930,17 @@ transformAExprOp(ParseState *pstate, A_Expr *a)
static Node *
transformAExprOpAny(ParseState *pstate, A_Expr *a)
{
- Node *lexpr = transformExprRecurse(pstate, a->lexpr);
- Node *rexpr = transformExprRecurse(pstate, a->rexpr);
+ Node *lexpr = a->lexpr;
+ Node *rexpr = a->rexpr;
+
+ if (operator_precedence_warning)
+ emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_OP,
+ strVal(llast(a->name)),
+ lexpr, NULL,
+ a->location);
+
+ lexpr = transformExprRecurse(pstate, lexpr);
+ rexpr = transformExprRecurse(pstate, rexpr);
return (Node *) make_scalar_array_op(pstate,
a->name,
@@ -872,8 +953,17 @@ transformAExprOpAny(ParseState *pstate, A_Expr *a)
static Node *
transformAExprOpAll(ParseState *pstate, A_Expr *a)
{
- Node *lexpr = transformExprRecurse(pstate, a->lexpr);
- Node *rexpr = transformExprRecurse(pstate, a->rexpr);
+ Node *lexpr = a->lexpr;
+ Node *rexpr = a->rexpr;
+
+ if (operator_precedence_warning)
+ emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_OP,
+ strVal(llast(a->name)),
+ lexpr, NULL,
+ a->location);
+
+ lexpr = transformExprRecurse(pstate, lexpr);
+ rexpr = transformExprRecurse(pstate, rexpr);
return (Node *) make_scalar_array_op(pstate,
a->name,
@@ -886,8 +976,16 @@ transformAExprOpAll(ParseState *pstate, A_Expr *a)
static Node *
transformAExprDistinct(ParseState *pstate, A_Expr *a)
{
- Node *lexpr = transformExprRecurse(pstate, a->lexpr);
- Node *rexpr = transformExprRecurse(pstate, a->rexpr);
+ Node *lexpr = a->lexpr;
+ Node *rexpr = a->rexpr;
+
+ if (operator_precedence_warning)
+ emit_precedence_warnings(pstate, PREC_GROUP_INFIX_IS, "IS",
+ lexpr, rexpr,
+ a->location);
+
+ lexpr = transformExprRecurse(pstate, lexpr);
+ rexpr = transformExprRecurse(pstate, rexpr);
if (lexpr && IsA(lexpr, RowExpr) &&
rexpr && IsA(rexpr, RowExpr))
@@ -944,20 +1042,27 @@ transformAExprNullIf(ParseState *pstate, A_Expr *a)
return (Node *) result;
}
+/*
+ * Checking an expression for match to a list of type names. Will result
+ * in a boolean constant node.
+ */
static Node *
transformAExprOf(ParseState *pstate, A_Expr *a)
{
- /*
- * Checking an expression for match to a list of type names. Will result
- * in a boolean constant node.
- */
- Node *lexpr = transformExprRecurse(pstate, a->lexpr);
+ Node *lexpr = a->lexpr;
Const *result;
ListCell *telem;
Oid ltype,
rtype;
bool matched = false;
+ if (operator_precedence_warning)
+ emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_IS, "IS",
+ lexpr, NULL,
+ a->location);
+
+ lexpr = transformExprRecurse(pstate, lexpr);
+
ltype = exprType(lexpr);
foreach(telem, (List *) a->rexpr)
{
@@ -1001,6 +1106,13 @@ transformAExprIn(ParseState *pstate, A_Expr *a)
else
useOr = true;
+ if (operator_precedence_warning)
+ emit_precedence_warnings(pstate,
+ useOr ? PREC_GROUP_IN : PREC_GROUP_NOT_IN,
+ "IN",
+ a->lexpr, NULL,
+ a->location);
+
/*
* We try to generate a ScalarArrayOpExpr from IN/NOT IN, but this is only
* possible if there is a suitable array type available. If not, we fall
@@ -1153,6 +1265,22 @@ transformAExprBetween(ParseState *pstate, A_Expr *a)
bexpr = (Node *) linitial(args);
cexpr = (Node *) lsecond(args);
+ if (operator_precedence_warning)
+ {
+ int opgroup;
+ const char *opname;
+
+ opgroup = operator_precedence_group((Node *) a, &opname);
+ emit_precedence_warnings(pstate, opgroup, opname,
+ aexpr, cexpr,
+ a->location);
+ /* We can ignore bexpr thanks to syntactic restrictions */
+ /* Wrap subexpressions to prevent extra warnings */
+ aexpr = (Node *) makeA_Expr(AEXPR_PAREN, NIL, aexpr, NULL, -1);
+ bexpr = (Node *) makeA_Expr(AEXPR_PAREN, NIL, bexpr, NULL, -1);
+ cexpr = (Node *) makeA_Expr(AEXPR_PAREN, NIL, cexpr, NULL, -1);
+ }
+
/*
* Build the equivalent comparison expression. Make copies of
* multiply-referenced subexpressions for safety. (XXX this is really
@@ -1657,6 +1785,19 @@ transformSubLink(ParseState *pstate, SubLink *sublink)
List *right_list;
ListCell *l;
+ if (operator_precedence_warning)
+ {
+ if (sublink->operName == NIL)
+ emit_precedence_warnings(pstate, PREC_GROUP_IN, "IN",
+ sublink->testexpr, NULL,
+ sublink->location);
+ else
+ emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_OP,
+ strVal(llast(sublink->operName)),
+ sublink->testexpr, NULL,
+ sublink->location);
+ }
+
/*
* If the source was "x IN (select)", convert to "x = ANY (select)".
*/
@@ -2000,6 +2141,11 @@ transformXmlExpr(ParseState *pstate, XmlExpr *x)
ListCell *lc;
int i;
+ if (operator_precedence_warning && x->op == IS_DOCUMENT)
+ emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_IS, "IS",
+ (Node *) linitial(x->args), NULL,
+ x->location);
+
newx = makeNode(XmlExpr);
newx->op = x->op;
if (x->name)
@@ -2172,6 +2318,11 @@ transformBooleanTest(ParseState *pstate, BooleanTest *b)
{
const char *clausename;
+ if (operator_precedence_warning)
+ emit_precedence_warnings(pstate, PREC_GROUP_POSTFIX_IS, "IS",
+ (Node *) b->arg, NULL,
+ b->location);
+
switch (b->booltesttype)
{
case IS_TRUE:
@@ -2689,6 +2840,309 @@ make_distinct_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
}
/*
+ * 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 3724330dc84..2d85cf08e70 100644
--- a/src/backend/parser/parse_target.c
+++ b/src/backend/parser/parse_target.c
@@ -1654,12 +1654,17 @@ FigureColnameInternal(Node *node, char **name)
*name = strVal(llast(((FuncCall *) node)->funcname));
return 2;
case T_A_Expr:
- /* make nullif() act like a regular function */
if (((A_Expr *) node)->kind == AEXPR_NULLIF)
{
+ /* make nullif() act like a regular function */
*name = "nullif";
return 2;
}
+ if (((A_Expr *) node)->kind == AEXPR_PAREN)
+ {
+ /* look through dummy parenthesis node */
+ return FigureColnameInternal(((A_Expr *) node)->lexpr, name);
+ }
break;
case T_TypeCast:
strength = FigureColnameInternal(((TypeCast *) node)->arg,
diff --git a/src/backend/parser/parser.c b/src/backend/parser/parser.c
index b17771d4cca..fdf5a6a1caf 100644
--- a/src/backend/parser/parser.c
+++ b/src/backend/parser/parser.c
@@ -107,6 +107,9 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
*/
switch (cur_token)
{
+ case NOT:
+ cur_token_length = 3;
+ break;
case NULLS_P:
cur_token_length = 5;
break;
@@ -151,6 +154,20 @@ base_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, core_yyscan_t yyscanner)
/* Replace cur_token if needed, based on lookahead */
switch (cur_token)
{
+ case NOT:
+ /* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
+ switch (next_token)
+ {
+ case BETWEEN:
+ case IN_P:
+ case LIKE:
+ case ILIKE:
+ case SIMILAR:
+ cur_token = NOT_LA;
+ break;
+ }
+ break;
+
case NULLS_P:
/* Replace NULLS_P by NULLS_LA if it's followed by FIRST or LAST */
switch (next_token)
diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l
index a0e20434ef3..82b20c6e5f6 100644
--- a/src/backend/parser/scan.l
+++ b/src/backend/parser/scan.l
@@ -331,10 +331,15 @@ ident_cont [A-Za-z\200-\377_0-9\$]
identifier {ident_start}{ident_cont}*
+/* Assorted special-case operators and operator-like tokens */
typecast "::"
dot_dot \.\.
colon_equals ":="
equals_greater "=>"
+less_equals "<="
+greater_equals ">="
+less_greater "<>"
+not_equals "!="
/*
* "self" is the set of chars that should be returned as single-character
@@ -814,6 +819,28 @@ other .
return EQUALS_GREATER;
}
+{less_equals} {
+ SET_YYLLOC();
+ return LESS_EQUALS;
+ }
+
+{greater_equals} {
+ SET_YYLLOC();
+ return GREATER_EQUALS;
+ }
+
+{less_greater} {
+ /* We accept both "<>" and "!=" as meaning NOT_EQUALS */
+ SET_YYLLOC();
+ return NOT_EQUALS;
+ }
+
+{not_equals} {
+ /* We accept both "<>" and "!=" as meaning NOT_EQUALS */
+ SET_YYLLOC();
+ return NOT_EQUALS;
+ }
+
{self} {
SET_YYLLOC();
return yytext[0];
@@ -891,11 +918,7 @@ other .
if (nchars >= NAMEDATALEN)
yyerror("operator too long");
- /* Convert "!=" operator to "<>" for compatibility */
- if (strcmp(yytext, "!=") == 0)
- yylval->str = pstrdup("<>");
- else
- yylval->str = pstrdup(yytext);
+ yylval->str = pstrdup(yytext);
return Op;
}
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 6eaab4352aa..7196b0b2157 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -1600,6 +1600,16 @@ static struct config_bool ConfigureNamesBool[] =
},
{
+ {"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 7590a6f056d..d7a61f1fc4b 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -586,6 +586,7 @@
#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 2b2dec95be7..bb134a42d8a 100644
--- a/src/bin/psql/psqlscan.l
+++ b/src/bin/psql/psqlscan.l
@@ -355,10 +355,15 @@ ident_cont [A-Za-z\200-\377_0-9\$]
identifier {ident_start}{ident_cont}*
+/* Assorted special-case operators and operator-like tokens */
typecast "::"
dot_dot \.\.
colon_equals ":="
equals_greater "=>"
+less_equals "<="
+greater_equals ">="
+less_greater "<>"
+not_equals "!="
/*
* "self" is the set of chars that should be returned as single-character
@@ -674,6 +679,22 @@ other .
ECHO;
}
+{less_equals} {
+ ECHO;
+ }
+
+{greater_equals} {
+ ECHO;
+ }
+
+{less_greater} {
+ ECHO;
+ }
+
+{not_equals} {
+ ECHO;
+ }
+
/*
* These rules are specific to psql --- they implement parenthesis
* counting and detection of command-ending semicolon. These must
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
index 497559df588..c226b039cf1 100644
--- a/src/include/nodes/parsenodes.h
+++ b/src/include/nodes/parsenodes.h
@@ -239,7 +239,8 @@ typedef enum A_Expr_Kind
AEXPR_BETWEEN, /* name must be "BETWEEN" */
AEXPR_NOT_BETWEEN, /* name must be "NOT BETWEEN" */
AEXPR_BETWEEN_SYM, /* name must be "BETWEEN SYMMETRIC" */
- AEXPR_NOT_BETWEEN_SYM /* name must be "NOT BETWEEN SYMMETRIC" */
+ AEXPR_NOT_BETWEEN_SYM, /* name must be "NOT BETWEEN SYMMETRIC" */
+ AEXPR_PAREN /* nameless dummy node for parentheses */
} A_Expr_Kind;
typedef struct A_Expr
diff --git a/src/include/parser/parse_expr.h b/src/include/parser/parse_expr.h
index 66391dfc74d..fbc3f17c688 100644
--- a/src/include/parser/parse_expr.h
+++ b/src/include/parser/parse_expr.h
@@ -16,6 +16,7 @@
#include "parser/parse_node.h"
/* GUC parameters */
+extern bool operator_precedence_warning;
extern bool Transform_null_equals;
extern Node *transformExpr(ParseState *pstate, Node *expr, ParseExprKind exprKind);
diff --git a/src/include/parser/scanner.h b/src/include/parser/scanner.h
index 0e22d031f14..f941977865a 100644
--- a/src/include/parser/scanner.h
+++ b/src/include/parser/scanner.h
@@ -51,6 +51,7 @@ typedef union core_YYSTYPE
* %token <str> IDENT FCONST SCONST BCONST XCONST Op
* %token <ival> ICONST PARAM
* %token TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
+ * %token LESS_EQUALS GREATER_EQUALS NOT_EQUALS
* The above token definitions *must* be the first ones declared in any
* bison parser built atop this scanner, so that they will have consistent
* numbers assigned to them (specifically, IDENT = 258 and so on).
diff --git a/src/interfaces/ecpg/preproc/parse.pl b/src/interfaces/ecpg/preproc/parse.pl
index 7ae7acc6130..588bb63e53f 100644
--- a/src/interfaces/ecpg/preproc/parse.pl
+++ b/src/interfaces/ecpg/preproc/parse.pl
@@ -42,12 +42,17 @@ my %replace_token = (
# or in the block
my %replace_string = (
+ 'NOT_LA' => 'not',
'NULLS_LA' => 'nulls',
'WITH_LA' => 'with',
'TYPECAST' => '::',
'DOT_DOT' => '..',
'COLON_EQUALS' => ':=',
- 'EQUALS_GREATER' => '=>',);
+ 'EQUALS_GREATER' => '=>',
+ 'LESS_EQUALS' => '<=',
+ 'GREATER_EQUALS' => '>=',
+ 'NOT_EQUALS' => '<>',
+);
# specific replace_types for specific non-terminals - never include the ':'
# ECPG-only replace_types are defined in ecpg-replace_types
diff --git a/src/interfaces/ecpg/preproc/parser.c b/src/interfaces/ecpg/preproc/parser.c
index 099a213d118..662a90a3f6b 100644
--- a/src/interfaces/ecpg/preproc/parser.c
+++ b/src/interfaces/ecpg/preproc/parser.c
@@ -75,6 +75,9 @@ filtered_base_yylex(void)
*/
switch (cur_token)
{
+ case NOT:
+ cur_token_length = 3;
+ break;
case NULLS_P:
cur_token_length = 5;
break;
@@ -119,6 +122,20 @@ filtered_base_yylex(void)
/* Replace cur_token if needed, based on lookahead */
switch (cur_token)
{
+ case NOT:
+ /* Replace NOT by NOT_LA if it's followed by BETWEEN, IN, etc */
+ switch (next_token)
+ {
+ case BETWEEN:
+ case IN_P:
+ case LIKE:
+ case ILIKE:
+ case SIMILAR:
+ cur_token = NOT_LA;
+ break;
+ }
+ break;
+
case NULLS_P:
/* Replace NULLS_P by NULLS_LA if it's followed by FIRST or LAST */
switch (next_token)
diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l
index a8cc3d877c0..c70f2986962 100644
--- a/src/interfaces/ecpg/preproc/pgc.l
+++ b/src/interfaces/ecpg/preproc/pgc.l
@@ -233,10 +233,16 @@ ident_cont [A-Za-z\200-\377_0-9\$]
identifier {ident_start}{ident_cont}*
array ({ident_cont}|{whitespace}|[\[\]\+\-\*\%\/\(\)\>\.])*
+
+/* Assorted special-case operators and operator-like tokens */
typecast "::"
dot_dot \.\.
colon_equals ":="
equals_greater "=>"
+less_equals "<="
+greater_equals ">="
+less_greater "<>"
+not_equals "!="
/*
* "self" is the set of chars that should be returned as single-character
@@ -622,6 +628,10 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})(.*\\{space})*.
<SQL>{dot_dot} { return DOT_DOT; }
<SQL>{colon_equals} { return COLON_EQUALS; }
<SQL>{equals_greater} { return EQUALS_GREATER; }
+<SQL>{less_equals} { return LESS_EQUALS; }
+<SQL>{greater_equals} { return GREATER_EQUALS; }
+<SQL>{less_greater} { return NOT_EQUALS; }
+<SQL>{not_equals} { return NOT_EQUALS; }
<SQL>{informix_special} {
/* are we simulating Informix? */
if (INFORMIX_MODE)
@@ -701,11 +711,7 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})(.*\\{space})*.
return yytext[0];
}
- /* Convert "!=" operator to "<>" for compatibility */
- if (strcmp(yytext, "!=") == 0)
- yylval.str = mm_strdup("<>");
- else
- yylval.str = mm_strdup(yytext);
+ yylval.str = mm_strdup(yytext);
return Op;
}
<SQL>{param} {
diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y
index a1758e03c3f..46217fd64bd 100644
--- a/src/pl/plpgsql/src/pl_gram.y
+++ b/src/pl/plpgsql/src/pl_gram.y
@@ -227,6 +227,7 @@ static void check_raise_parameters(PLpgSQL_stmt_raise *stmt);
%token <str> IDENT FCONST SCONST BCONST XCONST Op
%token <ival> ICONST PARAM
%token TYPECAST DOT_DOT COLON_EQUALS EQUALS_GREATER
+%token LESS_EQUALS GREATER_EQUALS NOT_EQUALS
/*
* Other tokens recognized by plpgsql's lexer interface layer (pl_scanner.c).