Create the infrastructure for planner support functions.

Rename/repurpose pg_proc.protransform as "prosupport".  The idea is
still that it names an internal function that provides knowledge to
the planner about the behavior of the function it's attached to;
but redesign the API specification so that it's not limited to doing
just one thing, but can support an extensible set of requests.

The original purpose of simplifying a function call is handled by
the first request type to be invented, SupportRequestSimplify.
Adjust all the existing transform functions to handle this API,
and rename them fron "xxx_transform" to "xxx_support" to reflect
the potential generalization of what they do.  (Since we never
previously provided any way for extensions to add transform functions,
this change doesn't create an API break for them.)

Also add DDL and pg_dump support for attaching a support function to a
user-defined function.  Unfortunately, DDL access has to be restricted
to superusers, at least for now; but seeing that support functions
will pretty much have to be written in C, that limitation is just
theoretical.  (This support is untested in this patch, but a follow-on
patch will add cases that exercise it.)

Discussion: https://postgr.es/m/15193.1548028093@sss.pgh.pa.us
This commit is contained in:
Tom Lane 2019-02-09 18:08:48 -05:00
parent 1a8d5afb0d
commit 1fb57af920
41 changed files with 698 additions and 294 deletions

View File

@ -5146,11 +5146,11 @@ SCRAM-SHA-256$<replaceable>&lt;iteration count&gt;</replaceable>:<replaceable>&l
</row>
<row>
<entry><structfield>protransform</structfield></entry>
<entry><structfield>prosupport</structfield></entry>
<entry><type>regproc</type></entry>
<entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></entry>
<entry>Calls to this function can be simplified by this other function
(see <xref linkend="xfunc-transform-functions"/>)</entry>
<entry>Optional planner support function for this function
(see <xref linkend="xfunc-optimization"/>)</entry>
</row>
<row>

View File

@ -4521,6 +4521,13 @@
<entry>reserved</entry>
<entry>reserved</entry>
</row>
<row>
<entry><token>SUPPORT</token></entry>
<entry>non-reserved</entry>
<entry></entry>
<entry></entry>
<entry></entry>
</row>
<row>
<entry><token>SYMMETRIC</token></entry>
<entry>reserved</entry>

View File

@ -40,6 +40,7 @@ ALTER FUNCTION <replaceable>name</replaceable> [ ( [ [ <replaceable class="param
PARALLEL { UNSAFE | RESTRICTED | SAFE }
COST <replaceable class="parameter">execution_cost</replaceable>
ROWS <replaceable class="parameter">result_rows</replaceable>
SUPPORT <replaceable class="parameter">support_function</replaceable>
SET <replaceable class="parameter">configuration_parameter</replaceable> { TO | = } { <replaceable class="parameter">value</replaceable> | DEFAULT }
SET <replaceable class="parameter">configuration_parameter</replaceable> FROM CURRENT
RESET <replaceable class="parameter">configuration_parameter</replaceable>
@ -248,6 +249,24 @@ ALTER FUNCTION <replaceable>name</replaceable> [ ( [ [ <replaceable class="param
</listitem>
</varlistentry>
<varlistentry>
<term><literal>SUPPORT</literal> <replaceable class="parameter">support_function</replaceable></term>
<listitem>
<para>
Set or change the planner support function to use for this function.
See <xref linkend="xfunc-optimization"/> for details. You must be
superuser to use this option.
</para>
<para>
This option cannot be used to remove the support function altogether,
since it must name a new support function. Use <command>CREATE OR
REPLACE FUNCTION</command> if you need to do that.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>configuration_parameter</replaceable></term>
<term><replaceable>value</replaceable></term>

View File

@ -33,6 +33,7 @@ CREATE [ OR REPLACE ] FUNCTION
| PARALLEL { UNSAFE | RESTRICTED | SAFE }
| COST <replaceable class="parameter">execution_cost</replaceable>
| ROWS <replaceable class="parameter">result_rows</replaceable>
| SUPPORT <replaceable class="parameter">support_function</replaceable>
| SET <replaceable class="parameter">configuration_parameter</replaceable> { TO <replaceable class="parameter">value</replaceable> | = <replaceable class="parameter">value</replaceable> | FROM CURRENT }
| AS '<replaceable class="parameter">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
@ -477,6 +478,19 @@ CREATE [ OR REPLACE ] FUNCTION
</listitem>
</varlistentry>
<varlistentry>
<term><literal>SUPPORT</literal> <replaceable class="parameter">support_function</replaceable></term>
<listitem>
<para>
The name (optionally schema-qualified) of a <firstterm>planner support
function</firstterm> to use for this function. See
<xref linkend="xfunc-optimization"/> for details.
You must be superuser to use this option.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><replaceable>configuration_parameter</replaceable></term>
<term><replaceable>value</replaceable></term>

View File

@ -3241,40 +3241,6 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray
</para>
</sect2>
<sect2 id="xfunc-transform-functions">
<title>Transform Functions</title>
<para>
Some function calls can be simplified during planning based on
properties specific to the function. For example,
<literal>int4mul(n, 1)</literal> could be simplified to just <literal>n</literal>.
To define such function-specific optimizations, write a
<firstterm>transform function</firstterm> and place its OID in the
<structfield>protransform</structfield> field of the primary function's
<structname>pg_proc</structname> entry. The transform function must have the SQL
signature <literal>protransform(internal) RETURNS internal</literal>. The
argument, actually <type>FuncExpr *</type>, is a dummy node representing a
call to the primary function. If the transform function's study of the
expression tree proves that a simplified expression tree can substitute
for all possible concrete calls represented thereby, build and return
that simplified expression. Otherwise, return a <literal>NULL</literal>
pointer (<emphasis>not</emphasis> a SQL null).
</para>
<para>
We make no guarantee that <productname>PostgreSQL</productname> will never call the
primary function in cases that the transform function could simplify.
Ensure rigorous equivalence between the simplified expression and an
actual call to the primary function.
</para>
<para>
Currently, this facility is not exposed to users at the SQL level
because of security concerns, so it is only practical to use for
optimizing built-in functions.
</para>
</sect2>
<sect2>
<title>Shared Memory and LWLocks</title>
@ -3388,3 +3354,89 @@ if (!ptr)
</sect2>
</sect1>
<sect1 id="xfunc-optimization">
<title>Function Optimization Information</title>
<indexterm zone="xfunc-optimization">
<primary>optimization information</primary>
<secondary>for functions</secondary>
</indexterm>
<para>
By default, a function is just a <quote>black box</quote> that the
database system knows very little about the behavior of. However,
that means that queries using the function may be executed much less
efficiently than they could be. It is possible to supply additional
knowledge that helps the planner optimize function calls.
</para>
<para>
Some basic facts can be supplied by declarative annotations provided in
the <xref linkend="sql-createfunction"/> command. Most important of
these is the function's <link linkend="xfunc-volatility">volatility
category</link> (<literal>IMMUTABLE</literal>, <literal>STABLE</literal>,
or <literal>VOLATILE</literal>); one should always be careful to
specify this correctly when defining a function.
The parallel safety property (<literal>PARALLEL
UNSAFE</literal>, <literal>PARALLEL RESTRICTED</literal>, or
<literal>PARALLEL SAFE</literal>) must also be specified if you hope
to use the function in parallelized queries.
It can also be useful to specify the function's estimated execution
cost, and/or the number of rows a set-returning function is estimated
to return. However, the declarative way of specifying those two
facts only allows specifying a constant value, which is often
inadequate.
</para>
<para>
It is also possible to attach a <firstterm>planner support
function</firstterm> to a SQL-callable function (called
its <firstterm>target function</firstterm>), and thereby provide
knowledge about the target function that is too complex to be
represented declaratively. Planner support functions have to be
written in C (although their target functions might not be), so this is
an advanced feature that relatively few people will use.
</para>
<para>
A planner support function must have the SQL signature
<programlisting>
supportfn(internal) returns internal
</programlisting>
It is attached to its target function by specifying
the <literal>SUPPORT</literal> clause when creating the target function.
</para>
<para>
The details of the API for planner support functions can be found in
file <filename>src/include/nodes/supportnodes.h</filename> in the
<productname>PostgreSQL</productname> source code. Here we provide
just an overview of what planner support functions can do.
The set of possible requests to a support function is extensible,
so more things might be possible in future versions.
</para>
<para>
Some function calls can be simplified during planning based on
properties specific to the function. For example,
<literal>int4mul(n, 1)</literal> could be simplified to
just <literal>n</literal>. This type of transformation can be
performed by a planner support function, by having it implement
the <literal>SupportRequestSimplify</literal> request type.
The support function will be called for each instance of its target
function found in a query parse tree. If it finds that the particular
call can be simplified into some other form, it can build and return a
parse tree representing that expression. This will automatically work
for operators based on the function, too &mdash; in the example just
given, <literal>n * 1</literal> would also be simplified to
<literal>n</literal>.
(But note that this is just an example; this particular
optimization is not actually performed by
standard <productname>PostgreSQL</productname>.)
We make no guarantee that <productname>PostgreSQL</productname> will
never call the target function in cases that the support function could
simplify. Ensure rigorous equivalence between the simplified
expression and an actual execution of the target function.
</para>
</sect1>

View File

@ -78,6 +78,11 @@ SELECT (a + b) AS c FROM test_complex;
<sect1 id="xoper-optimization">
<title>Operator Optimization Information</title>
<indexterm zone="xoper-optimization">
<primary>optimization information</primary>
<secondary>for operators</secondary>
</indexterm>
<para>
A <productname>PostgreSQL</productname> operator definition can include
several optional clauses that tell the system useful things about how
@ -97,6 +102,13 @@ SELECT (a + b) AS c FROM test_complex;
the ones that release &version; understands.
</para>
<para>
It is also possible to attach a planner support function to the function
that underlies an operator, providing another way of telling the system
about the behavior of the operator.
See <xref linkend="xfunc-optimization"/> for more information.
</para>
<sect2>
<title><literal>COMMUTATOR</literal></title>

View File

@ -632,6 +632,7 @@ AggregateCreate(const char *aggName,
parameterDefaults, /* parameterDefaults */
PointerGetDatum(NULL), /* trftypes */
PointerGetDatum(NULL), /* proconfig */
InvalidOid, /* no prosupport */
1, /* procost */
0); /* prorows */
procOid = myself.objectId;

View File

@ -286,9 +286,12 @@ deleteDependencyRecordsForClass(Oid classId, Oid objectId,
* newRefObjectId is the new referenced object (must be of class refClassId).
*
* Note the lack of objsubid parameters. If there are subobject references
* they will all be readjusted.
* they will all be readjusted. Also, there is an expectation that we are
* dealing with NORMAL dependencies: if we have to replace an (implicit)
* dependency on a pinned object with an explicit dependency on an unpinned
* one, the new one will be NORMAL.
*
* Returns the number of records updated.
* Returns the number of records updated -- zero indicates a problem.
*/
long
changeDependencyFor(Oid classId, Oid objectId,
@ -301,35 +304,52 @@ changeDependencyFor(Oid classId, Oid objectId,
SysScanDesc scan;
HeapTuple tup;
ObjectAddress objAddr;
ObjectAddress depAddr;
bool oldIsPinned;
bool newIsPinned;
depRel = table_open(DependRelationId, RowExclusiveLock);
/*
* If oldRefObjectId is pinned, there won't be any dependency entries on
* it --- we can't cope in that case. (This isn't really worth expending
* code to fix, in current usage; it just means you can't rename stuff out
* of pg_catalog, which would likely be a bad move anyway.)
* Check to see if either oldRefObjectId or newRefObjectId is pinned.
* Pinned objects should not have any dependency entries pointing to them,
* so in these cases we should add or remove a pg_depend entry, or do
* nothing at all, rather than update an entry as in the normal case.
*/
objAddr.classId = refClassId;
objAddr.objectId = oldRefObjectId;
objAddr.objectSubId = 0;
if (isObjectPinned(&objAddr, depRel))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot remove dependency on %s because it is a system object",
getObjectDescription(&objAddr))));
oldIsPinned = isObjectPinned(&objAddr, depRel);
/*
* We can handle adding a dependency on something pinned, though, since
* that just means deleting the dependency entry.
*/
objAddr.objectId = newRefObjectId;
newIsPinned = isObjectPinned(&objAddr, depRel);
/* Now search for dependency records */
if (oldIsPinned)
{
table_close(depRel, RowExclusiveLock);
/*
* If both are pinned, we need do nothing. However, return 1 not 0,
* else callers will think this is an error case.
*/
if (newIsPinned)
return 1;
/*
* There is no old dependency record, but we should insert a new one.
* Assume a normal dependency is wanted.
*/
depAddr.classId = classId;
depAddr.objectId = objectId;
depAddr.objectSubId = 0;
recordDependencyOn(&depAddr, &objAddr, DEPENDENCY_NORMAL);
return 1;
}
/* There should be existing dependency record(s), so search. */
ScanKeyInit(&key[0],
Anum_pg_depend_classid,
BTEqualStrategyNumber, F_OIDEQ,

View File

@ -88,6 +88,7 @@ ProcedureCreate(const char *procedureName,
List *parameterDefaults,
Datum trftypes,
Datum proconfig,
Oid prosupport,
float4 procost,
float4 prorows)
{
@ -319,7 +320,7 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_procost - 1] = Float4GetDatum(procost);
values[Anum_pg_proc_prorows - 1] = Float4GetDatum(prorows);
values[Anum_pg_proc_provariadic - 1] = ObjectIdGetDatum(variadicType);
values[Anum_pg_proc_protransform - 1] = ObjectIdGetDatum(InvalidOid);
values[Anum_pg_proc_prosupport - 1] = ObjectIdGetDatum(prosupport);
values[Anum_pg_proc_prokind - 1] = CharGetDatum(prokind);
values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer);
values[Anum_pg_proc_proleakproof - 1] = BoolGetDatum(isLeakProof);
@ -656,6 +657,15 @@ ProcedureCreate(const char *procedureName,
recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
NIL, DEPENDENCY_NORMAL);
/* dependency on support function, if any */
if (OidIsValid(prosupport))
{
referenced.classId = ProcedureRelationId;
referenced.objectId = prosupport;
referenced.objectSubId = 0;
recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
}
/* dependency on owner */
if (!is_update)
recordDependencyOnOwner(ProcedureRelationId, retval, proowner);

View File

@ -479,6 +479,7 @@ compute_common_attribute(ParseState *pstate,
List **set_items,
DefElem **cost_item,
DefElem **rows_item,
DefElem **support_item,
DefElem **parallel_item)
{
if (strcmp(defel->defname, "volatility") == 0)
@ -537,6 +538,15 @@ compute_common_attribute(ParseState *pstate,
*rows_item = defel;
}
else if (strcmp(defel->defname, "support") == 0)
{
if (is_procedure)
goto procedure_error;
if (*support_item)
goto duplicate_error;
*support_item = defel;
}
else if (strcmp(defel->defname, "parallel") == 0)
{
if (is_procedure)
@ -635,6 +645,45 @@ update_proconfig_value(ArrayType *a, List *set_items)
return a;
}
static Oid
interpret_func_support(DefElem *defel)
{
List *procName = defGetQualifiedName(defel);
Oid procOid;
Oid argList[1];
/*
* Support functions always take one INTERNAL argument and return
* INTERNAL.
*/
argList[0] = INTERNALOID;
procOid = LookupFuncName(procName, 1, argList, true);
if (!OidIsValid(procOid))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_FUNCTION),
errmsg("function %s does not exist",
func_signature_string(procName, 1, NIL, argList))));
if (get_func_rettype(procOid) != INTERNALOID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
errmsg("support function %s must return type %s",
NameListToString(procName), "internal")));
/*
* Someday we might want an ACL check here; but for now, we insist that
* you be superuser to specify a support function, so privilege on the
* support function is moot.
*/
if (!superuser())
ereport(ERROR,
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
errmsg("must be superuser to specify a support function")));
return procOid;
}
/*
* Dissect the list of options assembled in gram.y into function
@ -655,6 +704,7 @@ compute_function_attributes(ParseState *pstate,
ArrayType **proconfig,
float4 *procost,
float4 *prorows,
Oid *prosupport,
char *parallel_p)
{
ListCell *option;
@ -669,6 +719,7 @@ compute_function_attributes(ParseState *pstate,
List *set_items = NIL;
DefElem *cost_item = NULL;
DefElem *rows_item = NULL;
DefElem *support_item = NULL;
DefElem *parallel_item = NULL;
foreach(option, options)
@ -726,6 +777,7 @@ compute_function_attributes(ParseState *pstate,
&set_items,
&cost_item,
&rows_item,
&support_item,
&parallel_item))
{
/* recognized common option */
@ -788,6 +840,8 @@ compute_function_attributes(ParseState *pstate,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ROWS must be positive")));
}
if (support_item)
*prosupport = interpret_func_support(support_item);
if (parallel_item)
*parallel_p = interpret_func_parallel(parallel_item);
}
@ -893,6 +947,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
ArrayType *proconfig;
float4 procost;
float4 prorows;
Oid prosupport;
HeapTuple languageTuple;
Form_pg_language languageStruct;
List *as_clause;
@ -917,6 +972,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
proconfig = NULL;
procost = -1; /* indicates not set */
prorows = -1; /* indicates not set */
prosupport = InvalidOid;
parallel = PROPARALLEL_UNSAFE;
/* Extract non-default attributes from stmt->options list */
@ -926,7 +982,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
&as_clause, &language, &transformDefElem,
&isWindowFunc, &volatility,
&isStrict, &security, &isLeakProof,
&proconfig, &procost, &prorows, &parallel);
&proconfig, &procost, &prorows,
&prosupport, &parallel);
/* Look up the language and validate permissions */
languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
@ -1113,6 +1170,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
parameterDefaults,
PointerGetDatum(trftypes),
PointerGetDatum(proconfig),
prosupport,
procost,
prorows);
}
@ -1187,6 +1245,7 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
List *set_items = NIL;
DefElem *cost_item = NULL;
DefElem *rows_item = NULL;
DefElem *support_item = NULL;
DefElem *parallel_item = NULL;
ObjectAddress address;
@ -1194,6 +1253,8 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
funcOid = LookupFuncWithArgs(stmt->objtype, stmt->func, false);
ObjectAddressSet(address, ProcedureRelationId, funcOid);
tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid));
if (!HeapTupleIsValid(tup)) /* should not happen */
elog(ERROR, "cache lookup failed for function %u", funcOid);
@ -1228,6 +1289,7 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
&set_items,
&cost_item,
&rows_item,
&support_item,
&parallel_item) == false)
elog(ERROR, "option \"%s\" not recognized", defel->defname);
}
@ -1266,6 +1328,28 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ROWS is not applicable when function does not return a set")));
}
if (support_item)
{
/* interpret_func_support handles the privilege check */
Oid newsupport = interpret_func_support(support_item);
/* Add or replace dependency on support function */
if (OidIsValid(procForm->prosupport))
changeDependencyFor(ProcedureRelationId, funcOid,
ProcedureRelationId, procForm->prosupport,
newsupport);
else
{
ObjectAddress referenced;
referenced.classId = ProcedureRelationId;
referenced.objectId = newsupport;
referenced.objectSubId = 0;
recordDependencyOn(&address, &referenced, DEPENDENCY_NORMAL);
}
procForm->prosupport = newsupport;
}
if (set_items)
{
Datum datum;
@ -1308,8 +1392,6 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
InvokeObjectPostAlterHook(ProcedureRelationId, funcOid, 0);
ObjectAddressSet(address, ProcedureRelationId, funcOid);
table_close(rel, NoLock);
heap_freetuple(tup);

View File

@ -141,6 +141,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
NIL,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
InvalidOid,
1,
0);
handlerOid = tmpAddr.objectId;
@ -180,6 +181,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
NIL,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
InvalidOid,
1,
0);
inlineOid = tmpAddr.objectId;
@ -222,6 +224,7 @@ CreateProceduralLanguage(CreatePLangStmt *stmt)
NIL,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
InvalidOid,
1,
0);
valOid = tmpAddr.objectId;

