diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 07214bfd76a77e2889e0f4d92fbdecc68423c828..d0f43c64af3e7430e28c4c236d0a0a8c1c5c23eb 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' </listitem> </varlistentry> + <varlistentry id="guc-operator-precedence-warning" xreflabel="operator_precedence_warning"> + <term><varname>operator_precedence_warning</varname> (<type>boolean</type>) + <indexterm> + <primary><varname>operator_precedence_warning</> configuration parameter</primary> + </indexterm> + </term> + <listitem> + <para> + When on, the parser will emit a warning for any construct that might + have changed meanings since <productname>PostgreSQL</> 9.4 as a result + of changes in operator precedence. This is useful for auditing + applications to see if precedence changes have broken anything; but it + is not meant to be kept turned on in production, since it will warn + about some perfectly valid, standard-compliant SQL code. + The default is <literal>off</>. + </para> + + <para> + See <xref linkend="sql-precedence"> for more information. + </para> + </listitem> + </varlistentry> + <varlistentry id="guc-quote-all-identifiers" xreflabel="quote-all-identifiers"> <term><varname>quote_all_identifiers</varname> (<type>boolean</type>) <indexterm> diff --git a/doc/src/sgml/syntax.sgml b/doc/src/sgml/syntax.sgml index 8283e252582b39aa8708fb1cf3c63dacef3a4ef6..ff2c3e2b9a371bab67594b89ac3d462503551627 100644 --- a/doc/src/sgml/syntax.sgml +++ b/doc/src/sgml/syntax.sgml @@ -984,10 +984,11 @@ CAST ( '<replaceable>string</replaceable>' AS <replaceable>type</replaceable> ) associativity of the operators in <productname>PostgreSQL</>. Most operators have the same precedence and are left-associative. The precedence and associativity of the operators is hard-wired - into the parser. This can lead to non-intuitive behavior; for - example the Boolean operators <literal><</> and - <literal>></> have a different precedence than the Boolean - operators <literal><=</> and <literal>>=</>. Also, you will + into the parser. + </para> + + <para> + You will sometimes need to add parentheses when using combinations of binary and unary operators. For instance: <programlisting> @@ -1008,7 +1009,7 @@ SELECT (5 !) - 6; </para> <table id="sql-precedence-table"> - <title>Operator Precedence (decreasing)</title> + <title>Operator Precedence (highest to lowest)</title> <tgroup cols="3"> <thead> @@ -1063,41 +1064,11 @@ SELECT (5 !) - 6; </row> <row> - <entry><token>IS</token></entry> - <entry></entry> - <entry><literal>IS TRUE</>, <literal>IS FALSE</>, <literal>IS NULL</>, etc</entry> - </row> - - <row> - <entry><token>ISNULL</token></entry> - <entry></entry> - <entry>test for null</entry> - </row> - - <row> - <entry><token>NOTNULL</token></entry> - <entry></entry> - <entry>test for not null</entry> - </row> - - <row> - <entry>(any other)</entry> + <entry>(any other operator)</entry> <entry>left</entry> <entry>all other native and user-defined operators</entry> </row> - <row> - <entry><token>IN</token></entry> - <entry></entry> - <entry>set membership</entry> - </row> - - <row> - <entry><token>BETWEEN</token></entry> - <entry></entry> - <entry>range containment</entry> - </row> - <row> <entry><token>OVERLAPS</token></entry> <entry></entry> @@ -1105,21 +1076,23 @@ SELECT (5 !) - 6; </row> <row> - <entry><token>LIKE</token> <token>ILIKE</token> <token>SIMILAR</token></entry> + <entry><token>BETWEEN</token> <token>IN</token> <token>LIKE</token> <token>ILIKE</token> <token>SIMILAR</token></entry> <entry></entry> - <entry>string pattern matching</entry> + <entry>range containment, set membership, string matching</entry> </row> <row> - <entry><token><</token> <token>></token></entry> + <entry><token><</token> <token>></token> <token>=</token> <token><=</token> <token>>=</token> <token><></token> +</entry> <entry></entry> - <entry>less than, greater than</entry> + <entry>comparison operators</entry> </row> <row> - <entry><token>=</token></entry> - <entry>right</entry> - <entry>equality, assignment</entry> + <entry><token>IS</token> <token>ISNULL</token> <token>NOTNULL</token></entry> + <entry></entry> + <entry><literal>IS TRUE</>, <literal>IS FALSE</>, <literal>IS + NULL</>, <literal>IS DISTINCT FROM</>, etc</entry> </row> <row> @@ -1159,9 +1132,32 @@ SELECT (5 !) - 6; SELECT 3 OPERATOR(pg_catalog.+) 4; </programlisting> the <literal>OPERATOR</> construct is taken to have the default precedence - shown in <xref linkend="sql-precedence-table"> for <quote>any other</> operator. This is true no matter + shown in <xref linkend="sql-precedence-table"> for + <quote>any other operator</>. This is true no matter which specific operator appears inside <literal>OPERATOR()</>. </para> + + <note> + <para> + <productname>PostgreSQL</> versions before 9.5 used slightly different + operator precedence rules. In particular, <token><=</token> + <token>>=</token> and <token><></token> used to be treated as + generic operators; <literal>IS</> tests used to have higher priority; + and <literal>NOT BETWEEN</> and related constructs acted inconsistently, + being taken in some cases as having the precedence of <literal>NOT</> + rather than <literal>BETWEEN</>. These rules were changed for better + compliance with the SQL standard and to reduce confusion from + inconsistent treatment of logically equivalent constructs. In most + cases, these changes will result in no behavioral change, or perhaps + in <quote>no such operator</> failures which can be resolved by adding + parentheses. However there are corner cases in which a query might + change behavior without any parsing error being reported. If you are + concerned about whether these changes have silently broken something, + you can test your application with the configuration + parameter <xref linkend="guc-operator-precedence-warning"> turned on + to see if any warnings are logged. + </para> + </note> </sect2> </sect1> diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c index 775f482abda73baad1d98dedf3febc8f765bdf13..03f8adaae3ee010a508beb6a5c91cb981ba8cb67 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 21b8897038f2602d09bce5f9e5df175f82a3c985..cf0d31744e15a571a0082cb7972ea59715d4c860 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 130e52b26c4f20fe909811baa70d5a854f12792c..f759606f88bc9c53a5527c60cdcb3d75fbc5cc10 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 3724330dc842561a1162a10c9f365ee99120b5ac..2d85cf08e706b374fcd835347d162fd41bc809f5 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 b17771d4cca2ee532e6b519dab7ba92324b9e6ea..fdf5a6a1cafc6f5559a9ce2438cbc45a36ec0e60 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 a0e20434ef392c1fd68b3926c1eb42186e8854a4..82b20c6e5f695054acbebf7e6f40a169fd7c5597 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 6eaab4352aa53febf7b813bdc12af2c5b1b08951..7196b0b2157b2bcf8de50e5e1196effd94bf46de 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 7590a6f056d9ae7b76266925ec313a4e108fe731..d7a61f1fc4b8da58e424cd9baa0838f93e7ba0cc 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 2b2dec95be72a7222230e37f7b2ee14900a76bd9..bb134a42d8a983e40a6a2e929703c6aba90aea71 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 497559df5887982b12386d37881ed3b6045e0bff..c226b039cf1c023ebec09d87e083833b13b80726 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 66391dfc74d1eb58c653f09e2324e763932a51a3..fbc3f17c688e93ce1bc467d59566cf49f4a0388f 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 0e22d031f140d6a7472d33c8b03a17b02de82423..f941977865ae4c9bef86037427915cc19198f2c9 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 7ae7acc6130193604a4794ff3f4b017c70f7f60a..588bb63e53ff1f914cee5658d0963b7d494c3471 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 099a213d11872f01796d1f83621996c2dc2f4ea6..662a90a3f6bd311b3b8fe996e12401f4dc40b58b 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 a8cc3d877c091f33bc14624a3ad7190f4b59692b..c70f2986962d6323b8ae197e8a21f1985649cacc 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 a1758e03c3f0398dcae8d7e38ddc4bef9c6e234f..46217fd64bd7a2c109d7190152e53b69ad90b2b5 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).