| /*------------------------------------------------------------------------- |
| * |
| * 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); |
| } |