Coverage Report

Created: 2025-07-03 06:49

/src/postgres/src/backend/access/index/amvalidate.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * amvalidate.c
4
 *    Support routines for index access methods' amvalidate and
5
 *    amadjustmembers functions.
6
 *
7
 * Copyright (c) 2016-2025, PostgreSQL Global Development Group
8
 *
9
 *
10
 * IDENTIFICATION
11
 *    src/backend/access/index/amvalidate.c
12
 *
13
 *-------------------------------------------------------------------------
14
 */
15
#include "postgres.h"
16
17
#include "access/amvalidate.h"
18
#include "access/htup_details.h"
19
#include "catalog/pg_am.h"
20
#include "catalog/pg_amop.h"
21
#include "catalog/pg_amproc.h"
22
#include "catalog/pg_opclass.h"
23
#include "catalog/pg_operator.h"
24
#include "catalog/pg_proc.h"
25
#include "catalog/pg_type.h"
26
#include "parser/parse_coerce.h"
27
#include "utils/syscache.h"
28
29
30
/*
31
 * identify_opfamily_groups() returns a List of OpFamilyOpFuncGroup structs,
32
 * one for each combination of lefttype/righttype present in the family's
33
 * operator and support function lists.  If amopstrategy K is present for
34
 * this datatype combination, we set bit 1 << K in operatorset, and similarly
35
 * for the support functions.  With uint64 fields we can handle operator and
36
 * function numbers up to 63, which is plenty for the foreseeable future.
37
 *
38
 * The given CatCLists are expected to represent a single opfamily fetched
39
 * from the AMOPSTRATEGY and AMPROCNUM caches, so that they will be in
40
 * order by those caches' second and third cache keys, namely the datatypes.
41
 */
42
List *
43
identify_opfamily_groups(CatCList *oprlist, CatCList *proclist)
44
0
{
45
0
  List     *result = NIL;
46
0
  OpFamilyOpFuncGroup *thisgroup;
47
0
  Form_pg_amop oprform;
48
0
  Form_pg_amproc procform;
49
0
  int     io,
50
0
        ip;
51
52
  /* We need the lists to be ordered; should be true in normal operation */
53
0
  if (!oprlist->ordered || !proclist->ordered)
54
0
    elog(ERROR, "cannot validate operator family without ordered data");
55
56
  /*
57
   * Advance through the lists concurrently.  Thanks to the ordering, we
58
   * should see all operators and functions of a given datatype pair
59
   * consecutively.
60
   */
61
0
  thisgroup = NULL;
62
0
  io = ip = 0;
63
0
  if (io < oprlist->n_members)
64
0
  {
65
0
    oprform = (Form_pg_amop) GETSTRUCT(&oprlist->members[io]->tuple);
66
0
    io++;
67
0
  }
68
0
  else
69
0
    oprform = NULL;
70
0
  if (ip < proclist->n_members)
71
0
  {
72
0
    procform = (Form_pg_amproc) GETSTRUCT(&proclist->members[ip]->tuple);
73
0
    ip++;
74
0
  }
75
0
  else
76
0
    procform = NULL;
77
78
0
  while (oprform || procform)
79
0
  {
80
0
    if (oprform && thisgroup &&
81
0
      oprform->amoplefttype == thisgroup->lefttype &&
82
0
      oprform->amoprighttype == thisgroup->righttype)
83
0
    {
84
      /* Operator belongs to current group; include it and advance */
85
86
      /* Ignore strategy numbers outside supported range */
87
0
      if (oprform->amopstrategy > 0 && oprform->amopstrategy < 64)
88
0
        thisgroup->operatorset |= ((uint64) 1) << oprform->amopstrategy;
89
90
0
      if (io < oprlist->n_members)
91
0
      {
92
0
        oprform = (Form_pg_amop) GETSTRUCT(&oprlist->members[io]->tuple);
93
0
        io++;
94
0
      }
95
0
      else
96
0
        oprform = NULL;
97
0
      continue;
98
0
    }
99
100
0
    if (procform && thisgroup &&
101
0
      procform->amproclefttype == thisgroup->lefttype &&
102
0
      procform->amprocrighttype == thisgroup->righttype)
103
0
    {
104
      /* Procedure belongs to current group; include it and advance */
105
106
      /* Ignore function numbers outside supported range */
107
0
      if (procform->amprocnum > 0 && procform->amprocnum < 64)
108
0
        thisgroup->functionset |= ((uint64) 1) << procform->amprocnum;
109
110
0
      if (ip < proclist->n_members)
111
0
      {
112
0
        procform = (Form_pg_amproc) GETSTRUCT(&proclist->members[ip]->tuple);
113
0
        ip++;
114
0
      }
115
0
      else
116
0
        procform = NULL;
117
0
      continue;
118
0
    }
119
120
    /* Time for a new group */
121
0
    thisgroup = (OpFamilyOpFuncGroup *) palloc(sizeof(OpFamilyOpFuncGroup));
122
0
    if (oprform &&
123
0
      (!procform ||
124
0
       (oprform->amoplefttype < procform->amproclefttype ||
125
0
        (oprform->amoplefttype == procform->amproclefttype &&
126
0
         oprform->amoprighttype < procform->amprocrighttype))))
127
0
    {
128
0
      thisgroup->lefttype = oprform->amoplefttype;
129
0
      thisgroup->righttype = oprform->amoprighttype;
130
0
    }
131
0
    else
132
0
    {
133
0
      thisgroup->lefttype = procform->amproclefttype;
134
0
      thisgroup->righttype = procform->amprocrighttype;
135
0
    }
136
0
    thisgroup->operatorset = thisgroup->functionset = 0;
137
0
    result = lappend(result, thisgroup);
138
0
  }
139
140
0
  return result;
141
0
}
142
143
/*
144
 * Validate the signature (argument and result types) of an opclass support
145
 * function.  Return true if OK, false if not.
146
 *
147
 * The "..." represents maxargs argument-type OIDs.  If "exact" is true, they
148
 * must match the function arg types exactly, else only binary-coercibly.
149
 * In any case the function result type must match restype exactly.
150
 */
