postgresql/src/test/regress/sql/alter_operator.sql
Tom Lane 2b5154beab Extend ALTER OPERATOR to allow setting more optimization attributes.
Allow the COMMUTATOR, NEGATOR, MERGES, and HASHES attributes to be set
by ALTER OPERATOR.  However, we don't allow COMMUTATOR/NEGATOR to be
changed once set, nor allow the MERGES/HASHES flags to be unset once
set.  Changes like that might invalidate plans already made, and
dealing with the consequences seems like more trouble than it's worth.
The main use-case we foresee for this is to allow addition of missed
properties in extension update scripts, such as extending an existing
operator to support hashing.  So only transitions from not-set to set
states seem very useful.

This patch also causes us to reject some incorrect cases that formerly
resulted in inconsistent catalog state, such as trying to set the
commutator of an operator to be some other operator that already has a
(different) commutator.

While at it, move the InvokeObjectPostCreateHook call for CREATE
OPERATOR to not occur until after we've fixed up commutator or negator
links as needed.  The previous ordering could only be justified by
thinking of the OperatorUpd call as a kind of ALTER OPERATOR step;
but we don't call InvokeObjectPostAlterHook therein.  It seems better
to let the hook see the final state of the operator object.

In the documentation, move the discussion of how to establish
commutator pairs from xoper.sgml to the CREATE OPERATOR ref page.

Tommy Pavlicek, reviewed and editorialized a bit by me

Discussion: https://postgr.es/m/CAEhP-W-vGVzf4udhR5M8Bdv88UYnPrhoSkj3ieR3QNrsGQoqdg@mail.gmail.com
2023-10-20 12:28:46 -04:00

225 lines
7.5 KiB
PL/PgSQL

