From 4b727366b7fbc395201e74fddccbf106bfac8fe6 Mon Sep 17 00:00:00 2001 From: "Paul A. Jungwirth" Date: Fri, 29 Dec 2023 14:36:27 -0800 Subject: [PATCH v28 4/9] 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 | 114 ++++++++++++++++++++++++- doc/src/sgml/xindex.sgml | 8 +- src/backend/access/gist/gistvalidate.c | 7 +- src/backend/access/index/amvalidate.c | 24 +++++- src/include/access/amvalidate.h | 1 + src/include/access/gist.h | 3 +- src/include/catalog/pg_amproc.dat | 6 ++ 7 files changed, 156 insertions(+), 7 deletions(-) diff --git a/doc/src/sgml/gist.sgml b/doc/src/sgml/gist.sgml index 8a19f156d83..e67dd4b859f 100644 --- a/doc/src/sgml/gist.sgml +++ b/doc/src/sgml/gist.sgml @@ -272,7 +272,7 @@ CREATE INDEX ON my_table USING GIST (my_inet_column inet_ops); There are five methods that an index operator class for - GiST must provide, and seven that are optional. + GiST must provide, and eight that are optional. Correctness of the index is ensured by proper implementation of the same, consistent and union methods, while efficiency (size and speed) of the @@ -300,6 +300,10 @@ CREATE INDEX ON my_table USING GIST (my_inet_column inet_ops); src/include/access/stratnum.h) into strategy numbers used by the operator class. This lets the core code look up operators for temporal constraint indexes. + The optional thirteenth method referencedagg is used by + temporal foreign keys to combined referenced rows with the same + non-WITHOUT OVERLAPS value(s) into one + WITHOUT OVERLAPS span to compare against referencing rows. @@ -1244,6 +1248,114 @@ my_stratnum(PG_FUNCTION_ARGS) + + referencedagg + + + 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 range_ops + opclass uses range_agg here, so that foreign + keys can check fkperiod @> range_agg(pkperiod). + + + This is used for temporal foreign key constraints. + If you omit this support function, your type cannot be used + as the PERIOD part of a foreign key. + + + + The SQL declaration of the function must look like + this (using my_range_agg as an example): + + +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 +}; + + + + + The matching code in the C module could then follow this example: + + +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)); +} + + + + diff --git a/doc/src/sgml/xindex.sgml b/doc/src/sgml/xindex.sgml index 3a19dab15e0..93df136eba3 100644 --- a/doc/src/sgml/xindex.sgml +++ b/doc/src/sgml/xindex.sgml @@ -508,7 +508,7 @@ - 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 . (For more information see .) @@ -596,6 +596,12 @@ used by the operator class (optional) 12 + + referencedagg + aggregates referenced rows' WITHOUT OVERLAPS + part + 13 + diff --git a/src/backend/access/gist/gistvalidate.c b/src/backend/access/gist/gistvalidate.c index 0901543a60a..96467567eaa 100644 --- a/src/backend/access/gist/gistvalidate.c +++ b/src/backend/access/gist/gistvalidate.c @@ -150,6 +150,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) + && check_amproc_is_aggregate(procform->amproc); + break; default: ereport(INFO, (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), @@ -271,7 +276,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 32bb477f328..9eb1b172ae1 100644 --- a/src/backend/access/index/amvalidate.c +++ b/src/backend/access/index/amvalidate.c @@ -140,13 +140,30 @@ identify_opfamily_groups(CatCList *oprlist, CatCList *proclist) return result; } +bool +check_amproc_is_aggregate(Oid funcid) +{ + bool result; + HeapTuple tp; + Form_pg_proc procform; + + tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid)); + if (!HeapTupleIsValid(tp)) + elog(ERROR, "cache lookup failed for function %u", funcid); + procform = (Form_pg_proc) GETSTRUCT(tp); + result = procform->prokind == 'a'; + ReleaseSysCache(tp); + return result; +} + /* * Validate the signature (argument and result types) of an opclass support * function. Return true if OK, false if not. * * 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 +180,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/amvalidate.h b/src/include/access/amvalidate.h index 424ab63fa5a..c795a4bc1bf 100644 --- a/src/include/access/amvalidate.h +++ b/src/include/access/amvalidate.h @@ -28,6 +28,7 @@ typedef struct OpFamilyOpFuncGroup /* Functions in access/index/amvalidate.c */ extern List *identify_opfamily_groups(CatCList *oprlist, CatCList *proclist); +extern bool check_amproc_is_aggregate(Oid funcid); extern bool check_amproc_signature(Oid funcid, Oid restype, bool exact, int minargs, int maxargs,...); extern bool check_amoptsproc_signature(Oid funcid); diff --git a/src/include/access/gist.h b/src/include/access/gist.h index 22dd04c1418..641677e191c 100644 --- a/src/include/access/gist.h +++ b/src/include/access/gist.h @@ -40,7 +40,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 352558c1f06..1d3d5fcf4d8 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