Coverage Report

Created: 2026-11-12 07:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/postgres/src/backend/utils/adt/amutils.c
Line
Count
Source
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
lookup_prop_name(const char *name)
91
0
{
92
0
  for (size_t i = 0; i < lengthof(am_propnames); i++)
93
0
  {
94
0
    if (pg_strcasecmp(am_propnames[i].name, name) == 0)
95
0
      return am_propnames[i].prop;
96
0
  }
97
98
  /* We do not throw an error, so that AMs can define their own properties */
99
0
  return AMPROP_UNKNOWN;
100
0
}
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
test_indoption(HeapTuple tuple, int attno, bool guard,
116
         int16 iopt_mask, int16 iopt_expect,
117
         bool *res)
118
0
{
119
0
  Datum   datum;
120
0
  int2vector *indoption;
121
0
  int16   indoption_val;
122
123
0
  if (!guard)
124
0
  {
125
0
    *res = false;
126
0
    return true;
127
0
  }
128
129
0
  datum = SysCacheGetAttrNotNull(INDEXRELID, tuple, Anum_pg_index_indoption);
130
131
0
  indoption = ((int2vector *) DatumGetPointer(datum));
132
0
  indoption_val = indoption->values[attno - 1];
133
134
0
  *res = (indoption_val & iopt_mask) == iopt_expect;
135
136
0
  return true;
137
0
}
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
indexam_property(FunctionCallInfo fcinfo,
150
         const char *propname,
151
         Oid amoid, Oid index_oid, int attno)
152
0
{
153
0
  bool    res = false;
154
0
  bool    isnull = false;
155
0
  int     natts = 0;
156
0
  IndexAMProperty prop;
157
0
  const IndexAmRoutine *routine;
158
159
  /* Try to convert property name to enum (no error if not known) */
160
0
  prop = lookup_prop_name(propname);
161
162
  /* If we have an index OID, look up the AM, and get # of columns too */
163
0
  if (OidIsValid(index_oid))
164
0
  {
165
0
    HeapTuple tuple;
166
0
    Form_pg_class rd_rel;
167
168
0
    Assert(!OidIsValid(amoid));
169
0
    tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(index_oid));
170
0
    if (!HeapTupleIsValid(tuple))
171
0
      PG_RETURN_NULL();
172
0
    rd_rel = (Form_pg_class) GETSTRUCT(tuple);
173
0
    if (rd_rel->relkind != RELKIND_INDEX &&
174
0
      rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
175
0
    {
176
0
      ReleaseSysCache(tuple);
177
0
      PG_RETURN_NULL();
178
0
    }
179
0
    amoid = rd_rel->relam;
180
0
    natts = rd_rel->relnatts;
181
0
    ReleaseSysCache(tuple);
182
0
  }
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
0
  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
0
  routine = GetIndexAmRoutineByAmId(amoid, true);
197
0
  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
0
  if (routine->amproperty &&
205
0
    routine->amproperty(index_oid, attno, prop, propname,
206
0
              &res, &isnull))
207
0
  {
208
0
    if (isnull)
209
0
      PG_RETURN_NULL();
210
0
    PG_RETURN_BOOL(res);
211
0
  }
212
213
0
  if (attno > 0)
214
0
  {
215
0
    HeapTuple tuple;
216
0
    Form_pg_index rd_index;
217
0
    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
0
    tuple = SearchSysCache1(INDEXRELID, ObjectIdGetDatum(index_oid));
225
0
    if (!HeapTupleIsValid(tuple))
226
0
      PG_RETURN_NULL();
227
0
    rd_index = (Form_pg_index) GETSTRUCT(tuple);
228
229
0
    Assert(index_oid == rd_index->indexrelid);
230
0
    Assert(attno > 0 && attno <= rd_index->indnatts);
231
232
0
    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
0
    if (routine->amcaninclude
240
0
      && attno > rd_index->indnkeyatts)
241
0
      iskey = false;
242
243
0
    switch (prop)
244
0
    {
245
0
      case AMPROP_ASC:
246
0
        if (iskey &&
247
0
          test_indoption(tuple, attno, routine->amcanorder,
248
0
                   INDOPTION_DESC, 0, &res))
249
0
          isnull = false;
250
0
        break;
251
252
0
      case AMPROP_DESC:
253
0
        if (iskey &&
254
0
          test_indoption(tuple, attno, routine->amcanorder,
255
0
                   INDOPTION_DESC, INDOPTION_DESC, &res))
256
0
          isnull = false;
257
0
        break;
258
259
0
      case AMPROP_NULLS_FIRST:
260
0
        if (iskey &&
261
0
          test_indoption(tuple, attno, routine->amcanorder,
262
0
                   INDOPTION_NULLS_FIRST, INDOPTION_NULLS_FIRST, &res))
263
0
          isnull = false;
264
0
        break;
265
266
0
      case AMPROP_NULLS_LAST:
267
0
        if (iskey &&
268
0
          test_indoption(tuple, attno, routine->amcanorder,
269
0
                   INDOPTION_NULLS_FIRST, 0, &res))
270
0
          isnull = false;
271
0
        break;
272
273
0
      case AMPROP_ORDERABLE:
274
275
        /*
276
         * generic assumption is that nonkey columns are not orderable
277
         */
278
0
        res = iskey ? routine->amcanorder : false;
279
0
        isnull = false;
280
0
        break;
281
282
0
      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
0
        if (!iskey || !routine->amcanorderbyop)
297
0
        {
298
0
          res = false;
299
0
          isnull = false;
300
0
        }
301
0
        break;
302
303
0
      case AMPROP_RETURNABLE:
304
305
        /* note that we ignore iskey for this property */
306
307
0
        isnull = false;
308
0
        res = false;
309
310
0
        if (routine->amcanreturn)
311
0
        {
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
0
          Relation  indexrel = index_open(index_oid, AccessShareLock);
318
319
0
          res = index_can_return(indexrel, attno);
320
0
          index_close(indexrel, AccessShareLock);
321
0
        }
322
0
        break;
323
324
0
      case AMPROP_SEARCH_ARRAY:
325
0
        if (iskey)
326
0
        {
327
0
          res = routine->amsearcharray;
328
0
          isnull = false;
329
0
        }
330
0
        break;
331
332
0
      case AMPROP_SEARCH_NULLS:
333
0
        if (iskey)
334
0
        {
335
0
          res = routine->amsearchnulls;
336
0
          isnull = false;
337
0
        }
338
0
        break;
339
340
0
      default:
341
0
        break;
342
0
    }
343
344
0
    ReleaseSysCache(tuple);
345
346
0
    if (!isnull)
347
0
      PG_RETURN_BOOL(res);
348
0
    PG_RETURN_NULL();
349
0
  }
350
351
0
  if (OidIsValid(index_oid))
352
0
  {
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
0
    switch (prop)
359
0
    {
360
0
      case AMPROP_CLUSTERABLE:
361
0
        PG_RETURN_BOOL(routine->amclusterable);
362
363
0
      case AMPROP_INDEX_SCAN:
364
0
        PG_RETURN_BOOL(routine->amgettuple ? true : false);
365
366
0
      case AMPROP_BITMAP_SCAN:
367
0
        PG_RETURN_BOOL(routine->amgetbitmap ? true : false);
368
369
0
      case AMPROP_BACKWARD_SCAN:
370
0
        PG_RETURN_BOOL(routine->amcanbackward);
371
372
0
      default:
373
0
        PG_RETURN_NULL();
374
0
    }
375
0
  }
376
377
  /*
378
   * Handle AM-level properties (those that control what you can say in
379
   * CREATE INDEX).
380
   */
381
0
  switch (prop)
382
0
  {
383
0
    case AMPROP_CAN_ORDER:
384
0
      PG_RETURN_BOOL(routine->amcanorder);
385
386
0
    case AMPROP_CAN_UNIQUE:
387
0
      PG_RETURN_BOOL(routine->amcanunique);
388
389
0
    case AMPROP_CAN_MULTI_COL:
390
0
      PG_RETURN_BOOL(routine->amcanmulticol);
391
392
0
    case AMPROP_CAN_EXCLUDE:
393
0
      PG_RETURN_BOOL(routine->amgettuple ? true : false);
394
395
0
    case AMPROP_CAN_INCLUDE:
396
0
      PG_RETURN_BOOL(routine->amcaninclude);
397
398
0
    default:
399
0
      PG_RETURN_NULL();
400
0
  }
401
0
}
402
403
/*
404
 * Test property of an AM specified by AM OID
405
 */