151
bool
152
check_amproc_signature(Oid funcid, Oid restype, bool exact,
153
             int minargs, int maxargs,...)
154
0
{
155
0
  bool    result = true;
156
0
  HeapTuple tp;
157
0
  Form_pg_proc procform;
158
0
  va_list   ap;
159
0
  int     i;
160
161
0
  tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(funcid));
162
0
  if (!HeapTupleIsValid(tp))
163
0
    elog(ERROR, "cache lookup failed for function %u", funcid);
164
0
  procform = (Form_pg_proc) GETSTRUCT(tp);
165
166
0
  if (procform->prorettype != restype || procform->proretset ||
167
0
    procform->pronargs < minargs || procform->pronargs > maxargs)
168
0
    result = false;
169
170
0
  va_start(ap, maxargs);
171
0
  for (i = 0; i < maxargs; i++)
172
0
  {
173
0
    Oid     argtype = va_arg(ap, Oid);
174
175
0
    if (i >= procform->pronargs)
176
0
      continue;
177
0
    if (exact ? (argtype != procform->proargtypes.values[i]) :
178
0
      !IsBinaryCoercible(argtype, procform->proargtypes.values[i]))
179
0
      result = false;
180
0
  }
181
0
  va_end(ap);
182
183
0
  ReleaseSysCache(tp);
184
0
  return result;
185
0
}
186
187
/*
188
 * Validate the signature of an opclass options support function, that should
189
 * be 'void(internal)'.
190
 */
191
bool
192
check_amoptsproc_signature(Oid funcid)
193
0
{
194
0
  return check_amproc_signature(funcid, VOIDOID, true, 1, 1, INTERNALOID);
195
0
}
196
197
/*
198
 * Validate the signature (argument and result types) of an opclass operator.
199
 * Return true if OK, false if not.
200
 *
201
 * Currently, we can hard-wire this as accepting only binary operators.  Also,
202
 * we can insist on exact type matches, since the given lefttype/righttype
203
 * come from pg_amop and should always match the operator exactly.
204
 */
205
bool
206
check_amop_signature(Oid opno, Oid restype, Oid lefttype, Oid righttype)
207
0
{
208
0
  bool    result = true;
209
0
  HeapTuple tp;
210
0
  Form_pg_operator opform;
211
212
0
  tp = SearchSysCache1(OPEROID, ObjectIdGetDatum(opno));
213
0
  if (!HeapTupleIsValid(tp)) /* shouldn't happen */
214
0
    elog(ERROR, "cache lookup failed for operator %u", opno);
215
0
  opform = (Form_pg_operator) GETSTRUCT(tp);
216
217
0
  if (opform->oprresult != restype || opform->oprkind != 'b' ||
218
0
    opform->oprleft != lefttype || opform->oprright != righttype)
219
0
    result = false;
220
221
0
  ReleaseSysCache(tp);
222
0
  return result;
223
0
}
224
225
/*
226
 * Get the OID of the opclass belonging to an opfamily and accepting
227
 * the specified type as input type.  Returns InvalidOid if no such opclass.
228
 *
229
 * If there is more than one such opclass, you get a random one of them.
230
 * Since that shouldn't happen, we don't waste cycles checking.
231
 *
232
 * We could look up the AM's OID from the opfamily, but all existing callers
233
 * know that or can get it without an extra lookup, so we make them pass it.
234
 */
235
Oid
236
opclass_for_family_datatype(Oid amoid, Oid opfamilyoid, Oid datatypeoid)
237
0
{
238
0
  Oid     result = InvalidOid;
239
0
  CatCList   *opclist;
240
0
  int     i;
241
242
  /*
243
   * We search through all the AM's opclasses to see if one matches.  This
244
   * is a bit inefficient but there is no better index available.  It also
245
   * saves making an explicit check that the opfamily belongs to the AM.
246
   */
247
0
  opclist = SearchSysCacheList1(CLAAMNAMENSP, ObjectIdGetDatum(amoid));
248
249
0
  for (i = 0; i < opclist->n_members; i++)
250
0
  {
251
0
    HeapTuple classtup = &opclist->members[i]->tuple;
252
0
    Form_pg_opclass classform = (Form_pg_opclass) GETSTRUCT(classtup);
253
254
0
    if (classform->opcfamily == opfamilyoid &&
255
0
      classform->opcintype == datatypeoid)
256
0
    {
257
0
      result = classform->oid;
258
0
      break;
259
0
    }
260
0
  }
261
262
0
  ReleaseCatCacheList(opclist);
263
264
0
  return result;
265
0
}
266
267
/*
268
 * Is the datatype a legitimate input type for the btree opfamily?
269
 */
270
bool
271
opfamily_can_sort_type(Oid opfamilyoid, Oid datatypeoid)
272
0
{
273
0
  return OidIsValid(opclass_for_family_datatype(BTREE_AM_OID,
274
0
                          opfamilyoid,
275
0
                          datatypeoid));
276
0
}