postgresql/src/backend/partitioning/partdesc.c

369 lines
11 KiB
C

/*-------------------------------------------------------------------------
*
* partdesc.c
* Support routines for manipulating partition descriptors
*
* Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* IDENTIFICATION
* src/backend/partitioning/partdesc.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/genam.h"
#include "access/htup_details.h"
#include "access/table.h"
#include "catalog/indexing.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
#include "partitioning/partbounds.h"
#include "partitioning/partdesc.h"
#include "storage/bufmgr.h"
#include "storage/sinval.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/hsearch.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/syscache.h"
typedef struct PartitionDirectoryData
{
MemoryContext pdir_mcxt;
HTAB *pdir_hash;
} PartitionDirectoryData;
typedef struct PartitionDirectoryEntry
{
Oid reloid;
Relation rel;
PartitionDesc pd;
} PartitionDirectoryEntry;
static void RelationBuildPartitionDesc(Relation rel);
/*
* RelationGetPartitionDesc -- get partition descriptor, if relation is partitioned
*
* Note: we arrange for partition descriptors to not get freed until the
* relcache entry's refcount goes to zero (see hacks in RelationClose,
* RelationClearRelation, and RelationBuildPartitionDesc). Therefore, even
* though we hand back a direct pointer into the relcache entry, it's safe
* for callers to continue to use that pointer as long as (a) they hold the
* relation open, and (b) they hold a relation lock strong enough to ensure
* that the data doesn't become stale.
*/
PartitionDesc
RelationGetPartitionDesc(Relation rel)
{
if (rel->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
return NULL;
if (unlikely(rel->rd_partdesc == NULL))
RelationBuildPartitionDesc(rel);
return rel->rd_partdesc;
}
/*
* RelationBuildPartitionDesc
* Form rel's partition descriptor, and store in relcache entry
*
* Partition descriptor is a complex structure; to avoid complicated logic to
* free individual elements whenever the relcache entry is flushed, we give it
* its own memory context, a child of CacheMemoryContext, which can easily be
* deleted on its own. To avoid leaking memory in that context in case of an
* error partway through this function, the context is initially created as a
* child of CurTransactionContext and only re-parented to CacheMemoryContext
* at the end, when no further errors are possible. Also, we don't make this
* context the current context except in very brief code sections, out of fear
* that some of our callees allocate memory on their own which would be leaked
* permanently.
*/
static void
RelationBuildPartitionDesc(Relation rel)
{
PartitionDesc partdesc;
PartitionBoundInfo boundinfo = NULL;
List *inhoids;
PartitionBoundSpec **boundspecs = NULL;
Oid *oids = NULL;
bool *is_leaf = NULL;
ListCell *cell;
int i,
nparts;
PartitionKey key = RelationGetPartitionKey(rel);
MemoryContext new_pdcxt;
MemoryContext oldcxt;
int *mapping;
/*
* Get partition oids from pg_inherits. This uses a single snapshot to
* fetch the list of children, so while more children may be getting added
* concurrently, whatever this function returns will be accurate as of
* some well-defined point in time.
*/
inhoids = find_inheritance_children(RelationGetRelid(rel), NoLock);
nparts = list_length(inhoids);
/* Allocate working arrays for OIDs, leaf flags, and boundspecs. */
if (nparts > 0)
{
oids = (Oid *) palloc(nparts * sizeof(Oid));
is_leaf = (bool *) palloc(nparts * sizeof(bool));
boundspecs = palloc(nparts * sizeof(PartitionBoundSpec *));
}
/* Collect bound spec nodes for each partition. */
i = 0;
foreach(cell, inhoids)
{
Oid inhrelid = lfirst_oid(cell);
HeapTuple tuple;
PartitionBoundSpec *boundspec = NULL;
/* Try fetching the tuple from the catcache, for speed. */
tuple = SearchSysCache1(RELOID, inhrelid);
if (HeapTupleIsValid(tuple))
{
Datum datum;
bool isnull;
datum = SysCacheGetAttr(RELOID, tuple,
Anum_pg_class_relpartbound,
&isnull);
if (!isnull)
boundspec = stringToNode(TextDatumGetCString(datum));
ReleaseSysCache(tuple);
}
/*
* The system cache may be out of date; if so, we may find no pg_class
* tuple or an old one where relpartbound is NULL. In that case, try
* the table directly. We can't just AcceptInvalidationMessages() and
* retry the system cache lookup because it's possible that a
* concurrent ATTACH PARTITION operation has removed itself to the
* ProcArray but yet added invalidation messages to the shared queue;
* InvalidateSystemCaches() would work, but seems excessive.
*
* Note that this algorithm assumes that PartitionBoundSpec we manage
* to fetch is the right one -- so this is only good enough for
* concurrent ATTACH PARTITION, not concurrent DETACH PARTITION or
* some hypothetical operation that changes the partition bounds.
*/
if (boundspec == NULL)
{
Relation pg_class;
SysScanDesc scan;
ScanKeyData key[1];
Datum datum;
bool isnull;
pg_class = table_open(RelationRelationId, AccessShareLock);
ScanKeyInit(&key[0],
Anum_pg_class_oid,
BTEqualStrategyNumber, F_OIDEQ,
ObjectIdGetDatum(inhrelid));
scan = systable_beginscan(pg_class, ClassOidIndexId, true,
NULL, 1, key);
tuple = systable_getnext(scan);
datum = heap_getattr(tuple, Anum_pg_class_relpartbound,
RelationGetDescr(pg_class), &isnull);
if (!isnull)
boundspec = stringToNode(TextDatumGetCString(datum));
systable_endscan(scan);
table_close(pg_class, AccessShareLock);
}
/* Sanity checks. */
if (!boundspec)
elog(ERROR, "missing relpartbound for relation %u", inhrelid);
if (!IsA(boundspec, PartitionBoundSpec))
elog(ERROR, "invalid relpartbound for relation %u", inhrelid);
/*
* If the PartitionBoundSpec says this is the default partition, its
* OID should match pg_partitioned_table.partdefid; if not, the
* catalog is corrupt.
*/
if (boundspec->is_default)
{
Oid partdefid;
partdefid = get_default_partition_oid(RelationGetRelid(rel));
if (partdefid != inhrelid)
elog(ERROR, "expected partdefid %u, but got %u",
inhrelid, partdefid);
}
/* Save results. */
oids[i] = inhrelid;
is_leaf[i] = (get_rel_relkind(inhrelid) != RELKIND_PARTITIONED_TABLE);
boundspecs[i] = boundspec;
++i;
}
/*
* Create PartitionBoundInfo and mapping, working in the caller's context.
* This could fail, but we haven't done any damage if so.
*/
if (nparts > 0)
boundinfo = partition_bounds_create(boundspecs, nparts, key, &mapping);
/*
* Now build the actual relcache partition descriptor, copying all the
* data into a new, small context. As per above comment, we don't make
* this a long-lived context until it's finished.
*/
new_pdcxt = AllocSetContextCreate(CurTransactionContext,
"partition descriptor",
ALLOCSET_SMALL_SIZES);
MemoryContextCopyAndSetIdentifier(new_pdcxt,
RelationGetRelationName(rel));
partdesc = (PartitionDescData *)
MemoryContextAllocZero(new_pdcxt, sizeof(PartitionDescData));
partdesc->nparts = nparts;
/* If there are no partitions, the rest of the partdesc can stay zero */
if (nparts > 0)
{
oldcxt = MemoryContextSwitchTo(new_pdcxt);
partdesc->boundinfo = partition_bounds_copy(boundinfo, key);
partdesc->oids = (Oid *) palloc(nparts * sizeof(Oid));
partdesc->is_leaf = (bool *) palloc(nparts * sizeof(bool));
/*
* Assign OIDs from the original array into mapped indexes of the
* result array. The order of OIDs in the former is defined by the
* catalog scan that retrieved them, whereas that in the latter is
* defined by canonicalized representation of the partition bounds.
* Also save leaf-ness of each partition.
*/
for (i = 0; i < nparts; i++)
{
int index = mapping[i];
partdesc->oids[index] = oids[i];
partdesc->is_leaf[index] = is_leaf[i];
}
MemoryContextSwitchTo(oldcxt);
}
/*
* We have a fully valid partdesc ready to store into the relcache.
* Reparent it so it has the right lifespan.
*/
MemoryContextSetParent(new_pdcxt, CacheMemoryContext);
/*
* But first, a kluge: if there's an old rd_pdcxt, it contains an old
* partition descriptor that may still be referenced somewhere. Preserve
* it, while not leaking it, by reattaching it as a child context of the
* new rd_pdcxt. Eventually it will get dropped by either RelationClose
* or RelationClearRelation.
*/
if (rel->rd_pdcxt != NULL)
MemoryContextSetParent(rel->rd_pdcxt, new_pdcxt);
rel->rd_pdcxt = new_pdcxt;
rel->rd_partdesc = partdesc;
}
/*
* CreatePartitionDirectory
* Create a new partition directory object.
*/
PartitionDirectory
CreatePartitionDirectory(MemoryContext mcxt)
{
MemoryContext oldcontext = MemoryContextSwitchTo(mcxt);
PartitionDirectory pdir;
HASHCTL ctl;
MemSet(&ctl, 0, sizeof(HASHCTL));
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(PartitionDirectoryEntry);
ctl.hcxt = mcxt;
pdir = palloc(sizeof(PartitionDirectoryData));
pdir->pdir_mcxt = mcxt;
pdir->pdir_hash = hash_create("partition directory", 256, &ctl,
HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
MemoryContextSwitchTo(oldcontext);
return pdir;
}
/*
* PartitionDirectoryLookup
* Look up the partition descriptor for a relation in the directory.
*
* The purpose of this function is to ensure that we get the same
* PartitionDesc for each relation every time we look it up. In the
* face of concurrent DDL, different PartitionDescs may be constructed with
* different views of the catalog state, but any single particular OID
* will always get the same PartitionDesc for as long as the same
* PartitionDirectory is used.
*/
PartitionDesc
PartitionDirectoryLookup(PartitionDirectory pdir, Relation rel)
{
PartitionDirectoryEntry *pde;
Oid relid = RelationGetRelid(rel);
bool found;
pde = hash_search(pdir->pdir_hash, &relid, HASH_ENTER, &found);
if (!found)
{
/*
* We must keep a reference count on the relation so that the
* PartitionDesc to which we are pointing can't get destroyed.
*/
RelationIncrementReferenceCount(rel);
pde->rel = rel;
pde->pd = RelationGetPartitionDesc(rel);
Assert(pde->pd != NULL);
}
return pde->pd;
}
/*
* DestroyPartitionDirectory
* Destroy a partition directory.
*
* Release the reference counts we're holding.
*/
void
DestroyPartitionDirectory(PartitionDirectory pdir)
{
HASH_SEQ_STATUS status;
PartitionDirectoryEntry *pde;
hash_seq_init(&status, pdir->pdir_hash);
while ((pde = hash_seq_search(&status)) != NULL)
RelationDecrementReferenceCount(pde->rel);
}
/*
* get_default_oid_from_partdesc
*
* Given a partition descriptor, return the OID of the default partition, if
* one exists; else, return InvalidOid.
*/
Oid
get_default_oid_from_partdesc(PartitionDesc partdesc)
{
if (partdesc && partdesc->boundinfo &&
partition_bound_has_default(partdesc->boundinfo))
return partdesc->oids[partdesc->boundinfo->default_index];
return InvalidOid;
}