citus/src/backend/distributed/utils/foreign_key_relationship.c

442 lines
13 KiB
C

/*-------------------------------------------------------------------------
*
* foreign_key_relationship.c
* This file contains functions for creating foreign key relationship graph
* between distributed tables. Created relationship graph will be hold by
* a static variable defined in this file until an invalidation comes in.
*
* Copyright (c) Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "distributed/pg_version_constants.h"
#if PG_VERSION_NUM >= PG_VERSION_12
#include "access/genam.h"
#endif
#include "access/htup_details.h"
#include "access/stratnum.h"
#if PG_VERSION_NUM >= PG_VERSION_12
#include "access/table.h"
#endif
#include "catalog/pg_constraint.h"
#include "distributed/foreign_key_relationship.h"
#include "distributed/hash_helpers.h"
#include "distributed/listutils.h"
#include "distributed/version_compat.h"
#include "nodes/pg_list.h"
#include "storage/lockdefs.h"
#include "utils/fmgroids.h"
#include "utils/hsearch.h"
#if PG_VERSION_NUM >= PG_VERSION_13
#include "common/hashfn.h"
#endif
#include "utils/memutils.h"
/*
* ForeignConstraintRelationshipGraph holds the graph data structure for foreign constraint relationship
* between relations. We will only have single static instance of that struct and it
* will be invalidated after change on any foreign constraint.
*/
typedef struct ForeignConstraintRelationshipGraph
{
HTAB *nodeMap;
bool isValid;
}ForeignConstraintRelationshipGraph;
/*
* ForeignConstraintRelationshipNode holds the data for each node of the ForeignConstraintRelationshipGraph
* For each node we have relation id, which is the Oid of that relation, visiting
* information for that node in the latest DFS and the list of adjacency nodes.
* Note that we also hold back adjacency nodes for getting referenced node over
* that one.
*/
typedef struct ForeignConstraintRelationshipNode
{
Oid relationId;
bool visited;
List *adjacencyList;
List *backAdjacencyList;
}ForeignConstraintRelationshipNode;
/*
* ForeignConstraintRelationshipEdge will only be used while creating the ForeignConstraintRelationshipGraph.
* It won't show edge information on the graph, yet will be used in the pre-processing
* phase.
*/
typedef struct ForeignConstraintRelationshipEdge
{
Oid referencingRelationOID;
Oid referencedRelationOID;
}ForeignConstraintRelationshipEdge;
static ForeignConstraintRelationshipGraph *fConstraintRelationshipGraph = NULL;
static void CreateForeignConstraintRelationshipGraph(void);
static void PopulateAdjacencyLists(void);
static int CompareForeignConstraintRelationshipEdges(const void *leftElement,
const void *rightElement);
static void AddForeignConstraintRelationshipEdge(Oid referencingOid, Oid referencedOid);
static ForeignConstraintRelationshipNode * CreateOrFindNode(HTAB *adjacencyLists, Oid
relid);
static void GetConnectedListHelper(ForeignConstraintRelationshipNode *node,
List **adjacentNodeList, bool
isReferencing);
static List * GetForeignConstraintRelationshipHelper(Oid relationId, bool isReferencing);
/*
* ReferencedRelationIdList is a wrapper function around GetForeignConstraintRelationshipHelper
* to get list of relation IDs which are referenced by the given relation id.
*
* Note that, if relation A is referenced by relation B and relation B is referenced
* by relation C, then the result list for relation A consists of the relation
* IDs of relation B and relation C.
*/
List *
ReferencedRelationIdList(Oid relationId)
{
return GetForeignConstraintRelationshipHelper(relationId, false);
}
/*
* ReferencingRelationIdList is a wrapper function around GetForeignConstraintRelationshipHelper
* to get list of relation IDs which are referencing by the given relation id.
*
* Note that, if relation A is referenced by relation B and relation B is referenced
* by relation C, then the result list for relation C consists of the relation
* IDs of relation A and relation B.
*/
List *
ReferencingRelationIdList(Oid relationId)
{
return GetForeignConstraintRelationshipHelper(relationId, true);
}
/*
* GetForeignConstraintRelationshipHelper returns the list of oids referenced or
* referencing given relation id. It is a helper function for providing results
* to public functions ReferencedRelationIdList and ReferencingRelationIdList.
*/
static List *
GetForeignConstraintRelationshipHelper(Oid relationId, bool isReferencing)
{
List *foreignConstraintList = NIL;
List *foreignNodeList = NIL;
bool isFound = false;
CreateForeignConstraintRelationshipGraph();
ForeignConstraintRelationshipNode *relationNode =
(ForeignConstraintRelationshipNode *) hash_search(
fConstraintRelationshipGraph->nodeMap, &relationId,
HASH_FIND, &isFound);
if (!isFound)
{
/*
* If there is no node with the given relation id, that means given table
* is not referencing and is not referenced by any table
*/
return NIL;
}
GetConnectedListHelper(relationNode, &foreignNodeList, isReferencing);
/*
* We need only their OIDs, we get back node list to make their visited
* variable to false for using them iteratively.
*/
ForeignConstraintRelationshipNode *currentNode = NULL;
foreach_ptr(currentNode, foreignNodeList)
{
foreignConstraintList = lappend_oid(foreignConstraintList,
currentNode->relationId);
currentNode->visited = false;
}
/* set to false separately, since we don't add itself to foreign node list */
relationNode->visited = false;
return foreignConstraintList;
}
/*
* CreateForeignConstraintRelationshipGraph creates the foreign constraint relation graph using
* foreign constraint provided by pg_constraint metadata table.
*/
static void
CreateForeignConstraintRelationshipGraph()
{
HASHCTL info;
/* if we have already created the graph, use it */
if (IsForeignConstraintRelationshipGraphValid())
{
return;
}
ClearForeignConstraintRelationshipGraphContext();
MemoryContext fConstraintRelationshipMemoryContext = AllocSetContextCreateExtended(
CacheMemoryContext,
"Forign Constraint Relationship Graph Context",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
MemoryContext oldContext = MemoryContextSwitchTo(
fConstraintRelationshipMemoryContext);
fConstraintRelationshipGraph = (ForeignConstraintRelationshipGraph *) palloc(
sizeof(ForeignConstraintRelationshipGraph));
fConstraintRelationshipGraph->isValid = false;
/* create (oid) -> [ForeignConstraintRelationshipNode] hash */
memset(&info, 0, sizeof(info));
info.keysize = sizeof(Oid);
info.entrysize = sizeof(ForeignConstraintRelationshipNode);
info.hash = oid_hash;
info.hcxt = CurrentMemoryContext;
uint32 hashFlags = (HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
fConstraintRelationshipGraph->nodeMap = hash_create(
"foreign key relationship map (oid)",
32, &info, hashFlags);
PopulateAdjacencyLists();
fConstraintRelationshipGraph->isValid = true;
MemoryContextSwitchTo(oldContext);
}
/*
* IsForeignConstraintGraphValid check whether there is a valid graph.
*/
bool
IsForeignConstraintRelationshipGraphValid()
{
if (fConstraintRelationshipGraph != NULL && fConstraintRelationshipGraph->isValid)
{
return true;
}
return false;
}
/*
* SetForeignConstraintGraphInvalid sets the validity of the graph to false.
*/
void
SetForeignConstraintRelationshipGraphInvalid()
{
if (fConstraintRelationshipGraph != NULL)
{
fConstraintRelationshipGraph->isValid = false;
}
}
/*
* GetConnectedListHelper is the function for getting nodes connected (or connecting) to
* the given relation. adjacentNodeList holds the result for recursive calls and
* by changing isReferencing caller function can select connected or connecting
* adjacency list.
*
*/
static void
GetConnectedListHelper(ForeignConstraintRelationshipNode *node, List **adjacentNodeList,
bool isReferencing)
{
List *neighbourList = NIL;
node->visited = true;
if (isReferencing)
{
neighbourList = node->backAdjacencyList;
}
else
{
neighbourList = node->adjacencyList;
}
ForeignConstraintRelationshipNode *neighborNode = NULL;
foreach_ptr(neighborNode, neighbourList)
{
if (neighborNode->visited == false)
{
*adjacentNodeList = lappend(*adjacentNodeList, neighborNode);
GetConnectedListHelper(neighborNode, adjacentNodeList, isReferencing);
}
}
}
/*
* PopulateAdjacencyLists gets foreign constraint relationship information from pg_constraint
* metadata table and populates them to the foreign constraint relation graph.
*/
static void
PopulateAdjacencyLists(void)
{
HeapTuple tuple;
ScanKeyData scanKey[1];
int scanKeyCount = 1;
Oid prevReferencingOid = InvalidOid;
Oid prevReferencedOid = InvalidOid;
List *frelEdgeList = NIL;
Relation pgConstraint = table_open(ConstraintRelationId, AccessShareLock);
ScanKeyInit(&scanKey[0], Anum_pg_constraint_contype, BTEqualStrategyNumber, F_CHAREQ,
CharGetDatum(CONSTRAINT_FOREIGN));
SysScanDesc scanDescriptor = systable_beginscan(pgConstraint, InvalidOid, false,
NULL, scanKeyCount, scanKey);
while (HeapTupleIsValid(tuple = systable_getnext(scanDescriptor)))
{
Form_pg_constraint constraintForm = (Form_pg_constraint) GETSTRUCT(tuple);
ForeignConstraintRelationshipEdge *currentFConstraintRelationshipEdge = palloc(
sizeof(ForeignConstraintRelationshipEdge));
currentFConstraintRelationshipEdge->referencingRelationOID =
constraintForm->conrelid;
currentFConstraintRelationshipEdge->referencedRelationOID =
constraintForm->confrelid;
frelEdgeList = lappend(frelEdgeList, currentFConstraintRelationshipEdge);
}
/*
* Since there is no index on columns we are planning to sort tuples
* sorting tuples manually instead of using scan keys
*/
frelEdgeList = SortList(frelEdgeList, CompareForeignConstraintRelationshipEdges);
ForeignConstraintRelationshipEdge *currentFConstraintRelationshipEdge = NULL;
foreach_ptr(currentFConstraintRelationshipEdge, frelEdgeList)
{
/* we just saw this edge, no need to add it twice */
if (currentFConstraintRelationshipEdge->referencingRelationOID ==
prevReferencingOid &&
currentFConstraintRelationshipEdge->referencedRelationOID ==
prevReferencedOid)
{
continue;
}
AddForeignConstraintRelationshipEdge(
currentFConstraintRelationshipEdge->referencingRelationOID,
currentFConstraintRelationshipEdge->
referencedRelationOID);
prevReferencingOid = currentFConstraintRelationshipEdge->referencingRelationOID;
prevReferencedOid = currentFConstraintRelationshipEdge->referencedRelationOID;
}
systable_endscan(scanDescriptor);
table_close(pgConstraint, AccessShareLock);
}
/*
* CompareForeignConstraintRelationshipEdges is a helper function to compare two
* ForeignConstraintRelationshipEdge using referencing and referenced ids respectively.
*/
static int
CompareForeignConstraintRelationshipEdges(const void *leftElement,
const void *rightElement)
{
const ForeignConstraintRelationshipEdge *leftEdge =
*((const ForeignConstraintRelationshipEdge **) leftElement);
const ForeignConstraintRelationshipEdge *rightEdge =
*((const ForeignConstraintRelationshipEdge **) rightElement);
int referencingDiff = leftEdge->referencingRelationOID -
rightEdge->referencingRelationOID;
int referencedDiff = leftEdge->referencedRelationOID -
rightEdge->referencedRelationOID;
if (referencingDiff != 0)
{
return referencingDiff;
}
return referencedDiff;
}
/*
* AddForeignConstraintRelationshipEdge adds edge between the nodes having given OIDs
* by adding referenced node to the adjacency list referencing node and adding
* referencing node to the back adjacency list of referenced node.
*/
static void
AddForeignConstraintRelationshipEdge(Oid referencingOid, Oid referencedOid)
{
ForeignConstraintRelationshipNode *referencingNode = CreateOrFindNode(
fConstraintRelationshipGraph->nodeMap, referencingOid);
ForeignConstraintRelationshipNode *referencedNode = CreateOrFindNode(
fConstraintRelationshipGraph->nodeMap, referencedOid);
referencingNode->adjacencyList = lappend(referencingNode->adjacencyList,
referencedNode);
referencedNode->backAdjacencyList = lappend(referencedNode->backAdjacencyList,
referencingNode);
}
/*
* CreateOrFindNode either gets or adds new node to the foreign constraint relation graph
*/
static ForeignConstraintRelationshipNode *
CreateOrFindNode(HTAB *adjacencyLists, Oid relid)
{
bool found = false;
ForeignConstraintRelationshipNode *node =
(ForeignConstraintRelationshipNode *) hash_search(adjacencyLists,
&relid, HASH_ENTER,
&found);
if (!found)
{
node->adjacencyList = NIL;
node->backAdjacencyList = NIL;
node->visited = false;
}
return node;
}
/*
* ClearForeignConstraintRelationshipGraphContext clear all the allocated memory obtained
* for foreign constraint relationship graph. Since all the variables of relationship
* graph was obtained within the same context, destroying hash map is enough as
* it deletes the context.
*/
void
ClearForeignConstraintRelationshipGraphContext()
{
if (fConstraintRelationshipGraph == NULL)
{
return;
}
hash_destroy(fConstraintRelationshipGraph->nodeMap);
fConstraintRelationshipGraph = NULL;
}