View File

@ -1664,6 +1664,7 @@ makeRangeConstructors(const char *name, Oid namespace,
NIL, /* parameterDefaults */
PointerGetDatum(NULL), /* trftypes */
PointerGetDatum(NULL), /* proconfig */
InvalidOid, /* prosupport */
1.0, /* procost */
0.0); /* prorows */

View File

@ -32,6 +32,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "optimizer/clauses.h"
#include "optimizer/cost.h"
#include "optimizer/optimizer.h"
@ -3985,13 +3986,16 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
args, funcvariadic,
func_tuple, context);
if (!newexpr && allow_non_const && OidIsValid(func_form->protransform))
if (!newexpr && allow_non_const && OidIsValid(func_form->prosupport))
{
/*
* Build a dummy FuncExpr node containing the simplified arg list. We
* use this approach to present a uniform interface to the transform
* function regardless of how the function is actually being invoked.
* Build a SupportRequestSimplify node to pass to the support
* function, pointing to a dummy FuncExpr node containing the
* simplified arg list. We use this approach to present a uniform
* interface to the support function regardless of how the target
* function is actually being invoked.
*/
SupportRequestSimplify req;
FuncExpr fexpr;
fexpr.xpr.type = T_FuncExpr;
@ -4005,9 +4009,16 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
fexpr.args = args;
fexpr.location = -1;
req.type = T_SupportRequestSimplify;
req.root = context->root;
req.fcall = &fexpr;
newexpr = (Expr *)
DatumGetPointer(OidFunctionCall1(func_form->protransform,
PointerGetDatum(&fexpr)));
DatumGetPointer(OidFunctionCall1(func_form->prosupport,
PointerGetDatum(&req)));
/* catch a possible API misunderstanding */
Assert(newexpr != (Expr *) &fexpr);
}
if (!newexpr && allow_non_const)

