diff --git a/doc/src/sgml/ref/alter_operator.sgml b/doc/src/sgml/ref/alter_operator.sgml
index a4a1af564f..04af611cca 100644
--- a/doc/src/sgml/ref/alter_operator.sgml
+++ b/doc/src/sgml/ref/alter_operator.sgml
@@ -29,8 +29,12 @@ ALTER OPERATOR name ( { left_typename ( { left_type | NONE } , right_type )
SET ( { RESTRICT = { res_proc | NONE }
- | JOIN = { join_proc | NONE }
- } [, ... ] )
+ | JOIN = { join_proc | NONE }
+ | COMMUTATOR = com_op
+ | NEGATOR = neg_op
+ | HASHES
+ | MERGES
+ } [, ... ] )
@@ -121,6 +125,42 @@ ALTER OPERATOR name ( { left_type
+
+ com_op
+
+
+ The commutator of this operator. Can only be set if the operator does not have an existing commutator.
+
+
+
+
+
+ neg_op
+
+
+ The negator of this operator. Can only be set if the operator does not have an existing negator.
+
+
+
+
+
+ HASHES
+
+
+ Indicates this operator can support a hash join. Can only be set when the operator does not support a hash join.
+
+
+
+
+
+ MERGES
+
+
+ Indicates this operator can support a merge join. Can only be set when the operator does not support a merge join.
+
+
+
+
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index cd7f83136f..3787c234d4 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -54,6 +54,7 @@
static Oid ValidateRestrictionEstimator(List *restrictionName);
static Oid ValidateJoinEstimator(List *joinName);
+static void ValidateBooleanIsTrue(DefElem *def, const char *paramName);
/*
* DefineOperator
@@ -359,6 +360,18 @@ ValidateJoinEstimator(List *joinName)
return joinOid;
}
+static void
+ValidateBooleanIsTrue(DefElem *def, const char *paramName)
+{
+ if (!defGetBoolean(def))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("alter operator cannot disable \"%s\"",
+ paramName)));
+ }
+}
+
/*
* Guts of operator deletion.
*/
@@ -426,6 +439,12 @@ AlterOperator(AlterOperatorStmt *stmt)
List *joinName = NIL; /* optional join sel. function */
bool updateJoin = false;
Oid joinOid;
+ List *commutatorName = NIL; /* optional commutator operator name */
+ Oid commutatorOid;
+ List *negatorName = NIL; /* optional negator operator name */
+ Oid negatorOid;
+ bool updateHashes = false;
+ bool updateMerges = false;
/* Look up the operator */
oprId = LookupOperWithArgs(stmt->opername, false);
@@ -456,6 +475,24 @@ AlterOperator(AlterOperatorStmt *stmt)
joinName = param;
updateJoin = true;
}
+ else if (strcmp(defel->defname, "commutator") == 0)
+ {
+ commutatorName = defGetQualifiedName(defel);
+ }
+ else if (strcmp(defel->defname, "negator") == 0)
+ {
+ negatorName = defGetQualifiedName(defel);
+ }
+ else if (strcmp(defel->defname, "hashes") == 0)
+ {
+ ValidateBooleanIsTrue(defel, "hashes");
+ updateHashes = true;
+ }
+ else if (strcmp(defel->defname, "merges") == 0)
+ {
+ ValidateBooleanIsTrue(defel, "merges");
+ updateMerges = true;
+ }
/*
* The rest of the options that CREATE accepts cannot be changed.
@@ -464,11 +501,7 @@ AlterOperator(AlterOperatorStmt *stmt)
else if (strcmp(defel->defname, "leftarg") == 0 ||
strcmp(defel->defname, "rightarg") == 0 ||
strcmp(defel->defname, "function") == 0 ||
- strcmp(defel->defname, "procedure") == 0 ||
- strcmp(defel->defname, "commutator") == 0 ||
- strcmp(defel->defname, "negator") == 0 ||
- strcmp(defel->defname, "hashes") == 0 ||
- strcmp(defel->defname, "merges") == 0)
+ strcmp(defel->defname, "procedure") == 0)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
@@ -488,7 +521,7 @@ AlterOperator(AlterOperatorStmt *stmt)
NameStr(oprForm->oprname));
/*
- * Look up restriction and join estimators if specified
+ * Look up Oid for any parameters specified
*/
if (restrictionName)
restrictionOid = ValidateRestrictionEstimator(restrictionName);
@@ -499,28 +532,77 @@ AlterOperator(AlterOperatorStmt *stmt)
else
joinOid = InvalidOid;
- /* Perform additional checks, like OperatorCreate does */
- if (!(OidIsValid(oprForm->oprleft) && OidIsValid(oprForm->oprright)))
+ if (commutatorName)
{
- /* If it's not a binary op, these things mustn't be set: */
- if (OidIsValid(joinOid))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("only binary operators can have join selectivity")));
+ commutatorOid = OperatorGetOrCreateValidCommutator(commutatorName,
+ oprForm->oid,
+ oprForm->oprname.data,
+ oprForm->oprnamespace,
+ oprForm->oprleft,
+ oprForm->oprright);
+
+ /*
+ * we don't need to do anything extra for a self commutator as in
+ * OperatorCreate as there we have to create the operator before
+ * setting the commutator, but here the operator already exists and
+ * the commutatorOid above is valid (and is the operator oid).
+ */
}
+ else
+ commutatorOid = InvalidOid;
+
+ if (negatorName)
+ negatorOid = OperatorGetOrCreateValidNegator(negatorName,
+ oprForm->oid,
+ oprForm->oprname.data,
+ oprForm->oprnamespace,
+ oprForm->oprleft,
+ oprForm->oprright);
+ else
+ negatorOid = InvalidOid;
- if (oprForm->oprresult != BOOLOID)
+ /*
+ * check that we're not changing any existing values that can't be changed
+ */
+ if (OidIsValid(commutatorOid) && OidIsValid(oprForm->oprcom))
{
- if (OidIsValid(restrictionOid))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("only boolean operators can have restriction selectivity")));
- if (OidIsValid(joinOid))
- ereport(ERROR,
- (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("only boolean operators can have join selectivity")));
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("operator attribute \"commutator\" cannot be changed if it has already been set")));
}
+ if (OidIsValid(negatorOid) && OidIsValid(oprForm->oprnegate))
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("operator attribute \"negator\" cannot be changed if it has already been set")));
+ }
+
+ if (updateHashes && oprForm->oprcanhash)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("operator attribute \"hashes\" cannot be changed if it has already been set")));
+ }
+
+ if (updateMerges && oprForm->oprcanmerge)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("operator attribute \"merges\" cannot be changed if it has already been set")));
+ }
+
+ /* Perform additional checks, like OperatorCreate does */
+ OperatorValidateParams(oprForm->oprleft,
+ oprForm->oprright,
+ oprForm->oprresult,
+ OidIsValid(commutatorOid),
+ OidIsValid(negatorOid),
+ OidIsValid(joinOid),
+ OidIsValid(restrictionOid),
+ updateMerges,
+ updateHashes);
+
/* Update the tuple */
for (i = 0; i < Natts_pg_operator; ++i)
{
@@ -539,6 +621,30 @@ AlterOperator(AlterOperatorStmt *stmt)
values[Anum_pg_operator_oprjoin - 1] = joinOid;
}
+ if (OidIsValid(commutatorOid))
+ {
+ replaces[Anum_pg_operator_oprcom - 1] = true;
+ values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(commutatorOid);
+ }
+
+ if (OidIsValid(negatorOid))
+ {
+ replaces[Anum_pg_operator_oprnegate - 1] = true;
+ values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(negatorOid);
+ }
+
+ if (updateMerges)
+ {
+ replaces[Anum_pg_operator_oprcanmerge - 1] = true;
+ values[Anum_pg_operator_oprcanmerge - 1] = true;
+ }
+
+ if (updateHashes)
+ {
+ replaces[Anum_pg_operator_oprcanhash - 1] = true;
+ values[Anum_pg_operator_oprcanhash - 1] = true;
+ }
+
tup = heap_modify_tuple(tup, RelationGetDescr(catalog),
values, nulls, replaces);
@@ -550,5 +656,8 @@ AlterOperator(AlterOperatorStmt *stmt)
table_close(catalog, NoLock);
+ if (OidIsValid(commutatorOid) || OidIsValid(negatorOid))
+ OperatorUpd(oprId, commutatorOid, negatorOid, false);
+
return address;
}
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 39ab7eac0d..84017da3ee 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -10101,6 +10101,8 @@ operator_def_elem: ColLabel '=' NONE
{ $$ = makeDefElem($1, NULL, @1); }
| ColLabel '=' operator_def_arg
{ $$ = makeDefElem($1, (Node *) $3, @1); }
+ | ColLabel
+ { $$ = makeDefElem($1, NULL, @1); }
;
/* must be similar enough to def_arg to avoid reduce/reduce conflicts */
diff --git a/src/test/regress/expected/alter_operator.out b/src/test/regress/expected/alter_operator.out
index 71bd484282..57cf4df155 100644
--- a/src/test/regress/expected/alter_operator.out
+++ b/src/test/regress/expected/alter_operator.out
@@ -25,7 +25,7 @@ ORDER BY 1;
(3 rows)
--
--- Reset and set params
+-- test reset and set restrict and join
--
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
ALTER OPERATOR === (boolean, boolean) SET (JOIN = NONE);
@@ -106,34 +106,240 @@ ORDER BY 1;
schema public | n
(3 rows)
---
--- Test invalid options.
---
-ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
-ERROR: operator attribute "commutator" cannot be changed
-ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
-ERROR: operator attribute "negator" cannot be changed
+-- test cannot set non existant function
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = non_existent_func);
ERROR: function non_existent_func(internal, oid, internal, integer) does not exist
ALTER OPERATOR === (boolean, boolean) SET (JOIN = non_existent_func);
ERROR: function non_existent_func(internal, oid, internal, smallint, internal) does not exist
-ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
-ERROR: operator attribute "commutator" cannot be changed
-ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
-ERROR: operator attribute "negator" cannot be changed
--- invalid: non-lowercase quoted identifiers
+-- test non-lowercase quoted identifiers invalid
ALTER OPERATOR & (bit, bit) SET ("Restrict" = _int_contsel, "Join" = _int_contjoinsel);
ERROR: operator attribute "Restrict" not recognized
--
--- Test permission check. Must be owner to ALTER OPERATOR.
+-- test must be owner of operator to ALTER OPERATOR.
--
CREATE USER regress_alter_op_user;
SET SESSION AUTHORIZATION regress_alter_op_user;
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
ERROR: must be owner of operator ===
--- Clean up
RESET SESSION AUTHORIZATION;
+--
+-- test set commutator, negator, hashes, and merges which can only be set if not
+-- already set
+--
+-- for these tests create operators with different left and right types so that
+-- we can validate that function signatures are handled correctly
+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
+);
+-- test cannot set hashes or merges to false
+ALTER OPERATOR === (boolean, real) SET (HASHES = false);
+ERROR: alter operator cannot disable "hashes"
+ALTER OPERATOR === (boolean, real) SET (MERGES = false);
+ERROR: alter operator cannot disable "merges"
+-- test cannot set commutator or negator without owning them
+SET SESSION AUTHORIZATION regress_alter_op_user;
+-- we need need a new operator owned by regress_alter_op_user so that we are
+-- allowed to alter it. ==== and !==== are owned by the test user so we expect
+-- the alters below to fail.
+CREATE OPERATOR ===@@@ (
+ LEFTARG = boolean,
+ RIGHTARG = real,
+ PROCEDURE = alter_op_test_fn_bool_real
+);
+ALTER OPERATOR ===@@@ (boolean, real) SET (COMMUTATOR= ====);
+ERROR: must be owner of operator ====
+ALTER OPERATOR ===@@@ (boolean, real) SET (NEGATOR = !====);
+ERROR: must be owner of operator !====
+-- validate operator is unchanged and commutator and negator are unset
+SELECT oprcom, oprnegate FROM pg_operator WHERE oprname = '===@@@'
+ AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+ oprcom | oprnegate
+--------+-----------
+ 0 | 0
+(1 row)
+
+DROP OPERATOR ===@@@ (boolean, real);
+RESET SESSION AUTHORIZATION;
+-- test cannot set self negator
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = ===);
+ERROR: operator cannot be its own negator
+-- validate no changes made
+SELECT oprcanmerge, oprcanhash, oprcom, oprnegate
+FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+ oprcanmerge | oprcanhash | oprcom | oprnegate
+-------------+------------+--------+-----------
+ f | f | 0 | 0
+(1 row)
+
+-- test set hashes
+ALTER OPERATOR === (boolean, real) SET (HASHES);
+SELECT oprcanhash FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+ oprcanhash
+------------
+ t
+(1 row)
+
+-- test set merges
+ALTER OPERATOR === (boolean, real) SET (MERGES);
+SELECT oprcanmerge FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+ oprcanmerge
+-------------
+ t
+(1 row)
+
+-- test set commutator
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====);
+-- validate that the commutator 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;
+ operator_name | commutator_name | commutator_func
+---------------+-----------------+----------------------------
+ === | ==== | alter_op_test_fn_real_bool
+(1 row)
+
+-- test set negator
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====);
+-- validate that the negator 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;
+ operator_name | negator_name | negator_func
+---------------+--------------+----------------------------
+ === | !==== | alter_op_test_fn_bool_real
+(1 row)
+
+-- validate 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;
+ oprcanmerge | oprcanhash | commutator | negator
+-------------+------------+-----------------------------+------------------------------
+ t | t | operator ====(real,boolean) | operator !====(boolean,real)
+(1 row)
+
+-- test cannot set commutator, negator, hashes, and merges when already set
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = =);
+ERROR: operator attribute "commutator" cannot be changed if it has already been set
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = =);
+ERROR: operator attribute "negator" cannot be changed if it has already been set
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = !=);
+ERROR: operator attribute "commutator" cannot be changed if it has already been set
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = !=);
+ERROR: operator attribute "negator" cannot be changed if it has already been set
+ALTER OPERATOR === (boolean, real) SET (HASHES);
+ERROR: operator attribute "hashes" cannot be changed if it has already been set
+ALTER OPERATOR === (boolean, real) SET (MERGES);
+ERROR: operator attribute "merges" cannot be changed if it has already been set
+ALTER OPERATOR === (boolean, real) SET (HASHES = false);
+ERROR: alter operator cannot disable "hashes"
+ALTER OPERATOR === (boolean, real) SET (MERGES = false);
+ERROR: alter operator cannot disable "merges"
+-- validate 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;
+ oprcanmerge | oprcanhash | commutator | negator
+-------------+------------+-----------------------------+------------------------------
+ t | t | operator ====(real,boolean) | operator !====(boolean,real)
+(1 row)
+
+--
+-- test setting undefined operator creates shell operator (matches the
+-- behaviour of CREATE OPERATOR)
+--
+DROP OPERATOR === (boolean, real);
+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 = !===@@@);
+-- validate that shell operators are created for the commutator and negator with
+-- the commutator having reversed args and the negator matching the operator.
+-- The shell operators should have an empty function below.
+SELECT pg_describe_object('pg_operator'::regclass, op.oid, 0) AS operator,
+ pg_describe_object('pg_operator'::regclass, com.oid, 0) AS commutator,
+ com.oprcode AS commutator_func,
+ pg_describe_object('pg_operator'::regclass, neg.oid, 0) AS negator,
+ neg.oprcode AS negator_func
+ FROM pg_operator op
+ INNER JOIN pg_operator com ON (op.oid = com.oprcom AND op.oprcom = com.oid)
+ INNER JOIN pg_operator neg ON (op.oid = neg.oprnegate AND op.oprnegate = neg.oid)
+ WHERE op.oprname = '===' AND com.oprname = '===@@@' AND neg.oprname = '!===@@@'
+ AND op.oprleft = 'boolean'::regtype AND op.oprright = 'real'::regtype;
+ operator | commutator | commutator_func | negator | negator_func
+----------------------------+-------------------------------+-----------------+--------------------------------+--------------
+ operator ===(boolean,real) | operator ===@@@(real,boolean) | - | operator !===@@@(boolean,real) | -
+(1 row)
+
+--
+-- test setting self commutator
+--
+DROP OPERATOR === (boolean, boolean);
+CREATE OPERATOR === (
+ LEFTARG = boolean,
+ RIGHTARG = boolean,
+ PROCEDURE = alter_op_test_fn
+);
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ===);
+-- validate that the oprcom is the operator oid
+SELECT oprname FROM pg_operator
+ WHERE oprname = '===' AND oid = oprcom
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::regtype;
+ oprname
+---------
+ ===
+(1 row)
+
+--
+-- 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);
diff --git a/src/test/regress/sql/alter_operator.sql b/src/test/regress/sql/alter_operator.sql
index fd40370165..a7e0d4331f 100644
--- a/src/test/regress/sql/alter_operator.sql
+++ b/src/test/regress/sql/alter_operator.sql
@@ -22,7 +22,7 @@ WHERE classid = 'pg_operator'::regclass AND
ORDER BY 1;
--
--- Reset and set params
+-- test reset and set restrict and join
--
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
@@ -71,30 +71,215 @@ WHERE classid = 'pg_operator'::regclass AND
objid = '===(bool,bool)'::regoperator
ORDER BY 1;
---
--- Test invalid options.
---
-ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
-ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
+-- test cannot set non existant function
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = non_existent_func);
ALTER OPERATOR === (boolean, boolean) SET (JOIN = non_existent_func);
-ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = !==);
-ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = !==);
--- invalid: non-lowercase quoted identifiers
+-- test non-lowercase quoted identifiers invalid
ALTER OPERATOR & (bit, bit) SET ("Restrict" = _int_contsel, "Join" = _int_contjoinsel);
--
--- Test permission check. Must be owner to ALTER OPERATOR.
+-- test must be owner of operator to ALTER OPERATOR.
--
CREATE USER regress_alter_op_user;
SET SESSION AUTHORIZATION regress_alter_op_user;
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
--- Clean up
RESET SESSION AUTHORIZATION;
+
+--
+-- test set commutator, negator, hashes, and merges which can only be set if not
+-- already set
+--
+
+-- for these tests create operators with different left and right types so that
+-- we can validate that function signatures are handled correctly
+
+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
+);
+
+-- test cannot set hashes or merges to false
+ALTER OPERATOR === (boolean, real) SET (HASHES = false);
+ALTER OPERATOR === (boolean, real) SET (MERGES = false);
+
+-- test cannot set commutator or negator without owning them
+SET SESSION AUTHORIZATION regress_alter_op_user;
+
+-- we need need a new operator owned by regress_alter_op_user so that we are
+-- allowed to alter it. ==== and !==== are owned by the test user so we expect
+-- the alters below to fail.
+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 = !====);
+
+-- validate operator is unchanged and commutator and negator are unset
+SELECT oprcom, oprnegate FROM pg_operator WHERE oprname = '===@@@'
+ AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+
+DROP OPERATOR ===@@@ (boolean, real);
+
+RESET SESSION AUTHORIZATION;
+
+-- test cannot set self negator
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = ===);
+
+-- validate no changes made
+SELECT oprcanmerge, oprcanhash, oprcom, oprnegate
+FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+
+-- test set hashes
+ALTER OPERATOR === (boolean, real) SET (HASHES);
+SELECT oprcanhash FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+
+-- test set merges
+ALTER OPERATOR === (boolean, real) SET (MERGES);
+SELECT oprcanmerge FROM pg_operator WHERE oprname = '==='
+ AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
+
+-- test set commutator
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====);
+
+-- validate that the commutator 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;
+
+-- test set negator
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====);
+
+-- validate that the negator 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;
+
+-- validate 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;
+
+-- test cannot set commutator, negator, hashes, and merges when already set
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = =);
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = =);
+ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = !=);
+ALTER OPERATOR === (boolean, real) SET (NEGATOR = !=);
+ALTER OPERATOR === (boolean, real) SET (HASHES);
+ALTER OPERATOR === (boolean, real) SET (MERGES);
+ALTER OPERATOR === (boolean, real) SET (HASHES = false);
+ALTER OPERATOR === (boolean, real) SET (MERGES = false);
+
+-- validate 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;
+
+--
+-- test setting undefined operator creates shell operator (matches the
+-- behaviour of CREATE OPERATOR)
+--
+
+DROP OPERATOR === (boolean, real);
+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 = !===@@@);
+
+-- validate that shell operators are created for the commutator and negator with
+-- the commutator having reversed args and the negator matching the operator.
+-- The shell operators should have an empty function below.
+SELECT pg_describe_object('pg_operator'::regclass, op.oid, 0) AS operator,
+ pg_describe_object('pg_operator'::regclass, com.oid, 0) AS commutator,
+ com.oprcode AS commutator_func,
+ pg_describe_object('pg_operator'::regclass, neg.oid, 0) AS negator,
+ neg.oprcode AS negator_func
+ FROM pg_operator op
+ INNER JOIN pg_operator com ON (op.oid = com.oprcom AND op.oprcom = com.oid)
+ INNER JOIN pg_operator neg ON (op.oid = neg.oprnegate AND op.oprnegate = neg.oid)
+ WHERE op.oprname = '===' AND com.oprname = '===@@@' AND neg.oprname = '!===@@@'
+ AND op.oprleft = 'boolean'::regtype AND op.oprright = 'real'::regtype;
+
+
+--
+-- test setting self commutator
+--
+
+DROP OPERATOR === (boolean, boolean);
+CREATE OPERATOR === (
+ LEFTARG = boolean,
+ RIGHTARG = boolean,
+ PROCEDURE = alter_op_test_fn
+);
+
+ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ===);
+
+-- validate that the oprcom is the operator oid
+SELECT oprname FROM pg_operator
+ WHERE oprname = '===' AND oid = oprcom
+ AND oprleft = 'boolean'::regtype AND oprright = 'boolean'::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);