*
* Routines for operator manipulation commands
*
- * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/commands/operatorcmds.c,v 1.32 2006/10/03 21:21:36 momjian Exp $
+ * src/backend/commands/operatorcmds.c
*
* DESCRIPTION
* The "DefineFoo" routines take the parse tree and pick out the
#include "postgres.h"
#include "access/heapam.h"
+#include "access/htup_details.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
-#include "catalog/namespace.h"
+#include "catalog/objectaccess.h"
#include "catalog/pg_operator.h"
+#include "catalog/pg_operator_fn.h"
+#include "catalog/pg_type.h"
+#include "commands/alter.h"
#include "commands/defrem.h"
#include "miscadmin.h"
+#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "parser/parse_type.h"
-#include "utils/acl.h"
+#include "utils/builtins.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
#include "utils/syscache.h"
-
-static void AlterOperatorOwner_internal(Relation rel, Oid operOid, Oid newOwnerId);
+static Oid ValidateRestrictionEstimator(List *restrictionName);
+static Oid ValidateJoinEstimator(List *joinName);
/*
* DefineOperator
*
* 'parameters' is a list of DefElem
*/
-void
+ObjectAddress
DefineOperator(List *names, List *parameters)
{
char *oprName;
Oid oprNamespace;
AclResult aclresult;
- bool canHash = false; /* operator hashes */
bool canMerge = false; /* operator merges */
+ bool canHash = false; /* operator hashes */
List *functionName = NIL; /* function for operator */
TypeName *typeName1 = NULL; /* first type name */
TypeName *typeName2 = NULL; /* second type name */
Oid typeId1 = InvalidOid; /* types converted to OID */
Oid typeId2 = InvalidOid;
+ Oid rettype;
List *commutatorName = NIL; /* optional commutator operator name */
List *negatorName = NIL; /* optional negator operator name */
List *restrictionName = NIL; /* optional restrict. sel. procedure */
List *joinName = NIL; /* optional join sel. procedure */
- List *leftSortName = NIL; /* optional left sort operator */
- List *rightSortName = NIL; /* optional right sort operator */
- List *ltCompareName = NIL; /* optional < compare operator */
- List *gtCompareName = NIL; /* optional > compare operator */
+ Oid functionOid; /* functions converted to OID */
+ Oid restrictionOid;
+ Oid joinOid;
+ Oid typeId[2]; /* to hold left and right arg */
+ int nargs;
ListCell *pl;
/* Convert list of names to a name and namespace */
if (typeName1->setof)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("setof type not allowed for operator argument")));
+ errmsg("SETOF type not allowed for operator argument")));
}
else if (pg_strcasecmp(defel->defname, "rightarg") == 0)
{
if (typeName2->setof)
ereport(ERROR,
(errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
- errmsg("setof type not allowed for operator argument")));
+ errmsg("SETOF type not allowed for operator argument")));
}
else if (pg_strcasecmp(defel->defname, "procedure") == 0)
functionName = defGetQualifiedName(defel);
canHash = defGetBoolean(defel);
else if (pg_strcasecmp(defel->defname, "merges") == 0)
canMerge = defGetBoolean(defel);
+ /* These obsolete options are taken as meaning canMerge */
else if (pg_strcasecmp(defel->defname, "sort1") == 0)
- leftSortName = defGetQualifiedName(defel);
+ canMerge = true;
else if (pg_strcasecmp(defel->defname, "sort2") == 0)
- rightSortName = defGetQualifiedName(defel);
+ canMerge = true;
else if (pg_strcasecmp(defel->defname, "ltcmp") == 0)
- ltCompareName = defGetQualifiedName(defel);
+ canMerge = true;
else if (pg_strcasecmp(defel->defname, "gtcmp") == 0)
- gtCompareName = defGetQualifiedName(defel);
+ canMerge = true;
else
+ {
+ /* WARNING, not ERROR, for historical backwards-compatibility */
ereport(WARNING,
(errcode(ERRCODE_SYNTAX_ERROR),
errmsg("operator attribute \"%s\" not recognized",
defel->defname)));
+ }
}
/*
if (typeName2)
typeId2 = typenameTypeId(NULL, typeName2);
+ if (!OidIsValid(typeId1) && !OidIsValid(typeId2))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION),
+ errmsg("at least one of leftarg or rightarg must be specified")));
+
+ if (typeName1)
+ {
+ aclresult = pg_type_aclcheck(typeId1, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, typeId1);
+ }
+
+ if (typeName2)
+ {
+ aclresult = pg_type_aclcheck(typeId2, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, typeId2);
+ }
+
/*
- * If any of the mergejoin support operators were given, then canMerge is
- * implicit. If canMerge is specified or implicit, fill in default
- * operator names for any missing mergejoin support operators.
+ * Look up the operator's underlying function.
*/
- if (leftSortName || rightSortName || ltCompareName || gtCompareName)
- canMerge = true;
-
- if (canMerge)
+ if (!OidIsValid(typeId1))
+ {
+ typeId[0] = typeId2;
+ nargs = 1;
+ }
+ else if (!OidIsValid(typeId2))
{
- if (!leftSortName)
- leftSortName = list_make1(makeString("<"));
- if (!rightSortName)
- rightSortName = list_make1(makeString("<"));
- if (!ltCompareName)
- ltCompareName = list_make1(makeString("<"));
- if (!gtCompareName)
- gtCompareName = list_make1(makeString(">"));
+ typeId[0] = typeId1;
+ nargs = 1;
}
+ else
+ {
+ typeId[0] = typeId1;
+ typeId[1] = typeId2;
+ nargs = 2;
+ }
+ functionOid = LookupFuncName(functionName, nargs, typeId, false);
+
+ /*
+ * We require EXECUTE rights for the function. This isn't strictly
+ * necessary, since EXECUTE will be checked at any attempted use of the
+ * operator, but it seems like a good idea anyway.
+ */
+ aclresult = pg_proc_aclcheck(functionOid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC,
+ NameListToString(functionName));
+
+ rettype = get_func_rettype(functionOid);
+ aclresult = pg_type_aclcheck(rettype, GetUserId(), ACL_USAGE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error_type(aclresult, rettype);
+
+ /*
+ * Look up restriction and join estimators if specified
+ */
+ if (restrictionName)
+ restrictionOid = ValidateRestrictionEstimator(restrictionName);
+ else
+ restrictionOid = InvalidOid;
+ if (joinName)
+ joinOid = ValidateJoinEstimator(joinName);
+ else
+ joinOid = InvalidOid;
/*
* now have OperatorCreate do all the work..
*/
- OperatorCreate(oprName, /* operator name */
- oprNamespace, /* namespace */
- typeId1, /* left type id */
- typeId2, /* right type id */
- functionName, /* function for operator */
- commutatorName, /* optional commutator operator name */
- negatorName, /* optional negator operator name */
- restrictionName, /* optional restrict. sel. procedure */
- joinName, /* optional join sel. procedure name */
- canHash, /* operator hashes */
- leftSortName, /* optional left sort operator */
- rightSortName, /* optional right sort operator */
- ltCompareName, /* optional < comparison op */
- gtCompareName); /* optional < comparison op */
+ return
+ OperatorCreate(oprName, /* operator name */
+ oprNamespace, /* namespace */
+ typeId1, /* left type id */
+ typeId2, /* right type id */
+ functionOid, /* function for operator */
+ commutatorName, /* optional commutator operator name */
+ negatorName, /* optional negator operator name */
+ restrictionOid, /* optional restrict. sel. procedure */
+ joinOid, /* optional join sel. procedure name */
+ canMerge, /* operator merges */
+ canHash); /* operator hashes */
}
-
/*
- * RemoveOperator
- * Deletes an operator.
+ * Look up a restriction estimator function ny name, and verify that it has
+ * the correct signature and we have the permissions to attach it to an
+ * operator.
*/
-void
-RemoveOperator(RemoveFuncStmt *stmt)
+static Oid
+ValidateRestrictionEstimator(List *restrictionName)
{
- List *operatorName = stmt->name;
- TypeName *typeName1 = (TypeName *) linitial(stmt->args);
- TypeName *typeName2 = (TypeName *) lsecond(stmt->args);
- Oid operOid;
- HeapTuple tup;
- ObjectAddress object;
-
- Assert(list_length(stmt->args) == 2);
- operOid = LookupOperNameTypeNames(NULL, operatorName,
- typeName1, typeName2,
- stmt->missing_ok, -1);
-
- if (stmt->missing_ok &&!OidIsValid(operOid) )
- {
- ereport(NOTICE,
- (errmsg("operator %s does not exist, skipping",
- NameListToString(operatorName))));
- return;
- }
-
- tup = SearchSysCache(OPEROID,
- ObjectIdGetDatum(operOid),
- 0, 0, 0);
- if (!HeapTupleIsValid(tup)) /* should not happen */
- elog(ERROR, "cache lookup failed for operator %u", operOid);
+ Oid typeId[4];
+ Oid restrictionOid;
+ AclResult aclresult;
- /* Permission check: must own operator or its namespace */
- if (!pg_oper_ownercheck(operOid, GetUserId()) &&
- !pg_namespace_ownercheck(((Form_pg_operator) GETSTRUCT(tup))->oprnamespace,
- GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER,
- NameListToString(operatorName));
+ typeId[0] = INTERNALOID; /* PlannerInfo */
+ typeId[1] = OIDOID; /* operator OID */
+ typeId[2] = INTERNALOID; /* args list */
+ typeId[3] = INT4OID; /* varRelid */
- ReleaseSysCache(tup);
+ restrictionOid = LookupFuncName(restrictionName, 4, typeId, false);
+
+ /* estimators must return float8 */
+ if (get_func_rettype(restrictionOid) != FLOAT8OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("restriction estimator function %s must return type \"%s\"",
+ NameListToString(restrictionName), "float8")));
+
+ /* Require EXECUTE rights for the estimator */
+ aclresult = pg_proc_aclcheck(restrictionOid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC,
+ NameListToString(restrictionName));
+
+ return restrictionOid;
+}
+
+/*
+ * Look up a join estimator function ny name, and verify that it has the
+ * correct signature and we have the permissions to attach it to an
+ * operator.
+ */
+static Oid
+ValidateJoinEstimator(List *joinName)
+{
+ Oid typeId[5];
+ Oid joinOid;
+ AclResult aclresult;
+
+ typeId[0] = INTERNALOID; /* PlannerInfo */
+ typeId[1] = OIDOID; /* operator OID */
+ typeId[2] = INTERNALOID; /* args list */
+ typeId[3] = INT2OID; /* jointype */
+ typeId[4] = INTERNALOID; /* SpecialJoinInfo */
/*
- * Do the deletion
+ * As of Postgres 8.4, the preferred signature for join estimators has 5
+ * arguments, but we still allow the old 4-argument form. Try the
+ * preferred form first.
*/
- object.classId = OperatorRelationId;
- object.objectId = operOid;
- object.objectSubId = 0;
+ joinOid = LookupFuncName(joinName, 5, typeId, true);
+ if (!OidIsValid(joinOid))
+ joinOid = LookupFuncName(joinName, 4, typeId, true);
+ /* If not found, reference the 5-argument signature in error msg */
+ if (!OidIsValid(joinOid))
+ joinOid = LookupFuncName(joinName, 5, typeId, false);
+
+ /* estimators must return float8 */
+ if (get_func_rettype(joinOid) != FLOAT8OID)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+ errmsg("join estimator function %s must return type \"%s\"",
+ NameListToString(joinName), "float8")));
+
+ /* Require EXECUTE rights for the estimator */
+ aclresult = pg_proc_aclcheck(joinOid, GetUserId(), ACL_EXECUTE);
+ if (aclresult != ACLCHECK_OK)
+ aclcheck_error(aclresult, ACL_KIND_PROC,
+ NameListToString(joinName));
- performDeletion(&object, stmt->behavior);
+ return joinOid;
}
/*
{
Relation relation;
HeapTuple tup;
+ Form_pg_operator op;
relation = heap_open(OperatorRelationId, RowExclusiveLock);
- tup = SearchSysCache(OPEROID,
- ObjectIdGetDatum(operOid),
- 0, 0, 0);
+ tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid));
if (!HeapTupleIsValid(tup)) /* should not happen */
elog(ERROR, "cache lookup failed for operator %u", operOid);
+ op = (Form_pg_operator) GETSTRUCT(tup);
+
+ /*
+ * Reset links from commutator and negator, if any. In case of a
+ * self-commutator or self-negator, this means we have to re-fetch the
+ * updated tuple. (We could optimize away updates on the tuple we're
+ * about to drop, but it doesn't seem worth convoluting the logic for.)
+ */
+ if (OidIsValid(op->oprcom) || OidIsValid(op->oprnegate))
+ {
+ OperatorUpd(operOid, op->oprcom, op->oprnegate, true);
+ if (operOid == op->oprcom || operOid == op->oprnegate)
+ {
+ ReleaseSysCache(tup);
+ tup = SearchSysCache1(OPEROID, ObjectIdGetDatum(operOid));
+ if (!HeapTupleIsValid(tup)) /* should not happen */
+ elog(ERROR, "cache lookup failed for operator %u", operOid);
+ }
+ }
simple_heap_delete(relation, &tup->t_self);
heap_close(relation, RowExclusiveLock);
}
-void
-AlterOperatorOwner_oid(Oid operOid, Oid newOwnerId)
-{
- Relation rel;
-
- rel = heap_open(OperatorRelationId, RowExclusiveLock);
-
- AlterOperatorOwner_internal(rel, operOid, newOwnerId);
-
- heap_close(rel, NoLock);
-}
-
/*
- * change operator owner
+ * AlterOperator
+ * routine implementing ALTER OPERATOR <operator> SET (option = ...).
+ *
+ * Currently, only RESTRICT and JOIN estimator functions can be changed.
*/
-void
-AlterOperatorOwner(List *name, TypeName *typeName1, TypeName *typeName2,
- Oid newOwnerId)
-{
- Oid operOid;
- Relation rel;
-
- rel = heap_open(OperatorRelationId, RowExclusiveLock);
-
- operOid = LookupOperNameTypeNames(NULL, name,
- typeName1, typeName2,
- false, -1);
-
- AlterOperatorOwner_internal(rel, operOid, newOwnerId);
-
- heap_close(rel, NoLock);
-}
-
-static void
-AlterOperatorOwner_internal(Relation rel, Oid operOid, Oid newOwnerId)
+ObjectAddress
+AlterOperator(AlterOperatorStmt *stmt)
{
+ ObjectAddress address;
+ Oid oprId;
+ Relation catalog;
HeapTuple tup;
- AclResult aclresult;
Form_pg_operator oprForm;
-
- Assert(RelationGetRelid(rel) == OperatorRelationId);
-
- tup = SearchSysCacheCopy(OPEROID,
- ObjectIdGetDatum(operOid),
- 0, 0, 0);
- if (!HeapTupleIsValid(tup)) /* should not happen */
- elog(ERROR, "cache lookup failed for operator %u", operOid);
-
+ int i;
+ ListCell *pl;
+ Datum values[Natts_pg_operator];
+ bool nulls[Natts_pg_operator];
+ bool replaces[Natts_pg_operator];
+ List *restrictionName = NIL; /* optional restrict. sel. procedure */
+ bool updateRestriction = false;
+ Oid restrictionOid;
+ List *joinName = NIL; /* optional join sel. procedure */
+ bool updateJoin = false;
+ Oid joinOid;
+
+ /* Look up the operator */
+ oprId = LookupOperNameTypeNames(NULL, stmt->opername,
+ (TypeName *) linitial(stmt->operargs),
+ (TypeName *) lsecond(stmt->operargs),
+ false, -1);
+ catalog = heap_open(OperatorRelationId, RowExclusiveLock);
+ tup = SearchSysCacheCopy1(OPEROID, ObjectIdGetDatum(oprId));
+ if (tup == NULL)
+ elog(ERROR, "cache lookup failed for operator %u", oprId);
oprForm = (Form_pg_operator) GETSTRUCT(tup);
- /*
- * If the new owner is the same as the existing owner, consider the
- * command to have succeeded. This is for dump restoration purposes.
- */
- if (oprForm->oprowner != newOwnerId)
+ /* Process options */
+ foreach(pl, stmt->options)
{
- /* Superusers can always do it */
- if (!superuser())
+ DefElem *defel = (DefElem *) lfirst(pl);
+ List *param;
+
+ if (defel->arg == NULL)
+ param = NIL; /* NONE, removes the function */
+ else
+ param = defGetQualifiedName(defel);
+
+ if (pg_strcasecmp(defel->defname, "restrict") == 0)
{
- /* Otherwise, must be owner of the existing object */
- if (!pg_oper_ownercheck(operOid, GetUserId()))
- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER,
- NameStr(oprForm->oprname));
-
- /* Must be able to become new owner */
- check_is_member_of_role(GetUserId(), newOwnerId);
-
- /* New owner must have CREATE privilege on namespace */
- aclresult = pg_namespace_aclcheck(oprForm->oprnamespace,
- newOwnerId,
- ACL_CREATE);
- if (aclresult != ACLCHECK_OK)
- aclcheck_error(aclresult, ACL_KIND_NAMESPACE,
- get_namespace_name(oprForm->oprnamespace));
+ restrictionName = param;
+ updateRestriction = true;
+ }
+ else if (pg_strcasecmp(defel->defname, "join") == 0)
+ {
+ joinName = param;
+ updateJoin = true;
}
/*
- * Modify the owner --- okay to scribble on tup because it's a copy
+ * The rest of the options that CREATE accepts cannot be changed.
+ * Check for them so that we can give a meaningful error message.
*/
- oprForm->oprowner = newOwnerId;
+ else if (pg_strcasecmp(defel->defname, "leftarg") == 0 ||
+ pg_strcasecmp(defel->defname, "rightarg") == 0 ||
+ pg_strcasecmp(defel->defname, "procedure") == 0 ||
+ pg_strcasecmp(defel->defname, "commutator") == 0 ||
+ pg_strcasecmp(defel->defname, "negator") == 0 ||
+ pg_strcasecmp(defel->defname, "hashes") == 0 ||
+ pg_strcasecmp(defel->defname, "merges") == 0)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("operator attribute \"%s\" can not be changed",
+ defel->defname)));
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("operator attribute \"%s\" not recognized",
+ defel->defname)));
+ }
- simple_heap_update(rel, &tup->t_self, tup);
+ /* Check permissions. Must be owner. */
+ if (!pg_oper_ownercheck(oprId, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPER,
+ NameStr(oprForm->oprname));
+
+ /*
+ * Look up restriction and join estimators if specified
+ */
+ if (restrictionName)
+ restrictionOid = ValidateRestrictionEstimator(restrictionName);
+ else
+ restrictionOid = InvalidOid;
+ if (joinName)
+ joinOid = ValidateJoinEstimator(joinName);
+ else
+ joinOid = InvalidOid;
+
+ /* Perform additional checks, like OperatorCreate does */
+ if (!(OidIsValid(oprForm->oprleft) && OidIsValid(oprForm->oprright)))
+ {
+ /* 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")));
+ }
- CatalogUpdateIndexes(rel, tup);
+ 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")));
+ }
- /* Update owner dependency reference */
- changeDependencyOnOwner(OperatorRelationId, operOid, newOwnerId);
+ /* Update the tuple */
+ for (i = 0; i < Natts_pg_operator; ++i)
+ {
+ values[i] = (Datum) 0;
+ replaces[i] = false;
+ nulls[i] = false;
+ }
+ if (updateRestriction)
+ {
+ replaces[Anum_pg_operator_oprrest - 1] = true;
+ values[Anum_pg_operator_oprrest - 1] = restrictionOid;
}
+ if (updateJoin)
+ {
+ replaces[Anum_pg_operator_oprjoin - 1] = true;
+ values[Anum_pg_operator_oprjoin - 1] = joinOid;
+ }
+
+ tup = heap_modify_tuple(tup, RelationGetDescr(catalog),
+ values, nulls, replaces);
+
+ simple_heap_update(catalog, &tup->t_self, tup);
+ CatalogUpdateIndexes(catalog, tup);
+
+ address = makeOperatorDependencies(tup, true);
+
+ InvokeObjectPostAlterHook(OperatorRelationId, oprId, 0);
+
+ heap_close(catalog, NoLock);
- heap_freetuple(tup);
+ return address;
}