Coverage Report

Created: 2025-08-12 06:43

/src/postgres/src/backend/access/nbtree/nbtvalidate.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * nbtvalidate.c
4
 *    Opclass validator for btree.
5
 *
6
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
7
 * Portions Copyright (c) 1994, Regents of the University of California
8
 *
9
 * IDENTIFICATION
10
 *    src/backend/access/nbtree/nbtvalidate.c
11
 *
12
 *-------------------------------------------------------------------------
13
 */
14
#include "postgres.h"
15
16
#include "access/amvalidate.h"
17
#include "access/htup_details.h"
18
#include "access/nbtree.h"
19
#include "access/xact.h"
20
#include "catalog/pg_am.h"
21
#include "catalog/pg_amop.h"
22
#include "catalog/pg_amproc.h"
23
#include "catalog/pg_opclass.h"
24
#include "catalog/pg_type.h"
25
#include "utils/builtins.h"
26
#include "utils/lsyscache.h"
27
#include "utils/regproc.h"
28
#include "utils/syscache.h"
29
30
31
/*
32
 * Validator for a btree opclass.
33
 *
34
 * Some of the checks done here cover the whole opfamily, and therefore are
35
 * redundant when checking each opclass in a family.  But they don't run long
36
 * enough to be much of a problem, so we accept the duplication rather than
37
 * complicate the amvalidate API.
38
 */
39
bool
40
btvalidate(Oid opclassoid)
41
0
{
42
0
  bool    result = true;
43
0
  HeapTuple classtup;
44
0
  Form_pg_opclass classform;
45
0
  Oid     opfamilyoid;
46
0
  Oid     opcintype;
47
0
  char     *opclassname;
48
0
  char     *opfamilyname;
49
0
  CatCList   *proclist,
50
0
         *oprlist;
51
0
  List     *grouplist;
52
0
  OpFamilyOpFuncGroup *opclassgroup;
53
0
  List     *familytypes;
54
0
  int     usefulgroups;
55
0
  int     i;
56
0
  ListCell   *lc;
57
58
  /* Fetch opclass information */
59
0
  classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid));
60
0
  if (!HeapTupleIsValid(classtup))
61
0
    elog(ERROR, "cache lookup failed for operator class %u", opclassoid);
62
0
  classform = (Form_pg_opclass) GETSTRUCT(classtup);
63
64
0
  opfamilyoid = classform->opcfamily;
65
0
  opcintype = classform->opcintype;
66
0
  opclassname = NameStr(classform->opcname);
67
68
  /* Fetch opfamily information */
69
0
  opfamilyname = get_opfamily_name(opfamilyoid, false);
70
71
  /* Fetch all operators and support functions of the opfamily */
72
0
  oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid));
73
0
  proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid));
74
75
  /* Check individual support functions */
76
0
  for (i = 0; i < proclist->n_members; i++)
77
0
  {
78
0
    HeapTuple proctup = &proclist->members[i]->tuple;
79
0
    Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup);
80
0
    bool    ok;
81
82
    /* Check procedure numbers and function signatures */
83
0
    switch (procform->amprocnum)
84
0
    {
85
0
      case BTORDER_PROC:
86
0
        ok = check_amproc_signature(procform->amproc, INT4OID, true,
87
0
                      2, 2, procform->amproclefttype,
88
0
                      procform->amprocrighttype);
89
0
        break;
90
0
      case BTSORTSUPPORT_PROC:
91
0
        ok = check_amproc_signature(procform->amproc, VOIDOID, true,
92
0
                      1, 1, INTERNALOID);
93
0
        break;
94
0
      case BTINRANGE_PROC:
95
0
        ok = check_amproc_signature(procform->amproc, BOOLOID, true,
96
0
                      5, 5,
97
0
                      procform->amproclefttype,
98
0
                      procform->amproclefttype,
99
0
                      procform->amprocrighttype,
100
0
                      BOOLOID, BOOLOID);
101
0
        break;
102
0
      case BTEQUALIMAGE_PROC:
103
0
        ok = check_amproc_signature(procform->amproc, BOOLOID, true,
104
0
                      1, 1, OIDOID);
105
0
        break;
106
0
      case BTOPTIONS_PROC:
107
0
        ok = check_amoptsproc_signature(procform->amproc);
108
0
        break;
109
0
      case BTSKIPSUPPORT_PROC:
110
0
        ok = check_amproc_signature(procform->amproc, VOIDOID, true,
111
0
                      1, 1, INTERNALOID);
112
0
        break;
113
0
      default:
114
0
        ereport(INFO,
115
0
            (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
116
0
             errmsg("operator family \"%s\" of access method %s contains function %s with invalid support number %d",
117
0
                opfamilyname, "btree",
118
0
                format_procedure(procform->amproc),
119
0
                procform->amprocnum)));
120
0
        result = false;
121
0
        continue;   /* don't want additional message */
122
0
    }
123
124
0
    if (!ok)
