Extend ALTER OPERATOR to allow setting more optimization attributes.

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

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

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

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

Tommy Pavlicek, reviewed and editorialized a bit by me

Discussion: https://postgr.es/m/CAEhP-W-vGVzf4udhR5M8Bdv88UYnPrhoSkj3ieR3QNrsGQoqdg@mail.gmail.com
This commit is contained in:
Tom Lane 2023-10-20 12:28:38 -04:00
parent dcd4454590
commit 2b5154beab
13 changed files with 871 additions and 207 deletions

View File

@ -30,7 +30,11 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</replaceable> | NONE } , <replaceable>right_type</replaceable> )
SET ( { RESTRICT = { <replaceable class="parameter">res_proc</replaceable> | NONE }
| JOIN = { <replaceable class="parameter">join_proc</replaceable> | NONE }
} [, ... ] )
| COMMUTATOR = <replaceable class="parameter">com_op</replaceable>
| NEGATOR = <replaceable class="parameter">neg_op</replaceable>
| HASHES
| MERGES
} [, ... ] )
</synopsis>
</refsynopsisdiv>
@ -121,9 +125,69 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">com_op</replaceable></term>
<listitem>
<para>
The commutator of this operator. Can only be changed if the operator
does not have an existing commutator.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable class="parameter">neg_op</replaceable></term>
<listitem>
<para>
The negator of this operator. Can only be changed if the operator does
not have an existing negator.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>HASHES</literal></term>
<listitem>
<para>
Indicates this operator can support a hash join. Can only be enabled and
not disabled.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><literal>MERGES</literal></term>
<listitem>
<para>
Indicates this operator can support a merge join. Can only be enabled
and not disabled.
</para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1>
<title>Notes</title>
<para>
Refer to <xref linkend="xoper"/> and
<xref linkend="xoper-optimization"/> for further information.
</para>
<para>
Since commutators come in pairs that are commutators of each other,
<literal>ALTER OPERATOR SET COMMUTATOR</literal> will also set the
commutator of the <replaceable class="parameter">com_op</replaceable>
to be the target operator. Likewise, <literal>ALTER OPERATOR SET
NEGATOR</literal> will also set the negator of
the <replaceable class="parameter">neg_op</replaceable> to be the
target operator. Therefore, you must own the commutator or negator
operator as well as the target operator.
</para>
</refsect1>
<refsect1>
<title>Examples</title>
@ -131,13 +195,25 @@ ALTER OPERATOR <replaceable>name</replaceable> ( { <replaceable>left_type</repla
Change the owner of a custom operator <literal>a @@ b</literal> for type <type>text</type>:
<programlisting>
ALTER OPERATOR @@ (text, text) OWNER TO joe;
</programlisting></para>
</programlisting>
</para>
<para>
Change the restriction and join selectivity estimator functions of a custom operator <literal>a &amp;&amp; b</literal> for type <type>int[]</type>:
Change the restriction and join selectivity estimator functions of a
custom operator <literal>a &amp;&amp; b</literal> for
type <type>int[]</type>:
<programlisting>
ALTER OPERATOR &amp;&amp; (_int4, _int4) SET (RESTRICT = _int_contsel, JOIN = _int_contjoinsel);
</programlisting></para>
ALTER OPERATOR &amp;&amp; (int[], int[]) SET (RESTRICT = _int_contsel, JOIN = _int_contjoinsel);
</programlisting>
</para>
<para>
Mark the <literal>&amp;&amp;</literal> operator as being its own
commutator:
<programlisting>
ALTER OPERATOR &amp;&amp; (int[], int[]) SET (COMMUTATOR = &amp;&amp;);
</programlisting>
</para>
</refsect1>

View File

@ -104,7 +104,7 @@ CREATE OPERATOR <replaceable>name</replaceable> (
</para>
<para>
The other clauses specify optional operator optimization clauses.
The other clauses specify optional operator optimization attributes.
Their meaning is detailed in <xref linkend="xoper-optimization"/>.
</para>
@ -112,7 +112,7 @@ CREATE OPERATOR <replaceable>name</replaceable> (
To be able to create an operator, you must have <literal>USAGE</literal>
privilege on the argument types and the return type, as well
as <literal>EXECUTE</literal> privilege on the underlying function. If a
commutator or negator operator is specified, you must own these operators.
commutator or negator operator is specified, you must own those operators.
</para>
</refsect1>
@ -231,7 +231,67 @@ COMMUTATOR = OPERATOR(myschema.===) ,
<title>Notes</title>
<para>
Refer to <xref linkend="xoper"/> for further information.
Refer to <xref linkend="xoper"/> and <xref linkend="xoper-optimization"/>
for further information.
</para>
<para>
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:
<itemizedlist>
<listitem>
<para>
One way is to omit the <literal>COMMUTATOR</literal> clause in the
first operator that you define, and then provide one in the second
operator's definition. Since <productname>PostgreSQL</productname>
knows that commutative operators come in pairs, when it sees the
second definition it will automatically go back and fill in the
missing <literal>COMMUTATOR</literal> clause in the first
definition.
</para>
</listitem>
<listitem>
<para>
Another, more straightforward way is just to
include <literal>COMMUTATOR</literal> clauses in both definitions.
When <productname>PostgreSQL</productname> processes the first
definition and realizes that <literal>COMMUTATOR</literal> 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
owner, since that's all that <productname>PostgreSQL</productname>
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.
</para>
</listitem>
<listitem>
<para>
Alternatively, both operators can be defined
without <literal>COMMUTATOR</literal> clauses
and then <command>ALTER OPERATOR</command> can be used to set their
commutator links. It's sufficient to <command>ALTER</command>
either one of the pair.
</para>
</listitem>
</itemizedlist>
In all three cases, you must own both operators in order to mark
them as commutators.
</para>
<para>
Pairs of negator operators can be defined using the same methods
as for commutator pairs.
</para>
<para>

View File

@ -146,44 +146,6 @@ SELECT (a + b) AS c FROM test_complex;
<literal>=</literal> operator must specify that it is valid, by marking the
operator with commutator information.
</para>
<para>
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:
<itemizedlist>
<listitem>
<para>
One way is to omit the <literal>COMMUTATOR</literal> clause in the first operator that
you define, and then provide one in the second operator's definition.
Since <productname>PostgreSQL</productname> knows that commutative
operators come in pairs, when it sees the second definition it will
automatically go back and fill in the missing <literal>COMMUTATOR</literal> clause in
the first definition.
</para>
</listitem>
<listitem>
<para>
The other, more straightforward way is just to include <literal>COMMUTATOR</literal> clauses
in both definitions. When <productname>PostgreSQL</productname> processes
the first definition and realizes that <literal>COMMUTATOR</literal> 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 <productname>PostgreSQL</productname> 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.
</para>
</listitem>
</itemizedlist>
</para>
</sect2>
<sect2 id="xoper-negator">
@ -217,12 +179,6 @@ SELECT (a + b) AS c FROM test_complex;
<literal>x &lt;&gt; y</literal>. This comes up more often than you might think, because
<literal>NOT</literal> operations can be inserted as a consequence of other rearrangements.
</para>
<para>
Pairs of negator operators can be defined using the same methods
explained above for commutator pairs.
</para>
</sect2>
<sect2 id="xoper-restrict">

View File

@ -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,
@ -57,8 +52,7 @@ static Oid OperatorShellMake(const char *operatorName,
static Oid get_other_operator(List *otherOp,
Oid otherLeftTypeId, Oid otherRightTypeId,
const char *operatorName, Oid operatorNamespace,
Oid leftTypeId, Oid rightTypeId,
bool isCommutator);
Oid leftTypeId, Oid rightTypeId);
/*
@ -166,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,
@ -361,53 +355,17 @@ OperatorCreate(const char *operatorName,
errmsg("\"%s\" is not a valid operator name",
operatorName)));
if (!(OidIsValid(leftTypeId) && OidIsValid(rightTypeId)))
{
/* If it's not a binary op, these things mustn't be set: */
if (commutatorName)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only binary operators can have commutators")));
if (OidIsValid(joinId))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only binary operators can have join selectivity")));
if (canMerge)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only binary operators can merge join")));
if (canHash)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only binary operators can hash")));
}
operResultType = get_func_rettype(procedureId);
if (operResultType != BOOLOID)
{
/* If it's not a boolean op, these things mustn't be set: */
if (negatorName)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only boolean operators can have negators")));
if (OidIsValid(restrictionId))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only boolean operators can have restriction selectivity")));
if (OidIsValid(joinId))
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only boolean operators can have join selectivity")));
if (canMerge)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only boolean operators can merge join")));
if (canHash)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only boolean operators can hash")));
}
OperatorValidateParams(leftTypeId,
rightTypeId,
operResultType,
commutatorName != NIL,
negatorName != NIL,
OidIsValid(restrictionId),
OidIsValid(joinId),
canMerge,
canHash);
operatorObjectId = OperatorGet(operatorName,
operatorNamespace,
@ -442,8 +400,7 @@ OperatorCreate(const char *operatorName,
commutatorId = get_other_operator(commutatorName,
rightTypeId, leftTypeId,
operatorName, operatorNamespace,
leftTypeId, rightTypeId,
true);
leftTypeId, rightTypeId);
/* Permission check: must own other operator */
if (OidIsValid(commutatorId) &&
@ -452,8 +409,9 @@ OperatorCreate(const char *operatorName,
NameListToString(commutatorName));
/*
* self-linkage to this operator; will fix below. Note that only
* self-linkage for commutation makes sense.
* If self-linkage to the new operator is requested, we'll fix it
* below. (In case of self-linkage to an existing shell operator, we
* need do nothing special.)
*/
if (!OidIsValid(commutatorId))
selfCommutator = true;
@ -467,14 +425,24 @@ OperatorCreate(const char *operatorName,
negatorId = get_other_operator(negatorName,
leftTypeId, rightTypeId,
operatorName, operatorNamespace,
leftTypeId, rightTypeId,
false);
leftTypeId, rightTypeId);
/* Permission check: must own other operator */
if (OidIsValid(negatorId) &&
!object_ownercheck(OperatorRelationId, negatorId, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR,
NameListToString(negatorName));
/*
* Prevent self negation, as it doesn't make sense. It's self
* negation if result is InvalidOid (negator would be the same
* operator but it doesn't exist yet) or operatorObjectId (we are
* replacing a shell that would need to be its own negator).
*/
if (!OidIsValid(negatorId) || negatorId == operatorObjectId)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("operator cannot be its own negator")));
}
else
negatorId = InvalidOid;
@ -548,11 +516,6 @@ OperatorCreate(const char *operatorName,
/* Add dependencies for the entry */
address = makeOperatorDependencies(tup, true, isUpdate);
/* Post creation hook for new operator */
InvokeObjectPostCreateHook(OperatorRelationId, operatorObjectId, 0);
table_close(pg_operator_desc, RowExclusiveLock);
/*
* If a commutator and/or negator link is provided, update the other
* operator(s) to point at this one, if they don't already have a link.
@ -570,21 +533,95 @@ OperatorCreate(const char *operatorName,
if (OidIsValid(commutatorId) || OidIsValid(negatorId))
OperatorUpd(operatorObjectId, commutatorId, negatorId, false);
/* Post creation hook for new operator */
InvokeObjectPostCreateHook(OperatorRelationId, operatorObjectId, 0);
table_close(pg_operator_desc, RowExclusiveLock);
return address;
}
/*
* Try to lookup another operator (commutator, etc)
* OperatorValidateParams
*
* If not found, check to see if it is exactly the operator we are trying
* to define; if so, return InvalidOid. (Note that this case is only
* sensible for a commutator, so we error out otherwise.) If it is not
* the same operator, create a shell operator.
* Check that an operator with argument types leftTypeId and rightTypeId,
* returning operResultType, can have the attributes that are set to true.
* Raise an error for any disallowed attribute.
*
* Note: in ALTER OPERATOR, we only bother to pass "true" for attributes
* the command is trying to set, not those that may already be set.
* This is OK as long as the attribute checks are independent.
*/
void
OperatorValidateParams(Oid leftTypeId,
Oid rightTypeId,
Oid operResultType,
bool hasCommutator,
bool hasNegator,
bool hasRestrictionSelectivity,
bool hasJoinSelectivity,
bool canMerge,
bool canHash)
{
if (!(OidIsValid(leftTypeId) && OidIsValid(rightTypeId)))
{
/* If it's not a binary op, these things mustn't be set: */
if (hasCommutator)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only binary operators can have commutators")));
if (hasJoinSelectivity)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only binary operators can have join selectivity")));
if (canMerge)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only binary operators can merge join")));
if (canHash)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only binary operators can hash")));
}
if (operResultType != BOOLOID)
{
/* If it's not a boolean op, these things mustn't be set: */
if (hasNegator)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only boolean operators can have negators")));
if (hasRestrictionSelectivity)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only boolean operators can have restriction selectivity")));
if (hasJoinSelectivity)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only boolean operators can have join selectivity")));
if (canMerge)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only boolean operators can merge join")));
if (canHash)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only boolean operators can hash")));
}
}
/*
* Try to lookup another operator (commutator, etc); return its OID
*
* If not found, check to see if it would be the same operator we are trying
* to define; if so, return InvalidOid. (Caller must decide whether
* that is sensible.) If it is not the same operator, create a shell
* operator.
*/
static Oid
get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
const char *operatorName, Oid operatorNamespace,
Oid leftTypeId, Oid rightTypeId, bool isCommutator)
Oid leftTypeId, Oid rightTypeId)
{
Oid other_oid;
bool otherDefined;
@ -611,14 +648,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
otherLeftTypeId == leftTypeId &&
otherRightTypeId == rightTypeId)
{
/*
* self-linkage to this operator; caller will fix later. Note that
* only self-linkage for commutation makes sense.
*/
if (!isCommutator)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("operator cannot be its own negator or sort operator")));
/* self-linkage to new operator; caller must handle this */
return InvalidOid;
}
@ -643,7 +673,7 @@ get_other_operator(List *otherOp, Oid otherLeftTypeId, Oid otherRightTypeId,
* For a given operator, look up its negator and commutator operators.
* When isDelete is false, update their negator and commutator fields to
* point back to the given operator; when isDelete is true, update those
* fields to no longer point back to the given operator.
* fields to be InvalidOid.
*
* The !isDelete case solves a problem for users who need to insert two new
* operators that are the negator or commutator of each other, while the
@ -681,17 +711,40 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId, bool isDelete)
bool update_commutator = false;
/*
* Out of due caution, we only change the commutator's oprcom field if
* it has the exact value we expected: InvalidOid when creating an
* operator, or baseId when dropping one.
* We can skip doing anything if the commutator's oprcom field is
* already what we want. While that's not expected in the isDelete
* case, it's perfectly possible when filling in a shell operator.
*/
if (isDelete && t->oprcom == baseId)
if (isDelete && OidIsValid(t->oprcom))
{
t->oprcom = InvalidOid;
update_commutator = true;
}
else if (!isDelete && !OidIsValid(t->oprcom))
else if (!isDelete && t->oprcom != baseId)
{
/*
* If commutator's oprcom field is already set to point to some
* third operator, it's an error. Changing its link would be
* unsafe, and letting the inconsistency stand would not be good
* either. This might be indicative of catalog corruption, so
* don't assume t->oprcom is necessarily a valid operator.
*/
if (OidIsValid(t->oprcom))
{
char *thirdop = get_opname(t->oprcom);
if (thirdop != NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("commutator operator %s is already the commutator of operator %s",
NameStr(t->oprname), thirdop)));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("commutator operator %s is already the commutator of operator %u",
NameStr(t->oprname), t->oprcom)));
}
t->oprcom = baseId;
update_commutator = true;
}
@ -726,17 +779,40 @@ OperatorUpd(Oid baseId, Oid commId, Oid negId, bool isDelete)
bool update_negator = false;
/*
* Out of due caution, we only change the negator's oprnegate field if
* it has the exact value we expected: InvalidOid when creating an
* operator, or baseId when dropping one.
* We can skip doing anything if the negator's oprnegate field is
* already what we want. While that's not expected in the isDelete
* case, it's perfectly possible when filling in a shell operator.
*/
if (isDelete && t->oprnegate == baseId)
if (isDelete && OidIsValid(t->oprnegate))
{
t->oprnegate = InvalidOid;
update_negator = true;
}
else if (!isDelete && !OidIsValid(t->oprnegate))
else if (!isDelete && t->oprnegate != baseId)
{
/*
* If negator's oprnegate field is already set to point to some
* third operator, it's an error. Changing its link would be
* unsafe, and letting the inconsistency stand would not be good
* either. This might be indicative of catalog corruption, so
* don't assume t->oprnegate is necessarily a valid operator.
*/
if (OidIsValid(t->oprnegate))
{
char *thirdop = get_opname(t->oprnegate);
if (thirdop != NULL)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("negator operator %s is already the negator of operator %s",
NameStr(t->oprname), thirdop)));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("negator operator %s is already the negator of operator %u",
NameStr(t->oprname), t->oprnegate)));
}
t->oprnegate = baseId;
update_negator = true;
}

