TLA Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * amutils.c
4 : * SQL-level APIs related to index access methods.
5 : *
6 : * Copyright (c) 2016-2023, 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 : "can_include", AMPROP_CAN_INCLUDE
86 : },
87 : };
88 :
89 : static IndexAMProperty
90 CBC 894 : lookup_prop_name(const char *name)
91 : {
92 : int i;
93 :
94 8547 : for (i = 0; i < lengthof(am_propnames); i++)
95 : {
96 8451 : if (pg_strcasecmp(am_propnames[i].name, name) == 0)
97 798 : return am_propnames[i].prop;
98 : }
99 :
100 : /* We do not throw an error, so that AMs can define their own properties */
101 96 : return AMPROP_UNKNOWN;
102 : }
103 :
104 : /*
105 : * Common code for properties that are just bit tests of indoptions.
106 : *
107 : * tuple: the pg_index heaptuple
108 : * attno: identify the index column to test the indoptions of.
109 : * guard: if false, a boolean false result is forced (saves code in caller).
110 : * iopt_mask: mask for interesting indoption bit.
111 : * iopt_expect: value for a "true" result (should be 0 or iopt_mask).
112 : *
113 : * Returns false to indicate a NULL result (for "unknown/inapplicable"),
114 : * otherwise sets *res to the boolean value to return.
115 : */
116 : static bool
117 168 : test_indoption(HeapTuple tuple, int attno, bool guard,
118 : int16 iopt_mask, int16 iopt_expect,
119 : bool *res)
120 : {
121 : Datum datum;
122 : int2vector *indoption;
123 : int16 indoption_val;
124 ECB :
125 GIC 168 : if (!guard)
126 ECB : {
127 CBC 84 : *res = false;
128 GIC 84 : return true;
129 : }
130 ECB :
131 GNC 84 : datum = SysCacheGetAttrNotNull(INDEXRELID, tuple, Anum_pg_index_indoption);
132 :
133 CBC 84 : indoption = ((int2vector *) DatumGetPointer(datum));
134 GIC 84 : indoption_val = indoption->values[attno - 1];
135 ECB :
136 GIC 84 : *res = (indoption_val & iopt_mask) == iopt_expect;
137 :
138 84 : return true;
139 : }
140 :
141 :
142 : /*
143 : * Test property of an index AM, index, or index column.
144 : *
145 : * This is common code for different SQL-level funcs, so the amoid and
146 : * index_oid parameters are mutually exclusive; we look up the amoid from the
147 : * index_oid if needed, or if no index oid is given, we're looking at AM-wide
148 ECB : * properties.
149 : */
150 : static Datum
151 GIC 894 : indexam_property(FunctionCallInfo fcinfo,
152 ECB : const char *propname,
153 : Oid amoid, Oid index_oid, int attno)
154 : {
155 GIC 894 : bool res = false;
156 894 : bool isnull = false;
157 894 : int natts = 0;
158 : IndexAMProperty prop;
159 ECB : IndexAmRoutine *routine;
160 :
161 : /* Try to convert property name to enum (no error if not known) */
162 CBC 894 : prop = lookup_prop_name(propname);
163 :
164 : /* If we have an index OID, look up the AM, and get # of columns too */
165 GIC 894 : if (OidIsValid(index_oid))
166 : {
167 ECB : HeapTuple tuple;
168 : Form_pg_class rd_rel;
169 :
170 GBC 672 : Assert(!OidIsValid(amoid));
171 CBC 672 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid));
172 672 : if (!HeapTupleIsValid(tuple))
173 UBC 0 : PG_RETURN_NULL();
174 GIC 672 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
175 GBC 672 : if (rd_rel->relkind != RELKIND_INDEX &&
176 UBC 0 : rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
177 : {
178 LBC 0 : ReleaseSysCache(tuple);
179 0 : PG_RETURN_NULL();
180 ECB : }
181 GIC 672 : amoid = rd_rel->relam;
182 672 : natts = rd_rel->relnatts;
183 672 : ReleaseSysCache(tuple);
184 : }
185 :
186 : /*
187 : * At this point, either index_oid == InvalidOid or it's a valid index
188 : * OID. Also, after this test and the one below, either attno == 0 for
189 ECB : * index-wide or AM-wide tests, or it's a valid column number in a valid
190 EUB : * index.
191 : */
192 GIC 894 : if (attno < 0 || attno > natts)
193 UIC 0 : PG_RETURN_NULL();
194 :
195 ECB : /*
196 : * Get AM information. If we don't have a valid AM OID, return NULL.
197 EUB : */
198 GIC 894 : routine = GetIndexAmRoutineByAmId(amoid, true);
199 894 : if (routine == NULL)
200 UIC 0 : PG_RETURN_NULL();
201 :
202 : /*
203 ECB : * If there's an AM property routine, give it a chance to override the
204 : * generic logic. Proceed if it returns false.
205 : */
206 GIC 1599 : if (routine->amproperty &&
207 CBC 705 : routine->amproperty(index_oid, attno, prop, propname,
208 EUB : &res, &isnull))
209 ECB : {
210 GIC 33 : if (isnull)
211 UIC 0 : PG_RETURN_NULL();
212 CBC 33 : PG_RETURN_BOOL(res);
213 : }
214 :
215 GIC 861 : if (attno > 0)
216 ECB : {
217 : HeapTuple tuple;
218 : Form_pg_index rd_index;
219 GIC 435 : bool iskey = true;
220 :
221 : /*
222 : * Handle column-level properties. Many of these need the pg_index row
223 ECB : * (which we also need to use to check for nonkey atts) so we fetch
224 : * that first.
225 EUB : */
226 CBC 435 : tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
227 GIC 435 : if (!HeapTupleIsValid(tuple))
228 LBC 0 : PG_RETURN_NULL();
229 CBC 435 : rd_index = (Form_pg_index) GETSTRUCT(tuple);
230 :
231 435 : Assert(index_oid == rd_index->indexrelid);
232 GIC 435 : Assert(attno > 0 && attno <= rd_index->indnatts);
233 :
234 435 : isnull = true;
235 :
236 : /*
237 : * If amcaninclude, we might be looking at an attno for a nonkey
238 ECB : * column, for which we (generically) assume that most properties are
239 : * null.
240 : */
241 GIC 435 : if (routine->amcaninclude
242 CBC 345 : && attno > rd_index->indnkeyatts)
243 GIC 42 : iskey = false;
244 ECB :
245 CBC 435 : switch (prop)
246 ECB : {
247 GIC 48 : case AMPROP_ASC:
248 CBC 90 : if (iskey &&
249 42 : test_indoption(tuple, attno, routine->amcanorder,
250 : INDOPTION_DESC, 0, &res))
251 42 : isnull = false;
252 48 : break;
253 ECB :
254 GIC 48 : case AMPROP_DESC:
255 CBC 90 : if (iskey &&
256 42 : test_indoption(tuple, attno, routine->amcanorder,
257 : INDOPTION_DESC, INDOPTION_DESC, &res))
258 42 : isnull = false;
259 48 : break;
260 ECB :
261 GIC 48 : case AMPROP_NULLS_FIRST:
262 CBC 90 : if (iskey &&
263 42 : test_indoption(tuple, attno, routine->amcanorder,
264 : INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &res))
265 42 : isnull = false;
266 48 : break;
267 ECB :
268 GIC 48 : case AMPROP_NULLS_LAST:
269 CBC 90 : if (iskey &&
270 42 : test_indoption(tuple, attno, routine->amcanorder,
271 : INDOPTION_NULLS_FIRST, 0, &res))
272 42 : isnull = false;
273 GIC 48 : break;
274 :
275 48 : case AMPROP_ORDERABLE:
276 :
277 ECB : /*
278 : * generic assumption is that nonkey columns are not orderable
279 : */
280 GIC 48 : res = iskey ? routine->amcanorder : false;
281 CBC 48 : isnull = false;
282 GIC 48 : break;
283 :
284 24 : case AMPROP_DISTANCE_ORDERABLE:
285 :
286 : /*
287 : * The conditions for whether a column is distance-orderable
288 : * are really up to the AM (at time of writing, only GiST
289 : * supports it at all). The planner has its own idea based on
290 : * whether it finds an operator with amoppurpose 'o', but
291 : * getting there from just the index column type seems like a
292 : * lot of work. So instead we expect the AM to handle this in
293 : * its amproperty routine. The generic result is to return
294 : * false if the AM says it never supports this, or if this is
295 ECB : * a nonkey column, and null otherwise (meaning we don't
296 : * know).
297 : */
298 CBC 24 : if (!iskey || !routine->amcanorderbyop)
299 : {
300 24 : res = false;
301 GIC 24 : isnull = false;
302 ECB : }
303 GIC 24 : break;
304 :
305 15 : case AMPROP_RETURNABLE:
306 ECB :
307 : /* note that we ignore iskey for this property */
308 :
309 CBC 15 : isnull = false;
310 GIC 15 : res = false;
311 :
312 15 : if (routine->amcanreturn)
313 : {
314 : /*
315 : * If possible, the AM should handle this test in its
316 ECB : * amproperty function without opening the rel. But this
317 : * is the generic fallback if it does not.
318 : */
319 CBC 6 : Relation indexrel = index_open(index_oid, AccessShareLock);
320 :
321 6 : res = index_can_return(indexrel, attno);
322 GIC 6 : index_close(indexrel, AccessShareLock);
323 ECB : }
324 CBC 15 : break;
325 :
326 27 : case AMPROP_SEARCH_ARRAY:
327 27 : if (iskey)
328 : {
329 27 : res = routine->amsearcharray;
330 GIC 27 : isnull = false;
331 ECB : }
332 CBC 27 : break;
333 :
334 27 : case AMPROP_SEARCH_NULLS:
335 27 : if (iskey)
336 : {
337 27 : res = routine->amsearchnulls;
338 GIC 27 : isnull = false;
339 ECB : }
340 CBC 27 : break;
341 :
342 GIC 102 : default:
343 CBC 102 : break;
344 : }
345 ECB :
346 CBC 435 : ReleaseSysCache(tuple);
347 ECB :
348 GIC 435 : if (!isnull)
349 309 : PG_RETURN_BOOL(res);
350 CBC 126 : PG_RETURN_NULL();
351 : }
352 :
353 GIC 426 : if (OidIsValid(index_oid))
354 : {
355 : /*
356 : * Handle index-level properties. Currently, these only depend on the
357 ECB : * AM, but that might not be true forever, so we make users name an
358 : * index not just an AM.
359 : */
360 CBC 204 : switch (prop)
361 : {
362 24 : case AMPROP_CLUSTERABLE:
363 24 : PG_RETURN_BOOL(routine->amclusterable);
364 :
365 24 : case AMPROP_INDEX_SCAN:
366 24 : PG_RETURN_BOOL(routine->amgettuple ? true : false);
367 :
368 24 : case AMPROP_BITMAP_SCAN:
369 24 : PG_RETURN_BOOL(routine->amgetbitmap ? true : false);
370 :
371 24 : case AMPROP_BACKWARD_SCAN:
372 24 : PG_RETURN_BOOL(routine->amcanbackward);
373 :
374 GIC 108 : default:
375 108 : PG_RETURN_NULL();
376 : }
377 : }
378 :
379 : /*
380 ECB : * Handle AM-level properties (those that control what you can say in
381 : * CREATE INDEX).
382 : */
383 CBC 222 : switch (prop)
384 : {
385 24 : case AMPROP_CAN_ORDER:
386 24 : PG_RETURN_BOOL(routine->amcanorder);
387 :
388 24 : case AMPROP_CAN_UNIQUE:
389 24 : PG_RETURN_BOOL(routine->amcanunique);
390 :
391 24 : case AMPROP_CAN_MULTI_COL:
392 24 : PG_RETURN_BOOL(routine->amcanmulticol);
393 :
394 24 : case AMPROP_CAN_EXCLUDE:
395 24 : PG_RETURN_BOOL(routine->amgettuple ? true : false);
396 :
397 24 : case AMPROP_CAN_INCLUDE:
398 24 : PG_RETURN_BOOL(routine->amcaninclude);
399 :
400 GIC 102 : default:
401 102 : PG_RETURN_NULL();
402 : }
403 : }
404 :
405 : /*
406 ECB : * Test property of an AM specified by AM OID
407 : */
408 : Datum
409 CBC 222 : pg_indexam_has_property(PG_FUNCTION_ARGS)
410 : {
411 222 : Oid amoid = PG_GETARG_OID(0);
412 GIC 222 : char *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
413 :
414 222 : return indexam_property(fcinfo, propname, amoid, InvalidOid, 0);
415 : }
416 :
417 : /*
418 ECB : * Test property of an index specified by index OID
419 : */
420 : Datum
421 CBC 204 : pg_index_has_property(PG_FUNCTION_ARGS)
422 : {
423 204 : Oid relid = PG_GETARG_OID(0);
424 GIC 204 : char *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
425 :
426 204 : return indexam_property(fcinfo, propname, InvalidOid, relid, 0);
427 : }
428 :
429 : /*
430 ECB : * Test property of an index column specified by index OID and column number
431 : */
432 : Datum
433 CBC 468 : pg_index_column_has_property(PG_FUNCTION_ARGS)
434 ECB : {
435 GIC 468 : Oid relid = PG_GETARG_OID(0);
436 468 : int32 attno = PG_GETARG_INT32(1);
437 CBC 468 : char *propname = text_to_cstring(PG_GETARG_TEXT_PP(2));
438 EUB :
439 : /* Reject attno 0 immediately, so that attno > 0 identifies this case */
440 CBC 468 : if (attno <= 0)
441 UIC 0 : PG_RETURN_NULL();
442 :
443 GIC 468 : return indexam_property(fcinfo, propname, InvalidOid, relid, attno);
444 : }
445 :
446 : /*
447 : * Return the name of the given phase, as used for progress reporting by the
448 EUB : * given AM.
449 : */
450 : Datum
451 UBC 0 : pg_indexam_progress_phasename(PG_FUNCTION_ARGS)
452 : {
453 UIC 0 : Oid amoid = PG_GETARG_OID(0);
454 0 : int32 phasenum = PG_GETARG_INT32(1);
455 EUB : IndexAmRoutine *routine;
456 : char *name;
457 :
458 UIC 0 : routine = GetIndexAmRoutineByAmId(amoid, true);
459 UBC 0 : if (routine == NULL || !routine->ambuildphasename)
460 0 : PG_RETURN_NULL();
461 EUB :
462 UIC 0 : name = routine->ambuildphasename(phasenum);
463 UBC 0 : if (!name)
464 UIC 0 : PG_RETURN_NULL();
465 :
466 UNC 0 : PG_RETURN_DATUM(CStringGetTextDatum(name));
467 : }
|