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