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);