Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * amutils.c
4 : * SQL-level APIs related to index access methods.
5 : *
6 : * Copyright (c) 2016-2026, 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 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 :
125 168 : if (!guard)
126 : {
127 84 : *res = false;
128 84 : return true;
129 : }
130 :
131 84 : datum = SysCacheGetAttrNotNull(INDEXRELID, tuple, Anum_pg_index_indoption);
132 :
133 84 : indoption = ((int2vector *) DatumGetPointer(datum));
134 84 : indoption_val = indoption->values[attno - 1];
135 :
136 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 : * properties.
149 : */
150 : static Datum
151 894 : indexam_property(FunctionCallInfo fcinfo,
152 : const char *propname,
153 : Oid amoid, Oid index_oid, int attno)
154 : {
155 894 : bool res = false;
156 894 : bool isnull = false;
157 894 : int natts = 0;
158 : IndexAMProperty prop;
159 : const IndexAmRoutine *routine;
160 :
161 : /* Try to convert property name to enum (no error if not known) */
162 894 : prop = lookup_prop_name(propname);
163 :
164 : /* If we have an index OID, look up the AM, and get # of columns too */
165 894 : if (OidIsValid(index_oid))
166 : {
167 : HeapTuple tuple;
168 : Form_pg_class rd_rel;
169 :
170 : Assert(!OidIsValid(amoid));
171 672 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid));
172 672 : if (!HeapTupleIsValid(tuple))
173 0 : PG_RETURN_NULL();
174 672 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
175 672 : if (rd_rel->relkind != RELKIND_INDEX &&
176 0 : rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
177 : {
178 0 : ReleaseSysCache(tuple);
179 0 : PG_RETURN_NULL();
180 : }
181 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 : * index-wide or AM-wide tests, or it's a valid column number in a valid
190 : * index.
191 : */
192 894 : if (attno < 0 || attno > natts)
193 0 : PG_RETURN_NULL();
194 :
195 : /*
196 : * Get AM information. If we don't have a valid AM OID, return NULL.
197 : */
198 894 : routine = GetIndexAmRoutineByAmId(amoid, true);
199 894 : if (routine == NULL)
200 0 : PG_RETURN_NULL();
201 :
202 : /*
203 : * If there's an AM property routine, give it a chance to override the
204 : * generic logic. Proceed if it returns false.
205 : */
206 1599 : if (routine->amproperty &&
207 705 : routine->amproperty(index_oid, attno, prop, propname,
208 : &res, &isnull))
209 : {
210 33 : if (isnull)
211 0 : PG_RETURN_NULL();
212 33 : PG_RETURN_BOOL(res);
213 : }
214 :
215 861 : if (attno > 0)
216 : {
217 : HeapTuple tuple;
218 : Form_pg_index rd_index;
219 435 : bool iskey = true;
220 :
221 : /*
222 : * Handle column-level properties. Many of these need the pg_index row
223 : * (which we also need to use to check for nonkey atts) so we fetch
224 : * that first.
225 : */
226 435 : tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
227 435 : if (!HeapTupleIsValid(tuple))
228 0 : PG_RETURN_NULL();
229 435 : rd_index = (Form_pg_index) GETSTRUCT(tuple);
230 :
231 : Assert(index_oid == rd_index->indexrelid);
232 : 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 : * column, for which we (generically) assume that most properties are
239 : * null.
240 : */
241 435 : if (routine->amcaninclude
242 345 : && attno > rd_index->indnkeyatts)
243 42 : iskey = false;
244 :
245 435 : switch (prop)
246 : {
247 48 : case AMPROP_ASC:
248 90 : if (iskey &&
249 42 : test_indoption(tuple, attno, routine->amcanorder,
250 : INDOPTION_DESC, 0, &res))
251 42 : isnull = false;
252 48 : break;
253 :
254 48 : case AMPROP_DESC:
255 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 :
261 48 : case AMPROP_NULLS_FIRST:
262 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 :
268 48 : case AMPROP_NULLS_LAST:
269 90 : if (iskey &&
270 42 : test_indoption(tuple, attno, routine->amcanorder,
271 : INDOPTION_NULLS_FIRST, 0, &res))
272 42 : isnull = false;
273 48 : break;
274 :
275 48 : case AMPROP_ORDERABLE:
276 :
277 : /*
278 : * generic assumption is that nonkey columns are not orderable
279 : */
280 48 : res = iskey ? routine->amcanorder : false;
281 48 : isnull = false;
282 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 : * a nonkey column, and null otherwise (meaning we don't
296 : * know).
297 : */
298 24 : if (!iskey || !routine->amcanorderbyop)
299 : {
300 24 : res = false;
301 24 : isnull = false;
302 : }
303 24 : break;
304 :
305 15 : case AMPROP_RETURNABLE:
306 :
307 : /* note that we ignore iskey for this property */
308 :
309 15 : isnull = false;
310 15 : res = false;
311 :
312 15 : if (routine->amcanreturn)
313 : {
314 : /*
315 : * If possible, the AM should handle this test in its
316 : * amproperty function without opening the rel. But this
317 : * is the generic fallback if it does not.
318 : */
319 6 : Relation indexrel = index_open(index_oid, AccessShareLock);
320 :
321 6 : res = index_can_return(indexrel, attno);
322 6 : index_close(indexrel, AccessShareLock);
323 : }
324 15 : break;
325 :
326 27 : case AMPROP_SEARCH_ARRAY:
327 27 : if (iskey)
328 : {
329 27 : res = routine->amsearcharray;
330 27 : isnull = false;
331 : }
332 27 : break;
333 :
334 27 : case AMPROP_SEARCH_NULLS:
335 27 : if (iskey)
336 : {
337 27 : res = routine->amsearchnulls;
338 27 : isnull = false;
339 : }
340 27 : break;
341 :
342 102 : default:
343 102 : break;
344 : }
345 :
346 435 : ReleaseSysCache(tuple);
347 :
348 435 : if (!isnull)
349 309 : PG_RETURN_BOOL(res);
350 126 : PG_RETURN_NULL();
351 : }
352 :
353 426 : if (OidIsValid(index_oid))
354 : {
355 : /*
356 : * Handle index-level properties. Currently, these only depend on the
357 : * AM, but that might not be true forever, so we make users name an
358 : * index not just an AM.
359 : */
360 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 108 : default:
375 108 : PG_RETURN_NULL();
376 : }
377 : }
378 :
379 : /*
380 : * Handle AM-level properties (those that control what you can say in
381 : * CREATE INDEX).
382 : */
383 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 102 : default:
401 102 : PG_RETURN_NULL();
402 : }
403 : }
404 :
405 : /*
406 : * Test property of an AM specified by AM OID
407 : */
408 : Datum
409 222 : pg_indexam_has_property(PG_FUNCTION_ARGS)
410 : {
411 222 : Oid amoid = PG_GETARG_OID(0);
412 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 : * Test property of an index specified by index OID
419 : */
420 : Datum
421 204 : pg_index_has_property(PG_FUNCTION_ARGS)
422 : {
423 204 : Oid relid = PG_GETARG_OID(0);
424 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 : * Test property of an index column specified by index OID and column number
431 : */
432 : Datum
433 468 : pg_index_column_has_property(PG_FUNCTION_ARGS)
434 : {
435 468 : Oid relid = PG_GETARG_OID(0);
436 468 : int32 attno = PG_GETARG_INT32(1);
437 468 : char *propname = text_to_cstring(PG_GETARG_TEXT_PP(2));
438 :
439 : /* Reject attno 0 immediately, so that attno > 0 identifies this case */
440 468 : if (attno <= 0)
441 0 : PG_RETURN_NULL();
442 :
443 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 : * given AM.
449 : */
450 : Datum
451 0 : pg_indexam_progress_phasename(PG_FUNCTION_ARGS)
452 : {
453 0 : Oid amoid = PG_GETARG_OID(0);
454 0 : int32 phasenum = PG_GETARG_INT32(1);
455 : const IndexAmRoutine *routine;
456 : char *name;
457 :
458 0 : routine = GetIndexAmRoutineByAmId(amoid, true);
459 0 : if (routine == NULL || !routine->ambuildphasename)
460 0 : PG_RETURN_NULL();
461 :
462 0 : name = routine->ambuildphasename(phasenum);
463 0 : if (!name)
464 0 : PG_RETURN_NULL();
465 :
466 0 : PG_RETURN_DATUM(CStringGetTextDatum(name));
467 : }
|