Coverage Report

Created: 2025-06-15 06:31

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