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