postgres.git / summary / log / commit / refs

commit    d64fad6669926c95f1a2e49ebec58ed4412be1a0
Author:   Andrew Gierth <rhodiumtoad@postgresql.org>
Date:     Thu Aug 23 17:29:18 2018 +0000

    Fix lexing of standard multi-character operators in edge cases.
    
    Commits c6b3c939b (which fixed the precedence of >=, <=, <> operators)
    and 865f14a2d (which added support for the standard => notation for
    named arguments) created a class of lexer tokens which look like
    multi-character operators but which have their own token IDs distinct
    from Op. However, longest-match rules meant that following any of
    these tokens with another operator character, as in (1<>-1), would
    cause them to be incorrectly returned as Op.
    
    The error here isn't immediately obvious, because the parser would
    usually still find the correct operator via the Op token, but there
    were more subtle problems:
    
    1. If immediately followed by a comment or +-, >= <= <> would be given
       the old precedence of Op rather than the correct new precedence;
    
    2. If followed by a comment, != would be returned as Op rather than as
       NOT_EQUAL, causing it not to be found at all;
    
    3. If followed by a comment or +-, the => token for named arguments
       would be lexed as Op, causing the argument to be mis-parsed as a
       simple expression, usually causing an error.
    
    Fix by explicitly checking for the operators in the {operator} code
    block in addition to all the existing special cases there.
    
    Backpatch to 9.5 where the problem was introduced.
    
    Analysis and patch by me; review by Tom Lane.
    Discussion: https://postgr.es/m/87va851ppl.fsf@news-spur.riddles.org.uk


