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