1 /*-------------------------------------------------------------------------
2  *
3  * partitionfuncs.c
4  *	  Functions for accessing partition-related metadata
5  *
6  * Portions Copyright (c) 1996-2020, 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  */
34 static bool
check_rel_can_be_partition(Oid relid)35 check_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  */
64 Datum
pg_partition_tree(PG_FUNCTION_ARGS)65 pg_partition_tree(PG_FUNCTION_ARGS)
66 {
67 #define PG_PARTITION_TREE_COLS	4
68 	Oid			rootrelid = PG_GETARG_OID(0);
69 	FuncCallContext *funcctx;
70 	List	   *partitions;
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 
78 		/* create a function context for cross-call persistence */
79 		funcctx = SRF_FIRSTCALL_INIT();
80 
81 		if (!check_rel_can_be_partition(rootrelid))
82 			SRF_RETURN_DONE(funcctx);
83 
84 		/* switch to memory context appropriate for multiple function calls */
85 		oldcxt = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
86 
87 		/*
88 		 * Find all members of inheritance set.  We only need AccessShareLock
89 		 * on the children for the partition information lookup.
90 		 */
91 		partitions = find_all_inheritors(rootrelid, AccessShareLock, NULL);
92 
93 		tupdesc = CreateTemplateTupleDesc(PG_PARTITION_TREE_COLS);
94 		TupleDescInitEntry(tupdesc, (AttrNumber) 1, "relid",
95 						   REGCLASSOID, -1, 0);
96 		TupleDescInitEntry(tupdesc, (AttrNumber) 2, "parentid",
97 						   REGCLASSOID, -1, 0);
98 		TupleDescInitEntry(tupdesc, (AttrNumber) 3, "isleaf",
99 						   BOOLOID, -1, 0);
100 		TupleDescInitEntry(tupdesc, (AttrNumber) 4, "level",
101 						   INT4OID, -1, 0);
102 
103 		funcctx->tuple_desc = BlessTupleDesc(tupdesc);
104 
105 		/* The only state we need is the partition list */
106 		funcctx->user_fctx = (void *) partitions;
107 
108 		MemoryContextSwitchTo(oldcxt);
109 	}
110 
111 	/* stuff done on every call of the function */
112 	funcctx = SRF_PERCALL_SETUP();
113 	partitions = (List *) funcctx->user_fctx;
114 
115 	if (funcctx->call_cntr < list_length(partitions))
116 	{
117 		Datum		result;
118 		Datum		values[PG_PARTITION_TREE_COLS];
119 		bool		nulls[PG_PARTITION_TREE_COLS];
120 		HeapTuple	tuple;
121 		Oid			parentid = InvalidOid;
122 		Oid			relid = list_nth_oid(partitions, funcctx->call_cntr);
123 		char		relkind = get_rel_relkind(relid);
124 		int			level = 0;
125 		List	   *ancestors = get_partition_ancestors(relid);
126 		ListCell   *lc;
127 
128 		/*
129 		 * Form tuple with appropriate data.
130 		 */
131 		MemSet(nulls, 0, sizeof(nulls));
132 		MemSet(values, 0, sizeof(values));
133 
134 		/* relid */
135 		values[0] = ObjectIdGetDatum(relid);
136 
137 		/* parentid */
138 		if (ancestors != NIL)
139 			parentid = linitial_oid(ancestors);
140 		if (OidIsValid(parentid))
141 			values[1] = ObjectIdGetDatum(parentid);
142 		else
143 			nulls[1] = true;
144 
145 		/* isleaf */
146 		values[2] = BoolGetDatum(relkind != RELKIND_PARTITIONED_TABLE &&
147 								 relkind != RELKIND_PARTITIONED_INDEX);
148 
149 		/* level */
150 		if (relid != rootrelid)
151 		{
152 			foreach(lc, ancestors)
153 			{
154 				level++;
155 				if (lfirst_oid(lc) == rootrelid)
156 					break;
157 			}
158 		}
159 		values[3] = Int32GetDatum(level);
160 
161 		tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls);
162 		result = HeapTupleGetDatum(tuple);
163 		SRF_RETURN_NEXT(funcctx, result);
164 	}
165 
166 	/* done when there are no more elements left */
167 	SRF_RETURN_DONE(funcctx);
168 }
169 
170 /*
171  * pg_partition_root
172  *
173  * Returns the top-most parent of the partition tree to which a given
174  * relation belongs, or NULL if it's not (or cannot be) part of any
175  * partition tree.
176  */
177 Datum
pg_partition_root(PG_FUNCTION_ARGS)178 pg_partition_root(PG_FUNCTION_ARGS)
179 {
180 	Oid			relid = PG_GETARG_OID(0);
181 	Oid			rootrelid;
182 	List	   *ancestors;
183 
184 	if (!check_rel_can_be_partition(relid))
185 		PG_RETURN_NULL();
186 
187 	/* fetch the list of ancestors */
188 	ancestors = get_partition_ancestors(relid);
189 
190 	/*
191 	 * If the input relation is already the top-most parent, just return
192 	 * itself.
193 	 */
194 	if (ancestors == NIL)
195 		PG_RETURN_OID(relid);
196 
197 	rootrelid = llast_oid(ancestors);
198 	list_free(ancestors);
199 
200 	/*
201 	 * "rootrelid" must contain a valid OID, given that the input relation is
202 	 * a valid partition tree member as checked above.
203 	 */
204 	Assert(OidIsValid(rootrelid));
205 	PG_RETURN_OID(rootrelid);
206 }
207 
208 /*
209  * pg_partition_ancestors
210  *
211  * Produces a view with one row per ancestor of the given partition,
212  * including the input relation itself.
213  */
214 Datum
pg_partition_ancestors(PG_FUNCTION_ARGS)215 pg_partition_ancestors(PG_FUNCTION_ARGS)
216 {
217 	Oid			relid = PG_GETARG_OID(0);
218 	FuncCallContext *funcctx;
219 	List	   *ancestors;
220 
221 	if (SRF_IS_FIRSTCALL())
222 	{
223 		MemoryContext oldcxt;
224 
225 		funcctx = SRF_FIRSTCALL_INIT();
226 
227 		if (!check_rel_can_be_partition(relid))
228 			SRF_RETURN_DONE(funcctx);
229 
230 		oldcxt = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
231 
232 		ancestors = get_partition_ancestors(relid);
233 		ancestors = lcons_oid(relid, ancestors);
234 
235 		/* The only state we need is the ancestors list */
236 		funcctx->user_fctx = (void *) ancestors;
237 
238 		MemoryContextSwitchTo(oldcxt);
239 	}
240 
241 	funcctx = SRF_PERCALL_SETUP();
242 	ancestors = (List *) funcctx->user_fctx;
243 
244 	if (funcctx->call_cntr < list_length(ancestors))
245 	{
246 		Oid			relid = list_nth_oid(ancestors, funcctx->call_cntr);
247 
248 		SRF_RETURN_NEXT(funcctx, ObjectIdGetDatum(relid));
249 	}
250 
251 	SRF_RETURN_DONE(funcctx);
252 }
253