Age Owner Branch data TLA Line data Source code
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
3520 tgl@sss.pgh.pa.us 40 :CBC 146 : btvalidate(Oid opclassoid)
41 : : {
3516 42 : 146 : bool result = true;
43 : : HeapTuple classtup;
44 : : Form_pg_opclass classform;
45 : : Oid opfamilyoid;
46 : : Oid opcintype;
47 : : char *opclassname;
48 : : char *opfamilyname;
49 : : CatCList *proclist,
50 : : *oprlist;
51 : : List *grouplist;
52 : : OpFamilyOpFuncGroup *opclassgroup;
53 : : List *familytypes;
54 : : int usefulgroups;
55 : : int i;
56 : : ListCell *lc;
57 : :
58 : : /* Fetch opclass information */
3520 59 : 146 : classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid));
60 [ - + ]: 146 : if (!HeapTupleIsValid(classtup))
3520 tgl@sss.pgh.pa.us 61 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for operator class %u", opclassoid);
3520 tgl@sss.pgh.pa.us 62 :CBC 146 : classform = (Form_pg_opclass) GETSTRUCT(classtup);
63 : :
64 : 146 : opfamilyoid = classform->opcfamily;
65 : 146 : opcintype = classform->opcintype;
3516 66 : 146 : opclassname = NameStr(classform->opcname);
67 : :
68 : : /* Fetch opfamily information */
225 peter@eisentraut.org 69 : 146 : opfamilyname = get_opfamily_name(opfamilyoid, false);
70 : :
71 : : /* Fetch all operators and support functions of the opfamily */
3520 tgl@sss.pgh.pa.us 72 : 146 : oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid));
73 : 146 : proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid));
74 : :
75 : : /* Check individual support functions */
76 [ + + ]: 1138 : for (i = 0; i < proclist->n_members; i++)
77 : : {
78 : 992 : HeapTuple proctup = &proclist->members[i]->tuple;
79 : 992 : Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup);
80 : : bool ok;
81 : :
82 : : /* Check procedure numbers and function signatures */
3516 83 [ + + + + : 992 : switch (procform->amprocnum)
- + - ]
84 : : {
85 : 551 : case BTORDER_PROC:
86 : 551 : ok = check_amproc_signature(procform->amproc, INT4OID, true,
87 : : 2, 2, procform->amproclefttype,
88 : : procform->amprocrighttype);
89 : 551 : break;
90 : 120 : case BTSORTSUPPORT_PROC:
91 : 120 : ok = check_amproc_signature(procform->amproc, VOIDOID, true,
92 : : 1, 1, INTERNALOID);
93 : 120 : break;
2768 94 : 114 : case BTINRANGE_PROC:
95 : 114 : ok = check_amproc_signature(procform->amproc, BOOLOID, true,
96 : : 5, 5,
97 : : procform->amproclefttype,
98 : : procform->amproclefttype,
99 : : procform->amprocrighttype,
100 : : BOOLOID, BOOLOID);
101 : 114 : break;
2019 pg@bowt.ie 102 : 141 : case BTEQUALIMAGE_PROC:
103 : 141 : ok = check_amproc_signature(procform->amproc, BOOLOID, true,
104 : : 1, 1, OIDOID);
105 : 141 : break;
1986 akorotkov@postgresql 106 :UBC 0 : case BTOPTIONS_PROC:
107 : 0 : ok = check_amoptsproc_signature(procform->amproc);
108 : 0 : break;
155 pg@bowt.ie 109 :CBC 66 : case BTSKIPSUPPORT_PROC:
110 : 66 : ok = check_amproc_signature(procform->amproc, VOIDOID, true,
111 : : 1, 1, INTERNALOID);
112 : 66 : break;
3516 tgl@sss.pgh.pa.us 113 :UBC 0 : default:
114 [ # # ]: 0 : ereport(INFO,
115 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
116 : : errmsg("operator family \"%s\" of access method %s contains function %s with invalid support number %d",
117 : : opfamilyname, "btree",
118 : : format_procedure(procform->amproc),
119 : : procform->amprocnum)));
120 : 0 : result = false;
121 : 0 : continue; /* don't want additional message */
122 : : }
123 : :
3516 tgl@sss.pgh.pa.us 124 [ - + ]:CBC 992 : if (!ok)
125 : : {
3516 tgl@sss.pgh.pa.us 126 [ # # ]:UBC 0 : ereport(INFO,
127 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
128 : : errmsg("operator family \"%s\" of access method %s contains function %s with wrong signature for support number %d",
129 : : opfamilyname, "btree",
130 : : format_procedure(procform->amproc),
131 : : procform->amprocnum)));
132 : 0 : result = false;
133 : : }
134 : : }
135 : :
136 : : /* Check individual operators */
3520 tgl@sss.pgh.pa.us 137 [ + + ]:CBC 2901 : for (i = 0; i < oprlist->n_members; i++)
138 : : {
139 : 2755 : HeapTuple oprtup = &oprlist->members[i]->tuple;
140 : 2755 : Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
141 : :
142 : : /* Check that only allowed strategy numbers exist */
143 [ + - ]: 2755 : if (oprform->amopstrategy < 1 ||
144 [ - + ]: 2755 : oprform->amopstrategy > BTMaxStrategyNumber)
145 : : {
3516 tgl@sss.pgh.pa.us 146 [ # # ]:UBC 0 : ereport(INFO,
147 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
148 : : errmsg("operator family \"%s\" of access method %s contains operator %s with invalid strategy number %d",
149 : : opfamilyname, "btree",
150 : : format_operator(oprform->amopopr),
151 : : oprform->amopstrategy)));
152 : 0 : result = false;
153 : : }
154 : :
155 : : /* btree doesn't support ORDER BY operators */
3516 tgl@sss.pgh.pa.us 156 [ + - ]:CBC 2755 : if (oprform->amoppurpose != AMOP_SEARCH ||
157 [ - + ]: 2755 : OidIsValid(oprform->amopsortfamily))
158 : : {
3516 tgl@sss.pgh.pa.us 159 [ # # ]:UBC 0 : ereport(INFO,
160 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
161 : : errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
162 : : opfamilyname, "btree",
163 : : format_operator(oprform->amopopr))));
164 : 0 : result = false;
165 : : }
166 : :
167 : : /* Check operator signature --- same for all btree strategies */
3516 tgl@sss.pgh.pa.us 168 [ - + ]:CBC 2755 : if (!check_amop_signature(oprform->amopopr, BOOLOID,
169 : : oprform->amoplefttype,
170 : : oprform->amoprighttype))
171 : : {
3516 tgl@sss.pgh.pa.us 172 [ # # ]:UBC 0 : ereport(INFO,
173 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
174 : : errmsg("operator family \"%s\" of access method %s contains operator %s with wrong signature",
175 : : opfamilyname, "btree",
176 : : format_operator(oprform->amopopr))));
177 : 0 : result = false;
178 : : }
179 : : }
180 : :
181 : : /* Now check for inconsistent groups of operators/functions */
3516 tgl@sss.pgh.pa.us 182 :CBC 146 : grouplist = identify_opfamily_groups(oprlist, proclist);
2768 183 : 146 : usefulgroups = 0;
3516 184 : 146 : opclassgroup = NULL;
185 : 146 : familytypes = NIL;
186 [ + - + + : 730 : foreach(lc, grouplist)
+ + ]
187 : : {
188 : 584 : 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 : : */
2768 198 [ + + ]: 584 : if (thisgroup->operatorset == 0 &&
199 [ + - ]: 33 : thisgroup->functionset == (1 << BTINRANGE_PROC))
200 : 33 : continue;
201 : :
202 : : /* Else count it as a relevant group */
203 : 551 : usefulgroups++;
204 : :
205 : : /* Remember the group exactly matching the test opclass */
3516 206 [ + + ]: 551 : if (thisgroup->lefttype == opcintype &&
207 [ + + ]: 217 : thisgroup->righttype == opcintype)
208 : 146 : 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 : 551 : familytypes = list_append_unique_oid(familytypes, thisgroup->lefttype);
216 : 551 : 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 [ - + ]: 551 : if (thisgroup->operatorset !=
224 : : ((1 << BTLessStrategyNumber) |
225 : : (1 << BTLessEqualStrategyNumber) |
226 : : (1 << BTEqualStrategyNumber) |
227 : : (1 << BTGreaterEqualStrategyNumber) |
228 : : (1 << BTGreaterStrategyNumber)))
229 : : {
3516 tgl@sss.pgh.pa.us 230 [ # # ]:UBC 0 : ereport(INFO,
231 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
232 : : errmsg("operator family \"%s\" of access method %s is missing operator(s) for types %s and %s",
233 : : opfamilyname, "btree",
234 : : format_type_be(thisgroup->lefttype),
235 : : format_type_be(thisgroup->righttype))));
236 : 0 : result = false;
237 : : }
3516 tgl@sss.pgh.pa.us 238 [ - + ]:CBC 551 : if ((thisgroup->functionset & (1 << BTORDER_PROC)) == 0)
239 : : {
3516 tgl@sss.pgh.pa.us 240 [ # # ]:UBC 0 : ereport(INFO,
241 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
242 : : errmsg("operator family \"%s\" of access method %s is missing support function for types %s and %s",
243 : : opfamilyname, "btree",
244 : : format_type_be(thisgroup->lefttype),
245 : : format_type_be(thisgroup->righttype))));
246 : 0 : result = false;
247 : : }
248 : : }
249 : :
250 : : /* Check that the originally-named opclass is supported */
251 : : /* (if group is there, we already checked it adequately above) */
3516 tgl@sss.pgh.pa.us 252 [ - + ]:CBC 146 : if (!opclassgroup)
253 : : {
3516 tgl@sss.pgh.pa.us 254 [ # # ]:UBC 0 : ereport(INFO,
255 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
256 : : errmsg("operator class \"%s\" of access method %s is missing operator(s)",
257 : : opclassname, "btree")));
258 : 0 : result = false;
259 : : }
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 : : */
2768 tgl@sss.pgh.pa.us 268 [ + + ]:CBC 146 : if (usefulgroups != (list_length(familytypes) * list_length(familytypes)))
269 : : {
3516 270 [ + - ]: 8 : ereport(INFO,
271 : : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
272 : : errmsg("operator family \"%s\" of access method %s is missing cross-type operator(s)",
273 : : opfamilyname, "btree")));
274 : 8 : result = false;
275 : : }
276 : :
3520 277 : 146 : ReleaseCatCacheList(proclist);
278 : 146 : ReleaseCatCacheList(oprlist);
3516 279 : 146 : ReleaseSysCache(classtup);
280 : :
281 : 146 : return result;
282 : : }
283 : :
284 : : /*
285 : : * Prechecking function for adding operators/functions to a btree opfamily.
286 : : */
287 : : void
1862 288 : 104 : btadjustmembers(Oid opfamilyoid,
289 : : Oid opclassoid,
290 : : List *operators,
291 : : List *functions)
292 : : {
293 : : Oid opcintype;
294 : : 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 [ + + ]: 104 : if (OidIsValid(opclassoid))
318 : : {
319 : : /* During CREATE OPERATOR CLASS, need CCI to see the pg_opclass row */
320 : 49 : CommandCounterIncrement();
321 : 49 : opcintype = get_opclass_input_type(opclassoid);
322 : : }
323 : : else
324 : 55 : 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 [ + + + + : 597 : foreach(lc, list_concat_copy(operators, functions))
+ + ]
331 : : {
332 : 493 : OpFamilyMember *op = (OpFamilyMember *) lfirst(lc);
333 : :
334 [ + + + + ]: 493 : if (op->is_func && op->number != BTORDER_PROC)
335 : : {
336 : : /* Optional support proc, so always a soft family dependency */
337 : 7 : op->ref_is_hard = false;
338 : 7 : op->ref_is_family = true;
339 : 7 : op->refobjid = opfamilyoid;
340 : : }
341 [ + + ]: 486 : else if (op->lefttype != op->righttype)
342 : : {
343 : : /* Cross-type, so always a soft family dependency */
344 : 209 : op->ref_is_hard = false;
345 : 209 : op->ref_is_family = true;
346 : 209 : op->refobjid = opfamilyoid;
347 : : }
348 : : else
349 : : {
350 : : /* Not cross-type; is there a suitable opclass? */
351 [ + + ]: 277 : if (op->lefttype != opcintype)
352 : : {
353 : : /* Avoid repeating this expensive lookup, even if it fails */
354 : 20 : opcintype = op->lefttype;
355 : 20 : opclassoid = opclass_for_family_datatype(BTREE_AM_OID,
356 : : opfamilyoid,
357 : : opcintype);
358 : : }
359 [ + + ]: 277 : if (OidIsValid(opclassoid))
360 : : {
361 : : /* Hard dependency on opclass */
362 : 257 : op->ref_is_hard = true;
363 : 257 : op->ref_is_family = false;
364 : 257 : op->refobjid = opclassoid;
365 : : }
366 : : else
367 : : {
368 : : /* We're stuck, so make a soft dependency on the opfamily */
369 : 20 : op->ref_is_hard = false;
370 : 20 : op->ref_is_family = true;
371 : 20 : op->refobjid = opfamilyoid;
372 : : }
373 : : }
374 : : }
375 : 104 : }
|