1 /*-------------------------------------------------------------------------
2  *
3  * foreign_key_relationship_query.c
4  *
5  * This file contains UDFs for getting foreign constraint relationship between
6  * distributed tables.
7  *
8  * Copyright (c), Citus Data, Inc.
9  *
10  *-------------------------------------------------------------------------
11  */
12 
13 #include "postgres.h"
14 #include "fmgr.h"
15 #include "funcapi.h"
16 
17 #include "catalog/dependency.h"
18 #include "catalog/pg_constraint.h"
19 #include "distributed/foreign_key_relationship.h"
20 #include "distributed/coordinator_protocol.h"
21 #include "distributed/listutils.h"
22 #include "distributed/metadata_cache.h"
23 #include "distributed/tuplestore.h"
24 #include "distributed/version_compat.h"
25 #include "utils/builtins.h"
26 
27 
28 #define GET_FKEY_CONNECTED_RELATIONS_COLUMNS 1
29 
30 
31 /* these functions are only exported in the regression tests */
32 PG_FUNCTION_INFO_V1(get_referencing_relation_id_list);
33 PG_FUNCTION_INFO_V1(get_referenced_relation_id_list);
34 PG_FUNCTION_INFO_V1(get_foreign_key_connected_relations);
35 PG_FUNCTION_INFO_V1(drop_constraint_cascade_via_perform_deletion);
36 
37 
38 /*
39  * drop_constraint_cascade_via_perform_deletion simply drops constraint on
40  * relation via performDeletion.
41  */
42 Datum
drop_constraint_cascade_via_perform_deletion(PG_FUNCTION_ARGS)43 drop_constraint_cascade_via_perform_deletion(PG_FUNCTION_ARGS)
44 {
45 	Oid relationId = PG_GETARG_OID(0);
46 
47 	if (PG_ARGISNULL(1))
48 	{
49 		/* avoid unexpected crashes in regression tests */
50 		ereport(ERROR, (errmsg("cannot perform operation without constraint "
51 							   "name argument")));
52 	}
53 
54 	text *constraintNameText = PG_GETARG_TEXT_P(1);
55 	char *constraintName = text_to_cstring(constraintNameText);
56 
57 	/* error if constraint does not exist */
58 	bool missingOk = false;
59 	Oid constraintId = get_relation_constraint_oid(relationId, constraintName, missingOk);
60 
61 	ObjectAddress constraintObjectAddress;
62 	constraintObjectAddress.classId = ConstraintRelationId;
63 	constraintObjectAddress.objectId = constraintId;
64 	constraintObjectAddress.objectSubId = 0;
65 
66 	performDeletion(&constraintObjectAddress, DROP_CASCADE, 0);
67 
68 	PG_RETURN_VOID();
69 }
70 
71 
72 /*
73  * get_referencing_relation_id_list returns the list of table oids that is referencing
74  * by given oid recursively. It uses the list cached in the distributed table cache
75  * entry.
76  */
77 Datum
get_referencing_relation_id_list(PG_FUNCTION_ARGS)78 get_referencing_relation_id_list(PG_FUNCTION_ARGS)
79 {
80 	CheckCitusVersion(ERROR);
81 
82 	FuncCallContext *functionContext = NULL;
83 	ListCell *foreignRelationCell = NULL;
84 
85 	/* for the first we call this UDF, we need to populate the result to return set */
86 	if (SRF_IS_FIRSTCALL())
87 	{
88 		Oid relationId = PG_GETARG_OID(0);
89 		CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry(relationId);
90 
91 		/* create a function context for cross-call persistence */
92 		functionContext = SRF_FIRSTCALL_INIT();
93 
94 		MemoryContext oldContext =
95 			MemoryContextSwitchTo(functionContext->multi_call_memory_ctx);
96 		List *refList = list_copy(
97 			cacheEntry->referencingRelationsViaForeignKey);
98 		ListCellAndListWrapper *wrapper = palloc0(sizeof(ListCellAndListWrapper));
99 		foreignRelationCell = list_head(refList);
100 		wrapper->list = refList;
101 		wrapper->listCell = foreignRelationCell;
102 		functionContext->user_fctx = wrapper;
103 		MemoryContextSwitchTo(oldContext);
104 	}
105 
106 	/*
107 	 * On every call to this function, we get the current position in the
108 	 * statement list. We then iterate to the next position in the list and
109 	 * return the current statement, if we have not yet reached the end of
110 	 * list.
111 	 */
112 	functionContext = SRF_PERCALL_SETUP();
113 
114 	ListCellAndListWrapper *wrapper =
115 		(ListCellAndListWrapper *) functionContext->user_fctx;
116 	if (wrapper->listCell != NULL)
117 	{
118 		Oid refId = lfirst_oid(wrapper->listCell);
119 
120 		wrapper->listCell = lnext_compat(wrapper->list, wrapper->listCell);
121 
122 		SRF_RETURN_NEXT(functionContext, PointerGetDatum(refId));
123 	}
124 	else
125 	{
126 		SRF_RETURN_DONE(functionContext);
127 	}
128 }
129 
130 
131 /*
132  * get_referenced_relation_id_list returns the list of table oids that is referenced
133  * by given oid recursively. It uses the list cached in the distributed table cache
134  * entry.
135  */
136 Datum
get_referenced_relation_id_list(PG_FUNCTION_ARGS)137 get_referenced_relation_id_list(PG_FUNCTION_ARGS)
138 {
139 	CheckCitusVersion(ERROR);
140 
141 	FuncCallContext *functionContext = NULL;
142 	ListCell *foreignRelationCell = NULL;
143 
144 	/* for the first we call this UDF, we need to populate the result to return set */
145 	if (SRF_IS_FIRSTCALL())
146 	{
147 		Oid relationId = PG_GETARG_OID(0);
148 		CitusTableCacheEntry *cacheEntry = GetCitusTableCacheEntry(relationId);
149 
150 		/* create a function context for cross-call persistence */
151 		functionContext = SRF_FIRSTCALL_INIT();
152 
153 		MemoryContext oldContext =
154 			MemoryContextSwitchTo(functionContext->multi_call_memory_ctx);
155 		List *refList = list_copy(cacheEntry->referencedRelationsViaForeignKey);
156 		foreignRelationCell = list_head(refList);
157 		ListCellAndListWrapper *wrapper = palloc0(sizeof(ListCellAndListWrapper));
158 		wrapper->list = refList;
159 		wrapper->listCell = foreignRelationCell;
160 		functionContext->user_fctx = wrapper;
161 		MemoryContextSwitchTo(oldContext);
162 	}
163 
164 	/*
165 	 * On every call to this function, we get the current position in the
166 	 * statement list. We then iterate to the next position in the list and
167 	 * return the current statement, if we have not yet reached the end of
168 	 * list.
169 	 */
170 	functionContext = SRF_PERCALL_SETUP();
171 
172 	ListCellAndListWrapper *wrapper =
173 		(ListCellAndListWrapper *) functionContext->user_fctx;
174 
175 	if (wrapper->listCell != NULL)
176 	{
177 		Oid refId = lfirst_oid(wrapper->listCell);
178 
179 		wrapper->listCell = lnext_compat(wrapper->list, wrapper->listCell);
180 
181 		SRF_RETURN_NEXT(functionContext, PointerGetDatum(refId));
182 	}
183 	else
184 	{
185 		SRF_RETURN_DONE(functionContext);
186 	}
187 }
188 
189 
190 /*
191  * get_foreign_key_connected_relations takes a relation, and returns relations
192  * that are connected to input relation via a foreign key graph.
193  */
194 Datum
get_foreign_key_connected_relations(PG_FUNCTION_ARGS)195 get_foreign_key_connected_relations(PG_FUNCTION_ARGS)
196 {
197 	CheckCitusVersion(ERROR);
198 
199 	Oid relationId = PG_GETARG_OID(0);
200 
201 	TupleDesc tupleDescriptor = NULL;
202 	Tuplestorestate *tupleStore = SetupTuplestore(fcinfo, &tupleDescriptor);
203 
204 	Oid connectedRelationId;
205 	List *fkeyConnectedRelationIdList = GetForeignKeyConnectedRelationIdList(relationId);
206 	foreach_oid(connectedRelationId, fkeyConnectedRelationIdList)
207 	{
208 		Datum values[GET_FKEY_CONNECTED_RELATIONS_COLUMNS];
209 		bool nulls[GET_FKEY_CONNECTED_RELATIONS_COLUMNS];
210 
211 		memset(values, 0, sizeof(values));
212 		memset(nulls, false, sizeof(nulls));
213 
214 		values[0] = ObjectIdGetDatum(connectedRelationId);
215 
216 		tuplestore_putvalues(tupleStore, tupleDescriptor, values, nulls);
217 	}
218 
219 	tuplestore_donestoring(tupleStore);
220 
221 	PG_RETURN_VOID();
222 }
223