src/backend/parser/scan.l | 28 ++++++++++ src/fe_utils/psqlscan.l | 9 ++++ src/interfaces/ecpg/preproc/pgc.l | 28 ++++++++++ src/test/regress/expected/create_operator.out | 74 +++++++++++++++++++++++++++ src/test/regress/expected/polymorphism.out | 36 +++++++++++++ src/test/regress/sql/create_operator.sql | 31 +++++++++++ src/test/regress/sql/polymorphism.sql | 15 ++++++ 7 files changed, 221 insertions(+) diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l index c1767efabb6..c95607da854 100644 --- a/src/backend/parser/scan.l +++ b/src/backend/parser/scan.l @@ -335,6 +335,15 @@ identifier {ident_start}{ident_cont}* typecast "::" dot_dot \.\. colon_equals ":=" + +/* + * These operator-like tokens (unlike the above ones) also match the {operator} + * rule, which means that they might be overridden by a longer match if they + * are followed by a comment start or a + or - character. Accordingly, if you + * add to this list, you must also add corresponding code to the {operator} + * block to return the correct token in such cases. (This is not needed in + * psqlscan.l since the token value is ignored there.) + */ equals_greater "=>" less_equals "<=" greater_equals ">=" @@ -925,6 +934,25 @@ other . if (nchars == 1 && strchr(",()[].;:+-*/%^<>=", yytext[0])) return yytext[0]; + /* + * Likewise, if what we have left is two chars, and + * those match the tokens ">=", "<=", "=>", "<>" or + * "!=", then we must return the appropriate token + * rather than the generic Op. + */ + if (nchars == 2) + { + if (yytext[0] == '=' && yytext[1] == '>') + return EQUALS_GREATER; + if (yytext[0] == '>' && yytext[1] == '=') + return GREATER_EQUALS; + if (yytext[0] == '<' && yytext[1] == '=') + return LESS_EQUALS; + if (yytext[0] == '<' && yytext[1] == '>') + return NOT_EQUALS; + if (yytext[0] == '!' && yytext[1] == '=') + return NOT_EQUALS; + } } /* diff --git a/src/fe_utils/psqlscan.l b/src/fe_utils/psqlscan.l index e6c7938f752..3f9a7b42b41 100644 --- a/src/fe_utils/psqlscan.l +++ b/src/fe_utils/psqlscan.l @@ -296,6 +296,15 @@ identifier {ident_start}{ident_cont}* typecast "::" dot_dot \.\. colon_equals ":=" + +/* + * These operator-like tokens (unlike the above ones) also match the {operator} + * rule, which means that they might be overridden by a longer match if they + * are followed by a comment start or a + or - character. Accordingly, if you + * add to this list, you must also add corresponding code to the {operator} + * block to return the correct token in such cases. (This is not needed in + * psqlscan.l since the token value is ignored there.) + */ equals_greater "=>" less_equals "<=" greater_equals ">=" diff --git a/src/interfaces/ecpg/preproc/pgc.l b/src/interfaces/ecpg/preproc/pgc.l index 0a90bead921..5ed185a20b6 100644 --- a/src/interfaces/ecpg/preproc/pgc.l +++ b/src/interfaces/ecpg/preproc/pgc.l @@ -241,6 +241,15 @@ array ({ident_cont}|{whitespace}|[\[\]\+\-\*\%\/\(\)\>\.])* typecast "::" dot_dot \.\. colon_equals ":=" + +/* + * These operator-like tokens (unlike the above ones) also match the {operator} + * rule, which means that they might be overridden by a longer match if they + * are followed by a comment start or a + or - character. Accordingly, if you + * add to this list, you must also add corresponding code to the {operator} + * block to return the correct token in such cases. (This is not needed in + * psqlscan.l since the token value is ignored there.) + */ equals_greater "=>" less_equals "<=" greater_equals ">=" @@ -728,6 +737,25 @@ cppline {space}*#([^i][A-Za-z]*|{if}|{ifdef}|{ifndef}|{import})((\/\*[^*/]*\*+ if (nchars == 1 && strchr(",()[].;:+-*/%^<>=", yytext[0])) return yytext[0]; + /* + * Likewise, if what we have left is two chars, and + * those match the tokens ">=", "<=", "=>", "<>" or + * "!=", then we must return the appropriate token + * rather than the generic Op. + */ + if (nchars == 2) + { + if (yytext[0] == '=' && yytext[1] == '>') + return EQUALS_GREATER; + if (yytext[0] == '>' && yytext[1] == '=') + return GREATER_EQUALS; + if (yytext[0] == '<' && yytext[1] == '=') + return LESS_EQUALS; + if (yytext[0] == '<' && yytext[1] == '>') + return NOT_EQUALS; + if (yytext[0] == '!' && yytext[1] == '=') + return NOT_EQUALS; + } } base_yylval.str = mm_strdup(yytext); diff --git a/src/test/regress/expected/create_operator.out b/src/test/regress/expected/create_operator.out index 3a216c2ca8f..f3e41777318 100644 --- a/src/test/regress/expected/create_operator.out +++ b/src/test/regress/expected/create_operator.out @@ -37,6 +37,80 @@ CREATE OPERATOR => ( ERROR: syntax error at or near "=>" LINE 1: CREATE OPERATOR => ( ^ +-- lexing of <=, >=, <>, != has a number of edge cases +-- (=> is tested elsewhere) +-- this is legal because ! is not allowed in sql ops +CREATE OPERATOR !=- ( + leftarg = int8, -- right unary + procedure = numeric_fac +); +SELECT 2 !=-; + ?column? +---------- + 2 +(1 row) + +-- make sure lexer returns != as <> even in edge cases +SELECT 2 !=/**/ 1, 2 !=/**/ 2; + ?column? | ?column? +----------+---------- + t | f +(1 row) + +SELECT 2 !=-- comment to be removed by psql + 1; + ?column? +---------- + t +(1 row) + +DO $$ -- use DO to protect -- from psql + declare r boolean; + begin + execute $e$ select 2 !=-- comment + 1 $e$ into r; + raise info 'r = %', r; + end; +$$; +INFO: r = t +-- check that <= etc. followed by more operator characters are returned +-- as the correct token with correct precedence +SELECT true<>-1 BETWEEN 1 AND 1; -- BETWEEN has prec. above <> but below Op + ?column? +---------- + t +(1 row) + +SELECT false<>/**/1 BETWEEN 1 AND 1; + ?column? +---------- + t +(1 row) + +SELECT false<=-1 BETWEEN 1 AND 1; + ?column? +---------- + t +(1 row) + +SELECT false>=-1 BETWEEN 1 AND 1; + ?column? +---------- + t +(1 row) + +SELECT 2<=/**/3, 3>=/**/2, 2<>/**/3; + ?column? | ?column? | ?column? +----------+----------+---------- + t | t | t +(1 row) + +SELECT 3<=/**/2, 2>=/**/3, 2<>/**/2; + ?column? | ?column? | ?column? +----------+----------+---------- + f | f | f +(1 row) + -- Should fail. CREATE OPERATOR requires USAGE on SCHEMA BEGIN TRANSACTION; CREATE ROLE regress_rol_op1; diff --git a/src/test/regress/expected/polymorphism.out b/src/test/regress/expected/polymorphism.out index 91cfb743b63..42253be2ee9 100644 --- a/src/test/regress/expected/polymorphism.out +++ b/src/test/regress/expected/polymorphism.out @@ -1478,6 +1478,42 @@ select dfunc('a'::text, 'b', flag => true); -- mixed notation a (1 row) +-- this tests lexer edge cases around => +select dfunc(a =>-1); + dfunc +------- + -1 +(1 row) + +select dfunc(a =>+1); + dfunc +------- + 1 +(1 row) + +select dfunc(a =>/**/1); + dfunc +------- + 1 +(1 row) + +select dfunc(a =>--comment to be removed by psql + 1); + dfunc +------- + 1 +(1 row) + +-- need DO to protect the -- from psql +do $$ + declare r integer; + begin + select dfunc(a=>-- comment + 1) into r; + raise info 'r = %', r; + end; +$$; +INFO: r = 1 -- check reverse-listing of named-arg calls CREATE VIEW dfview AS SELECT q1, q2, diff --git a/src/test/regress/sql/create_operator.sql b/src/test/regress/sql/create_operator.sql index 0e5d6356bc3..0579cb95e1d 100644 --- a/src/test/regress/sql/create_operator.sql +++ b/src/test/regress/sql/create_operator.sql @@ -41,6 +41,37 @@ CREATE OPERATOR => ( procedure = numeric_fac ); +-- lexing of <=, >=, <>, != has a number of edge cases +-- (=> is tested elsewhere) + +-- this is legal because ! is not allowed in sql ops +CREATE OPERATOR !=- ( + leftarg = int8, -- right unary + procedure = numeric_fac +); +SELECT 2 !=-; +-- make sure lexer returns != as <> even in edge cases +SELECT 2 !=/**/ 1, 2 !=/**/ 2; +SELECT 2 !=-- comment to be removed by psql + 1; +DO $$ -- use DO to protect -- from psql + declare r boolean; + begin + execute $e$ select 2 !=-- comment + 1 $e$ into r; + raise info 'r = %', r; + end; +$$; + +-- check that <= etc. followed by more operator characters are returned +-- as the correct token with correct precedence +SELECT true<>-1 BETWEEN 1 AND 1; -- BETWEEN has prec. above <> but below Op +SELECT false<>/**/1 BETWEEN 1 AND 1; +SELECT false<=-1 BETWEEN 1 AND 1; +SELECT false>=-1 BETWEEN 1 AND 1; +SELECT 2<=/**/3, 3>=/**/2, 2<>/**/3; +SELECT 3<=/**/2, 2>=/**/3, 2<>/**/2; + -- Should fail. CREATE OPERATOR requires USAGE on SCHEMA BEGIN TRANSACTION; CREATE ROLE regress_rol_op1; diff --git a/src/test/regress/sql/polymorphism.sql b/src/test/regress/sql/polymorphism.sql index 45ae7a23aa7..5cdfcd2e69c 100644 --- a/src/test/regress/sql/polymorphism.sql +++ b/src/test/regress/sql/polymorphism.sql @@ -785,6 +785,21 @@ select dfunc('a'::text, 'b', flag => false); -- mixed notation select dfunc('a'::text, 'b', true); -- full positional notation select dfunc('a'::text, 'b', flag => true); -- mixed notation +-- this tests lexer edge cases around => +select dfunc(a =>-1); +select dfunc(a =>+1); +select dfunc(a =>/**/1); +select dfunc(a =>--comment to be removed by psql + 1); +-- need DO to protect the -- from psql +do $$ + declare r integer; + begin + select dfunc(a=>-- comment + 1) into r; + raise info 'r = %', r; + end; +$$; -- check reverse-listing of named-arg calls CREATE VIEW dfview AS [parent: 2dbfbd630bc9]