1996-07-09 08:22:35 +02:00
|
|
|
/*-------------------------------------------------------------------------
|
|
|
|
*
|
1999-02-14 00:22:53 +01:00
|
|
|
* pg_aggregate.c
|
1997-09-07 07:04:48 +02:00
|
|
|
* routines to support manipulation of the pg_aggregate relation
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
2002-06-20 22:29:54 +02:00
|
|
|
* Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
|
2000-01-26 06:58:53 +01:00
|
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*
|
|
|
|
* IDENTIFICATION
|
2003-07-04 04:51:34 +02:00
|
|
|
* $Header: /cvsroot/pgsql/src/backend/catalog/pg_aggregate.c,v 1.60 2003/07/04 02:51:33 tgl Exp $
|
1996-07-09 08:22:35 +02:00
|
|
|
*
|
|
|
|
*-------------------------------------------------------------------------
|
|
|
|
*/
|
1999-07-16 01:04:24 +02:00
|
|
|
#include "postgres.h"
|
1996-07-09 08:22:35 +02:00
|
|
|
|
1999-07-16 01:04:24 +02:00
|
|
|
#include "access/heapam.h"
|
|
|
|
#include "catalog/catname.h"
|
2002-07-17 00:12:20 +02:00
|
|
|
#include "catalog/dependency.h"
|
1999-11-22 18:56:41 +01:00
|
|
|
#include "catalog/indexing.h"
|
2002-04-09 22:35:55 +02:00
|
|
|
#include "catalog/namespace.h"
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "catalog/pg_aggregate.h"
|
2002-04-11 22:00:18 +02:00
|
|
|
#include "catalog/pg_language.h"
|
1999-07-16 01:04:24 +02:00
|
|
|
#include "catalog/pg_proc.h"
|
2002-04-11 22:00:18 +02:00
|
|
|
#include "optimizer/cost.h"
|
2000-07-17 05:05:41 +02:00
|
|
|
#include "parser/parse_coerce.h"
|
|
|
|
#include "parser/parse_func.h"
|
1999-07-16 07:00:38 +02:00
|
|
|
#include "utils/builtins.h"
|
|
|
|
#include "utils/syscache.h"
|
1996-11-04 00:27:08 +01:00
|
|
|
|
2002-04-11 22:00:18 +02:00
|
|
|
|
2003-07-01 21:10:53 +02:00
|
|
|
static Oid lookup_agg_function(List *fnName, int nargs, Oid *input_types,
|
|
|
|
Oid *rettype);
|
|
|
|
|
|
|
|
|
2002-03-29 20:06:29 +01:00
|
|
|
/*
|
1996-07-09 08:22:35 +02:00
|
|
|
* AggregateCreate
|
|
|
|
*/
|
|
|
|
void
|
2002-03-29 20:06:29 +01:00
|
|
|
AggregateCreate(const char *aggName,
|
|
|
|
Oid aggNamespace,
|
2002-04-09 22:35:55 +02:00
|
|
|
List *aggtransfnName,
|
|
|
|
List *aggfinalfnName,
|
2002-03-29 20:06:29 +01:00
|
|
|
Oid aggBaseType,
|
|
|
|
Oid aggTransType,
|
|
|
|
const char *agginitval)
|
1996-07-09 08:22:35 +02:00
|
|
|
{
|
1997-09-08 04:41:22 +02:00
|
|
|
Relation aggdesc;
|
|
|
|
HeapTuple tup;
|
|
|
|
char nulls[Natts_pg_aggregate];
|
|
|
|
Datum values[Natts_pg_aggregate];
|
|
|
|
Form_pg_proc proc;
|
2000-07-17 05:05:41 +02:00
|
|
|
Oid transfn;
|
2001-03-22 05:01:46 +01:00
|
|
|
Oid finalfn = InvalidOid; /* can be omitted */
|
2003-07-01 21:10:53 +02:00
|
|
|
Oid rettype;
|
2000-07-17 05:05:41 +02:00
|
|
|
Oid finaltype;
|
2000-01-10 18:14:46 +01:00
|
|
|
Oid fnArgs[FUNC_MAX_ARGS];
|
2003-07-01 21:10:53 +02:00
|
|
|
int nargs_transfn;
|
2002-04-11 22:00:18 +02:00
|
|
|
Oid procOid;
|
1997-09-08 04:41:22 +02:00
|
|
|
TupleDesc tupDesc;
|
2000-07-17 05:05:41 +02:00
|
|
|
int i;
|
2002-09-04 22:31:48 +02:00
|
|
|
ObjectAddress myself,
|
|
|
|
referenced;
|
1997-09-07 07:04:48 +02:00
|
|
|
|
|
|
|
/* sanity checks */
|
|
|
|
if (!aggName)
|
2001-08-10 17:49:39 +02:00
|
|
|
elog(ERROR, "no aggregate name supplied");
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-07-17 05:05:41 +02:00
|
|
|
if (!aggtransfnName)
|
2001-08-10 17:49:39 +02:00
|
|
|
elog(ERROR, "aggregate must have a transition function");
|
2000-03-26 21:43:58 +02:00
|
|
|
|
2003-07-01 21:10:53 +02:00
|
|
|
/*
|
|
|
|
* If transtype is polymorphic, basetype must be polymorphic also;
|
|
|
|
* else we will have no way to deduce the actual transtype.
|
|
|
|
*/
|
|
|
|
if ((aggTransType == ANYARRAYOID || aggTransType == ANYELEMENTOID) &&
|
|
|
|
!(aggBaseType == ANYARRAYOID || aggBaseType == ANYELEMENTOID))
|
|
|
|
elog(ERROR, "an aggregate using ANYARRAY or ANYELEMENT as trans type "
|
|
|
|
"must also have one of them as its base type");
|
|
|
|
|
2000-07-17 05:05:41 +02:00
|
|
|
/* handle transfn */
|
2002-03-29 20:06:29 +01:00
|
|
|
MemSet(fnArgs, 0, FUNC_MAX_ARGS * sizeof(Oid));
|
|
|
|
fnArgs[0] = aggTransType;
|
2002-08-22 02:01:51 +02:00
|
|
|
if (aggBaseType == ANYOID)
|
2003-07-01 21:10:53 +02:00
|
|
|
nargs_transfn = 1;
|
2002-08-22 02:01:51 +02:00
|
|
|
else
|
1997-09-07 07:04:48 +02:00
|
|
|
{
|
2002-03-29 20:06:29 +01:00
|
|
|
fnArgs[1] = aggBaseType;
|
2003-07-01 21:10:53 +02:00
|
|
|
nargs_transfn = 2;
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
2003-07-01 21:10:53 +02:00
|
|
|
transfn = lookup_agg_function(aggtransfnName, nargs_transfn, fnArgs,
|
|
|
|
&rettype);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Return type of transfn (possibly after refinement by
|
|
|
|
* enforce_generic_type_consistency, if transtype isn't polymorphic)
|
|
|
|
* must exactly match declared transtype.
|
|
|
|
*
|
|
|
|
* In the non-polymorphic-transtype case, it might be okay to allow
|
|
|
|
* a rettype that's binary-coercible to transtype, but I'm not quite
|
|
|
|
* convinced that it's either safe or useful. When transtype is
|
|
|
|
* polymorphic we *must* demand exact equality.
|
|
|
|
*/
|
|
|
|
if (rettype != aggTransType)
|
|
|
|
elog(ERROR, "return type of transition function %s is not %s",
|
|
|
|
NameListToString(aggtransfnName), format_type_be(aggTransType));
|
|
|
|
|
2002-04-09 22:35:55 +02:00
|
|
|
tup = SearchSysCache(PROCOID,
|
|
|
|
ObjectIdGetDatum(transfn),
|
|
|
|
0, 0, 0);
|
2000-07-17 05:05:41 +02:00
|
|
|
if (!HeapTupleIsValid(tup))
|
2003-07-04 04:51:34 +02:00
|
|
|
elog(ERROR, "cache lookup of function %u failed", transfn);
|
2000-07-17 05:05:41 +02:00
|
|
|
proc = (Form_pg_proc) GETSTRUCT(tup);
|
2001-03-22 05:01:46 +01:00
|
|
|
|
2000-07-17 05:05:41 +02:00
|
|
|
/*
|
2001-03-22 05:01:46 +01:00
|
|
|
* If the transfn is strict and the initval is NULL, make sure input
|
2002-09-04 22:31:48 +02:00
|
|
|
* type and transtype are the same (or at least binary-compatible), so
|
|
|
|
* that it's OK to use the first input value as the initial
|
2001-03-22 05:01:46 +01:00
|
|
|
* transValue.
|
2000-07-17 05:05:41 +02:00
|
|
|
*/
|
2000-11-16 23:30:52 +01:00
|
|
|
if (proc->proisstrict && agginitval == NULL)
|
2000-07-17 05:05:41 +02:00
|
|
|
{
|
Extend pg_cast castimplicit column to a three-way value; this allows us
to be flexible about assignment casts without introducing ambiguity in
operator/function resolution. Introduce a well-defined promotion hierarchy
for numeric datatypes (int2->int4->int8->numeric->float4->float8).
Change make_const to initially label numeric literals as int4, int8, or
numeric (never float8 anymore).
Explicitly mark Func and RelabelType nodes to indicate whether they came
from a function call, explicit cast, or implicit cast; use this to do
reverse-listing more accurately and without so many heuristics.
Explicit casts to char, varchar, bit, varbit will truncate or pad without
raising an error (the pre-7.2 behavior), while assigning to a column without
any explicit cast will still raise an error for wrong-length data like 7.3.
This more nearly follows the SQL spec than 7.2 behavior (we should be
reporting a 'completion condition' in the explicit-cast cases, but we have
no mechanism for that, so just do silent truncation).
Fix some problems with enforcement of typmod for array elements;
it didn't work at all in 'UPDATE ... SET array[n] = foo', for example.
Provide a generalized array_length_coerce() function to replace the
specialized per-array-type functions that used to be needed (and were
missing for NUMERIC as well as all the datetime types).
Add missing conversions int8<->float4, text<->numeric, oid<->int8.
initdb forced.
2002-09-18 23:35:25 +02:00
|
|
|
if (!IsBinaryCoercible(aggBaseType, aggTransType))
|
2001-08-10 17:49:39 +02:00
|
|
|
elog(ERROR, "must not omit initval when transfn is strict and transtype is not compatible with input type");
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
2000-11-16 23:30:52 +01:00
|
|
|
ReleaseSysCache(tup);
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2000-07-17 05:05:41 +02:00
|
|
|
/* handle finalfn, if supplied */
|
1997-09-07 07:04:48 +02:00
|
|
|
if (aggfinalfnName)
|
|
|
|
{
|
2002-04-11 22:00:18 +02:00
|
|
|
MemSet(fnArgs, 0, FUNC_MAX_ARGS * sizeof(Oid));
|
2002-03-29 20:06:29 +01:00
|
|
|
fnArgs[0] = aggTransType;
|
2003-07-01 21:10:53 +02:00
|
|
|
finalfn = lookup_agg_function(aggfinalfnName, 1, fnArgs,
|
|
|
|
&finaltype);
|
1997-09-07 07:04:48 +02:00
|
|
|
}
|
2000-03-26 21:43:58 +02:00
|
|
|
else
|
|
|
|
{
|
2000-04-12 19:17:23 +02:00
|
|
|
/*
|
2000-07-17 05:05:41 +02:00
|
|
|
* If no finalfn, aggregate result type is type of the state value
|
2000-03-26 21:43:58 +02:00
|
|
|
*/
|
2002-03-29 20:06:29 +01:00
|
|
|
finaltype = aggTransType;
|
2000-03-26 21:43:58 +02:00
|
|
|
}
|
2000-07-17 05:05:41 +02:00
|
|
|
Assert(OidIsValid(finaltype));
|
1997-09-07 07:04:48 +02:00
|
|
|
|
2003-07-01 21:10:53 +02:00
|
|
|
/*
|
|
|
|
* If finaltype (i.e. aggregate return type) is polymorphic,
|
|
|
|
* basetype must be polymorphic also, else parser will fail to deduce
|
|
|
|
* result type. (Note: given the previous test on transtype and basetype,
|
|
|
|
* this cannot happen, unless someone has snuck a finalfn definition
|
|
|
|
* into the catalogs that itself violates the rule against polymorphic
|
|
|
|
* result with no polymorphic input.)
|
|
|
|
*/
|
|
|
|
if ((finaltype == ANYARRAYOID || finaltype == ANYELEMENTOID) &&
|
|
|
|
!(aggBaseType == ANYARRAYOID || aggBaseType == ANYELEMENTOID))
|
|
|
|
elog(ERROR, "an aggregate returning ANYARRAY or ANYELEMENT "
|
|
|
|
"must also have one of them as its base type");
|
|
|
|
|
2002-04-11 22:00:18 +02:00
|
|
|
/*
|
|
|
|
* Everything looks okay. Try to create the pg_proc entry for the
|
2002-09-04 22:31:48 +02:00
|
|
|
* aggregate. (This could fail if there's already a conflicting
|
|
|
|
* entry.)
|
2002-04-11 22:00:18 +02:00
|
|
|
*/
|
|
|
|
MemSet(fnArgs, 0, FUNC_MAX_ARGS * sizeof(Oid));
|
|
|
|
fnArgs[0] = aggBaseType;
|
|
|
|
|
|
|
|
procOid = ProcedureCreate(aggName,
|
|
|
|
aggNamespace,
|
2002-09-04 22:31:48 +02:00
|
|
|
false, /* no replacement */
|
|
|
|
false, /* doesn't return a set */
|
|
|
|
finaltype, /* returnType */
|
|
|
|
INTERNALlanguageId, /* languageObjectId */
|
2002-05-22 19:21:02 +02:00
|
|
|
0,
|
2002-09-04 22:31:48 +02:00
|
|
|
"aggregate_dummy", /* placeholder proc */
|
|
|
|
"-", /* probin */
|
|
|
|
true, /* isAgg */
|
|
|
|
false, /* security invoker (currently not
|
|
|
|
* definable for agg) */
|
|
|
|
false, /* isStrict (not needed for agg) */
|
|
|
|
PROVOLATILE_IMMUTABLE, /* volatility (not
|
|
|
|
* needed for agg) */
|
|
|
|
1, /* parameterCount */
|
|
|
|
fnArgs); /* parameterTypes */
|
2002-04-11 22:00:18 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Okay to create the pg_aggregate entry.
|
|
|
|
*/
|
|
|
|
|
1997-09-07 07:04:48 +02:00
|
|
|
/* initialize nulls and values */
|
|
|
|
for (i = 0; i < Natts_pg_aggregate; i++)
|
|
|
|
{
|
|
|
|
nulls[i] = ' ';
|
|
|
|
values[i] = (Datum) NULL;
|
|
|
|
}
|
2002-04-11 22:00:18 +02:00
|
|
|
values[Anum_pg_aggregate_aggfnoid - 1] = ObjectIdGetDatum(procOid);
|
2000-07-17 05:05:41 +02:00
|
|
|
values[Anum_pg_aggregate_aggtransfn - 1] = ObjectIdGetDatum(transfn);
|
|
|
|
values[Anum_pg_aggregate_aggfinalfn - 1] = ObjectIdGetDatum(finalfn);
|
2002-03-29 20:06:29 +01:00
|
|
|
values[Anum_pg_aggregate_aggtranstype - 1] = ObjectIdGetDatum(aggTransType);
|
2000-07-17 05:05:41 +02:00
|
|
|
if (agginitval)
|
|
|
|
values[Anum_pg_aggregate_agginitval - 1] =
|
|
|
|
DirectFunctionCall1(textin, CStringGetDatum(agginitval));
|
1997-09-07 07:04:48 +02:00
|
|
|
else
|
2000-07-17 05:05:41 +02:00
|
|
|
nulls[Anum_pg_aggregate_agginitval - 1] = 'n';
|
1997-09-07 07:04:48 +02:00
|
|
|
|
1999-09-18 21:08:25 +02:00
|
|
|
aggdesc = heap_openr(AggregateRelationName, RowExclusiveLock);
|
1997-09-07 07:04:48 +02:00
|
|
|
tupDesc = aggdesc->rd_att;
|
2002-04-11 22:00:18 +02:00
|
|
|
|
|
|
|
tup = heap_formtuple(tupDesc, values, nulls);
|
2002-05-22 00:05:55 +02:00
|
|
|
simple_heap_insert(aggdesc, tup);
|
1999-11-22 18:56:41 +01:00
|
|
|
|
2002-08-05 05:29:17 +02:00
|
|
|
CatalogUpdateIndexes(aggdesc, tup);
|
1999-11-22 18:56:41 +01:00
|
|
|
|
1999-09-18 21:08:25 +02:00
|
|
|
heap_close(aggdesc, RowExclusiveLock);
|
2002-07-17 00:12:20 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Create dependencies for the aggregate (above and beyond those
|
|
|
|
* already made by ProcedureCreate). Note: we don't need an explicit
|
|
|
|
* dependency on aggTransType since we depend on it indirectly through
|
|
|
|
* transfn.
|
|
|
|
*/
|
|
|
|
myself.classId = RelOid_pg_proc;
|
|
|
|
myself.objectId = procOid;
|
|
|
|
myself.objectSubId = 0;
|
|
|
|
|
|
|
|
/* Depends on transition function */
|
|
|
|
referenced.classId = RelOid_pg_proc;
|
|
|
|
referenced.objectId = transfn;
|
|
|
|
referenced.objectSubId = 0;
|
|
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
|
|
|
|
/* Depends on final function, if any */
|
|
|
|
if (OidIsValid(finalfn))
|
|
|
|
{
|
|
|
|
referenced.classId = RelOid_pg_proc;
|
|
|
|
referenced.objectId = finalfn;
|
|
|
|
referenced.objectSubId = 0;
|
|
|
|
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
|
|
|
|
}
|
1996-07-09 08:22:35 +02:00
|
|
|
}
|
2003-07-01 21:10:53 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* lookup_agg_function -- common code for finding both transfn and finalfn
|
|
|
|
*/
|
|
|
|
static Oid
|
|
|
|
lookup_agg_function(List *fnName,
|
|
|
|
int nargs,
|
|
|
|
Oid *input_types,
|
|
|
|
Oid *rettype)
|
|
|
|
{
|
|
|
|
Oid fnOid;
|
|
|
|
bool retset;
|
|
|
|
Oid *true_oid_array;
|
|
|
|
FuncDetailCode fdresult;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* func_get_detail looks up the function in the catalogs, does
|
|
|
|
* disambiguation for polymorphic functions, handles inheritance, and
|
|
|
|
* returns the funcid and type and set or singleton status of the
|
|
|
|
* function's return value. it also returns the true argument types
|
|
|
|
* to the function.
|
|
|
|
*/
|
|
|
|
fdresult = func_get_detail(fnName, NIL, nargs, input_types,
|
|
|
|
&fnOid, rettype, &retset,
|
|
|
|
&true_oid_array);
|
|
|
|
|
|
|
|
/* only valid case is a normal function not returning a set */
|
2003-07-04 04:51:34 +02:00
|
|
|
if (fdresult != FUNCDETAIL_NORMAL || !OidIsValid(fnOid))
|
|
|
|
elog(ERROR, "function %s does not exist",
|
|
|
|
func_signature_string(fnName, nargs, input_types));
|
|
|
|
if (retset)
|
|
|
|
elog(ERROR, "function %s returns a set",
|
|
|
|
func_signature_string(fnName, nargs, input_types));
|
2003-07-01 21:10:53 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If the given type(s) are all polymorphic, there's nothing we
|
|
|
|
* can check. Otherwise, enforce consistency, and possibly refine
|
|
|
|
* the result type.
|
|
|
|
*/
|
|
|
|
if ((input_types[0] == ANYARRAYOID || input_types[0] == ANYELEMENTOID) &&
|
|
|
|
(nargs == 1 ||
|
|
|
|
(input_types[1] == ANYARRAYOID || input_types[1] == ANYELEMENTOID)))
|
|
|
|
{
|
|
|
|
/* nothing to check here */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*rettype = enforce_generic_type_consistency(input_types,
|
|
|
|
true_oid_array,
|
|
|
|
nargs,
|
|
|
|
*rettype);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* func_get_detail will find functions requiring run-time argument type
|
|
|
|
* coercion, but nodeAgg.c isn't prepared to deal with that
|
|
|
|
*/
|
|
|
|
if (true_oid_array[0] != ANYARRAYOID &&
|
|
|
|
true_oid_array[0] != ANYELEMENTOID &&
|
|
|
|
!IsBinaryCoercible(input_types[0], true_oid_array[0]))
|
2003-07-04 04:51:34 +02:00
|
|
|
elog(ERROR, "function %s requires run-time type coercion",
|
|
|
|
func_signature_string(fnName, nargs, true_oid_array));
|
2003-07-01 21:10:53 +02:00
|
|
|
|
|
|
|
if (nargs == 2 &&
|
|
|
|
true_oid_array[1] != ANYARRAYOID &&
|
|
|
|
true_oid_array[1] != ANYELEMENTOID &&
|
|
|
|
!IsBinaryCoercible(input_types[1], true_oid_array[1]))
|
2003-07-04 04:51:34 +02:00
|
|
|
elog(ERROR, "function %s requires run-time type coercion",
|
|
|
|
func_signature_string(fnName, nargs, true_oid_array));
|
2003-07-01 21:10:53 +02:00
|
|
|
|
|
|
|
return fnOid;
|
|
|
|
}
|