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
90 1788 : lookup_prop_name(const char *name)
91 : {
92 : int i;
93 :
94 17094 : for (i = 0; i < lengthof(am_propnames); i++)
95 : {
96 16902 : if (pg_strcasecmp(am_propnames[i].name, name) == 0)
97 1596 : return am_propnames[i].prop;
98 : }
99 :
100 : /* We do not throw an error, so that AMs can define their own properties */
101 192 : 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 336 : 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 336 : if (!guard)
126 : {
127 168 : *res = false;
128 168 : return true;
129 : }
130 :
131 168 : datum = SysCacheGetAttrNotNull(INDEXRELID, tuple, Anum_pg_index_indoption);
132 :
133 168 : indoption = ((int2vector *) DatumGetPointer(datum));
134 168 : indoption_val = indoption->values[attno - 1];
135 :
136 168 : *res = (indoption_val & iopt_mask) == iopt_expect;
137 :
138 168 : 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 1788 : indexam_property(FunctionCallInfo fcinfo,
152 : const char *propname,
153 : Oid amoid, Oid index_oid, int attno)
154 : {
155 1788 : bool res = false;
156 1788 : bool isnull = false;
157 1788 : int natts = 0;
158 : IndexAMProperty prop;
159 : IndexAmRoutine *routine;
160 :
161 : /* Try to convert property name to enum (no error if not known) */
162 1788 : prop = lookup_prop_name(propname);
163 :
164 : /* If we have an index OID, look up the AM, and get # of columns too */
165 1788 : if (OidIsValid(index_oid))
166 : {
167 : HeapTuple tuple;
168 : Form_pg_class rd_rel;
169 :
170 : Assert(!OidIsValid(amoid));
171 1344 : tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid));
172 1344 : if (!HeapTupleIsValid(tuple))
173 0 : PG_RETURN_NULL();
174 1344 : rd_rel = (Form_pg_class) GETSTRUCT(tuple);
175 1344 : 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 1344 : amoid = rd_rel->relam;
182 1344 : natts = rd_rel->relnatts;
183 1344 : 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 1788 : 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 1788 : routine = GetIndexAmRoutineByAmId(amoid, true);
199 1788 : 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 3198 : if (routine->amproperty &&
207 1410 : routine->amproperty(index_oid, attno, prop, propname,
208 : &res, &isnull))
209 : {
210 66 : if (isnull)
211 0 : PG_RETURN_NULL();
212 66 : PG_RETURN_BOOL(res);
213 : }
214 :
215 1722 : if (attno > 0)
216 : {
217 : HeapTuple tuple;
218 : Form_pg_index rd_index;
219 870 : 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 870 : tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
227 870 : if (!HeapTupleIsValid(tuple))
228 0 : PG_RETURN_NULL();
229 870 : 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 870 : 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 870 : if (routine->amcaninclude
242 690 : && attno > rd_index->indnkeyatts)
243 84 : iskey = false;
244 :
245 870 : switch (prop)
246 : {
247 96 : case AMPROP_ASC:
248 180 : if (iskey &&
249 84 : test_indoption(tuple, attno, routine->amcanorder,
250 : INDOPTION_DESC, 0, &res))
251 84 : isnull = false;
252 96 : break;
253 :
254 96 : case AMPROP_DESC:
255 180 : if (iskey &&
256 84 : test_indoption(tuple, attno, routine->amcanorder,
257 : INDOPTION_DESC, INDOPTION_DESC, &res))
258 84 : isnull = false;
259 96 : break;
260 :
261 96 : case AMPROP_NULLS_FIRST:
262 180 : if (iskey &&
263 84 : test_indoption(tuple, attno, routine->amcanorder,
264 : INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &res))
265 84 : isnull = false;
266 96 : break;
267 :
268 96 : case AMPROP_NULLS_LAST:
269 180 : if (iskey &&
270 84 : test_indoption(tuple, attno, routine->amcanorder,
271 : INDOPTION_NULLS_FIRST, 0, &res))
272 84 : isnull = false;
273 96 : break;
274 :
275 96 : case AMPROP_ORDERABLE:
276 :
277 : /*
278 : * generic assumption is that nonkey columns are not orderable
279 : */
280 96 : res = iskey ? routine->amcanorder : false;
281 96 : isnull = false;
282 96 : break;
283 :
284 48 : 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 48 : if (!iskey || !routine->amcanorderbyop)
299 : {
300 48 : res = false;
301 48 : isnull = false;
302 : }
303 48 : break;
304 :
305 30 : case AMPROP_RETURNABLE:
306 :
307 : /* note that we ignore iskey for this property */
308 :
309 30 : isnull = false;
310 30 : res = false;
311 :
312 30 : 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 12 : Relation indexrel = index_open(index_oid, AccessShareLock);
320 :
321 12 : res = index_can_return(indexrel, attno);
322 12 : index_close(indexrel, AccessShareLock);
323 : }
324 30 : break;
325 :
326 54 : case AMPROP_SEARCH_ARRAY:
327 54 : if (iskey)
328 : {
329 54 : res = routine->amsearcharray;
330 54 : isnull = false;
331 : }
332 54 : break;
333 :
334 54 : case AMPROP_SEARCH_NULLS:
335 54 : if (iskey)
336 : {
337 54 : res = routine->amsearchnulls;
338 54 : isnull = false;
339 : }
340 54 : break;
341 :
342 204 : default:
343 204 : break;
344 : }
345 :
346 870 : ReleaseSysCache(tuple);
347 :
348 870 : if (!isnull)
349 618 : PG_RETURN_BOOL(res);
350 252 : PG_RETURN_NULL();
351 : }
352 :
353 852 : 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 408 : switch (prop)
361 : {
362 48 : case AMPROP_CLUSTERABLE:
363 48 : PG_RETURN_BOOL(routine->amclusterable);
364 :
365 48 : case AMPROP_INDEX_SCAN:
366 48 : PG_RETURN_BOOL(routine->amgettuple ? true : false);
367 :
368 48 : case AMPROP_BITMAP_SCAN:
369 48 : PG_RETURN_BOOL(routine->amgetbitmap ? true : false);
370 :
371 48 : case AMPROP_BACKWARD_SCAN:
372 48 : PG_RETURN_BOOL(routine->amcanbackward);
373 :
374 216 : default:
375 216 : 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 444 : switch (prop)
384 : {
385 48 : case AMPROP_CAN_ORDER:
386 48 : PG_RETURN_BOOL(routine->amcanorder);
387 :
388 48 : case AMPROP_CAN_UNIQUE:
389 48 : PG_RETURN_BOOL(routine->amcanunique);
390 :
391 48 : case AMPROP_CAN_MULTI_COL:
392 48 : PG_RETURN_BOOL(routine->amcanmulticol);
393 :
394 48 : case AMPROP_CAN_EXCLUDE:
395 48 : PG_RETURN_BOOL(routine->amgettuple ? true : false);
396 :
397 48 : case AMPROP_CAN_INCLUDE:
398 48 : PG_RETURN_BOOL(routine->amcaninclude);
399 :
400 204 : default:
401 204 : PG_RETURN_NULL();
402 : }
403 : }
404 :
405 : /*
406 : * Test property of an AM specified by AM OID
407 : */
408 : Datum
409 444 : pg_indexam_has_property(PG_FUNCTION_ARGS)
410 : {
411 444 : Oid amoid = PG_GETARG_OID(0);
412 444 : char *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
413 :
414 444 : 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 408 : pg_index_has_property(PG_FUNCTION_ARGS)
422 : {
423 408 : Oid relid = PG_GETARG_OID(0);
424 408 : char *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
425 :
426 408 : 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 936 : pg_index_column_has_property(PG_FUNCTION_ARGS)
434 : {
435 936 : Oid relid = PG_GETARG_OID(0);
436 936 : int32 attno = PG_GETARG_INT32(1);
437 936 : char *propname = text_to_cstring(PG_GETARG_TEXT_PP(2));
438 :
439 : /* Reject attno 0 immediately, so that attno > 0 identifies this case */
440 936 : if (attno <= 0)
441 0 : PG_RETURN_NULL();
442 :
443 936 : 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 : 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 : }
|