125
0
    {
126
0
      ereport(INFO,
127
0
          (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
128
0
           errmsg("operator family \"%s\" of access method %s contains function %s with wrong signature for support number %d",
129
0
              opfamilyname, "btree",
130
0
              format_procedure(procform->amproc),
131
0
              procform->amprocnum)));
132
0
      result = false;
133
0
    }
134
0
  }
135
136
  /* Check individual operators */
137
0
  for (i = 0; i < oprlist->n_members; i++)
138
0
  {
139
0
    HeapTuple oprtup = &oprlist->members[i]->tuple;
140
0
    Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
141
142
    /* Check that only allowed strategy numbers exist */
143
0
    if (oprform->amopstrategy < 1 ||
144
0
      oprform->amopstrategy > BTMaxStrategyNumber)
145
0
    {
146
0
      ereport(INFO,
147
0
          (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
148
0
           errmsg("operator family \"%s\" of access method %s contains operator %s with invalid strategy number %d",
149
0
              opfamilyname, "btree",
150
0
              format_operator(oprform->amopopr),
151
0
              oprform->amopstrategy)));
152
0
      result = false;
153
0
    }
154
155
    /* btree doesn't support ORDER BY operators */
156
0
    if (oprform->amoppurpose != AMOP_SEARCH ||
157
0
      OidIsValid(oprform->amopsortfamily))
158
0
    {
159
0
      ereport(INFO,
160
0
          (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
161
0
           errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
162
0
              opfamilyname, "btree",
163
0
              format_operator(oprform->amopopr))));
164
0
      result = false;
165
0
    }
166
167
    /* Check operator signature --- same for all btree strategies */
168
0
    if (!check_amop_signature(oprform->amopopr, BOOLOID,
169
0
                  oprform->amoplefttype,
170
0
                  oprform->amoprighttype))
171
0
    {
172
0
      ereport(INFO,
173
0
          (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
174
0
           errmsg("operator family \"%s\" of access method %s contains operator %s with wrong signature",
175
0
              opfamilyname, "btree",
176
0
              format_operator(oprform->amopopr))));
177
0
      result = false;
178
0
    }
179
0
  }
180
181
  /* Now check for inconsistent groups of operators/functions */
182
0
  grouplist = identify_opfamily_groups(oprlist, proclist);
183
0
  usefulgroups = 0;
184
0
  opclassgroup = NULL;
185
0
  familytypes = NIL;
186
0
  foreach(lc, grouplist)
187
0
  {
188
0
    OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc);
189
190
    /*
191
     * It is possible for an in_range support function to have a RHS type
192
     * that is otherwise irrelevant to the opfamily --- for instance, SQL
193
     * requires the datetime_ops opclass to have range support with an
194
     * interval offset.  So, if this group appears to contain only an
195
     * in_range function, ignore it: it doesn't represent a pair of
196
     * supported types.
197
     */
198
0
    if (thisgroup->operatorset == 0 &&
199
0
      thisgroup->functionset == (1 << BTINRANGE_PROC))
200
0
      continue;
201
202
    /* Else count it as a relevant group */
203
0
    usefulgroups++;
204
205
    /* Remember the group exactly matching the test opclass */
206
0
    if (thisgroup->lefttype == opcintype &&
207
0
      thisgroup->righttype == opcintype)
208
0
      opclassgroup = thisgroup;
209
210
    /*
211
     * Identify all distinct data types handled in this opfamily.  This
212
     * implementation is O(N^2), but there aren't likely to be enough
213
     * types in the family for it to matter.
214
     */
215
0
    familytypes = list_append_unique_oid(familytypes, thisgroup->lefttype);
216
0
    familytypes = list_append_unique_oid(familytypes, thisgroup->righttype);
217
218
    /*
219
     * Complain if there seems to be an incomplete set of either operators
220
     * or support functions for this datatype pair.  The sortsupport,
221
     * in_range, and equalimage functions are considered optional.
222
     */
223
0
    if (thisgroup->operatorset !=
224
0
      ((1 << BTLessStrategyNumber) |
225
0
       (1 << BTLessEqualStrategyNumber) |
226
0
       (1 << BTEqualStrategyNumber) |
227
0
       (1 << BTGreaterEqualStrategyNumber) |
228
0
       (1 << BTGreaterStrategyNumber)))
229
0
    {
230
0
      ereport(INFO,
231
0
          (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
232
0
           errmsg("operator family \"%s\" of access method %s is missing operator(s) for types %s and %s",
233
0
              opfamilyname, "btree",
234
0
              format_type_be(thisgroup->lefttype),
235
0
              format_type_be(thisgroup->righttype))));
236
0
      result = false;
237
0
    }
238
0
    if ((thisgroup->functionset & (1 << BTORDER_PROC)) == 0)
239
0
    {
240
0
      ereport(INFO,
241
0
          (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
242
0
           errmsg("operator family \"%s\" of access method %s is missing support function for types %s and %s",
243
0
              opfamilyname, "btree",
244
0
              format_type_be(thisgroup->lefttype),
245
0
              format_type_be(thisgroup->righttype))));
246
0
      result = false;
247
0
    }