View File

@ -54,6 +54,9 @@
static Oid ValidateRestrictionEstimator(List *restrictionName);
static Oid ValidateJoinEstimator(List *joinName);
static Oid ValidateOperatorReference(List *name,
Oid leftTypeId,
Oid rightTypeId);
/*
* DefineOperator
@ -359,6 +362,53 @@ ValidateJoinEstimator(List *joinName)
return joinOid;
}
/*
* Look up and return the OID of an operator,
* given a possibly-qualified name and left and right type IDs.
*
* Verifies that the operator is defined (not a shell) and owned by
* the current user, so that we have permission to associate it with
* the operator being altered. Rejecting shell operators is a policy
* choice to help catch mistakes, rather than something essential.
*/
static Oid
ValidateOperatorReference(List *name,
Oid leftTypeId,
Oid rightTypeId)
{
Oid oid;
bool defined;
oid = OperatorLookup(name,
leftTypeId,
rightTypeId,
&defined);
/* These message strings are chosen to match parse_oper.c */
if (!OidIsValid(oid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("operator does not exist: %s",
op_signature_string(name,
leftTypeId,
rightTypeId))));
if (!defined)
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("operator is only a shell: %s",
op_signature_string(name,
leftTypeId,
rightTypeId))));
if (!object_ownercheck(OperatorRelationId, oid, GetUserId()))
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR,
NameListToString(name));
return oid;
}
/*
* Guts of operator deletion.
*/
@ -406,6 +456,10 @@ RemoveOperatorById(Oid operOid)
* routine implementing ALTER OPERATOR <operator> SET (option = ...).
*
* Currently, only RESTRICT and JOIN estimator functions can be changed.
* COMMUTATOR, NEGATOR, MERGES, and HASHES attributes can be set if they
* have not been set previously. (Changing or removing one of these
* attributes could invalidate existing plans, which seems more trouble
* than it's worth.)
*/
ObjectAddress
AlterOperator(AlterOperatorStmt *stmt)
@ -426,6 +480,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 canMerge = false;
bool updateMerges = false;
bool canHash = false;
bool updateHashes = false;
/* Look up the operator */
oprId = LookupOperWithArgs(stmt->opername, false);
@ -456,6 +518,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, "merges") == 0)
{
canMerge = defGetBoolean(defel);
updateMerges = true;
}
else if (strcmp(defel->defname, "hashes") == 0)
{
canHash = defGetBoolean(defel);
updateHashes = true;
}
/*
* The rest of the options that CREATE accepts cannot be changed.
@ -464,11 +544,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 +564,7 @@ AlterOperator(AlterOperatorStmt *stmt)
NameStr(oprForm->oprname));
/*
* Look up restriction and join estimators if specified
* Look up OIDs for any parameters specified
*/
if (restrictionName)
restrictionOid = ValidateRestrictionEstimator(restrictionName);
@ -499,27 +575,78 @@ 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))
/* commutator has reversed arg types */
commutatorOid = ValidateOperatorReference(commutatorName,
oprForm->oprright,
oprForm->oprleft);
/*
* We don't need to do anything extra for a self commutator as in
* OperatorCreate, since the operator surely exists already.
*/
}
else
commutatorOid = InvalidOid;
if (negatorName)
{
negatorOid = ValidateOperatorReference(negatorName,
oprForm->oprleft,
oprForm->oprright);
/* Must reject self-negation */
if (negatorOid == oprForm->oid)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("only binary operators can have join selectivity")));
errmsg("operator cannot be its own negator")));
}
else
{
negatorOid = InvalidOid;
}
if (oprForm->oprresult != BOOLOID)
{
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")));
}
/*
* Check that we're not changing any attributes that might be depended on
* by plans, while allowing no-op updates.
*/
if (OidIsValid(commutatorOid) && OidIsValid(oprForm->oprcom) &&
commutatorOid != oprForm->oprcom)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("operator attribute \"%s\" cannot be changed if it has already been set",
"commutator")));
if (OidIsValid(negatorOid) && OidIsValid(oprForm->oprnegate) &&
negatorOid != oprForm->oprnegate)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("operator attribute \"%s\" cannot be changed if it has already been set",
"negator")));
if (updateMerges && oprForm->oprcanmerge && !canMerge)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("operator attribute \"%s\" cannot be changed if it has already been set",
"merges")));
if (updateHashes && oprForm->oprcanhash && !canHash)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
errmsg("operator attribute \"%s\" cannot be changed if it has already been set",
"hashes")));
/* Perform additional checks, like OperatorCreate does */
OperatorValidateParams(oprForm->oprleft,
oprForm->oprright,
oprForm->oprresult,
OidIsValid(commutatorOid),
OidIsValid(negatorOid),
OidIsValid(restrictionOid),
OidIsValid(joinOid),
canMerge,
canHash);
/* Update the tuple */
for (i = 0; i < Natts_pg_operator; ++i)
@ -531,12 +658,32 @@ AlterOperator(AlterOperatorStmt *stmt)
if (updateRestriction)
{
replaces[Anum_pg_operator_oprrest - 1] = true;
values[Anum_pg_operator_oprrest - 1] = restrictionOid;
values[Anum_pg_operator_oprrest - 1] = ObjectIdGetDatum(restrictionOid);
}
if (updateJoin)
{
replaces[Anum_pg_operator_oprjoin - 1] = true;
values[Anum_pg_operator_oprjoin - 1] = joinOid;
values[Anum_pg_operator_oprjoin - 1] = ObjectIdGetDatum(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] = BoolGetDatum(canMerge);
}
if (updateHashes)
{
replaces[Anum_pg_operator_oprcanhash - 1] = true;
values[Anum_pg_operator_oprcanhash - 1] = BoolGetDatum(canHash);
}
tup = heap_modify_tuple(tup, RelationGetDescr(catalog),
@ -546,6 +693,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);