CREATE FUNCTION alter_op_test_fn(boolean, boolean)
RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
CREATE FUNCTION customcontsel(internal, oid, internal, integer)
RETURNS float8 AS 'contsel' LANGUAGE internal STABLE STRICT;
CREATE OPERATOR === (
LEFTARG = boolean,
RIGHTARG = boolean,
PROCEDURE = alter_op_test_fn,
COMMUTATOR = ===,
NEGATOR = !==,
RESTRICT = customcontsel,
JOIN = contjoinsel,
HASHES, MERGES
);
SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
FROM pg_depend
WHERE classid = 'pg_operator'::regclass AND
objid = '===(bool,bool)'::regoperator
ORDER BY 1;
--
-- Test resetting and setting restrict and join attributes.
--
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
ALTER OPERATOR === (boolean, boolean) SET (JOIN = NONE);
SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
FROM pg_depend
WHERE classid = 'pg_operator'::regclass AND
objid = '===(bool,bool)'::regoperator
ORDER BY 1;
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = contsel);
ALTER OPERATOR === (boolean, boolean) SET (JOIN = contjoinsel);
SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
FROM pg_depend
WHERE classid = 'pg_operator'::regclass AND
objid = '===(bool,bool)'::regoperator
ORDER BY 1;
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE, JOIN = NONE);
SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
FROM pg_depend
WHERE classid = 'pg_operator'::regclass AND
objid = '===(bool,bool)'::regoperator
ORDER BY 1;
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = customcontsel, JOIN = contjoinsel);
SELECT oprrest, oprjoin FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
SELECT pg_describe_object(refclassid,refobjid,refobjsubid) as ref, deptype
FROM pg_depend
WHERE classid = 'pg_operator'::regclass AND
objid = '===(bool,bool)'::regoperator
ORDER BY 1;
--
-- Test invalid options.
--
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = non_existent_func);
ALTER OPERATOR === (boolean, boolean) SET (JOIN = non_existent_func);
-- invalid: non-lowercase quoted identifiers
ALTER OPERATOR & (bit, bit) SET ("Restrict" = _int_contsel, "Join" = _int_contjoinsel);
--
-- Test permission check. Must be owner to ALTER OPERATOR.
--
CREATE USER regress_alter_op_user;
SET SESSION AUTHORIZATION regress_alter_op_user;
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
RESET SESSION AUTHORIZATION;
--
-- Test setting commutator, negator, merges, and hashes attributes,
-- which can only be set if not already set
--
CREATE FUNCTION alter_op_test_fn_bool_real(boolean, real)
RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
CREATE FUNCTION alter_op_test_fn_real_bool(real, boolean)
RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
-- operator
CREATE OPERATOR === (
LEFTARG = boolean,
RIGHTARG = real,
PROCEDURE = alter_op_test_fn_bool_real
);
-- commutator
CREATE OPERATOR ==== (
LEFTARG = real,
RIGHTARG = boolean,
PROCEDURE = alter_op_test_fn_real_bool
);
-- negator
CREATE OPERATOR !==== (
LEFTARG = boolean,
RIGHTARG = real,
PROCEDURE = alter_op_test_fn_bool_real
);
-- No-op setting already false hashes and merges to false works
ALTER OPERATOR === (boolean, real) SET (MERGES = false);
ALTER OPERATOR === (boolean, real) SET (HASHES = false);
-- Test setting merges and hashes
ALTER OPERATOR === (boolean, real) SET (MERGES);
ALTER OPERATOR === (boolean, real) SET (HASHES);
SELECT oprcanmerge, oprcanhash
FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
-- Test setting commutator
ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====);
-- Check that oprcom has been set on both the operator and commutator,
-- that they reference each other, and that the operator used is the existing
-- one we created and not a new shell operator.
SELECT op.oprname AS operator_name, com.oprname AS commutator_name,
com.oprcode AS commutator_func
FROM pg_operator op
INNER JOIN pg_operator com ON (op.oid = com.oprcom AND op.oprcom = com.oid)
WHERE op.oprname = '==='
AND op.oprleft = 'boolean'::regtype AND op.oprright = 'real'::regtype;
-- Cannot set self as negator
ALTER OPERATOR === (boolean, real) SET (NEGATOR = ===);
-- Test setting negator
ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====);
-- Check that oprnegate has been set on both the operator and negator,
-- that they reference each other, and that the operator used is the existing
-- one we created and not a new shell operator.
SELECT op.oprname AS operator_name, neg.oprname AS negator_name,
neg.oprcode AS negator_func
FROM pg_operator op
INNER JOIN pg_operator neg ON (op.oid = neg.oprnegate AND op.oprnegate = neg.oid)
WHERE op.oprname = '==='
AND op.oprleft = 'boolean'::regtype AND op.oprright = 'real'::regtype;
-- Test that no-op set succeeds
ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====);
ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====);
ALTER OPERATOR === (boolean, real) SET (MERGES);
ALTER OPERATOR === (boolean, real) SET (HASHES);
-- Check that the final state of the operator is as we expect
SELECT oprcanmerge, oprcanhash,
pg_describe_object('pg_operator'::regclass, oprcom, 0) AS commutator,
pg_describe_object('pg_operator'::regclass, oprnegate, 0) AS negator
FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
-- Cannot change commutator, negator, merges, and hashes when already set
CREATE OPERATOR @= (
LEFTARG = real,
RIGHTARG = boolean,
PROCEDURE = alter_op_test_fn_real_bool
);
CREATE OPERATOR @!= (
LEFTARG = boolean,
RIGHTARG = real,
PROCEDURE = alter_op_test_fn_bool_real
);
ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = @=);
ALTER OPERATOR === (boolean, real) SET (NEGATOR = @!=);
ALTER OPERATOR === (boolean, real) SET (MERGES = false);
ALTER OPERATOR === (boolean, real) SET (HASHES = false);
-- Cannot set an operator that already has a commutator as the commutator
ALTER OPERATOR @=(real, boolean) SET (COMMUTATOR = ===);
-- Cannot set an operator that already has a negator as the negator
ALTER OPERATOR @!=(boolean, real) SET (NEGATOR = ===);
-- Check no changes made
SELECT oprcanmerge, oprcanhash,
pg_describe_object('pg_operator'::regclass, oprcom, 0) AS commutator,
pg_describe_object('pg_operator'::regclass, oprnegate, 0) AS negator
FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
--
-- Clean up
--
DROP USER regress_alter_op_user;
DROP OPERATOR === (boolean, boolean);
DROP OPERATOR === (boolean, real);
DROP OPERATOR ==== (real, boolean);
DROP OPERATOR !==== (boolean, real);
DROP OPERATOR @= (real, boolean);
DROP OPERATOR @!= (boolean, real);
DROP FUNCTION customcontsel(internal, oid, internal, integer);
DROP FUNCTION alter_op_test_fn(boolean, boolean);
DROP FUNCTION alter_op_test_fn_bool_real(boolean, real);
DROP FUNCTION alter_op_test_fn_real_bool(real, boolean);