postgresql/src/backend/utils/adt/partitionfuncs.c

240 lines
5.9 KiB
C

/*-------------------------------------------------------------------------
*
* partitionfuncs.c
* Functions for accessing partition-related metadata
*
* Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* src/backend/utils/adt/partitionfuncs.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/htup_details.h"
#include "catalog/partition.h"
#include "catalog/pg_class.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_type.h"
#include "funcapi.h"
#include "utils/fmgrprotos.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
/*
* Checks if a given relation can be part of a partition tree. Returns
* false if the relation cannot be processed, in which case it is up to
* the caller to decide what to do, by either raising an error or doing
* something else.
*/
static bool
check_rel_can_be_partition(Oid relid)
{
char relkind;
bool relispartition;
/* Check if relation exists */
if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
return false;
relkind = get_rel_relkind(relid);
relispartition = get_rel_relispartition(relid);
/* Only allow relation types that can appear in partition trees. */
if (!relispartition && !RELKIND_HAS_PARTITIONS(relkind))
return false;
return true;
}
/*
* pg_partition_tree
*
* Produce a view with one row per member of a partition tree, beginning
* from the top-most parent given by the caller. This gives information
* about each partition, its immediate partitioned parent, if it is
* a leaf partition and its level in the hierarchy.
*/
Datum
pg_partition_tree(PG_FUNCTION_ARGS)
{
#define PG_PARTITION_TREE_COLS 4
Oid rootrelid = PG_GETARG_OID(0);
FuncCallContext *funcctx;
List *partitions;
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcxt;
TupleDesc tupdesc;
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
if (!check_rel_can_be_partition(rootrelid))
SRF_RETURN_DONE(funcctx);
/* switch to memory context appropriate for multiple function calls */
oldcxt = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/*
* Find all members of inheritance set. We only need AccessShareLock
* on the children for the partition information lookup.
*/
partitions = find_all_inheritors(rootrelid, AccessShareLock, NULL);
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
elog(ERROR, "return type must be a row type");
funcctx->tuple_desc = tupdesc;
/* The only state we need is the partition list */
funcctx->user_fctx = (void *) partitions;
MemoryContextSwitchTo(oldcxt);
}
/* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP();
partitions = (List *) funcctx->user_fctx;
if (funcctx->call_cntr < list_length(partitions))
{
Datum result;
Datum values[PG_PARTITION_TREE_COLS] = {0};
bool nulls[PG_PARTITION_TREE_COLS] = {0};
HeapTuple tuple;
Oid parentid = InvalidOid;
Oid relid = list_nth_oid(partitions, funcctx->call_cntr);
char relkind = get_rel_relkind(relid);
int level = 0;
List *ancestors = get_partition_ancestors(relid);
ListCell *lc;
/*
* Form tuple with appropriate data.
*/
/* relid */
values[0] = ObjectIdGetDatum(relid);
/* parentid */
if (ancestors != NIL)
parentid = linitial_oid(ancestors);
if (OidIsValid(parentid))
values[1] = ObjectIdGetDatum(parentid);
else
nulls[1] = true;
/* isleaf */
values[2] = BoolGetDatum(!RELKIND_HAS_PARTITIONS(relkind));
/* level */
if (relid != rootrelid)
{
foreach(lc, ancestors)
{
level++;
if (lfirst_oid(lc) == rootrelid)
break;
}
}
values[3] = Int32GetDatum(level);
tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
result = HeapTupleGetDatum(tuple);
SRF_RETURN_NEXT(funcctx, result);
}
/* done when there are no more elements left */
SRF_RETURN_DONE(funcctx);
}
/*
* pg_partition_root
*
* Returns the top-most parent of the partition tree to which a given
* relation belongs, or NULL if it's not (or cannot be) part of any
* partition tree.
*/
Datum
pg_partition_root(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0);
Oid rootrelid;
List *ancestors;
if (!check_rel_can_be_partition(relid))
PG_RETURN_NULL();
/* fetch the list of ancestors */
ancestors = get_partition_ancestors(relid);
/*
* If the input relation is already the top-most parent, just return
* itself.
*/
if (ancestors == NIL)
PG_RETURN_OID(relid);
rootrelid = llast_oid(ancestors);
list_free(ancestors);
/*
* "rootrelid" must contain a valid OID, given that the input relation is
* a valid partition tree member as checked above.
*/
Assert(OidIsValid(rootrelid));
PG_RETURN_OID(rootrelid);
}
/*
* pg_partition_ancestors
*
* Produces a view with one row per ancestor of the given partition,
* including the input relation itself.
*/
Datum
pg_partition_ancestors(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0);
FuncCallContext *funcctx;
List *ancestors;
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcxt;
funcctx = SRF_FIRSTCALL_INIT();
if (!check_rel_can_be_partition(relid))
SRF_RETURN_DONE(funcctx);
oldcxt = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
ancestors = get_partition_ancestors(relid);
ancestors = lcons_oid(relid, ancestors);
/* The only state we need is the ancestors list */
funcctx->user_fctx = (void *) ancestors;
MemoryContextSwitchTo(oldcxt);
}
funcctx = SRF_PERCALL_SETUP();
ancestors = (List *) funcctx->user_fctx;
if (funcctx->call_cntr < list_length(ancestors))
{
Oid resultrel = list_nth_oid(ancestors, funcctx->call_cntr);
SRF_RETURN_NEXT(funcctx, ObjectIdGetDatum(resultrel));
}
SRF_RETURN_DONE(funcctx);
}