View File

@ -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 */

View File

@ -70,9 +70,7 @@ static FuncDetailCode oper_select_candidate(int nargs,
Oid *input_typeids,
FuncCandidateList candidates,
Oid *operOid);
static const char *op_signature_string(List *op, char oprkind,
Oid arg1, Oid arg2);
static void op_error(ParseState *pstate, List *op, char oprkind,
static void op_error(ParseState *pstate, List *op,
Oid arg1, Oid arg2,
FuncDetailCode fdresult, int location);
static bool make_oper_cache_key(ParseState *pstate, OprCacheKey *key,
@ -110,26 +108,16 @@ LookupOperName(ParseState *pstate, List *opername, Oid oprleft, Oid oprright,
/* we don't use op_error here because only an exact match is wanted */
if (!noError)
{
char oprkind;
if (!OidIsValid(oprleft))
oprkind = 'l';
else if (OidIsValid(oprright))
oprkind = 'b';
else
{
if (!OidIsValid(oprright))
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("postfix operators are not supported"),
parser_errposition(pstate, location)));
oprkind = 0; /* keep compiler quiet */
}
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("operator does not exist: %s",
op_signature_string(opername, oprkind,
oprleft, oprright)),
op_signature_string(opername, oprleft, oprright)),
parser_errposition(pstate, location)));
}
@ -446,7 +434,7 @@ oper(ParseState *pstate, List *opname, Oid ltypeId, Oid rtypeId,
make_oper_cache_entry(&key, operOid);
}
else if (!noError)
op_error(pstate, opname, 'b', ltypeId, rtypeId, fdresult, location);
op_error(pstate, opname, ltypeId, rtypeId, fdresult, location);
return (Operator) tup;
}
@ -483,7 +471,7 @@ compatible_oper(ParseState *pstate, List *op, Oid arg1, Oid arg2,
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("operator requires run-time type coercion: %s",
op_signature_string(op, 'b', arg1, arg2)),
op_signature_string(op, arg1, arg2)),
parser_errposition(pstate, location)));
return (Operator) NULL;
@ -597,7 +585,7 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
make_oper_cache_entry(&key, operOid);
}
else if (!noError)
op_error(pstate, op, 'l', InvalidOid, arg, fdresult, location);
op_error(pstate, op, InvalidOid, arg, fdresult, location);
return (Operator) tup;
}
@ -610,14 +598,14 @@ left_oper(ParseState *pstate, List *op, Oid arg, bool noError, int location)
* This is typically used in the construction of operator-not-found error
* messages.
*/
static const char *
op_signature_string(List *op, char oprkind, Oid arg1, Oid arg2)
const char *
op_signature_string(List *op, Oid arg1, Oid arg2)
{
StringInfoData argbuf;
initStringInfo(&argbuf);
if (oprkind != 'l')
if (OidIsValid(arg1))
appendStringInfo(&argbuf, "%s ", format_type_be(arg1));
appendStringInfoString(&argbuf, NameListToString(op));
@ -631,7 +619,7 @@ op_signature_string(List *op, char oprkind, Oid arg1, Oid arg2)
* op_error - utility routine to complain about an unresolvable operator
*/
static void
op_error(ParseState *pstate, List *op, char oprkind,
op_error(ParseState *pstate, List *op,
Oid arg1, Oid arg2,
FuncDetailCode fdresult, int location)
{
@ -639,7 +627,7 @@ op_error(ParseState *pstate, List *op, char oprkind,
ereport(ERROR,
(errcode(ERRCODE_AMBIGUOUS_FUNCTION),
errmsg("operator is not unique: %s",
op_signature_string(op, oprkind, arg1, arg2)),
op_signature_string(op, arg1, arg2)),
errhint("Could not choose a best candidate operator. "
"You might need to add explicit type casts."),
parser_errposition(pstate, location)));
@ -647,7 +635,7 @@ op_error(ParseState *pstate, List *op, char oprkind,
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("operator does not exist: %s",
op_signature_string(op, oprkind, arg1, arg2)),
op_signature_string(op, arg1, arg2)),
(!arg1 || !arg2) ?
errhint("No operator matches the given name and argument type. "
"You might need to add an explicit type cast.") :
@ -713,7 +701,6 @@ make_op(ParseState *pstate, List *opname, Node *ltree, Node *rtree,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("operator is only a shell: %s",
op_signature_string(opname,
opform->oprkind,
opform->oprleft,
opform->oprright)),
parser_errposition(pstate, location)));
@ -827,7 +814,6 @@ make_scalar_array_op(ParseState *pstate, List *opname,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("operator is only a shell: %s",
op_signature_string(opname,
opform->oprkind,
opform->oprleft,
opform->oprright)),
parser_errposition(pstate, location)));

View File

@ -86,6 +86,11 @@ DECLARE_UNIQUE_INDEX_PKEY(pg_operator_oid_index, 2688, OperatorOidIndexId, pg_op
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,
Oid leftTypeId,
@ -102,6 +107,16 @@ extern ObjectAddress makeOperatorDependencies(HeapTuple tuple,
bool makeExtensionDep,
bool isUpdate);
extern void OperatorValidateParams(Oid leftTypeId,
Oid rightTypeId,
Oid operResultType,
bool hasCommutator,
bool hasNegator,
bool hasRestrictionSelectivity,
bool hasJoinSelectivity,
bool canMerge,
bool canHash);
extern void OperatorUpd(Oid baseId, Oid commId, Oid negId, bool isDelete);
#endif /* PG_OPERATOR_H */

View File

@ -42,6 +42,9 @@ extern Operator compatible_oper(ParseState *pstate, List *op,
/* currently no need for compatible_left_oper/compatible_right_oper */
/* Error reporting support */
extern const char *op_signature_string(List *op, Oid arg1, Oid arg2);
/* Routines for identifying "<", "=", ">" operators for a type */
extern void get_sort_group_operators(Oid argtype,
bool needLT, bool needEQ, bool needGT,

View File

@ -25,7 +25,7 @@ ORDER BY 1;
(3 rows)
--
-- Reset and set params
-- Test resetting and setting restrict and join attributes.
--
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
ALTER OPERATOR === (boolean, boolean) SET (JOIN = NONE);
@ -109,18 +109,10 @@ ORDER BY 1;
--
-- 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
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
ALTER OPERATOR & (bit, bit) SET ("Restrict" = _int_contsel, "Join" = _int_contjoinsel);
ERROR: operator attribute "Restrict" not recognized
@ -131,9 +123,145 @@ 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 setting commutator, negator, merges, and hashes attributes,
-- which can only be set if not already set
--
CREATE FUNCTION alter_op_test_fn_bool_real(boolean, real)
RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
CREATE FUNCTION alter_op_test_fn_real_bool(real, boolean)
RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
-- operator
CREATE OPERATOR === (
LEFTARG = boolean,
RIGHTARG = real,
PROCEDURE = alter_op_test_fn_bool_real
);
-- commutator
CREATE OPERATOR ==== (
LEFTARG = real,
RIGHTARG = boolean,
PROCEDURE = alter_op_test_fn_real_bool
);
-- negator
CREATE OPERATOR !==== (
LEFTARG = boolean,
RIGHTARG = real,
PROCEDURE = alter_op_test_fn_bool_real
);
-- No-op setting already false hashes and merges to false works
ALTER OPERATOR === (boolean, real) SET (MERGES = false);
ALTER OPERATOR === (boolean, real) SET (HASHES = false);
-- Test setting merges and hashes
ALTER OPERATOR === (boolean, real) SET (MERGES);
ALTER OPERATOR === (boolean, real) SET (HASHES);
SELECT oprcanmerge, oprcanhash
FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
oprcanmerge | oprcanhash
-------------+------------
t | t
(1 row)
-- Test setting commutator
ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====);
-- Check that oprcom has been set on both the operator and commutator,
-- that they reference each other, and that the operator used is the existing
-- one we created and not a new shell operator.
SELECT op.oprname AS operator_name, com.oprname AS commutator_name,
com.oprcode AS commutator_func
FROM pg_operator op
INNER JOIN pg_operator com ON (op.oid = com.oprcom AND op.oprcom = com.oid)
WHERE op.oprname = '==='
AND op.oprleft = 'boolean'::regtype AND op.oprright = 'real'::regtype;
operator_name | commutator_name | commutator_func
---------------+-----------------+----------------------------
=== | ==== | alter_op_test_fn_real_bool
(1 row)
-- Cannot set self as negator
ALTER OPERATOR === (boolean, real) SET (NEGATOR = ===);
ERROR: operator cannot be its own negator
-- Test setting negator
ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====);
-- Check that oprnegate has been set on both the operator and negator,
-- that they reference each other, and that the operator used is the existing
-- one we created and not a new shell operator.
SELECT op.oprname AS operator_name, neg.oprname AS negator_name,
neg.oprcode AS negator_func
FROM pg_operator op
INNER JOIN pg_operator neg ON (op.oid = neg.oprnegate AND op.oprnegate = neg.oid)
WHERE op.oprname = '==='
AND op.oprleft = 'boolean'::regtype AND op.oprright = 'real'::regtype;
operator_name | negator_name | negator_func
---------------+--------------+----------------------------
=== | !==== | alter_op_test_fn_bool_real
(1 row)
-- Test that no-op set succeeds
ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====);
ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====);
ALTER OPERATOR === (boolean, real) SET (MERGES);
ALTER OPERATOR === (boolean, real) SET (HASHES);
-- Check that the final state of the operator is as we expect
SELECT oprcanmerge, oprcanhash,
pg_describe_object('pg_operator'::regclass, oprcom, 0) AS commutator,
pg_describe_object('pg_operator'::regclass, oprnegate, 0) AS negator
FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
oprcanmerge | oprcanhash | commutator | negator
-------------+------------+-----------------------------+------------------------------
t | t | operator ====(real,boolean) | operator !====(boolean,real)
(1 row)
-- Cannot change commutator, negator, merges, and hashes when already set
CREATE OPERATOR @= (
LEFTARG = real,
RIGHTARG = boolean,
PROCEDURE = alter_op_test_fn_real_bool
);
CREATE OPERATOR @!= (
LEFTARG = boolean,
RIGHTARG = real,
PROCEDURE = alter_op_test_fn_bool_real
);
ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = @=);
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 (MERGES = false);
ERROR: operator attribute "merges" cannot be changed if it has already been set
ALTER OPERATOR === (boolean, real) SET (HASHES = false);
ERROR: operator attribute "hashes" cannot be changed if it has already been set
-- Cannot set an operator that already has a commutator as the commutator
ALTER OPERATOR @=(real, boolean) SET (COMMUTATOR = ===);
ERROR: commutator operator === is already the commutator of operator ====
-- Cannot set an operator that already has a negator as the negator
ALTER OPERATOR @!=(boolean, real) SET (NEGATOR = ===);
ERROR: negator operator === is already the negator of operator !====
-- Check no changes made
SELECT oprcanmerge, oprcanhash,
pg_describe_object('pg_operator'::regclass, oprcom, 0) AS commutator,
pg_describe_object('pg_operator'::regclass, oprnegate, 0) AS negator
FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
oprcanmerge | oprcanhash | commutator | negator
-------------+------------+-----------------------------+------------------------------
t | t | operator ====(real,boolean) | operator !====(boolean,real)
(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);

