gpcontrib/gp_toolkit/gp_partition_maint.c (217 lines of code) (raw):
#include "postgres.h"
#include "access/htup_details.h"
#include "access/relation.h"
#include "catalog/pg_inherits.h"
#include "catalog/partition.h"
#include "funcapi.h"
#include "partitioning/partbounds.h"
#include "partitioning/partdesc.h"
#include "storage/lmgr.h"
#include "utils/builtins.h"
#include "utils/partcache.h"
typedef struct GetPartitionsQueueItem
{
Oid relid;
Oid parent_relid;
bool isleaf;
int level;
char strategy;
int rank;
bool isdefault;
} GetPartitionsQueueItem;
typedef struct GetPartitionsContext
{
List *queue;
} GetPartitionsContext;
static void add_partition_children(List **queue, Relation parent, int level);
extern Datum pg_partition_rank(PG_FUNCTION_ARGS);
extern Datum pg_partition_lowest_child(PG_FUNCTION_ARGS);
extern Datum pg_partition_highest_child(PG_FUNCTION_ARGS);
extern Datum gp_get_partitions(PG_FUNCTION_ARGS);
/*
* Calculate the rank (1-based) of a non-default range partition. We return
* NULL for any other relation type.
*/
PG_FUNCTION_INFO_V1(pg_partition_rank);
Datum
pg_partition_rank(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0);
Oid parentrelid = InvalidOid;
Relation parentrel = NULL;
PartitionDesc parentpartdesc = NULL;
if (!rel_is_range_part_nondefault(relid))
PG_RETURN_NULL();
parentrelid = get_partition_parent(relid, true /* even_if_detached */);
parentrel = relation_open(parentrelid, AccessShareLock);
parentpartdesc = RelationGetPartitionDesc(parentrel, false /* omit_detached */);
/* Child oids are already sorted by range bounds in ascending order. */
for (int i = 0; i < parentpartdesc->nparts; i++)
{
if (relid == parentpartdesc->oids[i])
{
relation_close(parentrel, AccessShareLock);
PG_RETURN_INT32(i + 1);
}
}
PG_RETURN_NULL(); /* unreachable, keep compiler happy */
}
/*
* Find the lowest child with respect to range bounds for a range partition.
* Default partitions are not considered in this calculation.
*/
PG_FUNCTION_INFO_V1(pg_partition_lowest_child);
Datum
pg_partition_lowest_child(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0);
Relation rel = NULL;
PartitionDesc partdesc = NULL;
PartitionKey partkey = NULL;
Oid childrelid = InvalidOid;
rel = relation_open(relid, AccessShareLock);
partkey = RelationGetPartitionKey(rel);
partdesc = RelationGetPartitionDesc(rel, false /* omit_detached */);
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
partkey->strategy == PARTITION_STRATEGY_RANGE &&
partdesc->nparts > 0)
{
/*
* Child oids are already sorted by range bounds in ascending order.
* If default partition exists, it is the last partition.
*/
if (partdesc->nparts > 1 || !OidIsValid(get_default_partition_oid(relid)))
childrelid = partdesc->oids[0];
}
relation_close(rel, AccessShareLock);
if (OidIsValid(childrelid))
PG_RETURN_OID(childrelid);
else
PG_RETURN_NULL();
}
/*
* Find the highest child with respect to range bounds for a range partition.
* Default partitions are not considered in this calculation.
*/
PG_FUNCTION_INFO_V1(pg_partition_highest_child);
Datum
pg_partition_highest_child(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0);
Relation rel = NULL;
PartitionDesc partdesc = NULL;
PartitionKey partkey = NULL;
Oid default_relid = InvalidOid;
Oid highest_relid = InvalidOid;
rel = relation_open(relid, AccessShareLock);
partkey = RelationGetPartitionKey(rel);
partdesc = RelationGetPartitionDesc(rel, false /* omit_detached */);
if (rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE &&
partkey->strategy == PARTITION_STRATEGY_RANGE &&
partdesc->nparts > 0)
{
/*
* Child oids are already sorted by range bounds in ascending order.
* If default partition exists, it is the last partition.
*/
default_relid = get_default_partition_oid(relid);
if (!OidIsValid(default_relid))
highest_relid = partdesc->oids[partdesc->nparts - 1];
else if (partdesc->nparts > 1)
{
Assert (default_relid == partdesc->oids[partdesc->nparts - 1]);
highest_relid = partdesc->oids[partdesc->nparts - 2];
}
}
relation_close(rel, AccessShareLock);
if (OidIsValid(highest_relid))
PG_RETURN_OID(highest_relid);
else
PG_RETURN_NULL();
}
/*
* gp_get_partitions
*
* Main driver for the gp_partitions view.
*
* Given the root (or interior node) of a partition hierarchy, return a result
* set akin to pg_partition_tree(), complete with rank, level etc. There will be
* 1 row per member (the root of the hierarchy isn't included, unlike
* pg_partition_tree()).
*
* To obtain this set, we perform a level-order traversal of the partition
* hierarchy.
*/
PG_FUNCTION_INFO_V1(gp_get_partitions);
Datum
gp_get_partitions(PG_FUNCTION_ARGS)
{
#define GP_GET_PARTITION_COLS 7
Oid rootrelid = PG_GETARG_OID(0);
FuncCallContext *funcctx;
GetPartitionsContext *context;
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
MemoryContext oldcxt;
TupleDesc tupdesc;
Relation rootrel;
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
rootrel = relation_open(rootrelid, AccessShareLock);
if (rootrel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
{
relation_close(rootrel, AccessShareLock);
SRF_RETURN_DONE(funcctx);
}
/*
* Grab access share locks on hierarchy, they will be implicitly
* released at transaction end. This is similar to pg_partition_tree().
*/
find_all_inheritors(rootrelid, AccessShareLock, NULL);
/* switch to memory context appropriate for multiple function calls */
oldcxt = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
tupdesc = CreateTemplateTupleDesc(GP_GET_PARTITION_COLS);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "relid",
REGCLASSOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 2, "parentid",
REGCLASSOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 3, "isleaf",
BOOLOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 4, "partitionlevel",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 5, "partitiontype",
TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 6, "partitionrank",
INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber) 7, "is_default",
BOOLOID, -1, 0);
funcctx->tuple_desc = BlessTupleDesc(tupdesc);
/* Add initial entries into the queue */
context = palloc0(sizeof(GetPartitionsContext));
add_partition_children(&context->queue, rootrel, 0);
funcctx->user_fctx = context;
MemoryContextSwitchTo(oldcxt);
}
/* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP();
context = (GetPartitionsContext *) funcctx->user_fctx;
if (list_length(context->queue) > 0)
{
Datum result;
Datum values[GP_GET_PARTITION_COLS];
bool nulls[GP_GET_PARTITION_COLS];
HeapTuple tuple;
GetPartitionsQueueItem *next;
MemoryContext oldcxt;
next = (GetPartitionsQueueItem *) linitial(context->queue);
/*
* Form tuple with appropriate data.
*/
MemSet(nulls, 0, sizeof(nulls));
MemSet(values, 0, sizeof(values));
/* relid */
values[0] = ObjectIdGetDatum(next->relid);
/* parentid */
values[1] = ObjectIdGetDatum(next->parent_relid);
/* isleaf */
values[2] = BoolGetDatum(next->isleaf);
/* partitionlevel */
values[3] = Int32GetDatum(next->level);
/* partitiontype */
values[4] = CStringGetTextDatum(PartitionStrategyGetName(next->strategy));
/*
* partitionrank:
* -1 signifies that partition rank here is N/A. See
* add_partition_children().
*/
if (next->rank != -1)
values[5] = Int32GetDatum(next->rank);
else
nulls[5] = true;
/* isdefault */
values[6] = next->isdefault;
tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
result = HeapTupleGetDatum(tuple);
/* switch to memory context appropriate for multiple function calls */
oldcxt = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
if (!next->isleaf)
{
/* add children to the level-order-traversal queue */
add_partition_children(&context->queue,
relation_open(next->relid, NoLock),
next->level);
}
/* de-queue */
context->queue = list_delete_first(context->queue);
MemoryContextSwitchTo(oldcxt);
SRF_RETURN_NEXT(funcctx, result);
}
/* done when there are no more elements left */
SRF_RETURN_DONE(funcctx);
}
/*
* For a given partition parent, add all its children to the
* level-order-traversal queue. Assumes that the parent relation is already open
* and locks have been taken earlier. We also close the parent relation at the
* end of this function, since we no longer need the parent's Relation object.
*/
static void
add_partition_children(List **queue, Relation parent, int level)
{
PartitionDesc pdesc;
Assert(RelationIsValid(parent));
Assert(parent->rd_rel->relkind == RELKIND_PARTITIONED_TABLE);
Assert(level >= 0);
/*
* Building the partition descriptor guarantees that its children are sorted
* in order of their partition boundaries.
*/
pdesc = RelationGetPartitionDesc(parent, false /* omit_detached */);
for (int i = 0; i < pdesc->nparts; i++)
{
GetPartitionsQueueItem *item = palloc0(sizeof(GetPartitionsQueueItem));
bool isdefault = (pdesc->boundinfo->default_index == i);
item->relid = pdesc->oids[i];
item->parent_relid = RelationGetRelid(parent);
item->isleaf = pdesc->is_leaf[i];
item->level = level + 1;
item->strategy = pdesc->boundinfo->strategy;
if (pdesc->boundinfo->strategy == PARTITION_STRATEGY_RANGE && !isdefault)
item->rank = i + 1; /* since oids array is sorted by part bounds */
else
item->rank = -1; /* doesn't have a rank */
item->isdefault = isdefault;
*queue = lappend(*queue, item);
}
/* now close the relation */
relation_close(parent, NoLock);
}