Re: Precedence of standard comparison operators - Mailing list pgsql-hackers
From | Tom Lane |
---|---|
Subject | Re: Precedence of standard comparison operators |
Date | |
Msg-id | 22908.1424652813@sss.pgh.pa.us Whole thread Raw |
In response to | Re: Precedence of standard comparison operators (Tom Lane <tgl@sss.pgh.pa.us>) |
Responses |
Re: Precedence of standard comparison operators
|
List | pgsql-hackers |
Attached is an improved patch that includes optional warnings for constructs that changed parsing. It's not quite 100% but I think it's about 90% correct; the difference in size between this and the previous patch should be a pretty fair indication of what it's going to cost us to have a warning capability. What's missing from this version is that it can't tell the difference between LIKE/ILIKE/SIMILAR TO and the underlying operators, that is, it sees "a LIKE b" as "a ~~ b" because that's what the grammar emits. However, those inputs have different operator-precedence behavior. Likewise, we can't tell the difference between xmlexpr IS NOT DOCUMENT NOT (xmlexpr IS DOCUMENT) because the grammar converts the former into the latter --- and again, those two things have different precedence behavior. It wouldn't take very much additional code to fix these things by changing what the grammar emits; but I'm running out of energy for today. In any case, I thought I should put this up and see if this general approach is going to satisfy people's concerns about making such a change. regards, tom lane diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 6bcb106..22a2d32 100644 *** a/doc/src/sgml/config.sgml --- b/doc/src/sgml/config.sgml *************** dynamic_library_path = 'C:\tools\postgre *** 6752,6757 **** --- 6752,6780 ---- </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 left 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 4b81b08..e7484c4 100644 *** a/doc/src/sgml/syntax.sgml --- b/doc/src/sgml/syntax.sgml *************** CAST ( '<replaceable>string</replaceable *** 984,993 **** 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 sometimes need to add parentheses when using combinations of binary and unary operators. For instance: <programlisting> --- 984,994 ---- 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. ! </para> ! ! <para> ! You will sometimes need to add parentheses when using combinations of binary and unary operators. For instance: <programlisting> *************** SELECT (5 !) - 6; *** 1008,1014 **** </para> <table id="sql-precedence-table"> ! <title>Operator Precedence (decreasing)</title> <tgroup cols="3"> <thead> --- 1009,1015 ---- </para> <table id="sql-precedence-table"> ! <title>Operator Precedence (highest to lowest)</title> <tgroup cols="3"> <thead> *************** SELECT (5 !) - 6; *** 1063,1087 **** </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>left</entry> <entry>all other native and user-defined operators</entry> </row> --- 1064,1070 ---- </row> <row> ! <entry>(any other operator)</entry> <entry>left</entry> <entry>all other native and user-defined operators</entry> </row> *************** SELECT (5 !) - 6; *** 1111,1125 **** </row> <row> ! <entry><token><</token> <token>></token></entry> <entry></entry> ! <entry>less than, greater than</entry> </row> <row> ! <entry><token>=</token></entry> ! <entry>right</entry> ! <entry>equality, assignment</entry> </row> <row> --- 1094,1110 ---- </row> <row> ! <entry><token><</token> <token>></token> <token>=</token> <token><=</token> <token>>=</token> <token><></token> ! </entry> <entry></entry> ! <entry>comparison operators</entry> </row> <row> ! <entry><token>IS</token> <token>ISNULL</token> <token>NOTNULL</token></entry> ! <entry></entry> ! <entry><literal>IS TRUE</>, <literal>IS FALSE</>, <literal>IS ! NULL</>, <literal>IS DISTINCT FROM</>, etc</entry> </row> <row> *************** SELECT (5 !) - 6; *** 1159,1167 **** 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 which specific operator appears inside <literal>OPERATOR()</>. </para> </sect2> </sect1> --- 1144,1172 ---- 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 which specific operator appears inside <literal>OPERATOR()</>. </para> + + <note> + <para> + <productname>PostgreSQL</> versions prior to 9.5 used a slightly + different operator precedence rule; in particular, <token><=</token> + <token>>=</token> and <token><></token> used to be treated as + generic operators, and <literal>IS</> tests used to have higher priority. + This was changed for better compliance with the SQL standard, and because + treating these operators differently from the other comparison operators + <token><</token> <token>></token> and <token>=</token> was + confusing. In most cases, this change will result in no behavioral + change or obvious <quote>no such operator</> failures; however there are + corner cases in which a query might change behavior without any parsing + error being reported. If you are concerned about whether this change has + 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 98aa5f0..a6d0c5e 100644 *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** _outAExpr(StringInfo str, const A_Expr * *** 2532,2537 **** --- 2532,2540 ---- 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 76b0aff..3c28349 100644 *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 58,63 **** --- 58,64 ---- #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" *************** static Node *makeRecursiveViewSelect(cha *** 532,537 **** --- 533,539 ---- %token <str> IDENT FCONST SCONST BCONST XCONST Op %token <ival> ICONST PARAM %token TYPECAST DOT_DOT COLON_EQUALS + %token LESS_EQUALS GREATER_EQUALS NOT_EQUALS /* * If you want to make any keyword changes, update the keyword table in *************** static Node *makeRecursiveViewSelect(cha *** 645,652 **** %left OR %left AND %right NOT ! %right '=' ! %nonassoc '<' '>' %nonassoc LIKE ILIKE SIMILAR %nonassoc ESCAPE %nonassoc OVERLAPS --- 647,654 ---- %left OR %left AND %right NOT ! %nonassoc IS ISNULL NOTNULL /* IS sets precedence for IS NULL, etc */ ! %nonassoc '<' '>' '=' LESS_EQUALS GREATER_EQUALS NOT_EQUALS %nonassoc LIKE ILIKE SIMILAR %nonassoc ESCAPE %nonassoc OVERLAPS *************** static Node *makeRecursiveViewSelect(cha *** 676,684 **** %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 '^' --- 678,683 ---- *************** a_expr: c_expr { $$ = $1; } *** 11204,11209 **** --- 11203,11214 ---- { $$ = (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); } *************** b_expr: c_expr *** 11564,11569 **** --- 11569,11580 ---- { $$ = (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 *************** c_expr: columnref { $$ = $1; } *** 11635,11640 **** --- 11646,11669 ---- 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; } *************** MathOp: '+' { $$ = "+"; } *** 12483,12488 **** --- 12512,12520 ---- | '<' { $$ = "<"; } | '>' { $$ = ">"; } | '=' { $$ = "="; } + | LESS_EQUALS { $$ = "<="; } + | GREATER_EQUALS { $$ = ">="; } + | NOT_EQUALS { $$ = "<>"; } ; qual_Op: Op diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index f314745..de19502 100644 *** a/src/backend/parser/parse_expr.c --- b/src/backend/parser/parse_expr.c *************** *** 37,42 **** --- 37,44 ---- #include "utils/xml.h" + /* GUC parameters */ + bool operator_precedence_warning = false; bool Transform_null_equals = false; static Node *transformExprRecurse(ParseState *pstate, Node *expr); *************** static Node *make_row_distinct_op(ParseS *** 76,81 **** --- 78,88 ---- 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); /* *************** transformExprRecurse(ParseState *pstate, *** 188,193 **** --- 195,203 ---- 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 */ *************** transformExprRecurse(ParseState *pstate, *** 249,254 **** --- 259,269 ---- { NullTest *n = (NullTest *) expr; + if (operator_precedence_warning) + emit_precedence_warnings(pstate, 1, "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)); *************** transformAExprOp(ParseState *pstate, A_E *** 773,778 **** --- 788,805 ---- 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 *************** transformAExprOpAll(ParseState *pstate, *** 877,884 **** static Node * transformAExprDistinct(ParseState *pstate, A_Expr *a) { ! Node *lexpr = transformExprRecurse(pstate, a->lexpr); ! Node *rexpr = transformExprRecurse(pstate, a->rexpr); if (lexpr && IsA(lexpr, RowExpr) && rexpr && IsA(rexpr, RowExpr)) --- 904,919 ---- static Node * transformAExprDistinct(ParseState *pstate, A_Expr *a) { ! Node *lexpr = a->lexpr; ! Node *rexpr = a->rexpr; ! ! if (operator_precedence_warning) ! emit_precedence_warnings(pstate, 1, "IS", ! lexpr, rexpr, ! a->location); ! ! lexpr = transformExprRecurse(pstate, lexpr); ! rexpr = transformExprRecurse(pstate, rexpr); if (lexpr && IsA(lexpr, RowExpr) && rexpr && IsA(rexpr, RowExpr)) *************** transformAExprNullIf(ParseState *pstate, *** 935,954 **** return (Node *) result; } 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); Const *result; ListCell *telem; Oid ltype, rtype; bool matched = false; ltype = exprType(lexpr); foreach(telem, (List *) a->rexpr) { --- 970,996 ---- 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) { ! Node *lexpr = a->lexpr; Const *result; ListCell *telem; Oid ltype, rtype; bool matched = false; + if (operator_precedence_warning) + emit_precedence_warnings(pstate, 1, "IS", + lexpr, NULL, + a->location); + + lexpr = transformExprRecurse(pstate, lexpr); + ltype = exprType(lexpr); foreach(telem, (List *) a->rexpr) { *************** transformBooleanTest(ParseState *pstate, *** 2157,2162 **** --- 2199,2209 ---- { const char *clausename; + if (operator_precedence_warning) + emit_precedence_warnings(pstate, 1, "IS", + (Node *) b->arg, NULL, + b->location); + switch (b->booltesttype) { case IS_TRUE: *************** make_distinct_op(ParseState *pstate, Lis *** 2674,2679 **** --- 2721,2904 ---- } /* + * Identify node's group for operator precedence warnings + * + * Groups are: + * 0: everything not classified below + * 1: IS tests (NullTest, BooleanTest, etc) + * 2: < > = + * 3: <= => <> + * 4: LIKE ILIKE SIMILAR BETWEEN IN + * 5: generic Op + * + * 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. Also, nodes in group 4 do have precedences + * relative to each other, but we don't care since those did not change. + */ + 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 || + strcmp(*nodename, "~~") == 0 || + strcmp(*nodename, "!~~") == 0 || + strcmp(*nodename, "~~*") == 0 || + strcmp(*nodename, "!~~*") == 0) + group = 4; /* LIKE, ILIKE, SIMILAR */ + else if (strcmp(*nodename, "<=") == 0 || + strcmp(*nodename, ">=") == 0 || + strcmp(*nodename, "<>") == 0) + group = 3; + else if (strcmp(*nodename, "<") == 0 || + strcmp(*nodename, ">") == 0 || + strcmp(*nodename, "=") == 0) + group = 2; + else + group = 5; + } + else + { + /* schema-qualified operator syntax */ + *nodename = "OPERATOR()"; + group = 5; + } + } + else if (aexpr->kind == AEXPR_DISTINCT || + aexpr->kind == AEXPR_OF) + { + *nodename = "IS"; + group = 1; + } + else if (aexpr->kind == AEXPR_IN) + { + *nodename = "IN"; + group = 4; + } + else if (aexpr->kind == AEXPR_BETWEEN || + aexpr->kind == AEXPR_NOT_BETWEEN || + aexpr->kind == AEXPR_BETWEEN_SYM || + aexpr->kind == AEXPR_NOT_BETWEEN_SYM) + { + Assert(list_length(aexpr->name) == 1); + *nodename = strVal(linitial(aexpr->name)); + group = 4; + } + } + else if (IsA(node, NullTest) ||IsA(node, BooleanTest)) + { + *nodename = "IS"; + group = 1; + } + else if (IsA(node, XmlExpr)) + { + XmlExpr *x = (XmlExpr *) node; + + if (x->op == IS_DOCUMENT) + { + *nodename = "IS"; + group = 1; + } + /* XXX what of IS NOT DOCUMENT? */ + } + + 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) + { + /*---------- + * Map precedence groupings to old precedence ordering + * + * Old precedence order: + * 2: < > = + * 4: LIKE ILIKE SIMILAR BETWEEN IN + * 5: generic Op, inclding 3: <= => <> + * 1: IS tests (NullTest, BooleanTest, etc) + *---------- + */ + static const int oldprecedence[] = {0, 4, 1, 3, 2, 3}; + 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. + */ + cgroup = operator_precedence_group(lchild, &copname); + if (cgroup > 0) + { + Assert(opgroup <= cgroup); + if (oldprecedence[cgroup] < oldprecedence[opgroup]) + 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. + */ + cgroup = operator_precedence_group(rchild, &copname); + if (cgroup > 0) + { + Assert(opgroup < cgroup); + if (oldprecedence[cgroup] <= oldprecedence[opgroup]) + 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 3724330..2d85cf0 100644 *** a/src/backend/parser/parse_target.c --- b/src/backend/parser/parse_target.c *************** FigureColnameInternal(Node *node, char * *** 1654,1665 **** *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) { *name = "nullif"; return 2; } break; case T_TypeCast: strength = FigureColnameInternal(((TypeCast *) node)->arg, --- 1654,1670 ---- *name = strVal(llast(((FuncCall *) node)->funcname)); return 2; case T_A_Expr: 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/scan.l b/src/backend/parser/scan.l index a78ce03..7ce7a47 100644 *** a/src/backend/parser/scan.l --- b/src/backend/parser/scan.l *************** ident_cont [A-Za-z\200-\377_0-9\$] *** 331,339 **** --- 331,344 ---- identifier {ident_start}{ident_cont}* + /* Assorted special-case operators and operator-like tokens */ typecast "::" dot_dot \.\. colon_equals ":=" + less_equals "<=" + greater_equals ">=" + less_greater "<>" + not_equals "!=" /* * "self" is the set of chars that should be returned as single-character *************** other . *** 808,813 **** --- 813,840 ---- return COLON_EQUALS; } + {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]; *************** other . *** 885,895 **** if (nchars >= NAMEDATALEN) yyerror("operator too long"); ! /* Convert "!=" operator to "<>" for compatibility */ ! if (strcmp(yytext, "!=") == 0) ! yylval->str = pstrdup("<>"); ! else ! yylval->str = pstrdup(yytext); return Op; } --- 912,918 ---- if (nchars >= NAMEDATALEN) yyerror("operator too long"); ! yylval->str = pstrdup(yytext); return Op; } diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 9572777..360dad9 100644 *** a/src/backend/utils/misc/guc.c --- b/src/backend/utils/misc/guc.c *************** static struct config_bool ConfigureNames *** 1514,1519 **** --- 1514,1529 ---- }, { + {"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 b053659..9b2ca28 100644 *** a/src/backend/utils/misc/postgresql.conf.sample --- b/src/backend/utils/misc/postgresql.conf.sample *************** *** 582,587 **** --- 582,588 ---- #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 fb3fa11..a37cd2c 100644 *** a/src/bin/psql/psqlscan.l --- b/src/bin/psql/psqlscan.l *************** ident_cont [A-Za-z\200-\377_0-9\$] *** 355,363 **** --- 355,368 ---- identifier {ident_start}{ident_cont}* + /* Assorted special-case operators and operator-like tokens */ typecast "::" dot_dot \.\. colon_equals ":=" + less_equals "<=" + greater_equals ">=" + less_greater "<>" + not_equals "!=" /* * "self" is the set of chars that should be returned as single-character *************** other . *** 669,674 **** --- 674,695 ---- 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 d7b6148..1987313 100644 *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** typedef enum A_Expr_Kind *** 236,242 **** 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" */ } A_Expr_Kind; typedef struct A_Expr --- 236,243 ---- 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_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 66391df..fbc3f17 100644 *** a/src/include/parser/parse_expr.h --- b/src/include/parser/parse_expr.h *************** *** 16,21 **** --- 16,22 ---- #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/pl/plpgsql/src/pl_gram.y b/src/pl/plpgsql/src/pl_gram.y index 506a313..761cfab 100644 *** a/src/pl/plpgsql/src/pl_gram.y --- b/src/pl/plpgsql/src/pl_gram.y *************** static void check_raise_parameters(PLp *** 227,232 **** --- 227,233 ---- %token <str> IDENT FCONST SCONST BCONST XCONST Op %token <ival> ICONST PARAM %token TYPECAST DOT_DOT COLON_EQUALS + %token LESS_EQUALS GREATER_EQUALS NOT_EQUALS /* * Other tokens recognized by plpgsql's lexer interface layer (pl_scanner.c).
pgsql-hackers by date: