postgresql/src/test/regress/sql/create_operator.sql
Andrew Gierth a40631a920 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
2018-08-23 21:42:40 +01:00

230 lines
5.8 KiB
PL/PgSQL

--
-- CREATE_OPERATOR
--
CREATE OPERATOR ## (
leftarg = path,
rightarg = path,
function = path_inter,
commutator = ##
);
CREATE OPERATOR <% (
leftarg = point,
rightarg = widget,
procedure = pt_in_widget,
commutator = >% ,
negator = >=%
);
CREATE OPERATOR @#@ (
rightarg = int8, -- left unary
procedure = numeric_fac
);
CREATE OPERATOR #@# (
leftarg = int8, -- right unary
procedure = numeric_fac
);
CREATE OPERATOR #%# (
leftarg = int8, -- right unary
procedure = numeric_fac
);
-- Test operator created above
SELECT point '(1,2)' <% widget '(0,0,3)' AS t,
point '(1,2)' <% widget '(0,0,1)' AS f;
-- Test comments
COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad right unary';
-- => is disallowed now
CREATE OPERATOR => (
leftarg = int8, -- right unary
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;
CREATE SCHEMA schema_op1;
GRANT USAGE ON SCHEMA schema_op1 TO PUBLIC;
REVOKE USAGE ON SCHEMA schema_op1 FROM regress_rol_op1;
SET ROLE regress_rol_op1;
CREATE OPERATOR schema_op1.#*# (
leftarg = int8, -- right unary
procedure = numeric_fac
);
ROLLBACK;
-- Should fail. SETOF type functions not allowed as argument (testing leftarg)
BEGIN TRANSACTION;
CREATE OPERATOR #*# (
leftarg = SETOF int8,
procedure = numeric_fac
);
ROLLBACK;
-- Should fail. SETOF type functions not allowed as argument (testing rightarg)
BEGIN TRANSACTION;
CREATE OPERATOR #*# (
rightarg = SETOF int8,
procedure = numeric_fac
);
ROLLBACK;
-- Should work. Sample text-book case
BEGIN TRANSACTION;
CREATE OR REPLACE FUNCTION fn_op2(boolean, boolean)
RETURNS boolean AS $$
SELECT NULL::BOOLEAN;
$$ LANGUAGE sql IMMUTABLE;
CREATE OPERATOR === (
LEFTARG = boolean,
RIGHTARG = boolean,
PROCEDURE = fn_op2,
COMMUTATOR = ===,
NEGATOR = !==,
RESTRICT = contsel,
JOIN = contjoinsel,
SORT1, SORT2, LTCMP, GTCMP, HASHES, MERGES
);
ROLLBACK;
-- Should fail. Invalid attribute
CREATE OPERATOR #@%# (
leftarg = int8, -- right unary
procedure = numeric_fac,
invalid_att = int8
);
-- Should fail. At least leftarg or rightarg should be mandatorily specified
CREATE OPERATOR #@%# (
procedure = numeric_fac
);
-- Should fail. Procedure should be mandatorily specified
CREATE OPERATOR #@%# (
leftarg = int8
);
-- Should fail. CREATE OPERATOR requires USAGE on TYPE
BEGIN TRANSACTION;
CREATE ROLE regress_rol_op3;
CREATE TYPE type_op3 AS ENUM ('new', 'open', 'closed');
CREATE FUNCTION fn_op3(type_op3, int8)
RETURNS int8 AS $$
SELECT NULL::int8;
$$ LANGUAGE sql IMMUTABLE;
REVOKE USAGE ON TYPE type_op3 FROM regress_rol_op3;
REVOKE USAGE ON TYPE type_op3 FROM PUBLIC; -- Need to do this so that regress_rol_op3 is not allowed USAGE via PUBLIC
SET ROLE regress_rol_op3;
CREATE OPERATOR #*# (
leftarg = type_op3,
rightarg = int8,
procedure = fn_op3
);
ROLLBACK;
-- Should fail. CREATE OPERATOR requires USAGE on TYPE (need to check separately for rightarg)
BEGIN TRANSACTION;
CREATE ROLE regress_rol_op4;
CREATE TYPE type_op4 AS ENUM ('new', 'open', 'closed');
CREATE FUNCTION fn_op4(int8, type_op4)
RETURNS int8 AS $$
SELECT NULL::int8;
$$ LANGUAGE sql IMMUTABLE;
REVOKE USAGE ON TYPE type_op4 FROM regress_rol_op4;
REVOKE USAGE ON TYPE type_op4 FROM PUBLIC; -- Need to do this so that regress_rol_op3 is not allowed USAGE via PUBLIC
SET ROLE regress_rol_op4;
CREATE OPERATOR #*# (
leftarg = int8,
rightarg = type_op4,
procedure = fn_op4
);
ROLLBACK;
-- Should fail. CREATE OPERATOR requires EXECUTE on function
BEGIN TRANSACTION;
CREATE ROLE regress_rol_op5;
CREATE TYPE type_op5 AS ENUM ('new', 'open', 'closed');
CREATE FUNCTION fn_op5(int8, int8)
RETURNS int8 AS $$
SELECT NULL::int8;
$$ LANGUAGE sql IMMUTABLE;
REVOKE EXECUTE ON FUNCTION fn_op5(int8, int8) FROM regress_rol_op5;
REVOKE EXECUTE ON FUNCTION fn_op5(int8, int8) FROM PUBLIC;-- Need to do this so that regress_rol_op3 is not allowed EXECUTE via PUBLIC
SET ROLE regress_rol_op5;
CREATE OPERATOR #*# (
leftarg = int8,
rightarg = int8,
procedure = fn_op5
);
ROLLBACK;
-- Should fail. CREATE OPERATOR requires USAGE on return TYPE
BEGIN TRANSACTION;
CREATE ROLE regress_rol_op6;
CREATE TYPE type_op6 AS ENUM ('new', 'open', 'closed');
CREATE FUNCTION fn_op6(int8, int8)
RETURNS type_op6 AS $$
SELECT NULL::type_op6;
$$ LANGUAGE sql IMMUTABLE;
REVOKE USAGE ON TYPE type_op6 FROM regress_rol_op6;
REVOKE USAGE ON TYPE type_op6 FROM PUBLIC; -- Need to do this so that regress_rol_op3 is not allowed USAGE via PUBLIC
SET ROLE regress_rol_op6;
CREATE OPERATOR #*# (
leftarg = int8,
rightarg = int8,
procedure = fn_op6
);
ROLLBACK;
-- invalid: non-lowercase quoted identifiers
CREATE OPERATOR ===
(
"Leftarg" = box,
"Rightarg" = box,
"Procedure" = area_equal_function,
"Commutator" = ===,
"Negator" = !==,
"Restrict" = area_restriction_function,
"Join" = area_join_function,
"Hashes",
"Merges"
);