View File

@ -676,7 +676,7 @@ static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_P
SUBSCRIPTION SUBSTRING SYMMETRIC SYSID SYSTEM_P
SUBSCRIPTION SUBSTRING SUPPORT SYMMETRIC SYSID SYSTEM_P
TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@ -7834,6 +7834,10 @@ common_func_opt_item:
{
$$ = makeDefElem("rows", (Node *)$2, @1);
}
| SUPPORT any_name
{
$$ = makeDefElem("support", (Node *)$2, @1);
}
| FunctionSetResetClause
{
/* we abuse the normal content of a DefElem here */
@ -15164,6 +15168,7 @@ unreserved_keyword:
| STRICT_P
| STRIP_P
| SUBSCRIPTION
| SUPPORT
| SYSID
| SYSTEM_P
| TABLES

View File

@ -24,6 +24,7 @@
#include "access/xact.h"
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/supportnodes.h"
#include "parser/scansup.h"
#include "utils/array.h"
#include "utils/builtins.h"
@ -1341,15 +1342,25 @@ make_time(PG_FUNCTION_ARGS)
}
/* time_transform()
* Flatten calls to time_scale() and timetz_scale() that solely represent
* increases in allowed precision.
/* time_support()
*
* Planner support function for the time_scale() and timetz_scale()
* length coercion functions (we need not distinguish them here).
*/
Datum
time_transform(PG_FUNCTION_ARGS)
time_support(PG_FUNCTION_ARGS)
{
PG_RETURN_POINTER(TemporalTransform(MAX_TIME_PRECISION,
(Node *) PG_GETARG_POINTER(0)));
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
Node *ret = NULL;
if (IsA(rawreq, SupportRequestSimplify))
{
SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq;
ret = TemporalSimplify(MAX_TIME_PRECISION, (Node *) req->fcall);
}
PG_RETURN_POINTER(ret);
}
/* time_scale()

View File

@ -4462,16 +4462,23 @@ CheckDateTokenTables(void)
}
/*
* Common code for temporal protransform functions. Types time, timetz,
* timestamp and timestamptz each have a range of allowed precisions. An
* unspecified precision is rigorously equivalent to the highest specifiable
* precision.
* Common code for temporal prosupport functions: simplify, if possible,
* a call to a temporal type's length-coercion function.
*
* Types time, timetz, timestamp and timestamptz each have a range of allowed
* precisions. An unspecified precision is rigorously equivalent to the
* highest specifiable precision. We can replace the function call with a
* no-op RelabelType if it is coercing to the same or higher precision as the
* input is known to have.
*
* The input Node is always a FuncExpr, but to reduce the #include footprint
* of datetime.h, we declare it as Node *.
*
* Note: timestamp_scale throws an error when the typmod is out of range, but
* we can't get there from a cast: our typmodin will have caught it already.
*/
Node *
TemporalTransform(int32 max_precis, Node *node)
TemporalSimplify(int32 max_precis, Node *node)
{
FuncExpr *expr = castNode(FuncExpr, node);
Node *ret = NULL;

View File

@ -34,6 +34,7 @@
#include "libpq/pqformat.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/float.h"
@ -890,45 +891,53 @@ numeric_send(PG_FUNCTION_ARGS)
/*
* numeric_transform() -
* numeric_support()
*
* Flatten calls to numeric's length coercion function that solely represent
* increases in allowable precision. Scale changes mutate every datum, so
* they are unoptimizable. Some values, e.g. 1E-1001, can only fit into an
* unconstrained numeric, so a change from an unconstrained numeric to any
* constrained numeric is also unoptimizable.
* Planner support function for the numeric() length coercion function.
*
* Flatten calls that solely represent increases in allowable precision.
* Scale changes mutate every datum, so they are unoptimizable. Some values,
* e.g. 1E-1001, can only fit into an unconstrained numeric, so a change from
* an unconstrained numeric to any constrained numeric is also unoptimizable.
*/
Datum
numeric_transform(PG_FUNCTION_ARGS)
numeric_support(PG_FUNCTION_ARGS)
{
FuncExpr *expr = castNode(FuncExpr, PG_GETARG_POINTER(0));
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
Node *ret = NULL;
Node *typmod;
Assert(list_length(expr->args) >= 2);
typmod = (Node *) lsecond(expr->args);
if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
if (IsA(rawreq, SupportRequestSimplify))
{
Node *source = (Node *) linitial(expr->args);
int32 old_typmod = exprTypmod(source);
int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
int32 old_scale = (old_typmod - VARHDRSZ) & 0xffff;
int32 new_scale = (new_typmod - VARHDRSZ) & 0xffff;
int32 old_precision = (old_typmod - VARHDRSZ) >> 16 & 0xffff;
int32 new_precision = (new_typmod - VARHDRSZ) >> 16 & 0xffff;
SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq;
FuncExpr *expr = req->fcall;
Node *typmod;
/*
* If new_typmod < VARHDRSZ, the destination is unconstrained; that's
* always OK. If old_typmod >= VARHDRSZ, the source is constrained,
* and we're OK if the scale is unchanged and the precision is not
* decreasing. See further notes in function header comment.
*/
if (new_typmod < (int32) VARHDRSZ ||
(old_typmod >= (int32) VARHDRSZ &&
new_scale == old_scale && new_precision >= old_precision))
ret = relabel_to_typmod(source, new_typmod);
Assert(list_length(expr->args) >= 2);
typmod = (Node *) lsecond(expr->args);
if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
{
Node *source = (Node *) linitial(expr->args);
int32 old_typmod = exprTypmod(source);
int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
int32 old_scale = (old_typmod - VARHDRSZ) & 0xffff;
int32 new_scale = (new_typmod - VARHDRSZ) & 0xffff;
int32 old_precision = (old_typmod - VARHDRSZ) >> 16 & 0xffff;
int32 new_precision = (new_typmod - VARHDRSZ) >> 16 & 0xffff;
/*
* If new_typmod < VARHDRSZ, the destination is unconstrained;
* that's always OK. If old_typmod >= VARHDRSZ, the source is
* constrained, and we're OK if the scale is unchanged and the
* precision is not decreasing. See further notes in function
* header comment.
*/
if (new_typmod < (int32) VARHDRSZ ||
(old_typmod >= (int32) VARHDRSZ &&
new_scale == old_scale && new_precision >= old_precision))
ret = relabel_to_typmod(source, new_typmod);
}
}
PG_RETURN_POINTER(ret);

View File

@ -2638,6 +2638,21 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
if (proc->prorows > 0 && proc->prorows != 1000)
appendStringInfo(&buf, " ROWS %g", proc->prorows);
if (proc->prosupport)
{
Oid argtypes[1];
/*
* We should qualify the support function's name if it wouldn't be
* resolved by lookup in the current search path.
*/
argtypes[0] = INTERNALOID;
appendStringInfo(&buf, " SUPPORT %s",
generate_function_name(proc->prosupport, 1,
NIL, argtypes,
false, NULL, EXPR_KIND_NONE));
}
if (oldlen != buf.len)
appendStringInfoChar(&buf, '\n');

View File

@ -29,6 +29,7 @@
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "parser/scansup.h"
#include "utils/array.h"
#include "utils/builtins.h"
@ -297,15 +298,26 @@ timestamptypmodout(PG_FUNCTION_ARGS)
}
/* timestamp_transform()
* Flatten calls to timestamp_scale() and timestamptz_scale() that solely
* represent increases in allowed precision.
/*
* timestamp_support()
*
* Planner support function for the timestamp_scale() and timestamptz_scale()
* length coercion functions (we need not distinguish them here).
*/
Datum
timestamp_transform(PG_FUNCTION_ARGS)
timestamp_support(PG_FUNCTION_ARGS)
{
PG_RETURN_POINTER(TemporalTransform(MAX_TIMESTAMP_PRECISION,
(Node *) PG_GETARG_POINTER(0)));
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
Node *ret = NULL;
if (IsA(rawreq, SupportRequestSimplify))
{
SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq;
ret = TemporalSimplify(MAX_TIMESTAMP_PRECISION, (Node *) req->fcall);
}
PG_RETURN_POINTER(ret);
}
/* timestamp_scale()
@ -1235,59 +1247,69 @@ intervaltypmodleastfield(int32 typmod)
}
/* interval_transform()
/*
* interval_support()
*
* Planner support function for interval_scale().
*
* Flatten superfluous calls to interval_scale(). The interval typmod is
* complex to permit accepting and regurgitating all SQL standard variations.
* For truncation purposes, it boils down to a single, simple granularity.
*/
Datum
interval_transform(PG_FUNCTION_ARGS)
interval_support(PG_FUNCTION_ARGS)
{
FuncExpr *expr = castNode(FuncExpr, PG_GETARG_POINTER(0));
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
Node *ret = NULL;
Node *typmod;
Assert(list_length(expr->args) >= 2);
typmod = (Node *) lsecond(expr->args);
if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
if (IsA(rawreq, SupportRequestSimplify))
{
Node *source = (Node *) linitial(expr->args);
int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
bool noop;
SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq;
FuncExpr *expr = req->fcall;
Node *typmod;
if (new_typmod < 0)
noop = true;
else
Assert(list_length(expr->args) >= 2);
typmod = (Node *) lsecond(expr->args);
if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
{
int32 old_typmod = exprTypmod(source);
int old_least_field;
int new_least_field;
int old_precis;
int new_precis;
Node *source = (Node *) linitial(expr->args);
int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
bool noop;
old_least_field = intervaltypmodleastfield(old_typmod);
new_least_field = intervaltypmodleastfield(new_typmod);
if (old_typmod < 0)
old_precis = INTERVAL_FULL_PRECISION;
if (new_typmod < 0)
noop = true;
else
old_precis = INTERVAL_PRECISION(old_typmod);
new_precis = INTERVAL_PRECISION(new_typmod);
{
int32 old_typmod = exprTypmod(source);
int old_least_field;
int new_least_field;
int old_precis;
int new_precis;
/*
* Cast is a no-op if least field stays the same or decreases
* while precision stays the same or increases. But precision,
* which is to say, sub-second precision, only affects ranges that
* include SECOND.
*/
noop = (new_least_field <= old_least_field) &&
(old_least_field > 0 /* SECOND */ ||
new_precis >= MAX_INTERVAL_PRECISION ||
new_precis >= old_precis);
old_least_field = intervaltypmodleastfield(old_typmod);
new_least_field = intervaltypmodleastfield(new_typmod);
if (old_typmod < 0)
old_precis = INTERVAL_FULL_PRECISION;
else
old_precis = INTERVAL_PRECISION(old_typmod);
new_precis = INTERVAL_PRECISION(new_typmod);
/*
* Cast is a no-op if least field stays the same or decreases
* while precision stays the same or increases. But
* precision, which is to say, sub-second precision, only
* affects ranges that include SECOND.
*/
noop = (new_least_field <= old_least_field) &&
(old_least_field > 0 /* SECOND */ ||
new_precis >= MAX_INTERVAL_PRECISION ||
new_precis >= old_precis);
}
if (noop)
ret = relabel_to_typmod(source, new_typmod);
}
if (noop)
ret = relabel_to_typmod(source, new_typmod);
}
PG_RETURN_POINTER(ret);
@ -1359,7 +1381,7 @@ AdjustIntervalForTypmod(Interval *interval, int32 typmod)
* can't do it consistently. (We cannot enforce a range limit on the
* highest expected field, since we do not have any equivalent of
* SQL's <interval leading field precision>.) If we ever decide to
* revisit this, interval_transform will likely require adjusting.
* revisit this, interval_support will likely require adjusting.
*
* Note: before PG 8.4 we interpreted a limited set of fields as
* actually causing a "modulo" operation on a given value, potentially
@ -5020,18 +5042,6 @@ interval_part(PG_FUNCTION_ARGS)
}
/* timestamp_zone_transform()
* The original optimization here caused problems by relabeling Vars that
* could be matched to index entries. It might be possible to resurrect it
* at some point by teaching the planner to be less cavalier with RelabelType
* nodes, but that will take careful analysis.
*/
Datum
timestamp_zone_transform(PG_FUNCTION_ARGS)
{
PG_RETURN_POINTER(NULL);
}
/* timestamp_zone()
* Encode timestamp type with specified time zone.
* This function is just timestamp2timestamptz() except instead of
@ -5125,18 +5135,6 @@ timestamp_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMPTZ(result);
}
/* timestamp_izone_transform()
* The original optimization here caused problems by relabeling Vars that
* could be matched to index entries. It might be possible to resurrect it
* at some point by teaching the planner to be less cavalier with RelabelType
* nodes, but that will take careful analysis.
*/
Datum
timestamp_izone_transform(PG_FUNCTION_ARGS)
{
PG_RETURN_POINTER(NULL);
}
/* timestamp_izone()
* Encode timestamp type with specified time interval as time zone.
*/

View File

@ -20,6 +20,7 @@
#include "common/int.h"
#include "libpq/pqformat.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/varbit.h"
@ -672,32 +673,41 @@ varbit_send(PG_FUNCTION_ARGS)
}
/*
* varbit_transform()
* Flatten calls to varbit's length coercion function that set the new maximum
* length >= the previous maximum length. We can ignore the isExplicit
* argument, since that only affects truncation cases.
* varbit_support()
*
* Planner support function for the varbit() length coercion function.
*
* Currently, the only interesting thing we can do is flatten calls that set
* the new maximum length >= the previous maximum length. We can ignore the
* isExplicit argument, since that only affects truncation cases.
*/
Datum
varbit_transform(PG_FUNCTION_ARGS)
varbit_support(PG_FUNCTION_ARGS)
{
FuncExpr *expr = castNode(FuncExpr, PG_GETARG_POINTER(0));
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
Node *ret = NULL;
Node *typmod;
Assert(list_length(expr->args) >= 2);
typmod = (Node *) lsecond(expr->args);
if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
if (IsA(rawreq, SupportRequestSimplify))
{
Node *source = (Node *) linitial(expr->args);
int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
int32 old_max = exprTypmod(source);
int32 new_max = new_typmod;
SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq;
FuncExpr *expr = req->fcall;
Node *typmod;
/* Note: varbit() treats typmod 0 as invalid, so we do too */
if (new_max <= 0 || (old_max > 0 && old_max <= new_max))
ret = relabel_to_typmod(source, new_typmod);
Assert(list_length(expr->args) >= 2);
typmod = (Node *) lsecond(expr->args);
if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
{
Node *source = (Node *) linitial(expr->args);
int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
int32 old_max = exprTypmod(source);
int32 new_max = new_typmod;
/* Note: varbit() treats typmod 0 as invalid, so we do too */
if (new_max <= 0 || (old_max > 0 && old_max <= new_max))
ret = relabel_to_typmod(source, new_typmod);
}
}
PG_RETURN_POINTER(ret);

View File

@ -21,6 +21,7 @@
#include "catalog/pg_type.h"
#include "libpq/pqformat.h"
#include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "utils/array.h"
#include "utils/builtins.h"
#include "utils/varlena.h"
@ -547,32 +548,41 @@ varcharsend(PG_FUNCTION_ARGS)
/*
* varchar_transform()
* Flatten calls to varchar's length coercion function that set the new maximum
* length >= the previous maximum length. We can ignore the isExplicit
* argument, since that only affects truncation cases.
* varchar_support()
*
* Planner support function for the varchar() length coercion function.
*
* Currently, the only interesting thing we can do is flatten calls that set
* the new maximum length >= the previous maximum length. We can ignore the
* isExplicit argument, since that only affects truncation cases.
*/
Datum
varchar_transform(PG_FUNCTION_ARGS)
varchar_support(PG_FUNCTION_ARGS)
{
FuncExpr *expr = castNode(FuncExpr, PG_GETARG_POINTER(0));
Node *rawreq = (Node *) PG_GETARG_POINTER(0);
Node *ret = NULL;
Node *typmod;
Assert(list_length(expr->args) >= 2);
typmod = (Node *) lsecond(expr->args);
if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
if (IsA(rawreq, SupportRequestSimplify))
{
Node *source = (Node *) linitial(expr->args);
int32 old_typmod = exprTypmod(source);
int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
int32 old_max = old_typmod - VARHDRSZ;
int32 new_max = new_typmod - VARHDRSZ;
SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq;
FuncExpr *expr = req->fcall;
Node *typmod;
if (new_typmod < 0 || (old_typmod >= 0 && old_max <= new_max))
ret = relabel_to_typmod(source, new_typmod);
Assert(list_length(expr->args) >= 2);
typmod = (Node *) lsecond(expr->args);
if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
{
Node *source = (Node *) linitial(expr->args);
int32 old_typmod = exprTypmod(source);
int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
int32 old_max = old_typmod - VARHDRSZ;
int32 new_max = new_typmod - VARHDRSZ;
if (new_typmod < 0 || (old_typmod >= 0 && old_max <= new_max))
ret = relabel_to_typmod(source, new_typmod);
}
}
PG_RETURN_POINTER(ret);

View File

@ -11446,6 +11446,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
char *proconfig;
char *procost;
char *prorows;
char *prosupport;
char *proparallel;
char *lanname;
char *rettypename;
@ -11468,7 +11469,26 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
asPart = createPQExpBuffer();
/* Fetch function-specific details */
if (fout->remoteVersion >= 110000)
if (fout->remoteVersion >= 120000)
{
/*
* prosupport was added in 12
*/
appendPQExpBuffer(query,
"SELECT proretset, prosrc, probin, "
"pg_catalog.pg_get_function_arguments(oid) AS funcargs, "
"pg_catalog.pg_get_function_identity_arguments(oid) AS funciargs, "
"pg_catalog.pg_get_function_result(oid) AS funcresult, "
"array_to_string(protrftypes, ' ') AS protrftypes, "
"prokind, provolatile, proisstrict, prosecdef, "
"proleakproof, proconfig, procost, prorows, "
"prosupport, proparallel, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid",
finfo->dobj.catId.oid);
}
else if (fout->remoteVersion >= 110000)
{
/*
* prokind was added in 11
@ -11481,7 +11501,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"array_to_string(protrftypes, ' ') AS protrftypes, "
"prokind, provolatile, proisstrict, prosecdef, "
"proleakproof, proconfig, procost, prorows, "
"proparallel, "
"'-' AS prosupport, proparallel, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid",
@ -11501,7 +11521,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"CASE WHEN proiswindow THEN 'w' ELSE 'f' END AS prokind, "
"provolatile, proisstrict, prosecdef, "
"proleakproof, proconfig, procost, prorows, "
"proparallel, "
"'-' AS prosupport, proparallel, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid",
@ -11521,6 +11541,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"CASE WHEN proiswindow THEN 'w' ELSE 'f' END AS prokind, "
"provolatile, proisstrict, prosecdef, "
"proleakproof, proconfig, procost, prorows, "
"'-' AS prosupport, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid",
@ -11539,6 +11560,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"CASE WHEN proiswindow THEN 'w' ELSE 'f' END AS prokind, "
"provolatile, proisstrict, prosecdef, "
"proleakproof, proconfig, procost, prorows, "
"'-' AS prosupport, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid",
@ -11559,6 +11581,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile, proisstrict, prosecdef, "
"false AS proleakproof, "
" proconfig, procost, prorows, "
"'-' AS prosupport, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid",
@ -11573,6 +11596,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile, proisstrict, prosecdef, "
"false AS proleakproof, "
"proconfig, procost, prorows, "
"'-' AS prosupport, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid",
@ -11587,6 +11611,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile, proisstrict, prosecdef, "
"false AS proleakproof, "
"null AS proconfig, 0 AS procost, 0 AS prorows, "
"'-' AS prosupport, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid",
@ -11603,6 +11628,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile, proisstrict, prosecdef, "
"false AS proleakproof, "
"null AS proconfig, 0 AS procost, 0 AS prorows, "
"'-' AS prosupport, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid",
@ -11640,6 +11666,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
proconfig = PQgetvalue(res, 0, PQfnumber(res, "proconfig"));
procost = PQgetvalue(res, 0, PQfnumber(res, "procost"));
prorows = PQgetvalue(res, 0, PQfnumber(res, "prorows"));
prosupport = PQgetvalue(res, 0, PQfnumber(res, "prosupport"));
if (PQfnumber(res, "proparallel") != -1)
proparallel = PQgetvalue(res, 0, PQfnumber(res, "proparallel"));
@ -11853,6 +11880,12 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
strcmp(prorows, "0") != 0 && strcmp(prorows, "1000") != 0)
appendPQExpBuffer(q, " ROWS %s", prorows);
if (strcmp(prosupport, "-") != 0)
{
/* We rely on regprocout to provide quoting and qualification */
appendPQExpBuffer(q, " SUPPORT %s", prosupport);
}
if (proparallel != NULL && proparallel[0] != PROPARALLEL_UNSAFE)
{
if (proparallel[0] == PROPARALLEL_SAFE)

View File

@ -1774,6 +1774,20 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, },
},
'CREATE FUNCTION ... SUPPORT' => {
create_order => 41,
create_sql =>
'CREATE FUNCTION dump_test.func_with_support() RETURNS int LANGUAGE sql AS $$ SELECT 1 $$ SUPPORT varchar_support;',
regexp => qr/^
\QCREATE FUNCTION dump_test.func_with_support() RETURNS integer\E
\n\s+\QLANGUAGE sql SUPPORT varchar_support\E
\n\s+AS\ \$\$\Q SELECT 1 \E\$\$;
/xm,
like =>
{ %full_runs, %dump_test_schema_runs, section_pre_data => 1, },
unlike => { exclude_dump_test_schema => 1, },
},
'CREATE PROCEDURE dump_test.ptest1' => {
create_order => 41,
create_sql => 'CREATE PROCEDURE dump_test.ptest1(a int)
@ -1883,9 +1897,9 @@ my %tests = (
'CREATE TRANSFORM FOR int' => {
create_order => 34,
create_sql =>
'CREATE TRANSFORM FOR int LANGUAGE SQL (FROM SQL WITH FUNCTION varchar_transform(internal), TO SQL WITH FUNCTION int4recv(internal));',
'CREATE TRANSFORM FOR int LANGUAGE SQL (FROM SQL WITH FUNCTION varchar_support(internal), TO SQL WITH FUNCTION int4recv(internal));',
regexp =>
qr/CREATE TRANSFORM FOR integer LANGUAGE sql \(FROM SQL WITH FUNCTION pg_catalog\.varchar_transform\(internal\), TO SQL WITH FUNCTION pg_catalog\.int4recv\(internal\)\);/m,
qr/CREATE TRANSFORM FOR integer LANGUAGE sql \(FROM SQL WITH FUNCTION pg_catalog\.varchar_support\(internal\), TO SQL WITH FUNCTION pg_catalog\.int4recv\(internal\)\);/m,
like => { %full_runs, section_pre_data => 1, },
},
@ -2880,7 +2894,7 @@ my %tests = (
procost,
prorows,
provariadic,
protransform,
prosupport,
prokind,
prosecdef,
proleakproof,
@ -2912,7 +2926,7 @@ my %tests = (
\QGRANT SELECT(procost) ON TABLE pg_catalog.pg_proc TO PUBLIC;\E\n.*
\QGRANT SELECT(prorows) ON TABLE pg_catalog.pg_proc TO PUBLIC;\E\n.*
\QGRANT SELECT(provariadic) ON TABLE pg_catalog.pg_proc TO PUBLIC;\E\n.*
\QGRANT SELECT(protransform) ON TABLE pg_catalog.pg_proc TO PUBLIC;\E\n.*
\QGRANT SELECT(prosupport) ON TABLE pg_catalog.pg_proc TO PUBLIC;\E\n.*
\QGRANT SELECT(prokind) ON TABLE pg_catalog.pg_proc TO PUBLIC;\E\n.*
\QGRANT SELECT(prosecdef) ON TABLE pg_catalog.pg_proc TO PUBLIC;\E\n.*
\QGRANT SELECT(proleakproof) ON TABLE pg_catalog.pg_proc TO PUBLIC;\E\n.*

View File

@ -53,6 +53,6 @@
*/
/* yyyymmddN */
#define CATALOG_VERSION_NO 201902081
#define CATALOG_VERSION_NO 201902091
#endif

View File

@ -1326,11 +1326,11 @@
{ oid => '668', descr => 'adjust char() to typmod length',
proname => 'bpchar', prorettype => 'bpchar',
proargtypes => 'bpchar int4 bool', prosrc => 'bpchar' },
{ oid => '3097', descr => 'transform a varchar length coercion',
proname => 'varchar_transform', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'varchar_transform' },
{ oid => '3097', descr => 'planner support for varchar length coercion',
proname => 'varchar_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'varchar_support' },
{ oid => '669', descr => 'adjust varchar() to typmod length',
proname => 'varchar', protransform => 'varchar_transform',
proname => 'varchar', prosupport => 'varchar_support',
prorettype => 'varchar', proargtypes => 'varchar int4 bool',
prosrc => 'varchar' },
@ -1954,13 +1954,9 @@
# OIDS 1000 - 1999
{ oid => '3994', descr => 'transform a time zone adjustment',
proname => 'timestamp_izone_transform', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'timestamp_izone_transform' },
{ oid => '1026', descr => 'adjust timestamp to new time zone',
proname => 'timezone', protransform => 'timestamp_izone_transform',
prorettype => 'timestamp', proargtypes => 'interval timestamptz',
prosrc => 'timestamptz_izone' },
proname => 'timezone', prorettype => 'timestamp',
proargtypes => 'interval timestamptz', prosrc => 'timestamptz_izone' },
{ oid => '1031', descr => 'I/O',
proname => 'aclitemin', provolatile => 's', prorettype => 'aclitem',
@ -2190,13 +2186,9 @@
{ oid => '1158', descr => 'convert UNIX epoch to timestamptz',
proname => 'to_timestamp', prorettype => 'timestamptz',
proargtypes => 'float8', prosrc => 'float8_timestamptz' },
{ oid => '3995', descr => 'transform a time zone adjustment',
proname => 'timestamp_zone_transform', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'timestamp_zone_transform' },
{ oid => '1159', descr => 'adjust timestamp to new time zone',
proname => 'timezone', protransform => 'timestamp_zone_transform',
prorettype => 'timestamp', proargtypes => 'text timestamptz',
prosrc => 'timestamptz_zone' },
proname => 'timezone', prorettype => 'timestamp',
proargtypes => 'text timestamptz', prosrc => 'timestamptz_zone' },
{ oid => '1160', descr => 'I/O',
proname => 'interval_in', provolatile => 's', prorettype => 'interval',
@ -2301,11 +2293,11 @@
# OIDS 1200 - 1299
{ oid => '3918', descr => 'transform an interval length coercion',
proname => 'interval_transform', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'interval_transform' },
{ oid => '3918', descr => 'planner support for interval length coercion',
proname => 'interval_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'interval_support' },
{ oid => '1200', descr => 'adjust interval precision',
proname => 'interval', protransform => 'interval_transform',
proname => 'interval', prosupport => 'interval_support',
prorettype => 'interval', proargtypes => 'interval int4',
prosrc => 'interval_scale' },
@ -3713,13 +3705,12 @@
{ oid => '1685', descr => 'adjust bit() to typmod length',
proname => 'bit', prorettype => 'bit', proargtypes => 'bit int4 bool',
prosrc => 'bit' },
{ oid => '3158', descr => 'transform a varbit length coercion',
proname => 'varbit_transform', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'varbit_transform' },
{ oid => '3158', descr => 'planner support for varbit length coercion',
proname => 'varbit_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'varbit_support' },
{ oid => '1687', descr => 'adjust varbit() to typmod length',
proname => 'varbit', protransform => 'varbit_transform',
prorettype => 'varbit', proargtypes => 'varbit int4 bool',
prosrc => 'varbit' },
proname => 'varbit', prosupport => 'varbit_support', prorettype => 'varbit',
proargtypes => 'varbit int4 bool', prosrc => 'varbit' },
{ oid => '1698', descr => 'position of sub-bitstring',
proname => 'position', prorettype => 'int4', proargtypes => 'bit bit',
@ -4081,11 +4072,11 @@
{ oid => '2918', descr => 'I/O typmod',
proname => 'numerictypmodout', prorettype => 'cstring', proargtypes => 'int4',
prosrc => 'numerictypmodout' },
{ oid => '3157', descr => 'transform a numeric length coercion',
proname => 'numeric_transform', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'numeric_transform' },
{ oid => '3157', descr => 'planner support for numeric length coercion',
proname => 'numeric_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'numeric_support' },
{ oid => '1703', descr => 'adjust numeric to typmod precision/scale',
proname => 'numeric', protransform => 'numeric_transform',
proname => 'numeric', prosupport => 'numeric_support',
prorettype => 'numeric', proargtypes => 'numeric int4', prosrc => 'numeric' },
{ oid => '1704',
proname => 'numeric_abs', prorettype => 'numeric', proargtypes => 'numeric',
@ -5448,15 +5439,15 @@
proname => 'bytea_sortsupport', prorettype => 'void',
proargtypes => 'internal', prosrc => 'bytea_sortsupport' },
{ oid => '3917', descr => 'transform a timestamp length coercion',
proname => 'timestamp_transform', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'timestamp_transform' },
{ oid => '3944', descr => 'transform a time length coercion',
proname => 'time_transform', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'time_transform' },
{ oid => '3917', descr => 'planner support for timestamp length coercion',
proname => 'timestamp_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'timestamp_support' },
{ oid => '3944', descr => 'planner support for time length coercion',
proname => 'time_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'time_support' },
{ oid => '1961', descr => 'adjust timestamp precision',
proname => 'timestamp', protransform => 'timestamp_transform',
proname => 'timestamp', prosupport => 'timestamp_support',
prorettype => 'timestamp', proargtypes => 'timestamp int4',
prosrc => 'timestamp_scale' },
@ -5468,14 +5459,14 @@
prosrc => 'oidsmaller' },
{ oid => '1967', descr => 'adjust timestamptz precision',
proname => 'timestamptz', protransform => 'timestamp_transform',
proname => 'timestamptz', prosupport => 'timestamp_support',
prorettype => 'timestamptz', proargtypes => 'timestamptz int4',
prosrc => 'timestamptz_scale' },
{ oid => '1968', descr => 'adjust time precision',
proname => 'time', protransform => 'time_transform', prorettype => 'time',
proname => 'time', prosupport => 'time_support', prorettype => 'time',
proargtypes => 'time int4', prosrc => 'time_scale' },
{ oid => '1969', descr => 'adjust time with time zone precision',
proname => 'timetz', protransform => 'time_transform', prorettype => 'timetz',
proname => 'timetz', prosupport => 'time_support', prorettype => 'timetz',
proargtypes => 'timetz int4', prosrc => 'timetz_scale' },
{ oid => '2003',
@ -5662,13 +5653,11 @@
prosrc => 'select pg_catalog.age(cast(current_date as timestamp without time zone), $1)' },
{ oid => '2069', descr => 'adjust timestamp to new time zone',
proname => 'timezone', protransform => 'timestamp_zone_transform',
prorettype => 'timestamptz', proargtypes => 'text timestamp',
prosrc => 'timestamp_zone' },
proname => 'timezone', prorettype => 'timestamptz',
proargtypes => 'text timestamp', prosrc => 'timestamp_zone' },
{ oid => '2070', descr => 'adjust timestamp to new time zone',
proname => 'timezone', protransform => 'timestamp_izone_transform',
prorettype => 'timestamptz', proargtypes => 'interval timestamp',
prosrc => 'timestamp_izone' },
proname => 'timezone', prorettype => 'timestamptz',
proargtypes => 'interval timestamp', prosrc => 'timestamp_izone' },
{ oid => '2071',
proname => 'date_pl_interval', prorettype => 'timestamp',
proargtypes => 'date interval', prosrc => 'date_pl_interval' },

View File

@ -53,8 +53,8 @@ CATALOG(pg_proc,1255,ProcedureRelationId) BKI_BOOTSTRAP BKI_ROWTYPE_OID(81,Proce
/* element type of variadic array, or 0 */
Oid provariadic BKI_DEFAULT(0) BKI_LOOKUP(pg_type);
/* transforms calls to it during planning */
regproc protransform BKI_DEFAULT(0) BKI_LOOKUP(pg_proc);
/* planner support function for this function, or 0 if none */
regproc prosupport BKI_DEFAULT(0) BKI_LOOKUP(pg_proc);
/* see PROKIND_ categories below */
char prokind BKI_DEFAULT(f);
@ -201,6 +201,7 @@ extern ObjectAddress ProcedureCreate(const char *procedureName,
List *parameterDefaults,
Datum trftypes,
Datum proconfig,
Oid prosupport,
float4 procost,
float4 prorows);

View File

@ -506,7 +506,8 @@ typedef enum NodeTag
T_IndexAmRoutine, /* in access/amapi.h */
T_TsmRoutine, /* in access/tsmapi.h */
T_ForeignKeyCacheInfo, /* in utils/rel.h */
T_CallContext /* in nodes/parsenodes.h */
T_CallContext, /* in nodes/parsenodes.h */
T_SupportRequestSimplify /* in nodes/supportnodes.h */
} NodeTag;
/*

View File

@ -0,0 +1,70 @@
/*-------------------------------------------------------------------------
*
* supportnodes.h
* Definitions for planner support functions.
*
* This file defines the API for "planner support functions", which
* are SQL functions (normally written in C) that can be attached to
* another "target" function to give the system additional knowledge
* about the target function. All the current capabilities have to do
* with planning queries that use the target function, though it is
* possible that future extensions will add functionality to be invoked
* by the parser or executor.
*
* A support function must have the SQL signature
* supportfn(internal) returns internal
* The argument is a pointer to one of the Node types defined in this file.
* The result is usually also a Node pointer, though its type depends on
* which capability is being invoked. In all cases, a NULL pointer result
* (that's PG_RETURN_POINTER(NULL), not PG_RETURN_NULL()) indicates that
* the support function cannot do anything useful for the given request.
* Support functions must return a NULL pointer, not fail, if they do not
* recognize the request node type or cannot handle the given case; this
* allows for future extensions of the set of request cases.
*
*
* Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* src/include/nodes/supportnodes.h
*
*-------------------------------------------------------------------------
*/
#ifndef SUPPORTNODES_H
#define SUPPORTNODES_H
#include "nodes/primnodes.h"
struct PlannerInfo; /* avoid including relation.h here */
/*
* The Simplify request allows the support function to perform plan-time
* simplification of a call to its target function. For example, a varchar
* length coercion that does not decrease the allowed length of its argument
* could be replaced by a RelabelType node, or "x + 0" could be replaced by
* "x". This is invoked during the planner's constant-folding pass, so the
* function's arguments can be presumed already simplified.
*
* The planner's PlannerInfo "root" is typically not needed, but can be
* consulted if it's necessary to obtain info about Vars present in
* the given node tree. Beware that root could be NULL in some usages.
*
* "fcall" will be a FuncExpr invoking the support function's target
* function. (This is true even if the original parsetree node was an
* operator call; a FuncExpr is synthesized for this purpose.)
*
* The result should be a semantically-equivalent transformed node tree,
* or NULL if no simplification could be performed. Do *not* return or
* modify *fcall, as it isn't really a separately allocated Node. But
* it's okay to use fcall->args, or parts of it, in the result tree.
*/
typedef struct SupportRequestSimplify
{
NodeTag type;
struct PlannerInfo *root; /* Planner's infrastructure */
FuncExpr *fcall; /* Function call to be simplified */
} SupportRequestSimplify;
#endif /* SUPPORTNODES_H */

View File

@ -387,6 +387,7 @@ PG_KEYWORD("strict", STRICT_P, UNRESERVED_KEYWORD)
PG_KEYWORD("strip", STRIP_P, UNRESERVED_KEYWORD)
PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD)
PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD)
PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD)
PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD)
PG_KEYWORD("system", SYSTEM_P, UNRESERVED_KEYWORD)

View File

@ -330,7 +330,7 @@ extern int DecodeUnits(int field, char *lowtoken, int *val);
extern int j2day(int jd);
extern Node *TemporalTransform(int32 max_precis, Node *node);
extern Node *TemporalSimplify(int32 max_precis, Node *node);
extern bool CheckDateTokenTables(void);

View File

@ -7,7 +7,7 @@
-- internal and as return argument the datatype of the transform done.
-- pl/plpgsql does not authorize the use of internal as data type.
CREATE TRANSFORM FOR int LANGUAGE SQL (
FROM SQL WITH FUNCTION varchar_transform(internal),
FROM SQL WITH FUNCTION varchar_support(internal),
TO SQL WITH FUNCTION int4recv(internal));
NOTICE: DDL test: type simple, tag CREATE TRANSFORM
DROP TRANSFORM FOR int LANGUAGE SQL;

View File

@ -8,7 +8,7 @@
-- internal and as return argument the datatype of the transform done.
-- pl/plpgsql does not authorize the use of internal as data type.
CREATE TRANSFORM FOR int LANGUAGE SQL (
FROM SQL WITH FUNCTION varchar_transform(internal),
FROM SQL WITH FUNCTION varchar_support(internal),
TO SQL WITH FUNCTION int4recv(internal));
DROP TRANSFORM FOR int LANGUAGE SQL;

View File

@ -3050,10 +3050,9 @@ DETAIL: System catalog modifications are currently disallowed.
-- instead create in public first, move to catalog
CREATE TABLE new_system_table(id serial primary key, othercol text);
ALTER TABLE new_system_table SET SCHEMA pg_catalog;
-- XXX: it's currently impossible to move relations out of pg_catalog
ALTER TABLE new_system_table SET SCHEMA public;
ERROR: cannot remove dependency on schema pg_catalog because it is a system object
-- move back, will be ignored -- already there
ALTER TABLE new_system_table SET SCHEMA pg_catalog;
-- will be ignored -- already there:
ALTER TABLE new_system_table SET SCHEMA pg_catalog;
ALTER TABLE new_system_table RENAME TO old_system_table;
CREATE INDEX old_system_table__othercol ON old_system_table (othercol);

View File

@ -38,7 +38,7 @@ CREATE USER MAPPING FOR regress_addr_user SERVER "integer";
ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user;
ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM regress_addr_user;
CREATE TRANSFORM FOR int LANGUAGE SQL (
FROM SQL WITH FUNCTION varchar_transform(internal),
FROM SQL WITH FUNCTION varchar_support(internal),
TO SQL WITH FUNCTION int4recv(internal));
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
CREATE SUBSCRIPTION addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);

View File

@ -809,12 +809,12 @@ WHERE provariadic != 0 AND
------+-------------
(0 rows)
SELECT ctid, protransform
SELECT ctid, prosupport
FROM pg_catalog.pg_proc fk
WHERE protransform != 0 AND
NOT EXISTS(SELECT 1 FROM pg_catalog.pg_proc pk WHERE pk.oid = fk.protransform);
ctid | protransform
------+--------------
WHERE prosupport != 0 AND
NOT EXISTS(SELECT 1 FROM pg_catalog.pg_proc pk WHERE pk.oid = fk.prosupport);
ctid | prosupport
------+------------
(0 rows)
SELECT ctid, prorettype

View File

@ -453,10 +453,10 @@ WHERE proallargtypes IS NOT NULL AND
-----+---------+-------------+----------------+-------------
(0 rows)
-- Check for protransform functions with the wrong signature
-- Check for prosupport functions with the wrong signature
SELECT p1.oid, p1.proname, p2.oid, p2.proname
FROM pg_proc AS p1, pg_proc AS p2
WHERE p2.oid = p1.protransform AND
WHERE p2.oid = p1.prosupport AND
(p2.prorettype != 'internal'::regtype OR p2.proretset OR p2.pronargs != 1
OR p2.proargtypes[0] != 'internal'::regtype);
oid | proname | oid | proname

View File

@ -1896,10 +1896,9 @@ CREATE TABLE pg_catalog.new_system_table();
-- instead create in public first, move to catalog
CREATE TABLE new_system_table(id serial primary key, othercol text);
ALTER TABLE new_system_table SET SCHEMA pg_catalog;
-- XXX: it's currently impossible to move relations out of pg_catalog
ALTER TABLE new_system_table SET SCHEMA public;
-- move back, will be ignored -- already there
ALTER TABLE new_system_table SET SCHEMA pg_catalog;
-- will be ignored -- already there:
ALTER TABLE new_system_table SET SCHEMA pg_catalog;
ALTER TABLE new_system_table RENAME TO old_system_table;
CREATE INDEX old_system_table__othercol ON old_system_table (othercol);

View File

@ -41,7 +41,7 @@ CREATE USER MAPPING FOR regress_addr_user SERVER "integer";
ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user IN SCHEMA public GRANT ALL ON TABLES TO regress_addr_user;
ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM regress_addr_user;
CREATE TRANSFORM FOR int LANGUAGE SQL (
FROM SQL WITH FUNCTION varchar_transform(internal),
FROM SQL WITH FUNCTION varchar_support(internal),
TO SQL WITH FUNCTION int4recv(internal));
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
CREATE SUBSCRIPTION addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE);

View File

@ -405,10 +405,10 @@ SELECT ctid, provariadic
FROM pg_catalog.pg_proc fk
WHERE provariadic != 0 AND
NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type pk WHERE pk.oid = fk.provariadic);
SELECT ctid, protransform
SELECT ctid, prosupport
FROM pg_catalog.pg_proc fk
WHERE protransform != 0 AND
NOT EXISTS(SELECT 1 FROM pg_catalog.pg_proc pk WHERE pk.oid = fk.protransform);
WHERE prosupport != 0 AND
NOT EXISTS(SELECT 1 FROM pg_catalog.pg_proc pk WHERE pk.oid = fk.prosupport);
SELECT ctid, prorettype
FROM pg_catalog.pg_proc fk
WHERE prorettype != 0 AND

View File

@ -353,10 +353,10 @@ WHERE proallargtypes IS NOT NULL AND
FROM generate_series(1, array_length(proallargtypes, 1)) g(i)
WHERE proargmodes IS NULL OR proargmodes[i] IN ('i', 'b', 'v'));
-- Check for protransform functions with the wrong signature
-- Check for prosupport functions with the wrong signature
SELECT p1.oid, p1.proname, p2.oid, p2.proname
FROM pg_proc AS p1, pg_proc AS p2
WHERE p2.oid = p1.protransform AND
WHERE p2.oid = p1.prosupport AND
(p2.prorettype != 'internal'::regtype OR p2.proretset OR p2.pronargs != 1
OR p2.proargtypes[0] != 'internal'::regtype);

View File

@ -161,7 +161,7 @@ Join pg_catalog.pg_proc.pronamespace => pg_catalog.pg_namespace.oid
Join pg_catalog.pg_proc.proowner => pg_catalog.pg_authid.oid
Join pg_catalog.pg_proc.prolang => pg_catalog.pg_language.oid
Join pg_catalog.pg_proc.provariadic => pg_catalog.pg_type.oid
Join pg_catalog.pg_proc.protransform => pg_catalog.pg_proc.oid
Join pg_catalog.pg_proc.prosupport => pg_catalog.pg_proc.oid
Join pg_catalog.pg_proc.prorettype => pg_catalog.pg_type.oid
Join pg_catalog.pg_range.rngtypid => pg_catalog.pg_type.oid
Join pg_catalog.pg_range.rngsubtype => pg_catalog.pg_type.oid