248
0
  }
249
250
  /* Check that the originally-named opclass is supported */
251
  /* (if group is there, we already checked it adequately above) */
252
0
  if (!opclassgroup)
253
0
  {
254
0
    ereport(INFO,
255
0
        (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
256
0
         errmsg("operator class \"%s\" of access method %s is missing operator(s)",
257
0
            opclassname, "btree")));
258
0
    result = false;
259
0
  }
260
261
  /*
262
   * Complain if the opfamily doesn't have entries for all possible
263
   * combinations of its supported datatypes.  While missing cross-type
264
   * operators are not fatal, they do limit the planner's ability to derive
265
   * additional qual clauses from equivalence classes, so it seems
266
   * reasonable to insist that all built-in btree opfamilies be complete.
267
   */
268
0
  if (usefulgroups != (list_length(familytypes) * list_length(familytypes)))
269
0
  {
270
0
    ereport(INFO,
271
0
        (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
272
0
         errmsg("operator family \"%s\" of access method %s is missing cross-type operator(s)",
273
0
            opfamilyname, "btree")));
274
0
    result = false;
275
0
  }
276
277
0
  ReleaseCatCacheList(proclist);
278
0
  ReleaseCatCacheList(oprlist);
279
0
  ReleaseSysCache(classtup);
280
281
0
  return result;
282
0
}
283
284
/*
285
 * Prechecking function for adding operators/functions to a btree opfamily.
286
 */
287
void
288
btadjustmembers(Oid opfamilyoid,
289
        Oid opclassoid,
290
        List *operators,
291
        List *functions)
292
0
{
293
0
  Oid     opcintype;
294
0
  ListCell   *lc;
295
296
  /*
297
   * Btree operators and comparison support functions are always "loose"
298
   * members of the opfamily if they are cross-type.  If they are not
299
   * cross-type, we prefer to tie them to the appropriate opclass ... but if
300
   * the user hasn't created one, we can't do that, and must fall back to
301
   * using the opfamily dependency.  (We mustn't force creation of an
302
   * opclass in such a case, as leaving an incomplete opclass laying about
303
   * would be bad.  Throwing an error is another undesirable alternative.)
304
   *
305
   * This behavior results in a bit of a dump/reload hazard, in that the
306
   * order of restoring objects could affect what dependencies we end up
307
   * with.  pg_dump's existing behavior will preserve the dependency choices
308
   * in most cases, but not if a cross-type operator has been bound tightly
309
   * into an opclass.  That's a mistake anyway, so silently "fixing" it
310
   * isn't awful.
311
   *
312
   * Optional support functions are always "loose" family members.
313
   *
314
   * To avoid repeated lookups, we remember the most recently used opclass's
315
   * input type.
316
   */
317
0
  if (OidIsValid(opclassoid))
318
0
  {
319
    /* During CREATE OPERATOR CLASS, need CCI to see the pg_opclass row */
320
0
    CommandCounterIncrement();
321
0
    opcintype = get_opclass_input_type(opclassoid);
322
0
  }
323
0
  else
324
0
    opcintype = InvalidOid;
325
326
  /*
327
   * We handle operators and support functions almost identically, so rather
328
   * than duplicate this code block, just join the lists.
329
   */
330
0
  foreach(lc, list_concat_copy(operators, functions))
331
0
  {
332
0
    OpFamilyMember *op = (OpFamilyMember *) lfirst(lc);
333
334
0
    if (op->is_func && op->number != BTORDER_PROC)
335
0
    {
336
      /* Optional support proc, so always a soft family dependency */
337
0
      op->ref_is_hard = false;
338
0
      op->ref_is_family = true;
339
0
      op->refobjid = opfamilyoid;
340
0
    }
341
0
    else if (op->lefttype != op->righttype)
342
0
    {
343
      /* Cross-type, so always a soft family dependency */
344
0
      op->ref_is_hard = false;
345
0
      op->ref_is_family = true;
346
0
      op->refobjid = opfamilyoid;
347
0
    }
348
0
    else
349
0
    {
350
      /* Not cross-type; is there a suitable opclass? */
351
0
      if (op->lefttype != opcintype)
352
0
      {
353
        /* Avoid repeating this expensive lookup, even if it fails */
354
0
        opcintype = op->lefttype;
355
0
        opclassoid = opclass_for_family_datatype(BTREE_AM_OID,
356
0
                             opfamilyoid,
357
0
                             opcintype);
358
0
      }
359
0
      if (OidIsValid(opclassoid))
360
0
      {
361
        /* Hard dependency on opclass */
362
0
        op->ref_is_hard = true;
363
0
        op->ref_is_family = false;
364
0
        op->refobjid = opclassoid;
365
0
      }
366
0
      else
367
0
      {
368
        /* We're stuck, so make a soft dependency on the opfamily */
369
0
        op->ref_is_hard = false;
370
0
        op->ref_is_family = true;
371
0
        op->refobjid = opfamilyoid;
372
0
      }
373
0
    }
374
0
  }
375
0
}