Age Owner Branch data TLA Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * amutils.c
4 : : * SQL-level APIs related to index access methods.
5 : : *
6 : : * Copyright (c) 2016-2024, 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
2801 tgl@sss.pgh.pa.us 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
2198 rhodiumtoad@postgres 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 : :
2801 tgl@sss.pgh.pa.us 125 [ + + ]: 168 : if (!guard)
126 : : {
127 : 84 : *res = false;
128 : 84 : return true;
129 : : }
130 : :
386 dgustafsson@postgres 131 : 84 : datum = SysCacheGetAttrNotNull(INDEXRELID, tuple, Anum_pg_index_indoption);
132 : :
2801 tgl@sss.pgh.pa.us 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 : : 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 [ - + ]: 672 : Assert(!OidIsValid(amoid));
171 : 672 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid));
172 [ - + ]: 672 : if (!HeapTupleIsValid(tuple))
2801 tgl@sss.pgh.pa.us 173 :UBC 0 : PG_RETURN_NULL();
2801 tgl@sss.pgh.pa.us 174 :CBC 672 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
2277 alvherre@alvh.no-ip. 175 [ - + ]: 672 : if (rd_rel->relkind != RELKIND_INDEX &&
2277 alvherre@alvh.no-ip. 176 [ # # ]:UBC 0 : rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
177 : : {
2801 tgl@sss.pgh.pa.us 178 : 0 : ReleaseSysCache(tuple);
179 : 0 : PG_RETURN_NULL();
180 : : }
2801 tgl@sss.pgh.pa.us 181 :CBC 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)
2801 tgl@sss.pgh.pa.us 193 :UBC 0 : PG_RETURN_NULL();
194 : :
195 : : /*
196 : : * Get AM information. If we don't have a valid AM OID, return NULL.
197 : : */
2801 tgl@sss.pgh.pa.us 198 :CBC 894 : routine = GetIndexAmRoutineByAmId(amoid, true);
199 [ - + ]: 894 : if (routine == NULL)
2801 tgl@sss.pgh.pa.us 200 :UBC 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 : : */
2801 tgl@sss.pgh.pa.us 206 [ + + + + ]:CBC 1599 : if (routine->amproperty &&
207 : 705 : routine->amproperty(index_oid, attno, prop, propname,
208 : : &res, &isnull))
209 : : {
210 [ - + ]: 33 : if (isnull)
2801 tgl@sss.pgh.pa.us 211 :UBC 0 : PG_RETURN_NULL();
2801 tgl@sss.pgh.pa.us 212 :CBC 33 : PG_RETURN_BOOL(res);
213 : : }
214 : :
215 [ + + ]: 861 : if (attno > 0)
216 : : {
217 : : HeapTuple tuple;
218 : : Form_pg_index rd_index;
2198 rhodiumtoad@postgres 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))
2198 rhodiumtoad@postgres 228 :UBC 0 : PG_RETURN_NULL();
2198 rhodiumtoad@postgres 229 :CBC 435 : rd_index = (Form_pg_index) GETSTRUCT(tuple);
230 : :
231 [ - + ]: 435 : Assert(index_oid == rd_index->indexrelid);
232 [ + - - + ]: 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 : : * 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 : :
2801 tgl@sss.pgh.pa.us 245 [ + + + + : 435 : switch (prop)
+ + + + +
+ ]
246 : : {
247 : 48 : case AMPROP_ASC:
2198 rhodiumtoad@postgres 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 : :
2801 tgl@sss.pgh.pa.us 254 : 48 : case AMPROP_DESC:
2198 rhodiumtoad@postgres 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 : :
2801 tgl@sss.pgh.pa.us 261 : 48 : case AMPROP_NULLS_FIRST:
2198 rhodiumtoad@postgres 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 : :
2801 tgl@sss.pgh.pa.us 268 : 48 : case AMPROP_NULLS_LAST:
2198 rhodiumtoad@postgres 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 : :
2801 tgl@sss.pgh.pa.us 275 : 48 : case AMPROP_ORDERABLE:
276 : :
277 : : /*
278 : : * generic assumption is that nonkey columns are not orderable
279 : : */
2198 rhodiumtoad@postgres 280 [ + + + + ]: 48 : res = iskey ? routine->amcanorder : false;
281 : 48 : isnull = false;
282 : 48 : break;
283 : :
2801 tgl@sss.pgh.pa.us 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 : : */
2198 rhodiumtoad@postgres 298 [ + + + - ]: 24 : if (!iskey || !routine->amcanorderbyop)
299 : : {
300 : 24 : res = false;
301 : 24 : isnull = false;
302 : : }
303 : 24 : break;
304 : :
2801 tgl@sss.pgh.pa.us 305 : 15 : case AMPROP_RETURNABLE:
306 : :
307 : : /* note that we ignore iskey for this property */
308 : :
2198 rhodiumtoad@postgres 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 : : */
2801 tgl@sss.pgh.pa.us 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 : : }
2198 rhodiumtoad@postgres 324 : 15 : break;
325 : :
2801 tgl@sss.pgh.pa.us 326 : 27 : case AMPROP_SEARCH_ARRAY:
2198 rhodiumtoad@postgres 327 [ + - ]: 27 : if (iskey)
328 : : {
329 : 27 : res = routine->amsearcharray;
330 : 27 : isnull = false;
331 : : }
332 : 27 : break;
333 : :
2801 tgl@sss.pgh.pa.us 334 : 27 : case AMPROP_SEARCH_NULLS:
2198 rhodiumtoad@postgres 335 [ + - ]: 27 : if (iskey)
336 : : {
337 : 27 : res = routine->amsearchnulls;
338 : 27 : isnull = false;
339 : : }
340 : 27 : break;
341 : :
2801 tgl@sss.pgh.pa.us 342 : 102 : default:
2198 rhodiumtoad@postgres 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 : :
2801 tgl@sss.pgh.pa.us 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 : :
2198 rhodiumtoad@postgres 397 : 24 : case AMPROP_CAN_INCLUDE:
398 : 24 : PG_RETURN_BOOL(routine->amcaninclude);
399 : :
2801 tgl@sss.pgh.pa.us 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)
2801 tgl@sss.pgh.pa.us 441 :UBC 0 : PG_RETURN_NULL();
442 : :
2801 tgl@sss.pgh.pa.us 443 :CBC 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
1839 alvherre@alvh.no-ip. 451 :UBC 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 : : 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 : :
595 peter@eisentraut.org 466 : 0 : PG_RETURN_DATUM(CStringGetTextDatum(name));
467 : : }
|