From c6b3c939b7e0f1d35f4ed4996e71420a993810d2 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Wed, 11 Mar 2015 13:22:52 -0400 Subject: [PATCH] Make operator precedence follow the SQL standard more closely. While the SQL standard is pretty vague on the overall topic of operator precedence (because it never presents a unified BNF for all expressions), it does seem reasonable to conclude from the spec for that OR has the lowest precedence, then AND, then NOT, then IS tests, then the six standard comparison operators, then everything else (since any non-boolean operator in a WHERE clause would need to be an argument of one of these). We were only sort of on board with that: most notably, while "<" ">" and "=" had properly low precedence, "<=" ">=" and "<>" were treated as generic operators and so had significantly higher precedence. And "IS" tests were even higher precedence than those, which is very clearly wrong per spec. Another problem was that "foo NOT SOMETHING bar" constructs, such as "x NOT LIKE y", were treated inconsistently because of a bison implementation artifact: they had the documented precedence with respect to operators to their right, but behaved like NOT (i.e., very low priority) with respect to operators to their left. Fixing the precedence issues is just a small matter of rearranging the precedence declarations in gram.y, except for the NOT problem, which requires adding an additional lookahead case in base_yylex() so that we can attach a different token precedence to NOT LIKE and allied two-word operators. The bulk of this patch is not the bug fix per se, but adding logic to parse_expr.c to allow giving warnings if an expression has changed meaning because of these precedence changes. These warnings are off by default and are enabled by the new GUC operator_precedence_warning. It's believed that very few applications will be affected by these changes, but it was agreed that a warning mechanism is essential to help debug any that are. --- doc/src/sgml/config.sgml | 23 + doc/src/sgml/syntax.sgml | 84 ++-- src/backend/nodes/outfuncs.c | 3 + src/backend/parser/gram.y | 97 +++- src/backend/parser/parse_expr.c | 476 +++++++++++++++++- src/backend/parser/parse_target.c | 7 +- src/backend/parser/parser.c | 17 + src/backend/parser/scan.l | 33 +- src/backend/utils/misc/guc.c | 10 + src/backend/utils/misc/postgresql.conf.sample | 1 + src/bin/psql/psqlscan.l | 21 + src/include/nodes/parsenodes.h | 3 +- src/include/parser/parse_expr.h | 1 + src/include/parser/scanner.h | 1 + src/interfaces/ecpg/preproc/parse.pl | 7 +- src/interfaces/ecpg/preproc/parser.c | 17 + src/interfaces/ecpg/preproc/pgc.l | 16 +- src/pl/plpgsql/src/pl_gram.y | 1 + 18 files changed, 723 insertions(+), 95 deletions(-) diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 07214bfd76..d0f43c64af 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -6816,6 +6816,29 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir' + + operator_precedence_warning (boolean) + + operator_precedence_warning configuration parameter + + + + + When on, the parser will emit a warning for any construct that might + have changed meanings since 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 off. + + + + See for more information. + + + + quote_all_identifiers (boolean) diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index 8283e25258..ff2c3e2b9a 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -984,10 +984,11 @@ CAST ( 'string' AS type ) associativity of the operators in 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 < and - > have a different precedence than the Boolean - operators <= and >=. Also, you will + into the parser. + + + + You will sometimes need to add parentheses when using combinations of binary and unary operators. For instance: @@ -1008,7 +1009,7 @@ SELECT (5 !) - 6; - Operator Precedence (decreasing) + Operator Precedence (highest to lowest) @@ -1063,41 +1064,11 @@ SELECT (5 !) - 6; - IS - - IS TRUE, IS FALSE, IS NULL, etc - - - - ISNULL - - test for null - - - - NOTNULL - - test for not null - - - - (any other) + (any other operator) left all other native and user-defined operators - - IN - - set membership - - - - BETWEEN - - range containment - - OVERLAPS @@ -1105,21 +1076,23 @@ SELECT (5 !) - 6; - LIKE ILIKE SIMILAR + BETWEEN IN LIKE ILIKE SIMILAR - string pattern matching + range containment, set membership, string matching - < > + < > = <= >= <> + - less than, greater than + comparison operators - = - right - equality, assignment + IS ISNULL NOTNULL + + IS TRUE, IS FALSE, IS + NULL, IS DISTINCT FROM, etc @@ -1159,9 +1132,32 @@ SELECT (5 !) - 6; SELECT 3 OPERATOR(pg_catalog.+) 4; the OPERATOR construct is taken to have the default precedence - shown in for any other operator. This is true no matter + shown in for + any other operator. This is true no matter which specific operator appears inside OPERATOR(). + + + + PostgreSQL versions before 9.5 used slightly different + operator precedence rules. In particular, <= + >= and <> used to be treated as + generic operators; IS tests used to have higher priority; + and NOT BETWEEN and related constructs acted inconsistently, + being taken in some cases as having the precedence of NOT + rather than 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 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 turned on + to see if any warnings are logged. + + diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 775f482abd..03f8adaae3 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 21b8897038..cf0d31744e 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 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 @@ -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 130e52b26c..f759606f88 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: @@ -2688,6 +2839,309 @@ make_distinct_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree, 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. * diff --git a/src/backend/parser/parse_target.c b/src/backend/parser/parse_target.c index 3724330dc8..2d85cf08e7 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 b17771d4cc..fdf5a6a1ca 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 a0e20434ef..82b20c6e5f 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 6eaab4352a..7196b0b215 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -1599,6 +1599,16 @@ static struct config_bool ConfigureNamesBool[] = 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."), diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 7590a6f056..d7a61f1fc4 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 2b2dec95be..bb134a42d8 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 497559df58..c226b039cf 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 66391dfc74..fbc3f17c68 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 0e22d031f1..f941977865 100644 --- a/src/include/parser/scanner.h +++ b/src/include/parser/scanner.h @@ -51,6 +51,7 @@ typedef union core_YYSTYPE * %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 * 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 7ae7acc613..588bb63e53 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 099a213d11..662a90a3f6 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 a8cc3d877c..c70f298696 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})*. {dot_dot} { return DOT_DOT; } {colon_equals} { return COLON_EQUALS; } {equals_greater} { return EQUALS_GREATER; } +{less_equals} { return LESS_EQUALS; } +{greater_equals} { return GREATER_EQUALS; } +{less_greater} { return NOT_EQUALS; } +{not_equals} { return NOT_EQUALS; } {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; } {param} { diff --git a/src/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index a1758e03c3..46217fd64b 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 IDENT FCONST SCONST BCONST XCONST Op %token 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). -- 2.40.0