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 <boolean value
expression> 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.
This commit is contained in:
Tom Lane 2015-03-11 13:22:52 -04:00
parent 21dcda2713
commit c6b3c939b7
18 changed files with 723 additions and 95 deletions

View File

@ -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>

View File

@ -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>&lt;</> and
<literal>&gt;</> have a different precedence than the Boolean
operators <literal>&lt;=</> and <literal>&gt;=</>. 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>&lt;</token> <token>&gt;</token></entry>
<entry><token>&lt;</token> <token>&gt;</token> <token>=</token> <token>&lt;=</token> <token>&gt;=</token> <token>&lt;&gt;</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>&lt;=</token>
<token>&gt;=</token> and <token>&lt;&gt;</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>

View File

@ -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;

View File

@ -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),

View File

@ -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.
*

View File

@ -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,

View File

@ -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)

View File

@ -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;
}

View File

@ -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."),

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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).

View File

@ -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

View File

@ -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)

View File

@ -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} {

View File

@ -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).