1/*-------------------------------------------------------------------------
2 *
3 * partitionfuncs.c
4 * Functions for accessing partition-related metadata
5 *
6 * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
7 * Portions Copyright (c) 1994, Regents of the University of California
8 *
9 *
10 * IDENTIFICATION
11 * src/backend/utils/adt/partitionfuncs.c
12 *
13 *-------------------------------------------------------------------------
14 */
15
16#include "postgres.h"
17
18#include "access/htup_details.h"
19#include "catalog/partition.h"
20#include "catalog/pg_class.h"
21#include "catalog/pg_inherits.h"
22#include "catalog/pg_type.h"
23#include "funcapi.h"
24#include "utils/fmgrprotos.h"
25#include "utils/lsyscache.h"
26#include "utils/syscache.h"
27
28/*
29 * Checks if a given relation can be part of a partition tree. Returns
30 * false if the relation cannot be processed, in which case it is up to
31 * the caller to decide what to do, by either raising an error or doing
32 * something else.
33 */
34static bool
35check_rel_can_be_partition(Oid relid)
36{
37 char relkind;
38 bool relispartition;
39
40 /* Check if relation exists */
41 if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)))
42 return false;
43
44 relkind = get_rel_relkind(relid);
45 relispartition = get_rel_relispartition(relid);
46
47 /* Only allow relation types that can appear in partition trees. */
48 if (!relispartition &&
49 relkind != RELKIND_PARTITIONED_TABLE &&
50 relkind != RELKIND_PARTITIONED_INDEX)
51 return false;
52
53 return true;
54}
55
56/*
57 * pg_partition_tree
58 *
59 * Produce a view with one row per member of a partition tree, beginning
60 * from the top-most parent given by the caller. This gives information
61 * about each partition, its immediate partitioned parent, if it is
62 * a leaf partition and its level in the hierarchy.
63 */
64Datum
65pg_partition_tree(PG_FUNCTION_ARGS)
66{
67#define PG_PARTITION_TREE_COLS 4
68 Oid rootrelid = PG_GETARG_OID(0);
69 FuncCallContext *funcctx;
70 ListCell **next;
71
72 /* stuff done only on the first call of the function */
73 if (SRF_IS_FIRSTCALL())
74 {
75 MemoryContext oldcxt;
76 TupleDesc tupdesc;
77 List *partitions;
78
79 /* create a function context for cross-call persistence */
80 funcctx = SRF_FIRSTCALL_INIT();
81
82 if (!check_rel_can_be_partition(rootrelid))
83 SRF_RETURN_DONE(funcctx);
84
85 /* switch to memory context appropriate for multiple function calls */
86 oldcxt = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
87
88 /*
89 * Find all members of inheritance set. We only need AccessShareLock
90 * on the children for the partition information lookup.
91 */
92 partitions = find_all_inheritors(rootrelid, AccessShareLock, NULL);
93
94 tupdesc = CreateTemplateTupleDesc(PG_PARTITION_TREE_COLS);
95 TupleDescInitEntry(tupdesc, (AttrNumber) 1, "relid",
96 REGCLASSOID, -1, 0);
97 TupleDescInitEntry(tupdesc, (AttrNumber) 2, "parentid",
98 REGCLASSOID, -1, 0);
99 TupleDescInitEntry(tupdesc, (AttrNumber) 3, "isleaf",
100 BOOLOID, -1, 0);
101 TupleDescInitEntry(tupdesc, (AttrNumber) 4, "level",
102 INT4OID, -1, 0);
103
104 funcctx->tuple_desc = BlessTupleDesc(tupdesc);
105
106 /* allocate memory for user context */
107 next = (ListCell **) palloc(sizeof(ListCell *));
108 *next = list_head(partitions);
109 funcctx->user_fctx = (void *) next;
110
111 MemoryContextSwitchTo(oldcxt);
112 }
113
114 /* stuff done on every call of the function */
115 funcctx = SRF_PERCALL_SETUP();
116 next = (ListCell **) funcctx->user_fctx;
117
118 if (*next != NULL)
119 {
120 Datum result;
121 Datum values[PG_PARTITION_TREE_COLS];
122 bool nulls[PG_PARTITION_TREE_COLS];
123 HeapTuple tuple;
124 Oid parentid = InvalidOid;
125 Oid relid = lfirst_oid(*next);
126 char relkind = get_rel_relkind(relid);
127 int level = 0;
128 List *ancestors = get_partition_ancestors(lfirst_oid(*next));
129 ListCell *lc;
130
131 /*
132 * Form tuple with appropriate data.
133 */
134 MemSet(nulls, 0, sizeof(nulls));
135 MemSet(values, 0, sizeof(values));
136
137 /* relid */
138 values[0] = ObjectIdGetDatum(relid);
139
140 /* parentid */
141 if (ancestors != NIL)
142 parentid = linitial_oid(ancestors);
143 if (OidIsValid(parentid))
144 values[1] = ObjectIdGetDatum(parentid);
145 else
146 nulls[1] = true;
147
148 /* isleaf */
149 values[2] = BoolGetDatum(relkind != RELKIND_PARTITIONED_TABLE &&
150 relkind != RELKIND_PARTITIONED_INDEX);
151
152 /* level */
153 if (relid != rootrelid)
154 {
155 foreach(lc, ancestors)
156 {
157 level++;
158 if (lfirst_oid(lc) == rootrelid)
159 break;
160 }
161 }
162 values[3] = Int32GetDatum(level);
163
164 *next = lnext(*next);
165
166 tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
167 result = HeapTupleGetDatum(tuple);
168 SRF_RETURN_NEXT(funcctx, result);
169 }
170
171 /* done when there are no more elements left */
172 SRF_RETURN_DONE(funcctx);
173}
174
175/*
176 * pg_partition_root
177 *
178 * Returns the top-most parent of the partition tree to which a given
179 * relation belongs, or NULL if it's not (or cannot be) part of any
180 * partition tree.
181 */
182Datum
183pg_partition_root(PG_FUNCTION_ARGS)
184{
185 Oid relid = PG_GETARG_OID(0);
186 Oid rootrelid;
187 List *ancestors;
188
189 if (!check_rel_can_be_partition(relid))
190 PG_RETURN_NULL();
191
192 /* fetch the list of ancestors */
193 ancestors = get_partition_ancestors(relid);
194
195 /*
196 * If the input relation is already the top-most parent, just return
197 * itself.
198 */
199 if (ancestors == NIL)
200 PG_RETURN_OID(relid);
201
202 rootrelid = llast_oid(ancestors);
203 list_free(ancestors);
204
205 /*
206 * "rootrelid" must contain a valid OID, given that the input relation is
207 * a valid partition tree member as checked above.
208 */
209 Assert(OidIsValid(rootrelid));
210 PG_RETURN_OID(rootrelid);
211}
212
213/*
214 * pg_partition_ancestors
215 *
216 * Produces a view with one row per ancestor of the given partition,
217 * including the input relation itself.
218 */
219Datum
220pg_partition_ancestors(PG_FUNCTION_ARGS)
221{
222 Oid relid = PG_GETARG_OID(0);
223 FuncCallContext *funcctx;
224 ListCell **next;
225
226 if (SRF_IS_FIRSTCALL())
227 {
228 MemoryContext oldcxt;
229 List *ancestors;
230
231 funcctx = SRF_FIRSTCALL_INIT();
232
233 if (!check_rel_can_be_partition(relid))
234 SRF_RETURN_DONE(funcctx);
235
236 oldcxt = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
237
238 ancestors = get_partition_ancestors(relid);
239 ancestors = lcons_oid(relid, ancestors);
240
241 next = (ListCell **) palloc(sizeof(ListCell *));
242 *next = list_head(ancestors);
243 funcctx->user_fctx = (void *) next;
244
245 MemoryContextSwitchTo(oldcxt);
246 }
247
248 funcctx = SRF_PERCALL_SETUP();
249 next = (ListCell **) funcctx->user_fctx;
250
251 if (*next != NULL)
252 {
253 Oid relid = lfirst_oid(*next);
254
255 *next = lnext(*next);
256 SRF_RETURN_NEXT(funcctx, ObjectIdGetDatum(relid));
257 }
258
259 SRF_RETURN_DONE(funcctx);
260}
261