diff --git a/doc/src/sgml/ref/alter_operator.sgml b/doc/src/sgml/ref/alter_operator.sgml index a4a1af564f..ce447e22b0 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,9 +125,64 @@ 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 enabled and not disabled. + + + + + + MERGES + + + Indicates this operator can support a merge join. Can only be enabled and not disabled. + + + + + + Notes + + + Refer to and for further information. + + + + Since commutators come in pairs and are commutators of each other, + PostgreSQL will set the commutator of the + com_op to the operator. + + + + Like with commutators, PostgreSQL will set the + negator of the neg_op to the operator. + + + Examples diff --git a/doc/src/sgml/ref/create_operator.sgml b/doc/src/sgml/ref/create_operator.sgml index e27512ff39..9801eb3ed0 100644 --- a/doc/src/sgml/ref/create_operator.sgml +++ b/doc/src/sgml/ref/create_operator.sgml @@ -231,9 +231,60 @@ COMMUTATOR = OPERATOR(myschema.===) , Notes - Refer to for further information. + Refer to and for further information. + + When you are defining a self-commutative operator, you just do it. + When you are defining a pair of commutative operators, things are + a little trickier: how can the first one to be defined refer to the + other one, which you haven't defined yet? There are three solutions + to this problem: + + + + + One way is to omit the COMMUTATOR clause in the first operator that + you define, and then provide one in the second operator's definition. + Since PostgreSQL knows that commutative + operators come in pairs, when it sees the second definition it will + automatically go back and fill in the missing COMMUTATOR clause in + the first definition. + + + + + + Another, more straightforward way is just to include COMMUTATOR clauses + in both definitions. When PostgreSQL processes + the first definition and realizes that COMMUTATOR refers to a nonexistent + operator, the system will make a dummy entry for that operator in the + system catalog. This dummy entry will have valid data only + for the operator name, left and right operand types, and result type, + since that's all that PostgreSQL can deduce + at this point. The first operator's catalog entry will link to this + dummy entry. Later, when you define the second operator, the system + updates the dummy entry with the additional information from the second + definition. If you try to use the dummy operator before it's been filled + in, you'll just get an error message. + + + + + + Alternatively, both operators can be defined without a COMMUTATOR + and then ALTER OPERATOR can be used to set the COMMUTATOR + for the operator. + + + + + + + Pairs of negator operators can be defined using the same methods + explained above for commutator pairs. + + It is not possible to specify an operator's lexical precedence in CREATE OPERATOR, because the parser's precedence behavior diff --git a/doc/src/sgml/xoper.sgml b/doc/src/sgml/xoper.sgml index a929ced07d..31bc8df3de 100644 --- a/doc/src/sgml/xoper.sgml +++ b/doc/src/sgml/xoper.sgml @@ -146,44 +146,6 @@ SELECT (a + b) AS c FROM test_complex; = operator must specify that it is valid, by marking the operator with commutator information. - - - When you are defining a self-commutative operator, you just do it. - When you are defining a pair of commutative operators, things are - a little trickier: how can the first one to be defined refer to the - other one, which you haven't defined yet? There are two solutions - to this problem: - - - - - One way is to omit the COMMUTATOR clause in the first operator that - you define, and then provide one in the second operator's definition. - Since PostgreSQL knows that commutative - operators come in pairs, when it sees the second definition it will - automatically go back and fill in the missing COMMUTATOR clause in - the first definition. - - - - - - The other, more straightforward way is just to include COMMUTATOR clauses - in both definitions. When PostgreSQL processes - the first definition and realizes that COMMUTATOR refers to a nonexistent - operator, the system will make a dummy entry for that operator in the - system catalog. This dummy entry will have valid data only - for the operator name, left and right operand types, and result type, - since that's all that PostgreSQL can deduce - at this point. The first operator's catalog entry will link to this - dummy entry. Later, when you define the second operator, the system - updates the dummy entry with the additional information from the second - definition. If you try to use the dummy operator before it's been filled - in, you'll just get an error message. - - - - @@ -218,12 +180,7 @@ SELECT (a + b) AS c FROM test_complex; NOT operations can be inserted as a consequence of other rearrangements. - - Pairs of negator operators can be defined using the same methods - explained above for commutator pairs. - - - + <literal>RESTRICT</literal> diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c index b75f7a9bc1..578c89b6f9 100644 --- a/src/backend/catalog/pg_operator.c +++ b/src/backend/catalog/pg_operator.c @@ -44,11 +44,6 @@ static Oid OperatorGet(const char *operatorName, Oid rightObjectId, bool *defined); -static Oid OperatorLookup(List *operatorName, - Oid leftObjectId, - Oid rightObjectId, - bool *defined); - static Oid OperatorShellMake(const char *operatorName, Oid operatorNamespace, Oid leftTypeId, @@ -165,7 +160,7 @@ OperatorGet(const char *operatorName, * * *defined is set true if defined (not a shell) */ -static Oid +Oid OperatorLookup(List *operatorName, Oid leftObjectId, Oid rightObjectId, diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c index cd7f83136f..6666bf476f 100644 --- a/src/backend/commands/operatorcmds.c +++ b/src/backend/commands/operatorcmds.c @@ -54,6 +54,9 @@ static Oid ValidateRestrictionEstimator(List *restrictionName); static Oid ValidateJoinEstimator(List *joinName); +static Oid ValidateOperatorDefined(List *name, + Oid leftTypeId, + Oid rightTypeId); /* * DefineOperator @@ -359,6 +362,44 @@ ValidateJoinEstimator(List *joinName) return joinOid; } +/* + * Looks up and returns an oid for an operator given a possibly-qualified name + * and left and right type IDs, verifies the operator is defined (non-shell), + * and owned by the current user. Raises an error otherwise. + */ +static Oid +ValidateOperatorDefined(List *name, + Oid leftTypeId, + Oid rightTypeId) +{ + Oid oid; + bool defined; + + oid = OperatorLookup(name, + leftTypeId, + rightTypeId, + &defined); + + if (!OidIsValid(oid)) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("operator %s does not exist", + NameListToString(name)))); + + if (!defined) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_FUNCTION), + errmsg("operator %s is undefined shell operator", + NameListToString(name)))); + + if (!object_ownercheck(OperatorRelationId, oid, GetUserId())) + aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR, + NameListToString(name)); + + return oid; +} + + /* * Guts of operator deletion. */ @@ -406,6 +447,8 @@ RemoveOperatorById(Oid operOid) * routine implementing ALTER OPERATOR SET (option = ...). * * Currently, only RESTRICT and JOIN estimator functions can be changed. + * COMMUTATOR and NEGATOR, HASHES, and MERGES can be set if they have not been set + * previously. */ ObjectAddress AlterOperator(AlterOperatorStmt *stmt) @@ -426,6 +469,14 @@ 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 canHash = false; + bool updateMerges = false; + bool canMerge = false; /* Look up the operator */ oprId = LookupOperWithArgs(stmt->opername, false); @@ -456,6 +507,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) + { + canHash = defGetBoolean(defel); + updateHashes = true; + } + else if (strcmp(defel->defname, "merges") == 0) + { + canMerge = defGetBoolean(defel); + updateMerges = true; + } /* * The rest of the options that CREATE accepts cannot be changed. @@ -464,11 +533,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 +553,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 +564,85 @@ 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"))); + /* commutator has reversed arg types */ + commutatorOid = ValidateOperatorDefined(commutatorName, + oprForm->oprright, + oprForm->oprleft); + + /* + * 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 (oprForm->oprresult != BOOLOID) + if (negatorName) { - if (OidIsValid(restrictionOid)) - ereport(ERROR, - (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("only boolean operators can have restriction selectivity"))); - if (OidIsValid(joinOid)) + negatorOid = ValidateOperatorDefined(negatorName, + oprForm->oprleft, + oprForm->oprright); + + if (negatorOid == oprForm->oid) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), - errmsg("only boolean operators can have join selectivity"))); + errmsg("operator cannot be its own negator"))); + } + else + { + negatorOid = InvalidOid; + } + + /* + * check that we're not changing any existing values that might be + * depended on elsewhere and may be expected to never change, while + * allowing no-ops. + */ + if (OidIsValid(commutatorOid) && OidIsValid(oprForm->oprcom) + && commutatorOid != oprForm->oprcom) + { + 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) + && negatorOid != 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 && !canHash) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("operator attribute \"hashes\" cannot be disabled after it has been enabled"))); + } + + if (updateMerges && oprForm->oprcanmerge && !canMerge) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), + errmsg("operator attribute \"merges\" cannot be disabled after it has been enabled"))); } + /* 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 +661,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] = canMerge; + } + + if (updateHashes) + { + replaces[Anum_pg_operator_oprcanhash - 1] = true; + values[Anum_pg_operator_oprcanhash - 1] = canHash; + } + tup = heap_modify_tuple(tup, RelationGetDescr(catalog), values, nulls, replaces); @@ -546,6 +692,9 @@ AlterOperator(AlterOperatorStmt *stmt) address = makeOperatorDependencies(tup, false, true); + if (OidIsValid(commutatorOid) || OidIsValid(negatorOid)) + OperatorUpd(oprId, commutatorOid, negatorOid, false); + InvokeObjectPostAlterHook(OperatorRelationId, oprId, 0); table_close(catalog, NoLock); diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index e56cbe77cb..0c7a1f77e0 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -10151,6 +10151,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/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h index a670973ab2..13ebc592f2 100644 --- a/src/include/catalog/pg_operator.h +++ b/src/include/catalog/pg_operator.h @@ -85,6 +85,11 @@ typedef FormData_pg_operator *Form_pg_operator; DECLARE_UNIQUE_INDEX_PKEY(pg_operator_oid_index, 2688, OperatorOidIndexId, pg_operator, btree(oid oid_ops)); DECLARE_UNIQUE_INDEX(pg_operator_oprname_l_r_n_index, 2689, OperatorNameNspIndexId, pg_operator, btree(oprname name_ops, oprleft oid_ops, oprright oid_ops, oprnamespace oid_ops)); +extern Oid + OperatorLookup(List *operatorName, + Oid leftObjectId, + Oid rightObjectId, + bool *defined); extern ObjectAddress OperatorCreate(const char *operatorName, Oid operatorNamespace, diff --git a/src/test/regress/expected/alter_operator.out b/src/test/regress/expected/alter_operator.out index 71bd484282..ea5d79b054 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,282 @@ 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 no-op setting already false hashes and merges to false works +ALTER OPERATOR === (boolean, real) SET (HASHES = false); +ALTER OPERATOR === (boolean, real) SET (MERGES = false); +-- validate still false after no-op +SELECT oprcanhash, oprcanmerge FROM pg_operator WHERE oprname = '===' + AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype; + oprcanhash | oprcanmerge +------------+------------- + f | f +(1 row) + +-- 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 no-op set 'succeeds' +ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====); +ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====); +ALTER OPERATOR === (boolean, real) SET (HASHES); +ALTER OPERATOR === (boolean, real) SET (MERGES); +-- test cannot change commutator, negator, hashes, and merges 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 = @=); +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 = false); +ERROR: operator attribute "hashes" cannot be disabled after it has been enabled +ALTER OPERATOR === (boolean, real) SET (MERGES = false); +ERROR: operator attribute "merges" cannot be disabled after it has been enabled +-- test cannot set an operator that aleady has a commutator as the commutator +ALTER OPERATOR @=(real, boolean) SET (COMMUTATOR = ===); +ERROR: commutator commutates another operator +SELECT oprcom FROM pg_operator WHERE oprname = '@='; + oprcom +-------- + 0 +(1 row) + +-- test cannot set an operator that already has a negator as the negator +ALTER OPERATOR @!=(boolean, real) SET (NEGATOR = ===); +ERROR: negator negates another operator +SELECT oprnegate FROM pg_operator WHERE oprname = '@!='; + oprnegate +----------- + 0 +(1 row) + +-- 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) + +DROP OPERATOR @=(real, boolean); +DROP OPERATOR @!=(boolean, real); +-- +-- test setting commutator and negator to undefined or shell operator fails +-- +DROP OPERATOR === (boolean, real); +CREATE OPERATOR === ( + LEFTARG = boolean, + RIGHTARG = real, + PROCEDURE = alter_op_test_fn_bool_real +); +-- test cannot set undefined +ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ===@@@); +ERROR: operator ===@@@ does not exist +ALTER OPERATOR === (boolean, real) SET (NEGATOR = !===@@@); +ERROR: operator !===@@@ does not exist +-- create shell operators for ===@@@ and !===@@@ +CREATE OPERATOR ===@@ ( + LEFTARG = boolean, + RIGHTARG = real, + PROCEDURE = alter_op_test_fn_bool_real, + COMMUTATOR = ===@@@, + NEGATOR = !===@@@ +); +-- test cannot set shell operators +ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ===@@@); +ERROR: operator ===@@@ is undefined shell operator +ALTER OPERATOR === (boolean, real) SET (NEGATOR = !===@@@); +ERROR: operator !===@@@ is undefined shell operator +-- 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 +-------------+------------+------------+--------- + f | f | | +(1 row) + +DROP OPERATOR ===@@(boolean, real); +DROP OPERATOR ===@@@(real, boolean); +DROP OPERATOR !===@@@(boolean, real); +-- +-- 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 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..85004d4191 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,251 @@ 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 no-op setting already false hashes and merges to false works +ALTER OPERATOR === (boolean, real) SET (HASHES = false); +ALTER OPERATOR === (boolean, real) SET (MERGES = false); + +-- validate still false after no-op +SELECT oprcanhash, oprcanmerge FROM pg_operator WHERE oprname = '===' + AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype; + +-- 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 no-op set 'succeeds' +ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====); +ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====); +ALTER OPERATOR === (boolean, real) SET (HASHES); +ALTER OPERATOR === (boolean, real) SET (MERGES); + +-- test cannot change commutator, negator, hashes, and merges 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 (HASHES = false); +ALTER OPERATOR === (boolean, real) SET (MERGES = false); + +-- test cannot set an operator that aleady has a commutator as the commutator +ALTER OPERATOR @=(real, boolean) SET (COMMUTATOR = ===); +SELECT oprcom FROM pg_operator WHERE oprname = '@='; + +-- test cannot set an operator that already has a negator as the negator +ALTER OPERATOR @!=(boolean, real) SET (NEGATOR = ===); +SELECT oprnegate FROM pg_operator WHERE oprname = '@!='; + +-- 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; + +DROP OPERATOR @=(real, boolean); +DROP OPERATOR @!=(boolean, real); + +-- +-- test setting commutator and negator to undefined or shell operator fails +-- + +DROP OPERATOR === (boolean, real); +CREATE OPERATOR === ( + LEFTARG = boolean, + RIGHTARG = real, + PROCEDURE = alter_op_test_fn_bool_real +); + +-- test cannot set undefined +ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ===@@@); +ALTER OPERATOR === (boolean, real) SET (NEGATOR = !===@@@); + +-- create shell operators for ===@@@ and !===@@@ +CREATE OPERATOR ===@@ ( + LEFTARG = boolean, + RIGHTARG = real, + PROCEDURE = alter_op_test_fn_bool_real, + COMMUTATOR = ===@@@, + NEGATOR = !===@@@ +); + +-- test cannot set shell operators +ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ===@@@); +ALTER OPERATOR === (boolean, real) SET (NEGATOR = !===@@@); + +-- 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; + +DROP OPERATOR ===@@(boolean, real); +DROP OPERATOR ===@@@(real, boolean); +DROP OPERATOR !===@@@(boolean, real); + +-- +-- 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 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);