View File

@ -260,6 +260,50 @@ CREATE OPERATOR #*# (
);
ERROR: permission denied for type type_op6
ROLLBACK;
-- Should fail. An operator cannot be its own negator.
BEGIN TRANSACTION;
CREATE OPERATOR === (
leftarg = integer,
rightarg = integer,
procedure = int4eq,
negator = ===
);
ERROR: operator cannot be its own negator
ROLLBACK;
-- Should fail. An operator cannot be its own negator. Here we check that
-- this error is detected when replacing a shell operator.
BEGIN TRANSACTION;
-- create a shell operator for ===!!! by referencing it as a commutator
CREATE OPERATOR === (
leftarg = integer,
rightarg = integer,
procedure = int4eq,
commutator = ===!!!
);
CREATE OPERATOR ===!!! (
leftarg = integer,
rightarg = integer,
procedure = int4ne,
negator = ===!!!
);
ERROR: operator cannot be its own negator
ROLLBACK;
-- test that we can't use part of an existing commutator or negator pair
-- as a commutator or negator
CREATE OPERATOR === (
leftarg = integer,
rightarg = integer,
procedure = int4eq,
commutator = =
);
ERROR: commutator operator = is already the commutator of operator =
CREATE OPERATOR === (
leftarg = integer,
rightarg = integer,
procedure = int4eq,
negator = <>
);
ERROR: negator operator <> is already the negator of operator =
-- invalid: non-lowercase quoted identifiers
CREATE OPERATOR ===
(

View File

@ -22,7 +22,7 @@ WHERE classid = 'pg_operator'::regclass AND
ORDER BY 1;
--
-- Reset and set params
-- Test resetting and setting restrict and join attributes.
--
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
@ -74,12 +74,8 @@ ORDER BY 1;
--
-- Test invalid options.
--
ALTER OPERATOR === (boolean, boolean) SET (COMMUTATOR = ====);
ALTER OPERATOR === (boolean, boolean) SET (NEGATOR = ====);
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
ALTER OPERATOR & (bit, bit) SET ("Restrict" = _int_contsel, "Join" = _int_contjoinsel);
@ -92,9 +88,138 @@ SET SESSION AUTHORIZATION regress_alter_op_user;
ALTER OPERATOR === (boolean, boolean) SET (RESTRICT = NONE);
-- Clean up
RESET SESSION AUTHORIZATION;
--
-- Test setting commutator, negator, merges, and hashes attributes,
-- which can only be set if not already set
--
CREATE FUNCTION alter_op_test_fn_bool_real(boolean, real)
RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
CREATE FUNCTION alter_op_test_fn_real_bool(real, boolean)
RETURNS boolean AS $$ SELECT NULL::BOOLEAN; $$ LANGUAGE sql IMMUTABLE;
-- operator
CREATE OPERATOR === (
LEFTARG = boolean,
RIGHTARG = real,
PROCEDURE = alter_op_test_fn_bool_real
);
-- commutator
CREATE OPERATOR ==== (
LEFTARG = real,
RIGHTARG = boolean,
PROCEDURE = alter_op_test_fn_real_bool
);
-- negator
CREATE OPERATOR !==== (
LEFTARG = boolean,
RIGHTARG = real,
PROCEDURE = alter_op_test_fn_bool_real
);
-- No-op setting already false hashes and merges to false works
ALTER OPERATOR === (boolean, real) SET (MERGES = false);
ALTER OPERATOR === (boolean, real) SET (HASHES = false);
-- Test setting merges and hashes
ALTER OPERATOR === (boolean, real) SET (MERGES);
ALTER OPERATOR === (boolean, real) SET (HASHES);
SELECT oprcanmerge, oprcanhash
FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
-- Test setting commutator
ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====);
-- Check that oprcom has been set on both the operator and commutator,
-- that they reference each other, and that the operator used is the existing
-- one we created and not a new shell operator.
SELECT op.oprname AS operator_name, com.oprname AS commutator_name,
com.oprcode AS commutator_func
FROM pg_operator op
INNER JOIN pg_operator com ON (op.oid = com.oprcom AND op.oprcom = com.oid)
WHERE op.oprname = '==='
AND op.oprleft = 'boolean'::regtype AND op.oprright = 'real'::regtype;
-- Cannot set self as negator
ALTER OPERATOR === (boolean, real) SET (NEGATOR = ===);
-- Test setting negator
ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====);
-- Check that oprnegate has been set on both the operator and negator,
-- that they reference each other, and that the operator used is the existing
-- one we created and not a new shell operator.
SELECT op.oprname AS operator_name, neg.oprname AS negator_name,
neg.oprcode AS negator_func
FROM pg_operator op
INNER JOIN pg_operator neg ON (op.oid = neg.oprnegate AND op.oprnegate = neg.oid)
WHERE op.oprname = '==='
AND op.oprleft = 'boolean'::regtype AND op.oprright = 'real'::regtype;
-- Test that no-op set succeeds
ALTER OPERATOR === (boolean, real) SET (NEGATOR = !====);
ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = ====);
ALTER OPERATOR === (boolean, real) SET (MERGES);
ALTER OPERATOR === (boolean, real) SET (HASHES);
-- Check that the final state of the operator is as we expect
SELECT oprcanmerge, oprcanhash,
pg_describe_object('pg_operator'::regclass, oprcom, 0) AS commutator,
pg_describe_object('pg_operator'::regclass, oprnegate, 0) AS negator
FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
-- Cannot change commutator, negator, merges, and hashes when already set
CREATE OPERATOR @= (
LEFTARG = real,
RIGHTARG = boolean,
PROCEDURE = alter_op_test_fn_real_bool
);
CREATE OPERATOR @!= (
LEFTARG = boolean,
RIGHTARG = real,
PROCEDURE = alter_op_test_fn_bool_real
);
ALTER OPERATOR === (boolean, real) SET (COMMUTATOR = @=);
ALTER OPERATOR === (boolean, real) SET (NEGATOR = @!=);
ALTER OPERATOR === (boolean, real) SET (MERGES = false);
ALTER OPERATOR === (boolean, real) SET (HASHES = false);
-- Cannot set an operator that already has a commutator as the commutator
ALTER OPERATOR @=(real, boolean) SET (COMMUTATOR = ===);
-- Cannot set an operator that already has a negator as the negator
ALTER OPERATOR @!=(boolean, real) SET (NEGATOR = ===);
-- Check no changes made
SELECT oprcanmerge, oprcanhash,
pg_describe_object('pg_operator'::regclass, oprcom, 0) AS commutator,
pg_describe_object('pg_operator'::regclass, oprnegate, 0) AS negator
FROM pg_operator WHERE oprname = '==='
AND oprleft = 'boolean'::regtype AND oprright = 'real'::regtype;
--
-- Clean up
--
DROP USER regress_alter_op_user;
DROP OPERATOR === (boolean, boolean);
DROP OPERATOR === (boolean, real);
DROP OPERATOR ==== (real, boolean);
DROP OPERATOR !==== (boolean, real);
DROP OPERATOR @= (real, boolean);
DROP OPERATOR @!= (boolean, real);
DROP FUNCTION customcontsel(internal, oid, internal, integer);
DROP FUNCTION alter_op_test_fn(boolean, boolean);
DROP FUNCTION alter_op_test_fn_bool_real(boolean, real);
DROP FUNCTION alter_op_test_fn_real_bool(real, boolean);

View File

@ -210,6 +210,49 @@ CREATE OPERATOR #*# (
);
ROLLBACK;
-- Should fail. An operator cannot be its own negator.
BEGIN TRANSACTION;
CREATE OPERATOR === (
leftarg = integer,
rightarg = integer,
procedure = int4eq,
negator = ===
);
ROLLBACK;
-- Should fail. An operator cannot be its own negator. Here we check that
-- this error is detected when replacing a shell operator.
BEGIN TRANSACTION;
-- create a shell operator for ===!!! by referencing it as a commutator
CREATE OPERATOR === (
leftarg = integer,
rightarg = integer,
procedure = int4eq,
commutator = ===!!!
);
CREATE OPERATOR ===!!! (
leftarg = integer,
rightarg = integer,
procedure = int4ne,
negator = ===!!!
);
ROLLBACK;
-- test that we can't use part of an existing commutator or negator pair
-- as a commutator or negator
CREATE OPERATOR === (
leftarg = integer,
rightarg = integer,
procedure = int4eq,
commutator = =
);
CREATE OPERATOR === (
leftarg = integer,
rightarg = integer,
procedure = int4eq,
negator = <>
);
-- invalid: non-lowercase quoted identifiers
CREATE OPERATOR ===
(