406
Datum
407
pg_indexam_has_property(PG_FUNCTION_ARGS)
408
0
{
409
0
  Oid     amoid = PG_GETARG_OID(0);
410
0
  char     *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
411
412
0
  return indexam_property(fcinfo, propname, amoid, InvalidOid, 0);
413
0
}
414
415
/*
416
 * Test property of an index specified by index OID
417
 */
418
Datum
419
pg_index_has_property(PG_FUNCTION_ARGS)
420
0
{
421
0
  Oid     relid = PG_GETARG_OID(0);
422
0
  char     *propname = text_to_cstring(PG_GETARG_TEXT_PP(1));
423
424
0
  return indexam_property(fcinfo, propname, InvalidOid, relid, 0);
425
0
}
426
427
/*
428
 * Test property of an index column specified by index OID and column number
429
 */
430
Datum
431
pg_index_column_has_property(PG_FUNCTION_ARGS)
432
0
{
433
0
  Oid     relid = PG_GETARG_OID(0);
434
0
  int32   attno = PG_GETARG_INT32(1);
435
0
  char     *propname = text_to_cstring(PG_GETARG_TEXT_PP(2));
436
437
  /* Reject attno 0 immediately, so that attno > 0 identifies this case */
438
0
  if (attno <= 0)
439
0
    PG_RETURN_NULL();
440
441
0
  return indexam_property(fcinfo, propname, InvalidOid, relid, attno);
442
0
}
443
444
/*
445
 * Return the name of the given phase, as used for progress reporting by the
446
 * given AM.
447
 */
448
Datum
449
pg_indexam_progress_phasename(PG_FUNCTION_ARGS)
450
0
{
451
0
  Oid     amoid = PG_GETARG_OID(0);
452
0
  int64   phasenum = PG_GETARG_INT64(1);
453
0
  const IndexAmRoutine *routine;
454
0
  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
0
}