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

656 lines
20 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"
#include "access/genam.h"
#include "access/htup_details.h"
#include "access/stratnum.h"
#include "access/table.h"
#include "catalog/pg_constraint.h"
#include "distributed/commands.h"
#include "distributed/hash_helpers.h"
#include "distributed/foreign_key_relationship.h"
#include "distributed/hash_helpers.h"
#include "distributed/listutils.h"
#include "distributed/metadata_cache.h"
#include "distributed/version_compat.h"
#include "nodes/pg_list.h"
#include "storage/lockdefs.h"
#include "utils/catcache.h"
#include "utils/fmgroids.h"
#include "utils/hsearch.h"
#include "common/hashfn.h"
#include "utils/inval.h"
#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;
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 List * GetRelationshipNodesForFKeyConnectedRelations(
ForeignConstraintRelationshipNode *relationshipNode);
static List * GetAllNeighboursList(ForeignConstraintRelationshipNode *relationshipNode);
static ForeignConstraintRelationshipNode * GetRelationshipNodeForRelationId(Oid
relationId,
bool *isFound);
static void CreateForeignConstraintRelationshipGraph(void);
static bool IsForeignConstraintRelationshipGraphValid(void);
static List * GetNeighbourList(ForeignConstraintRelationshipNode *relationshipNode,
bool isReferencing);
static List * GetRelationIdsFromRelationshipNodeList(List *fKeyRelationshipNodeList);
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 List * GetConnectedListHelper(ForeignConstraintRelationshipNode *node,
bool isReferencing);
static List * GetForeignConstraintRelationshipHelper(Oid relationId, bool isReferencing);
MemoryContext ForeignConstraintRelationshipMemoryContext = NULL;
/*
* GetForeignKeyConnectedRelationIdList returns a list of relation id's for
* relations that are connected to relation with relationId via a foreign
* key graph.
*/
List *
GetForeignKeyConnectedRelationIdList(Oid relationId)
{
/* use ShareRowExclusiveLock to prevent concurent foreign key creation */
LOCKMODE lockMode = ShareRowExclusiveLock;
Relation relation = try_relation_open(relationId, lockMode);
if (!RelationIsValid(relation))
{
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("relation with OID %d does not exist",
relationId)));
}
relation_close(relation, NoLock);
bool foundInFKeyGraph = false;
ForeignConstraintRelationshipNode *relationshipNode =
GetRelationshipNodeForRelationId(relationId, &foundInFKeyGraph);
if (!foundInFKeyGraph)
{
/*
* Relation could not be found in foreign key graph, then it has no
* foreign key relationships.
*/
return NIL;
}
List *fKeyConnectedRelationshipNodeList =
GetRelationshipNodesForFKeyConnectedRelations(relationshipNode);
List *fKeyConnectedRelationIdList =
GetRelationIdsFromRelationshipNodeList(fKeyConnectedRelationshipNodeList);
return fKeyConnectedRelationIdList;
}
/*
* ShouldUndistributeCitusLocalTable returns true if given relationId needs
* to be undistributed. Here we do not undistribute table if it's converted by the user,
* or connected to a table converted by the user, or a reference table, via foreign keys.
*/
bool
ShouldUndistributeCitusLocalTable(Oid relationId)
{
CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry(relationId);
if (!cacheEntry->autoConverted)
{
/*
* The relation is not added to metadata automatically,
* we shouldn't undistribute it.
*/
return false;
}
/*
* As we will operate on foreign key connected relations, here we
* invalidate foreign key graph so that we act on fresh graph.
*/
InvalidateForeignKeyGraph();
List *fkeyConnectedRelations = GetForeignKeyConnectedRelationIdList(relationId);
return !RelationIdListHasReferenceTable(fkeyConnectedRelations);
}
/*
* GetRelationshipNodesForFKeyConnectedRelations performs breadth-first search
* starting from input ForeignConstraintRelationshipNode and returns a list
* of ForeignConstraintRelationshipNode objects for relations that are connected
* to given relation node via a foreign key relationhip graph.
*/
static List *
GetRelationshipNodesForFKeyConnectedRelations(
ForeignConstraintRelationshipNode *relationshipNode)
{
HTAB *oidVisitedMap = CreateSimpleHashSetWithName(Oid, "oid visited hash set");
VisitOid(oidVisitedMap, relationshipNode->relationId);
List *relationshipNodeList = list_make1(relationshipNode);
ForeignConstraintRelationshipNode *currentNode = NULL;
foreach_ptr_append(currentNode, relationshipNodeList)
{
List *allNeighboursList = GetAllNeighboursList(currentNode);
ForeignConstraintRelationshipNode *neighbourNode = NULL;
foreach_ptr(neighbourNode, allNeighboursList)
{
Oid neighbourRelationId = neighbourNode->relationId;
if (OidVisited(oidVisitedMap, neighbourRelationId))
{
continue;
}
VisitOid(oidVisitedMap, neighbourRelationId);
relationshipNodeList = lappend(relationshipNodeList, neighbourNode);
}
}
return relationshipNodeList;
}
/*
* GetAllNeighboursList returns a list of ForeignConstraintRelationshipNode
* objects by concatenating both (referencing & referenced) adjacency lists
* of given relationship node.
*/
static List *
GetAllNeighboursList(ForeignConstraintRelationshipNode *relationshipNode)
{
bool isReferencing = false;
List *referencedNeighboursList = GetNeighbourList(relationshipNode, isReferencing);
isReferencing = true;
List *referencingNeighboursList = GetNeighbourList(relationshipNode, isReferencing);
/*
* GetNeighbourList returns list from graph as is, so first copy it as
* list_concat might invalidate it.
*/
List *allNeighboursList = list_copy(referencedNeighboursList);
allNeighboursList = list_concat_unique_ptr(allNeighboursList,
referencingNeighboursList);
return allNeighboursList;
}
/*
* 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 to 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)
{
bool isFound = false;
ForeignConstraintRelationshipNode *relationshipNode =
GetRelationshipNodeForRelationId(relationId, &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;
}
List *connectedNodeList = GetConnectedListHelper(relationshipNode, isReferencing);
List *relationIdList = GetRelationIdsFromRelationshipNodeList(connectedNodeList);
return relationIdList;
}
/*
* GetRelationshipNodeForRelationId searches foreign key graph for relation
* with relationId and returns ForeignConstraintRelationshipNode object for
* relation if it exists in graph. Otherwise, sets isFound to false.
*
* Also before searching foreign key graph, this function implicitly builds
* foreign key graph if it's invalid or not built yet.
*/
static ForeignConstraintRelationshipNode *
GetRelationshipNodeForRelationId(Oid relationId, bool *isFound)
{
CreateForeignConstraintRelationshipGraph();
ForeignConstraintRelationshipNode *relationshipNode =
(ForeignConstraintRelationshipNode *) hash_search(
fConstraintRelationshipGraph->nodeMap, &relationId,
HASH_FIND, isFound);
return relationshipNode;
}
/*
* CreateForeignConstraintRelationshipGraph creates the foreign constraint relation graph using
* foreign constraint provided by pg_constraint metadata table.
*/
static void
CreateForeignConstraintRelationshipGraph()
{
/* if we have already created the graph, use it */
if (IsForeignConstraintRelationshipGraphValid())
{
return;
}
/*
* Lazily create our memory context once and reset on every reuse.
* Since we have cleared and invalidated the fConstraintRelationshipGraph, right
* before we can simply reset the context if it was already existing.
*/
if (ForeignConstraintRelationshipMemoryContext == NULL)
{
/* make sure we've initialized CacheMemoryContext */
if (CacheMemoryContext == NULL)
{
CreateCacheMemoryContext();
}
ForeignConstraintRelationshipMemoryContext = AllocSetContextCreate(
CacheMemoryContext,
"Foreign Constraint Relationship Graph Context",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
}
else
{
fConstraintRelationshipGraph = NULL;
MemoryContextReset(ForeignConstraintRelationshipMemoryContext);
}
Assert(fConstraintRelationshipGraph == NULL);
MemoryContext oldContext = MemoryContextSwitchTo(
ForeignConstraintRelationshipMemoryContext);
fConstraintRelationshipGraph = (ForeignConstraintRelationshipGraph *) palloc(
sizeof(ForeignConstraintRelationshipGraph));
fConstraintRelationshipGraph->isValid = false;
fConstraintRelationshipGraph->nodeMap = CreateSimpleHash(Oid,
ForeignConstraintRelationshipNode);
PopulateAdjacencyLists();
fConstraintRelationshipGraph->isValid = true;
MemoryContextSwitchTo(oldContext);
}
/*
* IsForeignConstraintGraphValid check whether there is a valid graph.
*/
static bool
IsForeignConstraintRelationshipGraphValid()
{
/*
* We might have some concurrent metadata changes. In order to get the changes,
* we first need to accept the cache invalidation messages.
*/
AcceptInvalidationMessages();
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 returns list of ForeignConstraintRelationshipNode
* objects for relations referenced by or referencing to given relation
* according to isReferencing flag.
*
*/
static List *
GetConnectedListHelper(ForeignConstraintRelationshipNode *node, bool isReferencing)
{
HTAB *oidVisitedMap = CreateSimpleHashSetWithName(Oid, "oid visited hash set");
List *connectedNodeList = NIL;
List *relationshipNodeStack = list_make1(node);
while (list_length(relationshipNodeStack) != 0)
{
/*
* Note that this loop considers leftmost element of
* relationshipNodeStack as top of the stack.
*/
/* pop top element from stack */
ForeignConstraintRelationshipNode *currentNode = linitial(relationshipNodeStack);
relationshipNodeStack = list_delete_first(relationshipNodeStack);
Oid currentRelationId = currentNode->relationId;
if (!OidVisited(oidVisitedMap, currentRelationId))
{
connectedNodeList = lappend(connectedNodeList, currentNode);
VisitOid(oidVisitedMap, currentRelationId);
}
List *neighbourList = GetNeighbourList(currentNode, isReferencing);
ForeignConstraintRelationshipNode *neighbourNode = NULL;
foreach_ptr(neighbourNode, neighbourList)
{
Oid neighbourRelationId = neighbourNode->relationId;
if (!OidVisited(oidVisitedMap, neighbourRelationId))
{
/* push to stack */
relationshipNodeStack = lcons(neighbourNode, relationshipNodeStack);
}
}
}
hash_destroy(oidVisitedMap);
/* finally remove yourself from list */
connectedNodeList = list_delete_first(connectedNodeList);
return connectedNodeList;
}
/*
* OidVisited returns true if given oid is visited according to given oid hash-set.
*/
bool
OidVisited(HTAB *oidVisitedMap, Oid oid)
{
bool found = false;
hash_search(oidVisitedMap, &oid, HASH_FIND, &found);
return found;
}
/*
* VisitOid sets given oid as visited in given hash-set.
*/
void
VisitOid(HTAB *oidVisitedMap, Oid oid)
{
bool found = false;
hash_search(oidVisitedMap, &oid, HASH_ENTER, &found);
}
/*
* GetNeighbourList returns copy of relevant adjacency list of given
* ForeignConstraintRelationshipNode object depending on the isReferencing
* flag.
*/
static List *
GetNeighbourList(ForeignConstraintRelationshipNode *relationshipNode, bool isReferencing)
{
if (isReferencing)
{
return relationshipNode->backAdjacencyList;
}
else
{
return relationshipNode->adjacencyList;
}
}
/*
* GetRelationIdsFromRelationshipNodeList returns list of relationId's for
* given ForeignConstraintRelationshipNode object list.
*/
static List *
GetRelationIdsFromRelationshipNodeList(List *fKeyRelationshipNodeList)
{
List *relationIdList = NIL;
ForeignConstraintRelationshipNode *fKeyRelationshipNode = NULL;
foreach_ptr(fKeyRelationshipNode, fKeyRelationshipNodeList)
{
Oid relationId = fKeyRelationshipNode->relationId;
relationIdList = lappend_oid(relationIdList, relationId);
}
return relationIdList;
}
/*
* 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;
}
return node;
}