Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * amutils.c
4 : * SQL-level APIs related to index access methods.
5 : *
6 : * Copyright (c) 2016-2017, PostgreSQL Global Development Group
7 : *
8 : *
9 : * IDENTIFICATION
10 : * src/backend/utils/adt/amutils.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 : #include "postgres.h"
15 :
16 : #include "access/amapi.h"
17 : #include "access/htup_details.h"
18 : #include "catalog/pg_class.h"
19 : #include "catalog/pg_index.h"
20 : #include "utils/builtins.h"
21 : #include "utils/syscache.h"
22 :
23 :
24 : /* Convert string property name to enum, for efficiency */
25 : struct am_propname
26 : {
27 : const char *name;
28 : IndexAMProperty prop;
29 : };
30 :
31 : static const struct am_propname am_propnames[] =
32 : {
33 : {
34 : "asc", AMPROP_ASC
35 : },
36 : {
37 : "desc", AMPROP_DESC
38 : },
39 : {
40 : "nulls_first", AMPROP_NULLS_FIRST
41 : },
42 : {
43 : "nulls_last", AMPROP_NULLS_LAST
44 : },
45 : {
46 : "orderable", AMPROP_ORDERABLE
47 : },
48 : {
49 : "distance_orderable", AMPROP_DISTANCE_ORDERABLE
50 : },
51 : {
52 : "returnable", AMPROP_RETURNABLE
53 : },
54 : {
55 : "search_array", AMPROP_SEARCH_ARRAY
56 : },
57 : {
58 : "search_nulls", AMPROP_SEARCH_NULLS
59 : },
60 : {
61 : "clusterable", AMPROP_CLUSTERABLE
62 : },
63 : {
64 : "index_scan", AMPROP_INDEX_SCAN
65 : },
66 : {
67 : "bitmap_scan", AMPROP_BITMAP_SCAN
68 : },
69 : {
70 : "backward_scan", AMPROP_BACKWARD_SCAN
71 : },
72 : {
73 : "can_order", AMPROP_CAN_ORDER
74 : },
75 : {
76 : "can_unique", AMPROP_CAN_UNIQUE
77 : },
78 : {
79 : "can_multi_col", AMPROP_CAN_MULTI_COL
80 : },
81 : {
82 : "can_exclude", AMPROP_CAN_EXCLUDE
83 : }
84 : };
85 :
86 : static IndexAMProperty
87 252 : lookup_prop_name(const char *name)
88 : {
89 : int i;
90 :
91 2400 : for (i = 0; i < lengthof(am_propnames); i++)
92 : {
93 2372 : if (pg_strcasecmp(am_propnames[i].name, name) == 0)
94 224 : return am_propnames[i].prop;
95 : }
96 :
97 : /* We do not throw an error, so that AMs can define their own properties */
98 28 : return AMPROP_UNKNOWN;
99 : }
100 :
101 : /*
102 : * Common code for properties that are just bit tests of indoptions.
103 : *
104 : * relid/attno: identify the index column to test the indoptions of.
105 : * guard: if false, a boolean false result is forced (saves code in caller).
106 : * iopt_mask: mask for interesting indoption bit.
107 : * iopt_expect: value for a "true" result (should be 0 or iopt_mask).
108 : *
109 : * Returns false to indicate a NULL result (for "unknown/inapplicable"),
110 : * otherwise sets *res to the boolean value to return.
111 : */
112 : static bool
113 48 : test_indoption(Oid relid, int attno, bool guard,
114 : int16 iopt_mask, int16 iopt_expect,
115 : bool *res)
116 : {
117 : HeapTuple tuple;
118 : Form_pg_index rd_index PG_USED_FOR_ASSERTS_ONLY;
119 : Datum datum;
120 : bool isnull;
121 : int2vector *indoption;
122 : int16 indoption_val;
123 :
124 48 : if (!guard)
125 : {
126 24 : *res = false;
127 24 : return true;
128 : }
129 :
130 24 : tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(relid));
131 24 : if (!HeapTupleIsValid(tuple))
132 0 : return false;
133 24 : rd_index = (Form_pg_index) GETSTRUCT(tuple);
134 :
135 24 : Assert(relid == rd_index->indexrelid);
136 24 : Assert(attno > 0 && attno <= rd_index->indnatts);
137 :
138 24 : datum = SysCacheGetAttr(INDEXRELID, tuple,
139 : Anum_pg_index_indoption, &isnull);
140 24 : Assert(!isnull);
141 :
142 24 : indoption = ((int2vector *) DatumGetPointer(datum));
143 24 : indoption_val = indoption->values[attno - 1];
144 :
145 24 : *res = (indoption_val & iopt_mask) == iopt_expect;
146 :
147 24 : ReleaseSysCache(tuple);
148 :
149 24 : return true;
150 : }
151 :
152 :
153 : /*
154 : * Test property of an index AM, index, or index column.
155 : *
156 : * This is common code for different SQL-level funcs, so the amoid and
157 : * index_oid parameters are mutually exclusive; we look up the amoid from the
158 : * index_oid if needed, or if no index oid is given, we're looking at AM-wide
159 : * properties.
160 : */
161 : static Datum
162 252 : indexam_property(FunctionCallInfo fcinfo,
163 : const char *propname,
164 : Oid amoid, Oid index_oid, int attno)
165 : {
166 252 : bool res = false;
167 252 : bool isnull = false;
168 252 : int natts = 0;
169 : IndexAMProperty prop;
170 : IndexAmRoutine *routine;
171 :
172 : /* Try to convert property name to enum (no error if not known) */
173 252 : prop = lookup_prop_name(propname);
174 :
175 : /* If we have an index OID, look up the AM, and get # of columns too */
176 252 : if (OidIsValid(index_oid))
177 : {
178 : HeapTuple tuple;
179 : Form_pg_class rd_rel;
180 :
181 186 : Assert(!OidIsValid(amoid));
182 186 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid));
183 186 : if (!HeapTupleIsValid(tuple))
184 0 : PG_RETURN_NULL();
185 186 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
186 186 : if (rd_rel->relkind != RELKIND_INDEX)
187 : {
188 0 : ReleaseSysCache(tuple);
189 0 : PG_RETURN_NULL();
190 : }
191 186 : amoid = rd_rel->relam;
192 186 : natts = rd_rel->relnatts;
193 186 : ReleaseSysCache(tuple);
194 : }
195 :
196 : /*
197 : * At this point, either index_oid == InvalidOid or it's a valid index
198 : * OID. Also, after this test, either attno == 0 for index-wide or
199 : * AM-wide tests, or it's a valid column number in a valid index.
200 : */
201 252 : if (attno < 0 || attno > natts)
202 0 : PG_RETURN_NULL();
203 :
204 : /*
205 : * Get AM information. If we don't have a valid AM OID, return NULL.
206 : */
207 252 : routine = GetIndexAmRoutineByAmId(amoid, true);
208 252 : if (routine == NULL)
209 0 : PG_RETURN_NULL();
210 :
211 : /*
212 : * If there's an AM property routine, give it a chance to override the
213 : * generic logic. Proceed if it returns false.
214 : */
215 424 : if (routine->amproperty &&
216 172 : routine->amproperty(index_oid, attno, prop, propname,
217 : &res, &isnull))
218 : {
219 6 : if (isnull)
220 0 : PG_RETURN_NULL();
221 6 : PG_RETURN_BOOL(res);
222 : }
223 :
224 246 : if (attno > 0)
225 : {
226 : /* Handle column-level properties */
227 114 : switch (prop)
228 : {
229 : case AMPROP_ASC:
230 12 : if (test_indoption(index_oid, attno, routine->amcanorder,
231 : INDOPTION_DESC, 0, &res))
232 12 : PG_RETURN_BOOL(res);
233 0 : PG_RETURN_NULL();
234 :
235 : case AMPROP_DESC:
236 12 : if (test_indoption(index_oid, attno, routine->amcanorder,
237 : INDOPTION_DESC, INDOPTION_DESC, &res))
238 12 : PG_RETURN_BOOL(res);
239 0 : PG_RETURN_NULL();
240 :
241 : case AMPROP_NULLS_FIRST:
242 12 : if (test_indoption(index_oid, attno, routine->amcanorder,
243 : INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &res))
244 12 : PG_RETURN_BOOL(res);
245 0 : PG_RETURN_NULL();
246 :
247 : case AMPROP_NULLS_LAST:
248 12 : if (test_indoption(index_oid, attno, routine->amcanorder,
249 : INDOPTION_NULLS_FIRST, 0, &res))
250 12 : PG_RETURN_BOOL(res);
251 0 : PG_RETURN_NULL();
252 :
253 : case AMPROP_ORDERABLE:
254 12 : PG_RETURN_BOOL(routine->amcanorder);
255 :
256 : case AMPROP_DISTANCE_ORDERABLE:
257 :
258 : /*
259 : * The conditions for whether a column is distance-orderable
260 : * are really up to the AM (at time of writing, only GiST
261 : * supports it at all). The planner has its own idea based on
262 : * whether it finds an operator with amoppurpose 'o', but
263 : * getting there from just the index column type seems like a
264 : * lot of work. So instead we expect the AM to handle this in
265 : * its amproperty routine. The generic result is to return
266 : * false if the AM says it never supports this, and null
267 : * otherwise (meaning we don't know).
268 : */
269 6 : if (!routine->amcanorderbyop)
270 6 : PG_RETURN_BOOL(false);
271 0 : PG_RETURN_NULL();
272 :
273 : case AMPROP_RETURNABLE:
274 4 : if (!routine->amcanreturn)
275 3 : PG_RETURN_BOOL(false);
276 :
277 : /*
278 : * If possible, the AM should handle this test in its
279 : * amproperty function without opening the rel. But this is
280 : * the generic fallback if it does not.
281 : */
282 : {
283 1 : Relation indexrel = index_open(index_oid, AccessShareLock);
284 :
285 1 : res = index_can_return(indexrel, attno);
286 1 : index_close(indexrel, AccessShareLock);
287 : }
288 :
289 1 : PG_RETURN_BOOL(res);
290 :
291 : case AMPROP_SEARCH_ARRAY:
292 8 : PG_RETURN_BOOL(routine->amsearcharray);
293 :
294 : case AMPROP_SEARCH_NULLS:
295 8 : PG_RETURN_BOOL(routine->amsearchnulls);
296 :
297 : default:
298 28 : PG_RETURN_NULL();
299 : }
300 : }
301 :
302 132 : if (OidIsValid(index_oid))
303 : {
304 : /*
305 : * Handle index-level properties. Currently, these only depend on the
306 : * AM, but that might not be true forever, so we make users name an
307 : * index not just an AM.
308 : */
309 66 : switch (prop)
310 : {
311 : case AMPROP_CLUSTERABLE:
312 8 : PG_RETURN_BOOL(routine->amclusterable);
313 :
314 : case AMPROP_INDEX_SCAN:
315 8 : PG_RETURN_BOOL(routine->amgettuple ? true : false);
316 :
317 : case AMPROP_BITMAP_SCAN:
318 8 : PG_RETURN_BOOL(routine->amgetbitmap ? true : false);
319 :
320 : case AMPROP_BACKWARD_SCAN:
321 8 : PG_RETURN_BOOL(routine->amcanbackward);
322 :
323 : default:
324 34 : PG_RETURN_NULL();
325 : }
326 : }
327 :
328 : /*
329 : * Handle AM-level properties (those that control what you can say in
330 : * CREATE INDEX).
331 : */
332 66 : switch (prop)
333 : {
334 : case AMPROP_CAN_ORDER:
335 8 : PG_RETURN_BOOL(routine->amcanorder);
336 :
337 : case AMPROP_CAN_UNIQUE:
338 8 : PG_RETURN_BOOL(routine->amcanunique);
339 :
340 : case AMPROP_CAN_MULTI_COL:
341 8 : PG_RETURN_BOOL(routine->amcanmulticol);
342 :
343 : case AMPROP_CAN_EXCLUDE:
344 8 : PG_RETURN_BOOL(routine->amgettuple ? true : false);
345 :
346 : default:
347 34 : PG_RETURN_NULL();
348 : }
349 : }
350 :
351 : /*
352 : * Test property of an AM specified by AM OID
353 : */
354 : Datum
355 66 : pg_indexam_has_property(PG_FUNCTION_ARGS)
356 : {
357 66 : Oid amoid = PG_GETARG_OID(0);
358 66 : char *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
359 :
360 66 : return indexam_property(fcinfo, propname, amoid, InvalidOid, 0);
361 : }
362 :
363 : /*
364 : * Test property of an index specified by index OID
365 : */
366 : Datum
367 66 : pg_index_has_property(PG_FUNCTION_ARGS)
368 : {
369 66 : Oid relid = PG_GETARG_OID(0);
370 66 : char *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
371 :
372 66 : return indexam_property(fcinfo, propname, InvalidOid, relid, 0);
373 : }
374 :
375 : /*
376 : * Test property of an index column specified by index OID and column number
377 : */
378 : Datum
379 120 : pg_index_column_has_property(PG_FUNCTION_ARGS)
380 : {
381 120 : Oid relid = PG_GETARG_OID(0);
382 120 : int32 attno = PG_GETARG_INT32(1);
383 120 : char *propname = text_to_cstring(PG_GETARG_TEXT_PP(2));
384 :
385 : /* Reject attno 0 immediately, so that attno > 0 identifies this case */
386 120 : if (attno <= 0)
387 0 : PG_RETURN_NULL();
388 :
389 120 : return indexam_property(fcinfo, propname, InvalidOid, relid, attno);
390 : }
|