diff --git a/src/backend/parser/scan.l b/src/backend/parser/scan.l index 967b2eaf9b9..6cf40ac6cb8 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 fe74abc1a6c..73eafebefec 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 bc47c23d038..3645582b439 100644 --- a/src/interfaces/ecpg/preproc/pgc.l +++ b/src/interfaces/ecpg/preproc/pgc.l @@ -242,6 +242,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 ">=" @@ -729,6 +738,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 68b88d33a16..f04c28e1c37 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