mirror of
https://github.com/postgres/postgres.git
synced 2026-01-26 09:41:40 +03:00
Up to now we've contented ourselves with a one-size-fits-all error hint when we fail to find any match to a function or procedure call. That was mostly okay in the beginning, but it was never great, and since the introduction of named arguments it's really not adequate. We at least ought to distinguish "function name doesn't exist" from "function name exists, but not with those argument names". And the rules for named-argument matching are arcane enough that some more detail seems warranted if we match the argument names but the call still doesn't work. This patch creates a framework for dealing with these problems: FuncnameGetCandidates and related code will now pass back a bitmask of flags showing how far the match succeeded. This allows a considerable amount of granularity in the reports. The set-bits-in-a-bitmask approach means that when there are multiple candidate functions, the report will reflect the match(es) that got the furthest, which seems correct. Also, we can avoid mentioning "maybe add casts" unless failure to match argument types is actually the issue. Extend the same return-a-bitmask approach to OpernameGetCandidates. The issues around argument names don't apply to operator syntax, but it still seems worth distinguishing between "there is no operator of that name" and "we couldn't match the argument types". While at it, adjust these messages and related ones to more strictly separate "detail" from "hint", following our message style guidelines' distinction between those. Reported-by: Dominique Devienne <ddevienne@gmail.com> Author: Tom Lane <tgl@sss.pgh.pa.us> Reviewed-by: Robert Haas <robertmhaas@gmail.com> Discussion: https://postgr.es/m/1756041.1754616558@sss.pgh.pa.us
276 lines
6.9 KiB
PL/PgSQL
276 lines
6.9 KiB
PL/PgSQL
--
|
|
-- CREATE_OPERATOR
|
|
--
|
|
|
|
CREATE OPERATOR ## (
|
|
leftarg = path,
|
|
rightarg = path,
|
|
function = path_inter,
|
|
commutator = ##
|
|
);
|
|
|
|
CREATE OPERATOR @#@ (
|
|
rightarg = int8, -- prefix
|
|
procedure = factorial
|
|
);
|
|
|
|
CREATE OPERATOR #%# (
|
|
leftarg = int8, -- fail, postfix is no longer supported
|
|
procedure = factorial
|
|
);
|
|
|
|
-- Test operator created above
|
|
SELECT @#@ 24;
|
|
|
|
-- Test error cases
|
|
select @@##@@ 24; -- no such operator
|
|
set search_path = pg_catalog;
|
|
select @#@ 24; -- wrong schema
|
|
reset search_path;
|
|
select @#@ 24.0; -- wrong data type
|
|
|
|
-- Test comments
|
|
COMMENT ON OPERATOR ###### (NONE, int4) IS 'bad prefix';
|
|
COMMENT ON OPERATOR ###### (int4, NONE) IS 'bad postfix';
|
|
COMMENT ON OPERATOR ###### (int4, int8) IS 'bad infix';
|
|
|
|
-- Check that DROP on a nonexistent op behaves sanely, too
|
|
DROP OPERATOR ###### (NONE, int4);
|
|
DROP OPERATOR ###### (int4, NONE);
|
|
DROP OPERATOR ###### (int4, int8);
|
|
|
|
-- => is disallowed as an operator name now
|
|
CREATE OPERATOR => (
|
|
rightarg = int8,
|
|
procedure = factorial
|
|
);
|
|
|
|
-- lexing of <=, >=, <>, != has a number of edge cases
|
|
-- (=> is tested elsewhere)
|
|
|
|
-- this is legal because ! is not allowed in sql ops
|
|
CREATE OPERATOR !=- (
|
|
rightarg = int8,
|
|
procedure = factorial
|
|
);
|
|
SELECT !=- 10;
|
|
-- postfix operators don't work anymore
|
|
SELECT 10 !=-;
|
|
-- 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.#*# (
|
|
rightarg = int8,
|
|
procedure = factorial
|
|
);
|
|
ROLLBACK;
|
|
|
|
|
|
-- Should fail. SETOF type functions not allowed as argument (testing leftarg)
|
|
BEGIN TRANSACTION;
|
|
CREATE OPERATOR #*# (
|
|
leftarg = SETOF int8,
|
|
procedure = factorial
|
|
);
|
|
ROLLBACK;
|
|
|
|
|
|
-- Should fail. SETOF type functions not allowed as argument (testing rightarg)
|
|
BEGIN TRANSACTION;
|
|
CREATE OPERATOR #*# (
|
|
rightarg = SETOF int8,
|
|
procedure = factorial
|
|
);
|
|
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 #@%# (
|
|
rightarg = int8,
|
|
procedure = factorial,
|
|
invalid_att = int8
|
|
);
|
|
|
|
-- Should fail. At least rightarg should be mandatorily specified
|
|
CREATE OPERATOR #@%# (
|
|
procedure = factorial
|
|
);
|
|
|
|
-- Should fail. Procedure should be mandatorily specified
|
|
CREATE OPERATOR #@%# (
|
|
rightarg = 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;
|
|
|
|
-- Should fail. An operator cannot be its own negator.
|
|
BEGIN TRANSACTION;
|
|
CREATE OPERATOR === (
|
|
leftarg = integer,
|
|
rightarg = integer,
|
|
procedure = int4eq,
|
|
negator = ===
|
|
);
|
|
ROLLBACK;
|
|
|
|
-- Should fail. An operator cannot be its own negator. Here we check that
|
|
-- this error is detected when replacing a shell operator.
|
|
BEGIN TRANSACTION;
|
|
-- create a shell operator for ===!!! by referencing it as a commutator
|
|
CREATE OPERATOR === (
|
|
leftarg = integer,
|
|
rightarg = integer,
|
|
procedure = int4eq,
|
|
commutator = ===!!!
|
|
);
|
|
CREATE OPERATOR ===!!! (
|
|
leftarg = integer,
|
|
rightarg = integer,
|
|
procedure = int4ne,
|
|
negator = ===!!!
|
|
);
|
|
ROLLBACK;
|
|
|
|
-- test that we can't use part of an existing commutator or negator pair
|
|
-- as a commutator or negator
|
|
CREATE OPERATOR === (
|
|
leftarg = integer,
|
|
rightarg = integer,
|
|
procedure = int4eq,
|
|
commutator = =
|
|
);
|
|
CREATE OPERATOR === (
|
|
leftarg = integer,
|
|
rightarg = integer,
|
|
procedure = int4eq,
|
|
negator = <>
|
|
);
|
|
|
|
-- 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"
|
|
);
|