v20-0003-Add-GiST-referencedagg-support-func.patch
text/x-patch
Filename: v20-0003-Add-GiST-referencedagg-support-func.patch
Type: text/x-patch
Part: 2
Message:
Re: SQL:2011 application time
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v20-0003
Subject: Add GiST referencedagg support func
| File | + | − |
|---|---|---|
| doc/src/sgml/gist.sgml | 112 | 0 |
| doc/src/sgml/xindex.sgml | 7 | 1 |
| src/backend/access/gist/gistvalidate.c | 6 | 1 |
| src/backend/access/index/amvalidate.c | 5 | 3 |
| src/include/access/gist.h | 2 | 1 |
| src/include/catalog/pg_amproc.dat | 6 | 0 |
From 5a427185c8b887ad82902fb7c78291c5137aad2f Mon Sep 17 00:00:00 2001
From: "Paul A. Jungwirth" <pj@illuminatedcomputing.com>
Date: Fri, 29 Dec 2023 14:36:27 -0800
Subject: [PATCH v20 3/8] Add GiST referencedagg support func
This is the thirteenth support function and lets us implement temporal
foreign keys without hardcoding the range_agg function. The support
function should be an aggregate function that takes a type matching the
foreign key's PERIOD element and returns a type that can appear on the
righthand side of a ContainedBy operator, so that for example we can say
`fkperiod <@ range_agg(pkperiod)`.
---
doc/src/sgml/gist.sgml | 112 +++++++++++++++++++++++++
doc/src/sgml/xindex.sgml | 8 +-
src/backend/access/gist/gistvalidate.c | 7 +-
src/backend/access/index/amvalidate.c | 8 +-
src/include/access/gist.h | 3 +-
src/include/catalog/pg_amproc.dat | 6 ++
6 files changed, 138 insertions(+), 6 deletions(-)
diff --git a/doc/src/sgml/gist.sgml b/doc/src/sgml/gist.sgml
index f05875d36c..c4909e6d8a 100644
--- a/doc/src/sgml/gist.sgml
+++ b/doc/src/sgml/gist.sgml
@@ -300,6 +300,10 @@ CREATE INDEX ON my_table USING GIST (my_inet_column inet_ops);
<filename>src/include/access/stratnum.h</filename>) into strategy numbers
used by the opclass. This lets the core code look up operators for temporal
constraint indexes.
+ The optional thirteenth method <function>referencedagg</function> is used by
+ temporal foreign keys to combined referenced rows with the same
+ non-<literal>WITHOUT OVERLAPS</literal> into one <literal>WITHOUT OVERLAPS</literal>
+ span to compare against referencing rows.
</para>
<variablelist>
@@ -1249,6 +1253,114 @@ my_stratnum(PG_FUNCTION_ARGS)
</para>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><function>referencedagg</function></term>
+ <listitem>
+ <para>
+ An aggregate function. Given values of this opclass,
+ it returns a value combining them all. The return value
+ need not be the same type as the input, but it must be a
+ type that can appear on the right hand side of the "contained by"
+ operator. For example the built-in <literal>range_ops</literal>
+ opclass uses <literal>range_agg</literal> here, so that foreign
+ keys can check <literal>fkperiod @> range_agg(pkperiod)</literal>.
+ </para>
+ <para>
+ This is used for temporal foreign key constraints.
+ If you omit this support function, your type cannot be used
+ as the <literal>PERIOD</literal> part of a foreign key.
+ </para>
+
+ <para>
+ The <acronym>SQL</acronym> declaration of the function must look like
+ this (using <literal>my_range_agg</literal> as an example):
+
+<programlisting>
+CREATE OR REPLACE FUNCTION my_range_agg_transfn(internal, anyrange)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE OR REPLACE FUNCTION my_range_agg_finalfn(internal, anyrange)
+RETURNS anymultirange
+AS 'MODULE_PATHNAME'
+LANGUAGE C STRICT;
+
+CREATE AGGREGATE my_range_agg(anyrange) {
+ SFUNC = my_range_agg_transfn,
+ STYPE = internal,
+ FINALFUNC = my_range_agg_finalfn
+};
+</programlisting>
+ </para>
+
+ <para>
+ The matching code in the C module could then follow this example:
+
+<programlisting>
+Datum
+my_range_agg_transfn(PG_FUNCTION_ARGS)
+{
+ MemoryContext aggContext;
+ Oid rngtypoid;
+ ArrayBuildState *state;
+
+ if (!AggCheckCallContext(fcinfo, &aggContext))
+ elog(ERROR, "range_agg_transfn called in non-aggregate context");
+
+ rngtypoid = get_fn_expr_argtype(fcinfo->flinfo, 1);
+ if (!type_is_range(rngtypoid))
+ elog(ERROR, "range_agg must be called with a range");
+
+ if (PG_ARGISNULL(0))
+ state = initArrayResult(rngtypoid, aggContext, false);
+ else
+ state = (ArrayBuildState *) PG_GETARG_POINTER(0);
+
+ /* skip NULLs */
+ if (!PG_ARGISNULL(1))
+ accumArrayResult(state, PG_GETARG_DATUM(1), false, rngtypoid, aggContext);
+
+ PG_RETURN_POINTER(state);
+}
+
+Datum
+my_range_agg_finalfn(PG_FUNCTION_ARGS)
+{
+ MemoryContext aggContext;
+ Oid mltrngtypoid;
+ TypeCacheEntry *typcache;
+ ArrayBuildState *state;
+ int32 range_count;
+ RangeType **ranges;
+ int i;
+
+ if (!AggCheckCallContext(fcinfo, &aggContext))
+ elog(ERROR, "range_agg_finalfn called in non-aggregate context");
+
+ state = PG_ARGISNULL(0) ? NULL : (ArrayBuildState *) PG_GETARG_POINTER(0);
+ if (state == NULL)
+ /* This shouldn't be possible, but just in case.... */
+ PG_RETURN_NULL();
+
+ /* Also return NULL if we had zero inputs, like other aggregates */
+ range_count = state->nelems;
+ if (range_count == 0)
+ PG_RETURN_NULL();
+
+ mltrngtypoid = get_fn_expr_rettype(fcinfo->flinfo);
+ typcache = multirange_get_typcache(fcinfo, mltrngtypoid);
+
+ ranges = palloc0(range_count * sizeof(RangeType *));
+ for (i = 0; i < range_count; i++)
+ ranges[i] = DatumGetRangeTypeP(state->dvalues[i]);
+
+ PG_RETURN_MULTIRANGE_P(make_multirange(mltrngtypoid, typcache->rngtype, range_count, ranges));
+}
+</programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
</variablelist>
<para>
diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml
index 8c4ede3105..3ee1361717 100644
--- a/doc/src/sgml/xindex.sgml
+++ b/doc/src/sgml/xindex.sgml
@@ -508,7 +508,7 @@
</table>
<para>
- GiST indexes have twelve support functions, seven of which are optional,
+ GiST indexes have thirteen support functions, eight of which are optional,
as shown in <xref linkend="xindex-gist-support-table"/>.
(For more information see <xref linkend="gist"/>.)
</para>
@@ -596,6 +596,12 @@
used by the opclass (optional)</entry>
<entry>12</entry>
</row>
+ <row>
+ <entry><function>referencedagg</function></entry>
+ <entry>aggregates referenced rows' <literal>WITHOUT OVERLAPS</literal>
+ part</entry>
+ <entry>13</entry>
+ </row>
</tbody>
</tgroup>
</table>
diff --git a/src/backend/access/gist/gistvalidate.c b/src/backend/access/gist/gistvalidate.c
index 4d1b2c4400..abfcde6cba 100644
--- a/src/backend/access/gist/gistvalidate.c
+++ b/src/backend/access/gist/gistvalidate.c
@@ -151,6 +151,11 @@ gistvalidate(Oid opclassoid)
ok = check_amproc_signature(procform->amproc, INT2OID, true,
1, 1, INT2OID);
break;
+ case GIST_REFERENCED_AGG_PROC:
+ ok = check_amproc_signature(procform->amproc, InvalidOid, false,
+ 1, 1, opcintype);
+ // TODO: must be aggregate
+ break;
default:
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
@@ -272,7 +277,7 @@ gistvalidate(Oid opclassoid)
if (i == GIST_DISTANCE_PROC || i == GIST_FETCH_PROC ||
i == GIST_COMPRESS_PROC || i == GIST_DECOMPRESS_PROC ||
i == GIST_OPTIONS_PROC || i == GIST_SORTSUPPORT_PROC ||
- i == GIST_STRATNUM_PROC)
+ i == GIST_STRATNUM_PROC || i == GIST_REFERENCED_AGG_PROC)
continue; /* optional methods */
ereport(INFO,
(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
diff --git a/src/backend/access/index/amvalidate.c b/src/backend/access/index/amvalidate.c
index 5c2d77e908..8eb99e9b1b 100644
--- a/src/backend/access/index/amvalidate.c
+++ b/src/backend/access/index/amvalidate.c
@@ -146,7 +146,8 @@ identify_opfamily_groups(CatCList *oprlist, CatCList *proclist)
*
* The "..." represents maxargs argument-type OIDs. If "exact" is true, they
* must match the function arg types exactly, else only binary-coercibly.
- * In any case the function result type must match restype exactly.
+ * In any case the function result type must match restype exactly,
+ * unless it is InvalidOid.
*/
bool
check_amproc_signature(Oid funcid, Oid restype, bool exact,
@@ -163,8 +164,9 @@ check_amproc_signature(Oid funcid, Oid restype, bool exact,
elog(ERROR, "cache lookup failed for function %u", funcid);
procform = (Form_pg_proc) GETSTRUCT(tp);
- if (procform->prorettype != restype || procform->proretset ||
- procform->pronargs < minargs || procform->pronargs > maxargs)
+ if ((procform->prorettype != restype && OidIsValid(restype))
+ || procform->proretset || procform->pronargs < minargs
+ || procform->pronargs > maxargs)
result = false;
va_start(ap, maxargs);
diff --git a/src/include/access/gist.h b/src/include/access/gist.h
index 1816352b94..a9c4ea9643 100644
--- a/src/include/access/gist.h
+++ b/src/include/access/gist.h
@@ -39,7 +39,8 @@
#define GIST_OPTIONS_PROC 10
#define GIST_SORTSUPPORT_PROC 11
#define GIST_STRATNUM_PROC 12
-#define GISTNProcs 12
+#define GIST_REFERENCED_AGG_PROC 13
+#define GISTNProcs 13
/*
* Page opaque data in a GiST index page.
diff --git a/src/include/catalog/pg_amproc.dat b/src/include/catalog/pg_amproc.dat
index 85d3b876ae..ae0abbe38c 100644
--- a/src/include/catalog/pg_amproc.dat
+++ b/src/include/catalog/pg_amproc.dat
@@ -610,6 +610,9 @@
{ amprocfamily => 'gist/range_ops', amproclefttype => 'anyrange',
amprocrighttype => 'anyrange', amprocnum => '12',
amproc => 'gist_stratnum_identity' },
+{ amprocfamily => 'gist/range_ops', amproclefttype => 'anyrange',
+ amprocrighttype => 'anyrange', amprocnum => '13',
+ amproc => 'range_agg(anyrange)' },
{ amprocfamily => 'gist/network_ops', amproclefttype => 'inet',
amprocrighttype => 'inet', amprocnum => '1',
amproc => 'inet_gist_consistent' },
@@ -650,6 +653,9 @@
{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
amprocrighttype => 'anymultirange', amprocnum => '12',
amproc => 'gist_stratnum_identity' },
+{ amprocfamily => 'gist/multirange_ops', amproclefttype => 'anymultirange',
+ amprocrighttype => 'anymultirange', amprocnum => '13',
+ amproc => 'range_agg(anymultirange)' },
# gin
{ amprocfamily => 'gin/array_ops', amproclefttype => 'anyarray',
--
2.42.0