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