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>
<row> <row>
<entry><structfield>protransform</structfield></entry> <entry><structfield>prosupport</structfield></entry>
<entry><type>regproc</type></entry> <entry><type>regproc</type></entry>
<entry><literal><link linkend="catalog-pg-proc"><structname>pg_proc</structname></link>.oid</literal></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 <entry>Optional planner support function for this function
(see <xref linkend="xfunc-transform-functions"/>)</entry> (see <xref linkend="xfunc-optimization"/>)</entry>
</row> </row>
<row> <row>

View File

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

View File

@ -40,6 +40,7 @@ ALTER FUNCTION <replaceable>name</replaceable> [ ( [ [ <replaceable class="param
PARALLEL { UNSAFE | RESTRICTED | SAFE } PARALLEL { UNSAFE | RESTRICTED | SAFE }
COST <replaceable class="parameter">execution_cost</replaceable> COST <replaceable class="parameter">execution_cost</replaceable>
ROWS <replaceable class="parameter">result_rows</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> { TO | = } { <replaceable class="parameter">value</replaceable> | DEFAULT }
SET <replaceable class="parameter">configuration_parameter</replaceable> FROM CURRENT SET <replaceable class="parameter">configuration_parameter</replaceable> FROM CURRENT
RESET <replaceable class="parameter">configuration_parameter</replaceable> RESET <replaceable class="parameter">configuration_parameter</replaceable>
@ -248,6 +249,24 @@ ALTER FUNCTION <replaceable>name</replaceable> [ ( [ [ <replaceable class="param
</listitem> </listitem>
</varlistentry> </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> <varlistentry>
<term><replaceable>configuration_parameter</replaceable></term> <term><replaceable>configuration_parameter</replaceable></term>
<term><replaceable>value</replaceable></term> <term><replaceable>value</replaceable></term>

View File

@ -33,6 +33,7 @@ CREATE [ OR REPLACE ] FUNCTION
| PARALLEL { UNSAFE | RESTRICTED | SAFE } | PARALLEL { UNSAFE | RESTRICTED | SAFE }
| COST <replaceable class="parameter">execution_cost</replaceable> | COST <replaceable class="parameter">execution_cost</replaceable>
| ROWS <replaceable class="parameter">result_rows</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 } | 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">definition</replaceable>'
| AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>' | AS '<replaceable class="parameter">obj_file</replaceable>', '<replaceable class="parameter">link_symbol</replaceable>'
@ -477,6 +478,19 @@ CREATE [ OR REPLACE ] FUNCTION
</listitem> </listitem>
</varlistentry> </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> <varlistentry>
<term><replaceable>configuration_parameter</replaceable></term> <term><replaceable>configuration_parameter</replaceable></term>
<term><replaceable>value</replaceable></term> <term><replaceable>value</replaceable></term>

View File

@ -3241,40 +3241,6 @@ CREATE FUNCTION make_array(anyelement) RETURNS anyarray
</para> </para>
</sect2> </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> <sect2>
<title>Shared Memory and LWLocks</title> <title>Shared Memory and LWLocks</title>
@ -3388,3 +3354,89 @@ if (!ptr)
</sect2> </sect2>
</sect1> </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"> <sect1 id="xoper-optimization">
<title>Operator Optimization Information</title> <title>Operator Optimization Information</title>
<indexterm zone="xoper-optimization">
<primary>optimization information</primary>
<secondary>for operators</secondary>
</indexterm>
<para> <para>
A <productname>PostgreSQL</productname> operator definition can include A <productname>PostgreSQL</productname> operator definition can include
several optional clauses that tell the system useful things about how 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. the ones that release &version; understands.
</para> </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> <sect2>
<title><literal>COMMUTATOR</literal></title> <title><literal>COMMUTATOR</literal></title>

View File

@ -632,6 +632,7 @@ AggregateCreate(const char *aggName,
parameterDefaults, /* parameterDefaults */ parameterDefaults, /* parameterDefaults */
PointerGetDatum(NULL), /* trftypes */ PointerGetDatum(NULL), /* trftypes */
PointerGetDatum(NULL), /* proconfig */ PointerGetDatum(NULL), /* proconfig */
InvalidOid, /* no prosupport */
1, /* procost */ 1, /* procost */
0); /* prorows */ 0); /* prorows */
procOid = myself.objectId; 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). * newRefObjectId is the new referenced object (must be of class refClassId).
* *
* Note the lack of objsubid parameters. If there are subobject references * 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 long
changeDependencyFor(Oid classId, Oid objectId, changeDependencyFor(Oid classId, Oid objectId,
@ -301,35 +304,52 @@ changeDependencyFor(Oid classId, Oid objectId,
SysScanDesc scan; SysScanDesc scan;
HeapTuple tup; HeapTuple tup;
ObjectAddress objAddr; ObjectAddress objAddr;
ObjectAddress depAddr;
bool oldIsPinned;
bool newIsPinned; bool newIsPinned;
depRel = table_open(DependRelationId, RowExclusiveLock); depRel = table_open(DependRelationId, RowExclusiveLock);
/* /*
* If oldRefObjectId is pinned, there won't be any dependency entries on * Check to see if either oldRefObjectId or newRefObjectId is pinned.
* it --- we can't cope in that case. (This isn't really worth expending * Pinned objects should not have any dependency entries pointing to them,
* code to fix, in current usage; it just means you can't rename stuff out * so in these cases we should add or remove a pg_depend entry, or do
* of pg_catalog, which would likely be a bad move anyway.) * nothing at all, rather than update an entry as in the normal case.
*/ */
objAddr.classId = refClassId; objAddr.classId = refClassId;
objAddr.objectId = oldRefObjectId; objAddr.objectId = oldRefObjectId;
objAddr.objectSubId = 0; objAddr.objectSubId = 0;
if (isObjectPinned(&objAddr, depRel)) oldIsPinned = isObjectPinned(&objAddr, depRel);
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot remove dependency on %s because it is a system object",
getObjectDescription(&objAddr))));
/*
* We can handle adding a dependency on something pinned, though, since
* that just means deleting the dependency entry.
*/
objAddr.objectId = newRefObjectId; objAddr.objectId = newRefObjectId;
newIsPinned = isObjectPinned(&objAddr, depRel); 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], ScanKeyInit(&key[0],
Anum_pg_depend_classid, Anum_pg_depend_classid,
BTEqualStrategyNumber, F_OIDEQ, BTEqualStrategyNumber, F_OIDEQ,

View File

@ -88,6 +88,7 @@ ProcedureCreate(const char *procedureName,
List *parameterDefaults, List *parameterDefaults,
Datum trftypes, Datum trftypes,
Datum proconfig, Datum proconfig,
Oid prosupport,
float4 procost, float4 procost,
float4 prorows) float4 prorows)
{ {
@ -319,7 +320,7 @@ ProcedureCreate(const char *procedureName,
values[Anum_pg_proc_procost - 1] = Float4GetDatum(procost); values[Anum_pg_proc_procost - 1] = Float4GetDatum(procost);
values[Anum_pg_proc_prorows - 1] = Float4GetDatum(prorows); values[Anum_pg_proc_prorows - 1] = Float4GetDatum(prorows);
values[Anum_pg_proc_provariadic - 1] = ObjectIdGetDatum(variadicType); 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_prokind - 1] = CharGetDatum(prokind);
values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer); values[Anum_pg_proc_prosecdef - 1] = BoolGetDatum(security_definer);
values[Anum_pg_proc_proleakproof - 1] = BoolGetDatum(isLeakProof); values[Anum_pg_proc_proleakproof - 1] = BoolGetDatum(isLeakProof);
@ -656,6 +657,15 @@ ProcedureCreate(const char *procedureName,
recordDependencyOnExpr(&myself, (Node *) parameterDefaults, recordDependencyOnExpr(&myself, (Node *) parameterDefaults,
NIL, DEPENDENCY_NORMAL); 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 */ /* dependency on owner */
if (!is_update) if (!is_update)
recordDependencyOnOwner(ProcedureRelationId, retval, proowner); recordDependencyOnOwner(ProcedureRelationId, retval, proowner);

View File

@ -479,6 +479,7 @@ compute_common_attribute(ParseState *pstate,
List **set_items, List **set_items,
DefElem **cost_item, DefElem **cost_item,
DefElem **rows_item, DefElem **rows_item,
DefElem **support_item,
DefElem **parallel_item) DefElem **parallel_item)
{ {
if (strcmp(defel->defname, "volatility") == 0) if (strcmp(defel->defname, "volatility") == 0)
@ -537,6 +538,15 @@ compute_common_attribute(ParseState *pstate,
*rows_item = defel; *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) else if (strcmp(defel->defname, "parallel") == 0)
{ {
if (is_procedure) if (is_procedure)
@ -635,6 +645,45 @@ update_proconfig_value(ArrayType *a, List *set_items)
return a; 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 * Dissect the list of options assembled in gram.y into function
@ -655,6 +704,7 @@ compute_function_attributes(ParseState *pstate,
ArrayType **proconfig, ArrayType **proconfig,
float4 *procost, float4 *procost,
float4 *prorows, float4 *prorows,
Oid *prosupport,
char *parallel_p) char *parallel_p)
{ {
ListCell *option; ListCell *option;
@ -669,6 +719,7 @@ compute_function_attributes(ParseState *pstate,
List *set_items = NIL; List *set_items = NIL;
DefElem *cost_item = NULL; DefElem *cost_item = NULL;
DefElem *rows_item = NULL; DefElem *rows_item = NULL;
DefElem *support_item = NULL;
DefElem *parallel_item = NULL; DefElem *parallel_item = NULL;
foreach(option, options) foreach(option, options)
@ -726,6 +777,7 @@ compute_function_attributes(ParseState *pstate,
&set_items, &set_items,
&cost_item, &cost_item,
&rows_item, &rows_item,
&support_item,
&parallel_item)) &parallel_item))
{ {
/* recognized common option */ /* recognized common option */
@ -788,6 +840,8 @@ compute_function_attributes(ParseState *pstate,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ROWS must be positive"))); errmsg("ROWS must be positive")));
} }
if (support_item)
*prosupport = interpret_func_support(support_item);
if (parallel_item) if (parallel_item)
*parallel_p = interpret_func_parallel(parallel_item); *parallel_p = interpret_func_parallel(parallel_item);
} }
@ -893,6 +947,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
ArrayType *proconfig; ArrayType *proconfig;
float4 procost; float4 procost;
float4 prorows; float4 prorows;
Oid prosupport;
HeapTuple languageTuple; HeapTuple languageTuple;
Form_pg_language languageStruct; Form_pg_language languageStruct;
List *as_clause; List *as_clause;
@ -917,6 +972,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
proconfig = NULL; proconfig = NULL;
procost = -1; /* indicates not set */ procost = -1; /* indicates not set */
prorows = -1; /* indicates not set */ prorows = -1; /* indicates not set */
prosupport = InvalidOid;
parallel = PROPARALLEL_UNSAFE; parallel = PROPARALLEL_UNSAFE;
/* Extract non-default attributes from stmt->options list */ /* Extract non-default attributes from stmt->options list */
@ -926,7 +982,8 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
&as_clause, &language, &transformDefElem, &as_clause, &language, &transformDefElem,
&isWindowFunc, &volatility, &isWindowFunc, &volatility,
&isStrict, &security, &isLeakProof, &isStrict, &security, &isLeakProof,
&proconfig, &procost, &prorows, &parallel); &proconfig, &procost, &prorows,
&prosupport, &parallel);
/* Look up the language and validate permissions */ /* Look up the language and validate permissions */
languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language)); languageTuple = SearchSysCache1(LANGNAME, PointerGetDatum(language));
@ -1113,6 +1170,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt)
parameterDefaults, parameterDefaults,
PointerGetDatum(trftypes), PointerGetDatum(trftypes),
PointerGetDatum(proconfig), PointerGetDatum(proconfig),
prosupport,
procost, procost,
prorows); prorows);
} }
@ -1187,6 +1245,7 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
List *set_items = NIL; List *set_items = NIL;
DefElem *cost_item = NULL; DefElem *cost_item = NULL;
DefElem *rows_item = NULL; DefElem *rows_item = NULL;
DefElem *support_item = NULL;
DefElem *parallel_item = NULL; DefElem *parallel_item = NULL;
ObjectAddress address; ObjectAddress address;
@ -1194,6 +1253,8 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
funcOid = LookupFuncWithArgs(stmt->objtype, stmt->func, false); funcOid = LookupFuncWithArgs(stmt->objtype, stmt->func, false);
ObjectAddressSet(address, ProcedureRelationId, funcOid);
tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid)); tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(funcOid));
if (!HeapTupleIsValid(tup)) /* should not happen */ if (!HeapTupleIsValid(tup)) /* should not happen */
elog(ERROR, "cache lookup failed for function %u", funcOid); elog(ERROR, "cache lookup failed for function %u", funcOid);
@ -1228,6 +1289,7 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
&set_items, &set_items,
&cost_item, &cost_item,
&rows_item, &rows_item,
&support_item,
&parallel_item) == false) &parallel_item) == false)
elog(ERROR, "option \"%s\" not recognized", defel->defname); elog(ERROR, "option \"%s\" not recognized", defel->defname);
} }
@ -1266,6 +1328,28 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
(errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("ROWS is not applicable when function does not return a set"))); 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) if (set_items)
{ {
Datum datum; Datum datum;
@ -1308,8 +1392,6 @@ AlterFunction(ParseState *pstate, AlterFunctionStmt *stmt)
InvokeObjectPostAlterHook(ProcedureRelationId, funcOid, 0); InvokeObjectPostAlterHook(ProcedureRelationId, funcOid, 0);
ObjectAddressSet(address, ProcedureRelationId, funcOid);
table_close(rel, NoLock); table_close(rel, NoLock);
heap_freetuple(tup); heap_freetuple(tup);

View File

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

View File

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

View File

@ -32,6 +32,7 @@
#include "miscadmin.h" #include "miscadmin.h"
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h" #include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "optimizer/clauses.h" #include "optimizer/clauses.h"
#include "optimizer/cost.h" #include "optimizer/cost.h"
#include "optimizer/optimizer.h" #include "optimizer/optimizer.h"
@ -3985,13 +3986,16 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
args, funcvariadic, args, funcvariadic,
func_tuple, context); 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 * Build a SupportRequestSimplify node to pass to the support
* use this approach to present a uniform interface to the transform * function, pointing to a dummy FuncExpr node containing the
* function regardless of how the function is actually being invoked. * 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; FuncExpr fexpr;
fexpr.xpr.type = T_FuncExpr; fexpr.xpr.type = T_FuncExpr;
@ -4005,9 +4009,16 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
fexpr.args = args; fexpr.args = args;
fexpr.location = -1; fexpr.location = -1;
req.type = T_SupportRequestSimplify;
req.root = context->root;
req.fcall = &fexpr;
newexpr = (Expr *) newexpr = (Expr *)
DatumGetPointer(OidFunctionCall1(func_form->protransform, DatumGetPointer(OidFunctionCall1(func_form->prosupport,
PointerGetDatum(&fexpr))); PointerGetDatum(&req)));
/* catch a possible API misunderstanding */
Assert(newexpr != (Expr *) &fexpr);
} }
if (!newexpr && allow_non_const) 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 SERIALIZABLE SERVER SESSION SESSION_USER SET SETS SETOF SHARE SHOW
SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P SIMILAR SIMPLE SKIP SMALLINT SNAPSHOT SOME SQL_P STABLE STANDALONE_P
START STATEMENT STATISTICS STDIN STDOUT STORAGE STRICT_P STRIP_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 TABLE TABLES TABLESAMPLE TABLESPACE TEMP TEMPLATE TEMPORARY TEXT_P THEN
TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM TIES TIME TIMESTAMP TO TRAILING TRANSACTION TRANSFORM
@ -7834,6 +7834,10 @@ common_func_opt_item:
{ {
$$ = makeDefElem("rows", (Node *)$2, @1); $$ = makeDefElem("rows", (Node *)$2, @1);
} }
| SUPPORT any_name
{
$$ = makeDefElem("support", (Node *)$2, @1);
}
| FunctionSetResetClause | FunctionSetResetClause
{ {
/* we abuse the normal content of a DefElem here */ /* we abuse the normal content of a DefElem here */
@ -15164,6 +15168,7 @@ unreserved_keyword:
| STRICT_P | STRICT_P
| STRIP_P | STRIP_P
| SUBSCRIPTION | SUBSCRIPTION
| SUPPORT
| SYSID | SYSID
| SYSTEM_P | SYSTEM_P
| TABLES | TABLES

View File

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

View File

@ -4462,16 +4462,23 @@ CheckDateTokenTables(void)
} }
/* /*
* Common code for temporal protransform functions. Types time, timetz, * Common code for temporal prosupport functions: simplify, if possible,
* timestamp and timestamptz each have a range of allowed precisions. An * a call to a temporal type's length-coercion function.
* unspecified precision is rigorously equivalent to the highest specifiable *
* precision. * 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 * 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. * we can't get there from a cast: our typmodin will have caught it already.
*/ */
Node * Node *
TemporalTransform(int32 max_precis, Node *node) TemporalSimplify(int32 max_precis, Node *node)
{ {
FuncExpr *expr = castNode(FuncExpr, node); FuncExpr *expr = castNode(FuncExpr, node);
Node *ret = NULL; Node *ret = NULL;

View File

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

View File

@ -2638,6 +2638,21 @@ pg_get_functiondef(PG_FUNCTION_ARGS)
if (proc->prorows > 0 && proc->prorows != 1000) if (proc->prorows > 0 && proc->prorows != 1000)
appendStringInfo(&buf, " ROWS %g", proc->prorows); 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) if (oldlen != buf.len)
appendStringInfoChar(&buf, '\n'); appendStringInfoChar(&buf, '\n');

View File

@ -29,6 +29,7 @@
#include "miscadmin.h" #include "miscadmin.h"
#include "nodes/makefuncs.h" #include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h" #include "nodes/nodeFuncs.h"
#include "nodes/supportnodes.h"
#include "parser/scansup.h" #include "parser/scansup.h"
#include "utils/array.h" #include "utils/array.h"
#include "utils/builtins.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 * timestamp_support()
* represent increases in allowed precision. *
* Planner support function for the timestamp_scale() and timestamptz_scale()
* length coercion functions (we need not distinguish them here).
*/ */
Datum Datum
timestamp_transform(PG_FUNCTION_ARGS) timestamp_support(PG_FUNCTION_ARGS)
{ {
PG_RETURN_POINTER(TemporalTransform(MAX_TIMESTAMP_PRECISION, Node *rawreq = (Node *) PG_GETARG_POINTER(0);
(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() /* 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 * Flatten superfluous calls to interval_scale(). The interval typmod is
* complex to permit accepting and regurgitating all SQL standard variations. * complex to permit accepting and regurgitating all SQL standard variations.
* For truncation purposes, it boils down to a single, simple granularity. * For truncation purposes, it boils down to a single, simple granularity.
*/ */
Datum 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 *ret = NULL;
Node *typmod;
Assert(list_length(expr->args) >= 2); if (IsA(rawreq, SupportRequestSimplify))
typmod = (Node *) lsecond(expr->args);
if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
{ {
Node *source = (Node *) linitial(expr->args); SupportRequestSimplify *req = (SupportRequestSimplify *) rawreq;
int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue); FuncExpr *expr = req->fcall;
bool noop; Node *typmod;
if (new_typmod < 0) Assert(list_length(expr->args) >= 2);
noop = true;
else typmod = (Node *) lsecond(expr->args);
if (IsA(typmod, Const) &&!((Const *) typmod)->constisnull)
{ {
int32 old_typmod = exprTypmod(source); Node *source = (Node *) linitial(expr->args);
int old_least_field; int32 new_typmod = DatumGetInt32(((Const *) typmod)->constvalue);
int new_least_field; bool noop;
int old_precis;
int new_precis;
old_least_field = intervaltypmodleastfield(old_typmod); if (new_typmod < 0)
new_least_field = intervaltypmodleastfield(new_typmod); noop = true;
if (old_typmod < 0)
old_precis = INTERVAL_FULL_PRECISION;
else 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;
/* old_least_field = intervaltypmodleastfield(old_typmod);
* Cast is a no-op if least field stays the same or decreases new_least_field = intervaltypmodleastfield(new_typmod);
* while precision stays the same or increases. But precision, if (old_typmod < 0)
* which is to say, sub-second precision, only affects ranges that old_precis = INTERVAL_FULL_PRECISION;
* include SECOND. else
*/ old_precis = INTERVAL_PRECISION(old_typmod);
noop = (new_least_field <= old_least_field) && new_precis = INTERVAL_PRECISION(new_typmod);
(old_least_field > 0 /* SECOND */ ||
new_precis >= MAX_INTERVAL_PRECISION || /*
new_precis >= old_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);
}
if (noop)
ret = relabel_to_typmod(source, new_typmod);
} }
if (noop)
ret = relabel_to_typmod(source, new_typmod);
} }
PG_RETURN_POINTER(ret); 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 * can't do it consistently. (We cannot enforce a range limit on the
* highest expected field, since we do not have any equivalent of * highest expected field, since we do not have any equivalent of
* SQL's <interval leading field precision>.) If we ever decide to * 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 * Note: before PG 8.4 we interpreted a limited set of fields as
* actually causing a "modulo" operation on a given value, potentially * 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() /* timestamp_zone()
* Encode timestamp type with specified time zone. * Encode timestamp type with specified time zone.
* This function is just timestamp2timestamptz() except instead of * This function is just timestamp2timestamptz() except instead of
@ -5125,18 +5135,6 @@ timestamp_zone(PG_FUNCTION_ARGS)
PG_RETURN_TIMESTAMPTZ(result); 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() /* timestamp_izone()
* Encode timestamp type with specified time interval as time zone. * Encode timestamp type with specified time interval as time zone.
*/ */

View File

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

View File

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

View File

@ -11446,6 +11446,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
char *proconfig; char *proconfig;
char *procost; char *procost;
char *prorows; char *prorows;
char *prosupport;
char *proparallel; char *proparallel;
char *lanname; char *lanname;
char *rettypename; char *rettypename;
@ -11468,7 +11469,26 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
asPart = createPQExpBuffer(); asPart = createPQExpBuffer();
/* Fetch function-specific details */ /* 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 * prokind was added in 11
@ -11481,7 +11501,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"array_to_string(protrftypes, ' ') AS protrftypes, " "array_to_string(protrftypes, ' ') AS protrftypes, "
"prokind, provolatile, proisstrict, prosecdef, " "prokind, provolatile, proisstrict, prosecdef, "
"proleakproof, proconfig, procost, prorows, " "proleakproof, proconfig, procost, prorows, "
"proparallel, " "'-' AS prosupport, proparallel, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc " "FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid", "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, " "CASE WHEN proiswindow THEN 'w' ELSE 'f' END AS prokind, "
"provolatile, proisstrict, prosecdef, " "provolatile, proisstrict, prosecdef, "
"proleakproof, proconfig, procost, prorows, " "proleakproof, proconfig, procost, prorows, "
"proparallel, " "'-' AS prosupport, proparallel, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc " "FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid", "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, " "CASE WHEN proiswindow THEN 'w' ELSE 'f' END AS prokind, "
"provolatile, proisstrict, prosecdef, " "provolatile, proisstrict, prosecdef, "
"proleakproof, proconfig, procost, prorows, " "proleakproof, proconfig, procost, prorows, "
"'-' AS prosupport, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc " "FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid", "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, " "CASE WHEN proiswindow THEN 'w' ELSE 'f' END AS prokind, "
"provolatile, proisstrict, prosecdef, " "provolatile, proisstrict, prosecdef, "
"proleakproof, proconfig, procost, prorows, " "proleakproof, proconfig, procost, prorows, "
"'-' AS prosupport, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc " "FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid", "WHERE oid = '%u'::pg_catalog.oid",
@ -11559,6 +11581,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile, proisstrict, prosecdef, " "provolatile, proisstrict, prosecdef, "
"false AS proleakproof, " "false AS proleakproof, "
" proconfig, procost, prorows, " " proconfig, procost, prorows, "
"'-' AS prosupport, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc " "FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid", "WHERE oid = '%u'::pg_catalog.oid",
@ -11573,6 +11596,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile, proisstrict, prosecdef, " "provolatile, proisstrict, prosecdef, "
"false AS proleakproof, " "false AS proleakproof, "
"proconfig, procost, prorows, " "proconfig, procost, prorows, "
"'-' AS prosupport, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc " "FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid", "WHERE oid = '%u'::pg_catalog.oid",
@ -11587,6 +11611,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile, proisstrict, prosecdef, " "provolatile, proisstrict, prosecdef, "
"false AS proleakproof, " "false AS proleakproof, "
"null AS proconfig, 0 AS procost, 0 AS prorows, " "null AS proconfig, 0 AS procost, 0 AS prorows, "
"'-' AS prosupport, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc " "FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid", "WHERE oid = '%u'::pg_catalog.oid",
@ -11603,6 +11628,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
"provolatile, proisstrict, prosecdef, " "provolatile, proisstrict, prosecdef, "
"false AS proleakproof, " "false AS proleakproof, "
"null AS proconfig, 0 AS procost, 0 AS prorows, " "null AS proconfig, 0 AS procost, 0 AS prorows, "
"'-' AS prosupport, "
"(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname " "(SELECT lanname FROM pg_catalog.pg_language WHERE oid = prolang) AS lanname "
"FROM pg_catalog.pg_proc " "FROM pg_catalog.pg_proc "
"WHERE oid = '%u'::pg_catalog.oid", "WHERE oid = '%u'::pg_catalog.oid",
@ -11640,6 +11666,7 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
proconfig = PQgetvalue(res, 0, PQfnumber(res, "proconfig")); proconfig = PQgetvalue(res, 0, PQfnumber(res, "proconfig"));
procost = PQgetvalue(res, 0, PQfnumber(res, "procost")); procost = PQgetvalue(res, 0, PQfnumber(res, "procost"));
prorows = PQgetvalue(res, 0, PQfnumber(res, "prorows")); prorows = PQgetvalue(res, 0, PQfnumber(res, "prorows"));
prosupport = PQgetvalue(res, 0, PQfnumber(res, "prosupport"));
if (PQfnumber(res, "proparallel") != -1) if (PQfnumber(res, "proparallel") != -1)
proparallel = PQgetvalue(res, 0, PQfnumber(res, "proparallel")); proparallel = PQgetvalue(res, 0, PQfnumber(res, "proparallel"));
@ -11853,6 +11880,12 @@ dumpFunc(Archive *fout, FuncInfo *finfo)
strcmp(prorows, "0") != 0 && strcmp(prorows, "1000") != 0) strcmp(prorows, "0") != 0 && strcmp(prorows, "1000") != 0)
appendPQExpBuffer(q, " ROWS %s", prorows); 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 != NULL && proparallel[0] != PROPARALLEL_UNSAFE)
{ {
if (proparallel[0] == PROPARALLEL_SAFE) if (proparallel[0] == PROPARALLEL_SAFE)

View File

@ -1774,6 +1774,20 @@ my %tests = (
unlike => { exclude_dump_test_schema => 1, }, 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 PROCEDURE dump_test.ptest1' => {
create_order => 41, create_order => 41,
create_sql => 'CREATE PROCEDURE dump_test.ptest1(a int) create_sql => 'CREATE PROCEDURE dump_test.ptest1(a int)
@ -1883,9 +1897,9 @@ my %tests = (
'CREATE TRANSFORM FOR int' => { 'CREATE TRANSFORM FOR int' => {
create_order => 34, create_order => 34,
create_sql => 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 => 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, }, like => { %full_runs, section_pre_data => 1, },
}, },
@ -2880,7 +2894,7 @@ my %tests = (
procost, procost,
prorows, prorows,
provariadic, provariadic,
protransform, prosupport,
prokind, prokind,
prosecdef, prosecdef,
proleakproof, proleakproof,
@ -2912,7 +2926,7 @@ my %tests = (
\QGRANT SELECT(procost) ON TABLE pg_catalog.pg_proc TO PUBLIC;\E\n.* \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(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(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(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(prosecdef) ON TABLE pg_catalog.pg_proc TO PUBLIC;\E\n.*
\QGRANT SELECT(proleakproof) 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 */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201902081 #define CATALOG_VERSION_NO 201902091
#endif #endif

View File

@ -1326,11 +1326,11 @@
{ oid => '668', descr => 'adjust char() to typmod length', { oid => '668', descr => 'adjust char() to typmod length',
proname => 'bpchar', prorettype => 'bpchar', proname => 'bpchar', prorettype => 'bpchar',
proargtypes => 'bpchar int4 bool', prosrc => 'bpchar' }, proargtypes => 'bpchar int4 bool', prosrc => 'bpchar' },
{ oid => '3097', descr => 'transform a varchar length coercion', { oid => '3097', descr => 'planner support for varchar length coercion',
proname => 'varchar_transform', prorettype => 'internal', proname => 'varchar_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'varchar_transform' }, proargtypes => 'internal', prosrc => 'varchar_support' },
{ oid => '669', descr => 'adjust varchar() to typmod length', { oid => '669', descr => 'adjust varchar() to typmod length',
proname => 'varchar', protransform => 'varchar_transform', proname => 'varchar', prosupport => 'varchar_support',
prorettype => 'varchar', proargtypes => 'varchar int4 bool', prorettype => 'varchar', proargtypes => 'varchar int4 bool',
prosrc => 'varchar' }, prosrc => 'varchar' },
@ -1954,13 +1954,9 @@
# OIDS 1000 - 1999 # 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', { oid => '1026', descr => 'adjust timestamp to new time zone',
proname => 'timezone', protransform => 'timestamp_izone_transform', proname => 'timezone', prorettype => 'timestamp',
prorettype => 'timestamp', proargtypes => 'interval timestamptz', proargtypes => 'interval timestamptz', prosrc => 'timestamptz_izone' },
prosrc => 'timestamptz_izone' },
{ oid => '1031', descr => 'I/O', { oid => '1031', descr => 'I/O',
proname => 'aclitemin', provolatile => 's', prorettype => 'aclitem', proname => 'aclitemin', provolatile => 's', prorettype => 'aclitem',
@ -2190,13 +2186,9 @@
{ oid => '1158', descr => 'convert UNIX epoch to timestamptz', { oid => '1158', descr => 'convert UNIX epoch to timestamptz',
proname => 'to_timestamp', prorettype => 'timestamptz', proname => 'to_timestamp', prorettype => 'timestamptz',
proargtypes => 'float8', prosrc => 'float8_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', { oid => '1159', descr => 'adjust timestamp to new time zone',
proname => 'timezone', protransform => 'timestamp_zone_transform', proname => 'timezone', prorettype => 'timestamp',
prorettype => 'timestamp', proargtypes => 'text timestamptz', proargtypes => 'text timestamptz', prosrc => 'timestamptz_zone' },
prosrc => 'timestamptz_zone' },
{ oid => '1160', descr => 'I/O', { oid => '1160', descr => 'I/O',
proname => 'interval_in', provolatile => 's', prorettype => 'interval', proname => 'interval_in', provolatile => 's', prorettype => 'interval',
@ -2301,11 +2293,11 @@
# OIDS 1200 - 1299 # OIDS 1200 - 1299
{ oid => '3918', descr => 'transform an interval length coercion', { oid => '3918', descr => 'planner support for interval length coercion',
proname => 'interval_transform', prorettype => 'internal', proname => 'interval_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'interval_transform' }, proargtypes => 'internal', prosrc => 'interval_support' },
{ oid => '1200', descr => 'adjust interval precision', { oid => '1200', descr => 'adjust interval precision',
proname => 'interval', protransform => 'interval_transform', proname => 'interval', prosupport => 'interval_support',
prorettype => 'interval', proargtypes => 'interval int4', prorettype => 'interval', proargtypes => 'interval int4',
prosrc => 'interval_scale' }, prosrc => 'interval_scale' },
@ -3713,13 +3705,12 @@
{ oid => '1685', descr => 'adjust bit() to typmod length', { oid => '1685', descr => 'adjust bit() to typmod length',
proname => 'bit', prorettype => 'bit', proargtypes => 'bit int4 bool', proname => 'bit', prorettype => 'bit', proargtypes => 'bit int4 bool',
prosrc => 'bit' }, prosrc => 'bit' },
{ oid => '3158', descr => 'transform a varbit length coercion', { oid => '3158', descr => 'planner support for varbit length coercion',
proname => 'varbit_transform', prorettype => 'internal', proname => 'varbit_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'varbit_transform' }, proargtypes => 'internal', prosrc => 'varbit_support' },
{ oid => '1687', descr => 'adjust varbit() to typmod length', { oid => '1687', descr => 'adjust varbit() to typmod length',
proname => 'varbit', protransform => 'varbit_transform', proname => 'varbit', prosupport => 'varbit_support', prorettype => 'varbit',
prorettype => 'varbit', proargtypes => 'varbit int4 bool', proargtypes => 'varbit int4 bool', prosrc => 'varbit' },
prosrc => 'varbit' },
{ oid => '1698', descr => 'position of sub-bitstring', { oid => '1698', descr => 'position of sub-bitstring',
proname => 'position', prorettype => 'int4', proargtypes => 'bit bit', proname => 'position', prorettype => 'int4', proargtypes => 'bit bit',
@ -4081,11 +4072,11 @@
{ oid => '2918', descr => 'I/O typmod', { oid => '2918', descr => 'I/O typmod',
proname => 'numerictypmodout', prorettype => 'cstring', proargtypes => 'int4', proname => 'numerictypmodout', prorettype => 'cstring', proargtypes => 'int4',
prosrc => 'numerictypmodout' }, prosrc => 'numerictypmodout' },
{ oid => '3157', descr => 'transform a numeric length coercion', { oid => '3157', descr => 'planner support for numeric length coercion',
proname => 'numeric_transform', prorettype => 'internal', proname => 'numeric_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'numeric_transform' }, proargtypes => 'internal', prosrc => 'numeric_support' },
{ oid => '1703', descr => 'adjust numeric to typmod precision/scale', { 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' }, prorettype => 'numeric', proargtypes => 'numeric int4', prosrc => 'numeric' },
{ oid => '1704', { oid => '1704',
proname => 'numeric_abs', prorettype => 'numeric', proargtypes => 'numeric', proname => 'numeric_abs', prorettype => 'numeric', proargtypes => 'numeric',
@ -5448,15 +5439,15 @@
proname => 'bytea_sortsupport', prorettype => 'void', proname => 'bytea_sortsupport', prorettype => 'void',
proargtypes => 'internal', prosrc => 'bytea_sortsupport' }, proargtypes => 'internal', prosrc => 'bytea_sortsupport' },
{ oid => '3917', descr => 'transform a timestamp length coercion', { oid => '3917', descr => 'planner support for timestamp length coercion',
proname => 'timestamp_transform', prorettype => 'internal', proname => 'timestamp_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'timestamp_transform' }, proargtypes => 'internal', prosrc => 'timestamp_support' },
{ oid => '3944', descr => 'transform a time length coercion', { oid => '3944', descr => 'planner support for time length coercion',
proname => 'time_transform', prorettype => 'internal', proname => 'time_support', prorettype => 'internal',
proargtypes => 'internal', prosrc => 'time_transform' }, proargtypes => 'internal', prosrc => 'time_support' },
{ oid => '1961', descr => 'adjust timestamp precision', { oid => '1961', descr => 'adjust timestamp precision',
proname => 'timestamp', protransform => 'timestamp_transform', proname => 'timestamp', prosupport => 'timestamp_support',
prorettype => 'timestamp', proargtypes => 'timestamp int4', prorettype => 'timestamp', proargtypes => 'timestamp int4',
prosrc => 'timestamp_scale' }, prosrc => 'timestamp_scale' },
@ -5468,14 +5459,14 @@
prosrc => 'oidsmaller' }, prosrc => 'oidsmaller' },
{ oid => '1967', descr => 'adjust timestamptz precision', { oid => '1967', descr => 'adjust timestamptz precision',
proname => 'timestamptz', protransform => 'timestamp_transform', proname => 'timestamptz', prosupport => 'timestamp_support',
prorettype => 'timestamptz', proargtypes => 'timestamptz int4', prorettype => 'timestamptz', proargtypes => 'timestamptz int4',
prosrc => 'timestamptz_scale' }, prosrc => 'timestamptz_scale' },
{ oid => '1968', descr => 'adjust time precision', { 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' }, proargtypes => 'time int4', prosrc => 'time_scale' },
{ oid => '1969', descr => 'adjust time with time zone precision', { 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' }, proargtypes => 'timetz int4', prosrc => 'timetz_scale' },
{ oid => '2003', { oid => '2003',
@ -5662,13 +5653,11 @@
prosrc => 'select pg_catalog.age(cast(current_date as timestamp without time zone), $1)' }, prosrc => 'select pg_catalog.age(cast(current_date as timestamp without time zone), $1)' },
{ oid => '2069', descr => 'adjust timestamp to new time zone', { oid => '2069', descr => 'adjust timestamp to new time zone',
proname => 'timezone', protransform => 'timestamp_zone_transform', proname => 'timezone', prorettype => 'timestamptz',
prorettype => 'timestamptz', proargtypes => 'text timestamp', proargtypes => 'text timestamp', prosrc => 'timestamp_zone' },
prosrc => 'timestamp_zone' },
{ oid => '2070', descr => 'adjust timestamp to new time zone', { oid => '2070', descr => 'adjust timestamp to new time zone',
proname => 'timezone', protransform => 'timestamp_izone_transform', proname => 'timezone', prorettype => 'timestamptz',
prorettype => 'timestamptz', proargtypes => 'interval timestamp', proargtypes => 'interval timestamp', prosrc => 'timestamp_izone' },
prosrc => 'timestamp_izone' },
{ oid => '2071', { oid => '2071',
proname => 'date_pl_interval', prorettype => 'timestamp', proname => 'date_pl_interval', prorettype => 'timestamp',
proargtypes => 'date interval', prosrc => 'date_pl_interval' }, 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 */ /* element type of variadic array, or 0 */
Oid provariadic BKI_DEFAULT(0) BKI_LOOKUP(pg_type); Oid provariadic BKI_DEFAULT(0) BKI_LOOKUP(pg_type);
/* transforms calls to it during planning */ /* planner support function for this function, or 0 if none */
regproc protransform BKI_DEFAULT(0) BKI_LOOKUP(pg_proc); regproc prosupport BKI_DEFAULT(0) BKI_LOOKUP(pg_proc);
/* see PROKIND_ categories below */ /* see PROKIND_ categories below */
char prokind BKI_DEFAULT(f); char prokind BKI_DEFAULT(f);
@ -201,6 +201,7 @@ extern ObjectAddress ProcedureCreate(const char *procedureName,
List *parameterDefaults, List *parameterDefaults,
Datum trftypes, Datum trftypes,
Datum proconfig, Datum proconfig,
Oid prosupport,
float4 procost, float4 procost,
float4 prorows); float4 prorows);

View File

@ -506,7 +506,8 @@ typedef enum NodeTag
T_IndexAmRoutine, /* in access/amapi.h */ T_IndexAmRoutine, /* in access/amapi.h */
T_TsmRoutine, /* in access/tsmapi.h */ T_TsmRoutine, /* in access/tsmapi.h */
T_ForeignKeyCacheInfo, /* in utils/rel.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; } 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("strip", STRIP_P, UNRESERVED_KEYWORD)
PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD) PG_KEYWORD("subscription", SUBSCRIPTION, UNRESERVED_KEYWORD)
PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD) PG_KEYWORD("substring", SUBSTRING, COL_NAME_KEYWORD)
PG_KEYWORD("support", SUPPORT, UNRESERVED_KEYWORD)
PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD) PG_KEYWORD("symmetric", SYMMETRIC, RESERVED_KEYWORD)
PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD) PG_KEYWORD("sysid", SYSID, UNRESERVED_KEYWORD)
PG_KEYWORD("system", SYSTEM_P, 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 int j2day(int jd);
extern Node *TemporalTransform(int32 max_precis, Node *node); extern Node *TemporalSimplify(int32 max_precis, Node *node);
extern bool CheckDateTokenTables(void); extern bool CheckDateTokenTables(void);

View File

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

View File

@ -8,7 +8,7 @@
-- internal and as return argument the datatype of the transform done. -- internal and as return argument the datatype of the transform done.
-- pl/plpgsql does not authorize the use of internal as data type. -- pl/plpgsql does not authorize the use of internal as data type.
CREATE TRANSFORM FOR int LANGUAGE SQL ( 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)); TO SQL WITH FUNCTION int4recv(internal));
DROP TRANSFORM FOR int LANGUAGE SQL; 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 -- instead create in public first, move to catalog
CREATE TABLE new_system_table(id serial primary key, othercol text); CREATE TABLE new_system_table(id serial primary key, othercol text);
ALTER TABLE new_system_table SET SCHEMA pg_catalog; 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; ALTER TABLE new_system_table SET SCHEMA public;
ERROR: cannot remove dependency on schema pg_catalog because it is a system object ALTER TABLE new_system_table SET SCHEMA pg_catalog;
-- move back, will be ignored -- already there -- will be ignored -- already there:
ALTER TABLE new_system_table SET SCHEMA pg_catalog; ALTER TABLE new_system_table SET SCHEMA pg_catalog;
ALTER TABLE new_system_table RENAME TO old_system_table; ALTER TABLE new_system_table RENAME TO old_system_table;
CREATE INDEX old_system_table__othercol ON old_system_table (othercol); 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 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; ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM regress_addr_user;
CREATE TRANSFORM FOR int LANGUAGE SQL ( 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)); TO SQL WITH FUNCTION int4recv(internal));
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable; CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
CREATE SUBSCRIPTION addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE); 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) (0 rows)
SELECT ctid, protransform SELECT ctid, prosupport
FROM pg_catalog.pg_proc fk FROM pg_catalog.pg_proc fk
WHERE protransform != 0 AND WHERE prosupport != 0 AND
NOT EXISTS(SELECT 1 FROM pg_catalog.pg_proc pk WHERE pk.oid = fk.protransform); NOT EXISTS(SELECT 1 FROM pg_catalog.pg_proc pk WHERE pk.oid = fk.prosupport);
ctid | protransform ctid | prosupport
------+-------------- ------+------------
(0 rows) (0 rows)
SELECT ctid, prorettype SELECT ctid, prorettype

View File

@ -453,10 +453,10 @@ WHERE proallargtypes IS NOT NULL AND
-----+---------+-------------+----------------+------------- -----+---------+-------------+----------------+-------------
(0 rows) (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 SELECT p1.oid, p1.proname, p2.oid, p2.proname
FROM pg_proc AS p1, pg_proc AS p2 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 (p2.prorettype != 'internal'::regtype OR p2.proretset OR p2.pronargs != 1
OR p2.proargtypes[0] != 'internal'::regtype); OR p2.proargtypes[0] != 'internal'::regtype);
oid | proname | oid | proname 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 -- instead create in public first, move to catalog
CREATE TABLE new_system_table(id serial primary key, othercol text); CREATE TABLE new_system_table(id serial primary key, othercol text);
ALTER TABLE new_system_table SET SCHEMA pg_catalog; 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; 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 SET SCHEMA pg_catalog;
ALTER TABLE new_system_table RENAME TO old_system_table; ALTER TABLE new_system_table RENAME TO old_system_table;
CREATE INDEX old_system_table__othercol ON old_system_table (othercol); 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 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; ALTER DEFAULT PRIVILEGES FOR ROLE regress_addr_user REVOKE DELETE ON TABLES FROM regress_addr_user;
CREATE TRANSFORM FOR int LANGUAGE SQL ( 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)); TO SQL WITH FUNCTION int4recv(internal));
CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable; CREATE PUBLICATION addr_pub FOR TABLE addr_nsp.gentable;
CREATE SUBSCRIPTION addr_sub CONNECTION '' PUBLICATION bar WITH (connect = false, slot_name = NONE); 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 FROM pg_catalog.pg_proc fk
WHERE provariadic != 0 AND WHERE provariadic != 0 AND
NOT EXISTS(SELECT 1 FROM pg_catalog.pg_type pk WHERE pk.oid = fk.provariadic); 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 FROM pg_catalog.pg_proc fk
WHERE protransform != 0 AND WHERE prosupport != 0 AND
NOT EXISTS(SELECT 1 FROM pg_catalog.pg_proc pk WHERE pk.oid = fk.protransform); NOT EXISTS(SELECT 1 FROM pg_catalog.pg_proc pk WHERE pk.oid = fk.prosupport);
SELECT ctid, prorettype SELECT ctid, prorettype
FROM pg_catalog.pg_proc fk FROM pg_catalog.pg_proc fk
WHERE prorettype != 0 AND 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) FROM generate_series(1, array_length(proallargtypes, 1)) g(i)
WHERE proargmodes IS NULL OR proargmodes[i] IN ('i', 'b', 'v')); 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 SELECT p1.oid, p1.proname, p2.oid, p2.proname
FROM pg_proc AS p1, pg_proc AS p2 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 (p2.prorettype != 'internal'::regtype OR p2.proretset OR p2.pronargs != 1
OR p2.proargtypes[0] != 'internal'::regtype); 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.proowner => pg_catalog.pg_authid.oid
Join pg_catalog.pg_proc.prolang => pg_catalog.pg_language.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.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_proc.prorettype => pg_catalog.pg_type.oid
Join pg_catalog.pg_range.rngtypid => 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 Join pg_catalog.pg_range.rngsubtype => pg_catalog.pg_type.oid