Hello hackers,
It seems useful to have [OR REPLACE] option in CREATE OPERATOR statement, as in
CREATE FUNCTION. This option may be good for writing extension update scripts,
to avoid errors with re-creating the same operator.
Because of cached query plans, only RESTRICT and JOIN options can be changed
for existing operator, as in ALTER OPERATOR statement.
(discussed here:
https://www.postgresql.org/message-id/flat/3348985.V7xMLFDaJO%40dinodell )
The attached patch will be proposed for September CF.
Best regards,
--
Svetlana Derevyanko
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
From 7398a8a14f29a6bbb59117a3e3059231f0b476d9 Mon Sep 17 00:00:00 2001
From: Svetlana Derevyanko <s.derevya...@postgrespro.ru>
Date: Tue, 14 Jun 2022 08:40:51 +0300
Subject: [PATCH v1] Add optional [OR REPLACE] in CREATE OPERATOR statement.
As in ALTER OPERATOR, only restrict and join params can be modified,
because of the cached query plans.
---
doc/src/sgml/ref/create_operator.sgml | 11 +-
src/backend/catalog/pg_operator.c | 121 +++++++++++++++---
src/backend/commands/operatorcmds.c | 6 +-
src/backend/parser/gram.y | 12 ++
src/backend/tcop/utility.c | 3 +-
src/include/catalog/pg_operator.h | 3 +-
src/include/commands/defrem.h | 2 +-
src/test/regress/expected/create_operator.out | 94 ++++++++++++++
src/test/regress/sql/create_operator.sql | 101 +++++++++++++++
9 files changed, 325 insertions(+), 28 deletions(-)
diff --git a/doc/src/sgml/ref/create_operator.sgml b/doc/src/sgml/ref/create_operator.sgml
index e27512ff39..0e03108876 100644
--- a/doc/src/sgml/ref/create_operator.sgml
+++ b/doc/src/sgml/ref/create_operator.sgml
@@ -21,7 +21,7 @@ PostgreSQL documentation
<refsynopsisdiv>
<synopsis>
-CREATE OPERATOR <replaceable>name</replaceable> (
+CREATE [ OR REPLACE ] OPERATOR <replaceable>name</replaceable> (
{FUNCTION|PROCEDURE} = <replaceable class="parameter">function_name</replaceable>
[, LEFTARG = <replaceable class="parameter">left_type</replaceable> ] [, RIGHTARG = <replaceable class="parameter">right_type</replaceable> ]
[, COMMUTATOR = <replaceable class="parameter">com_op</replaceable> ] [, NEGATOR = <replaceable class="parameter">neg_op</replaceable> ]
@@ -36,7 +36,8 @@ CREATE OPERATOR <replaceable>name</replaceable> (
<para>
<command>CREATE OPERATOR</command> defines a new operator,
- <replaceable class="parameter">name</replaceable>. The user who
+ <replaceable class="parameter">name</replaceable>. <command>CREATE OR REPLACE OPERATOR</command>
+ will either create a new operator, or replace an existing definition. The user who
defines an operator becomes its owner. If a schema name is given
then the operator is created in the specified schema. Otherwise it
is created in the current schema.
@@ -114,6 +115,12 @@ CREATE OPERATOR <replaceable>name</replaceable> (
as <literal>EXECUTE</literal> privilege on the underlying function. If a
commutator or negator operator is specified, you must own these operators.
</para>
+
+ <para>
+ When <command>CREATE OR REPLACE OPERATOR</command> is used to replace an
+ existing operator, only <replaceable class="parameter">res_proc</replaceable>
+ and <replaceable class="parameter">join_proc</replaceable> can be changed.
+ </para>
</refsect1>
<refsect1>
diff --git a/src/backend/catalog/pg_operator.c b/src/backend/catalog/pg_operator.c
index 630bf3e56c..eca235f735 100644
--- a/src/backend/catalog/pg_operator.c
+++ b/src/backend/catalog/pg_operator.c
@@ -303,6 +303,7 @@ OperatorShellMake(const char *operatorName,
* joinId X join selectivity procedure ID
* canMerge merge join can be used with this operator
* canHash hash join can be used with this operator
+ * replace replace operator if exists
*
* The caller should have validated properties and permissions for the
* objects passed as OID references. We must handle the commutator and
@@ -334,7 +335,8 @@ OperatorCreate(const char *operatorName,
Oid restrictionId,
Oid joinId,
bool canMerge,
- bool canHash)
+ bool canHash,
+ bool replace)
{
Relation pg_operator_desc;
HeapTuple tup;
@@ -415,12 +417,18 @@ OperatorCreate(const char *operatorName,
rightTypeId,
&operatorAlreadyDefined);
- if (operatorAlreadyDefined)
+ if (operatorAlreadyDefined && !replace)
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_FUNCTION),
errmsg("operator %s already exists",
operatorName)));
+ /*
+ * No such operator yet, so CREATE OR REPLACE is equivalent to CREATE
+ */
+ if (!OidIsValid(operatorObjectId) && replace)
+ replace = false;
+
/*
* At this point, if operatorObjectId is not InvalidOid then we are
* filling in a previously-created shell. Insist that the user own any
@@ -431,6 +439,59 @@ OperatorCreate(const char *operatorName,
aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_OPERATOR,
operatorName);
+ /*
+ * When operator is updated,
+ * params others than RESTRICT and JOIN should remain the same.
+ */
+ if (replace)
+ {
+ Form_pg_operator oprForm;
+
+ tup = SearchSysCache4(OPERNAMENSP,
+ PointerGetDatum(operatorName),
+ ObjectIdGetDatum(leftTypeId),
+ ObjectIdGetDatum(rightTypeId),
+ ObjectIdGetDatum(operatorNamespace));
+ oprForm = (Form_pg_operator) GETSTRUCT(tup);
+
+ if (oprForm->oprcanmerge != canMerge)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("operator attribute \"merges\" cannot be changed")));
+
+ if (oprForm->oprcanhash != canHash)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("operator attribute \"hashes\" cannot be changed")));
+
+ commutatorId = commutatorName ? get_other_operator(commutatorName,
+ rightTypeId, leftTypeId,
+ operatorName, operatorNamespace,
+ leftTypeId, rightTypeId,
+ true) : InvalidOid;
+ if (oprForm->oprcom != commutatorId)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("operator attribute \"commutator\" cannot be changed")));
+
+ negatorId = negatorName ? get_other_operator(negatorName,
+ leftTypeId, rightTypeId,
+ operatorName, operatorNamespace,
+ leftTypeId, rightTypeId,
+ false) : InvalidOid;
+ if (oprForm->oprnegate != negatorId)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("operator attribute \"negator\" cannot be changed")));
+
+ if (oprForm->oprcode != procedureId)
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("operator attribute \"function\" cannot be changed")));
+
+ ReleaseSysCache(tup);
+ }
+
/*
* Set up the other operators. If they do not currently exist, create
* shells in order to get ObjectId's.
@@ -486,25 +547,36 @@ OperatorCreate(const char *operatorName,
for (i = 0; i < Natts_pg_operator; ++i)
{
values[i] = (Datum) NULL;
- replaces[i] = true;
+ replaces[i] = !replace;
nulls[i] = false;
}
- namestrcpy(&oname, operatorName);
- values[Anum_pg_operator_oprname - 1] = NameGetDatum(&oname);
- values[Anum_pg_operator_oprnamespace - 1] = ObjectIdGetDatum(operatorNamespace);
- values[Anum_pg_operator_oprowner - 1] = ObjectIdGetDatum(GetUserId());
- values[Anum_pg_operator_oprkind - 1] = CharGetDatum(leftTypeId ? 'b' : 'l');
- values[Anum_pg_operator_oprcanmerge - 1] = BoolGetDatum(canMerge);
- values[Anum_pg_operator_oprcanhash - 1] = BoolGetDatum(canHash);
- values[Anum_pg_operator_oprleft - 1] = ObjectIdGetDatum(leftTypeId);
- values[Anum_pg_operator_oprright - 1] = ObjectIdGetDatum(rightTypeId);
- values[Anum_pg_operator_oprresult - 1] = ObjectIdGetDatum(operResultType);
- values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(commutatorId);
- values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(negatorId);
- values[Anum_pg_operator_oprcode - 1] = ObjectIdGetDatum(procedureId);
- values[Anum_pg_operator_oprrest - 1] = ObjectIdGetDatum(restrictionId);
- values[Anum_pg_operator_oprjoin - 1] = ObjectIdGetDatum(joinId);
+ if (!replace)
+ {
+ namestrcpy(&oname, operatorName);
+ values[Anum_pg_operator_oprname - 1] = NameGetDatum(&oname);
+ values[Anum_pg_operator_oprnamespace - 1] = ObjectIdGetDatum(operatorNamespace);
+ values[Anum_pg_operator_oprowner - 1] = ObjectIdGetDatum(GetUserId());
+ values[Anum_pg_operator_oprkind - 1] = CharGetDatum(leftTypeId ? 'b' : 'l');
+ values[Anum_pg_operator_oprcanmerge - 1] = BoolGetDatum(canMerge);
+ values[Anum_pg_operator_oprcanhash - 1] = BoolGetDatum(canHash);
+ values[Anum_pg_operator_oprleft - 1] = ObjectIdGetDatum(leftTypeId);
+ values[Anum_pg_operator_oprright - 1] = ObjectIdGetDatum(rightTypeId);
+ values[Anum_pg_operator_oprresult - 1] = ObjectIdGetDatum(operResultType);
+ values[Anum_pg_operator_oprcom - 1] = ObjectIdGetDatum(commutatorId);
+ values[Anum_pg_operator_oprnegate - 1] = ObjectIdGetDatum(negatorId);
+ values[Anum_pg_operator_oprcode - 1] = ObjectIdGetDatum(procedureId);
+ values[Anum_pg_operator_oprrest - 1] = ObjectIdGetDatum(restrictionId);
+ values[Anum_pg_operator_oprjoin - 1] = ObjectIdGetDatum(joinId);
+ }
+ else
+ {
+ replaces[Anum_pg_operator_oprrest - 1] = true;
+ values[Anum_pg_operator_oprrest - 1] = ObjectIdGetDatum(restrictionId);
+
+ replaces[Anum_pg_operator_oprrest - 1] = true;
+ values[Anum_pg_operator_oprjoin - 1] = ObjectIdGetDatum(joinId);
+ }
pg_operator_desc = table_open(OperatorRelationId, RowExclusiveLock);
@@ -546,10 +618,17 @@ OperatorCreate(const char *operatorName,
}
/* Add dependencies for the entry */
- address = makeOperatorDependencies(tup, true, isUpdate);
+ address = makeOperatorDependencies(tup, !replace, isUpdate);
- /* Post creation hook for new operator */
- InvokeObjectPostCreateHook(OperatorRelationId, operatorObjectId, 0);
+ if (replace)
+ {
+ InvokeObjectPostAlterHook(OperatorRelationId, operatorObjectId, 0);
+ }
+ else
+ {
+ /* Post creation hook for new operator */
+ InvokeObjectPostCreateHook(OperatorRelationId, operatorObjectId, 0);
+ }
table_close(pg_operator_desc, RowExclusiveLock);
diff --git a/src/backend/commands/operatorcmds.c b/src/backend/commands/operatorcmds.c
index a5924d7d56..474f1e0d8c 100644
--- a/src/backend/commands/operatorcmds.c
+++ b/src/backend/commands/operatorcmds.c
@@ -62,7 +62,7 @@ static Oid ValidateJoinEstimator(List *joinName);
* 'parameters' is a list of DefElem
*/
ObjectAddress
-DefineOperator(List *names, List *parameters)
+DefineOperator(List *names, List *parameters, bool replace)
{
char *oprName;
Oid oprNamespace;
@@ -261,7 +261,9 @@ DefineOperator(List *names, List *parameters)
restrictionOid, /* optional restrict. sel. function */
joinOid, /* optional join sel. function name */
canMerge, /* operator merges */
- canHash); /* operator hashes */
+ canHash, /* operator hashes */
+ replace); /* maybe replacement */
+
}
/*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index 969c9c158f..d5572b39a3 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -6194,6 +6194,18 @@ DefineStmt:
n->definition = $5;
$$ = (Node *) n;
}
+ | CREATE OR REPLACE OPERATOR any_operator definition
+ {
+ DefineStmt *n = makeNode(DefineStmt);
+
+ n->kind = OBJECT_OPERATOR;
+ n->oldstyle = false;
+ n->replace = true;
+ n->defnames = $5;
+ n->args = NIL;
+ n->definition = $6;
+ $$ = (Node *) n;
+ }
| CREATE OPERATOR any_operator definition
{
DefineStmt *n = makeNode(DefineStmt);
diff --git a/src/backend/tcop/utility.c b/src/backend/tcop/utility.c
index 6a5bcded55..6591d14529 100644
--- a/src/backend/tcop/utility.c
+++ b/src/backend/tcop/utility.c
@@ -1412,7 +1412,8 @@ ProcessUtilitySlow(ParseState *pstate,
case OBJECT_OPERATOR:
Assert(stmt->args == NIL);
address = DefineOperator(stmt->defnames,
- stmt->definition);
+ stmt->definition,
+ stmt->replace);
break;
case OBJECT_TYPE:
Assert(stmt->args == NIL);
diff --git a/src/include/catalog/pg_operator.h b/src/include/catalog/pg_operator.h
index 51263f550f..ebc1772646 100644
--- a/src/include/catalog/pg_operator.h
+++ b/src/include/catalog/pg_operator.h
@@ -96,7 +96,8 @@ extern ObjectAddress OperatorCreate(const char *operatorName,
Oid restrictionId,
Oid joinId,
bool canMerge,
- bool canHash);
+ bool canHash,
+ bool replace);
extern ObjectAddress makeOperatorDependencies(HeapTuple tuple,
bool makeExtensionDep,
diff --git a/src/include/commands/defrem.h b/src/include/commands/defrem.h
index 56d2bb6616..5143f47030 100644
--- a/src/include/commands/defrem.h
+++ b/src/include/commands/defrem.h
@@ -75,7 +75,7 @@ extern void interpret_function_parameter_list(ParseState *pstate,
Oid *requiredResultType);
/* commands/operatorcmds.c */
-extern ObjectAddress DefineOperator(List *names, List *parameters);
+extern ObjectAddress DefineOperator(List *names, List *parameters, bool replace);
extern void RemoveOperatorById(Oid operOid);
extern ObjectAddress AlterOperator(AlterOperatorStmt *stmt);
diff --git a/src/test/regress/expected/create_operator.out b/src/test/regress/expected/create_operator.out
index f71b601f2d..b92e1b8ff5 100644
--- a/src/test/regress/expected/create_operator.out
+++ b/src/test/regress/expected/create_operator.out
@@ -283,3 +283,97 @@ WARNING: operator attribute "Join" not recognized
WARNING: operator attribute "Hashes" not recognized
WARNING: operator attribute "Merges" not recognized
ERROR: operator function must be specified
+--
+-- CREATE OR REPLACE OPERATOR
+--
+CREATE OR REPLACE FUNCTION fn_op7(int, int)
+RETURNS boolean AS $$
+ SELECT $1 = $2;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7
+);
+-- Should work. Duplicate operator definition.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7
+);
+ROLLBACK;
+-- Should work. Change restriction selectivity estimator function.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7,
+ RESTRICT = eqsel
+);
+ROLLBACK;
+-- Should work. Change join selectivity estimator function.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7,
+ JOIN = eqjoinsel
+);
+ROLLBACK;
+-- Should fail. Underlying function can not be changed.
+BEGIN TRANSACTION;
+CREATE FUNCTION fn_op8(int, int)
+RETURNS boolean AS $$
+ SELECT $1 = $2;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op8
+);
+ERROR: operator attribute "function" cannot be changed
+ROLLBACK;
+-- Should fail. Commutator can not be changed.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7,
+ COMMUTATOR = ===
+);
+ERROR: operator attribute "commutator" cannot be changed
+ROLLBACK;
+-- Should fail. Negator can not be changed.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7,
+ NEGATOR = !===
+);
+ERROR: operator attribute "negator" cannot be changed
+ROLLBACK;
+-- Should fail. Hash join support param can not be changed.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7,
+ HASHES
+);
+ERROR: operator attribute "hashes" cannot be changed
+ROLLBACK;
+-- Should fail. Merge join support param can not be changed.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7,
+ MERGES
+);
+ERROR: operator attribute "merges" cannot be changed
+ROLLBACK;
+-- Clearing
+DROP OPERATOR === (int, int);
+DROP FUNCTION fn_op7;
diff --git a/src/test/regress/sql/create_operator.sql b/src/test/regress/sql/create_operator.sql
index f53e24db3c..ec6e1484e2 100644
--- a/src/test/regress/sql/create_operator.sql
+++ b/src/test/regress/sql/create_operator.sql
@@ -223,3 +223,104 @@ CREATE OPERATOR ===
"Hashes",
"Merges"
);
+
+--
+-- CREATE OR REPLACE OPERATOR
+--
+
+CREATE OR REPLACE FUNCTION fn_op7(int, int)
+RETURNS boolean AS $$
+ SELECT $1 = $2;
+$$ LANGUAGE sql IMMUTABLE;
+
+CREATE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7
+);
+
+-- Should work. Duplicate operator definition.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7
+);
+ROLLBACK;
+
+-- Should work. Change restriction selectivity estimator function.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7,
+ RESTRICT = eqsel
+);
+ROLLBACK;
+
+-- Should work. Change join selectivity estimator function.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7,
+ JOIN = eqjoinsel
+);
+ROLLBACK;
+
+-- Should fail. Underlying function can not be changed.
+BEGIN TRANSACTION;
+CREATE FUNCTION fn_op8(int, int)
+RETURNS boolean AS $$
+ SELECT $1 = $2;
+$$ LANGUAGE sql IMMUTABLE;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op8
+);
+ROLLBACK;
+
+-- Should fail. Commutator can not be changed.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7,
+ COMMUTATOR = ===
+);
+ROLLBACK;
+
+-- Should fail. Negator can not be changed.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7,
+ NEGATOR = !===
+);
+ROLLBACK;
+
+-- Should fail. Hash join support param can not be changed.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7,
+ HASHES
+);
+ROLLBACK;
+
+-- Should fail. Merge join support param can not be changed.
+BEGIN TRANSACTION;
+CREATE OR REPLACE OPERATOR === (
+ LEFTARG = int,
+ RIGHTARG = int,
+ FUNCTION = fn_op7,
+ MERGES
+);
+ROLLBACK;
+
+-- Clearing
+DROP OPERATOR === (int, int);
+DROP FUNCTION fn_op7;
--
2.30.2