mirror of https://github.com/citusdata/citus.git
442 lines
13 KiB
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;
|
|
}
|