Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * hashvalidate.c
4 : * Opclass validator for hash.
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/hash/hashvalidate.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 : #include "postgres.h"
15 :
16 : #include "access/amvalidate.h"
17 : #include "access/hash.h"
18 : #include "access/htup_details.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 hash 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 280 : hashvalidate(Oid opclassoid)
41 : {
42 280 : 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 280 : List *hashabletypes = NIL;
54 : int i;
55 : ListCell *lc;
56 :
57 : /* Fetch opclass information */
58 280 : classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid));
59 280 : if (!HeapTupleIsValid(classtup))
60 0 : elog(ERROR, "cache lookup failed for operator class %u", opclassoid);
61 280 : classform = (Form_pg_opclass) GETSTRUCT(classtup);
62 :
63 280 : opfamilyoid = classform->opcfamily;
64 280 : opcintype = classform->opcintype;
65 280 : opclassname = NameStr(classform->opcname);
66 :
67 : /* Fetch opfamily information */
68 280 : opfamilyname = get_opfamily_name(opfamilyoid, false);
69 :
70 : /* Fetch all operators and support functions of the opfamily */
71 280 : oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid));
72 280 : proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid));
73 :
74 : /* Check individual support functions */
75 1056 : for (i = 0; i < proclist->n_members; i++)
76 : {
77 776 : HeapTuple proctup = &proclist->members[i]->tuple;
78 776 : Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup);
79 : bool ok;
80 :
81 : /*
82 : * All hash functions should be registered with matching left/right
83 : * types
84 : */
85 776 : if (procform->amproclefttype != procform->amprocrighttype)
86 : {
87 0 : ereport(INFO,
88 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
89 : errmsg("operator family \"%s\" of access method %s contains support function %s with different left and right input types",
90 : opfamilyname, "hash",
91 : format_procedure(procform->amproc))));
92 0 : result = false;
93 : }
94 :
95 : /* Check procedure numbers and function signatures */
96 776 : switch (procform->amprocnum)
97 : {
98 446 : case HASHSTANDARD_PROC:
99 446 : ok = check_amproc_signature(procform->amproc, INT4OID, true,
100 : 1, 1, procform->amproclefttype);
101 446 : break;
102 330 : case HASHEXTENDED_PROC:
103 330 : ok = check_amproc_signature(procform->amproc, INT8OID, true,
104 : 2, 2, procform->amproclefttype, INT8OID);
105 330 : break;
106 0 : case HASHOPTIONS_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, "hash",
114 : format_procedure(procform->amproc),
115 : procform->amprocnum)));
116 0 : result = false;
117 0 : continue; /* don't want additional message */
118 : }
119 :
120 776 : 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, "hash",
126 : format_procedure(procform->amproc),
127 : procform->amprocnum)));
128 0 : result = false;
129 : }
130 :
131 : /* Remember which types we can hash */
132 776 : if (ok && (procform->amprocnum == HASHSTANDARD_PROC || procform->amprocnum == HASHEXTENDED_PROC))
133 : {
134 776 : hashabletypes = list_append_unique_oid(hashabletypes, procform->amproclefttype);
135 : }
136 : }
137 :
138 : /* Check individual operators */
139 1226 : for (i = 0; i < oprlist->n_members; i++)
140 : {
141 946 : HeapTuple oprtup = &oprlist->members[i]->tuple;
142 946 : Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
143 :
144 : /* Check that only allowed strategy numbers exist */
145 946 : if (oprform->amopstrategy < 1 ||
146 946 : oprform->amopstrategy > HTMaxStrategyNumber)
147 : {
148 0 : ereport(INFO,
149 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
150 : errmsg("operator family \"%s\" of access method %s contains operator %s with invalid strategy number %d",
151 : opfamilyname, "hash",
152 : format_operator(oprform->amopopr),
153 : oprform->amopstrategy)));
154 0 : result = false;
155 : }
156 :
157 : /* hash doesn't support ORDER BY operators */
158 946 : if (oprform->amoppurpose != AMOP_SEARCH ||
159 946 : OidIsValid(oprform->amopsortfamily))
160 : {
161 0 : ereport(INFO,
162 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
163 : errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
164 : opfamilyname, "hash",
165 : format_operator(oprform->amopopr))));
166 0 : result = false;
167 : }
168 :
169 : /* Check operator signature --- same for all hash strategies */
170 946 : if (!check_amop_signature(oprform->amopopr, BOOLOID,
171 : oprform->amoplefttype,
172 : oprform->amoprighttype))
173 : {
174 0 : ereport(INFO,
175 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
176 : errmsg("operator family \"%s\" of access method %s contains operator %s with wrong signature",
177 : opfamilyname, "hash",
178 : format_operator(oprform->amopopr))));
179 0 : result = false;
180 : }
181 :
182 : /* There should be relevant hash functions for each datatype */
183 946 : if (!list_member_oid(hashabletypes, oprform->amoplefttype) ||
184 946 : !list_member_oid(hashabletypes, oprform->amoprighttype))
185 : {
186 0 : ereport(INFO,
187 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
188 : errmsg("operator family \"%s\" of access method %s lacks support function for operator %s",
189 : opfamilyname, "hash",
190 : format_operator(oprform->amopopr))));
191 0 : result = false;
192 : }
193 : }
194 :
195 : /* Now check for inconsistent groups of operators/functions */
196 280 : grouplist = identify_opfamily_groups(oprlist, proclist);
197 280 : opclassgroup = NULL;
198 1226 : foreach(lc, grouplist)
199 : {
200 946 : OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc);
201 :
202 : /* Remember the group exactly matching the test opclass */
203 946 : if (thisgroup->lefttype == opcintype &&
204 386 : thisgroup->righttype == opcintype)
205 280 : opclassgroup = thisgroup;
206 :
207 : /*
208 : * Complain if there seems to be an incomplete set of operators for
209 : * this datatype pair (implying that we have a hash function but no
210 : * operator).
211 : */
212 946 : if (thisgroup->operatorset != (1 << HTEqualStrategyNumber))
213 : {
214 0 : ereport(INFO,
215 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
216 : errmsg("operator family \"%s\" of access method %s is missing operator(s) for types %s and %s",
217 : opfamilyname, "hash",
218 : format_type_be(thisgroup->lefttype),
219 : format_type_be(thisgroup->righttype))));
220 0 : result = false;
221 : }
222 : }
223 :
224 : /* Check that the originally-named opclass is supported */
225 : /* (if group is there, we already checked it adequately above) */
226 280 : if (!opclassgroup)
227 : {
228 0 : ereport(INFO,
229 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
230 : errmsg("operator class \"%s\" of access method %s is missing operator(s)",
231 : opclassname, "hash")));
232 0 : result = false;
233 : }
234 :
235 : /*
236 : * Complain if the opfamily doesn't have entries for all possible
237 : * combinations of its supported datatypes. While missing cross-type
238 : * operators are not fatal, it seems reasonable to insist that all
239 : * built-in hash opfamilies be complete.
240 : */
241 280 : if (list_length(grouplist) !=
242 280 : list_length(hashabletypes) * list_length(hashabletypes))
243 : {
244 16 : ereport(INFO,
245 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
246 : errmsg("operator family \"%s\" of access method %s is missing cross-type operator(s)",
247 : opfamilyname, "hash")));
248 16 : result = false;
249 : }
250 :
251 280 : ReleaseCatCacheList(proclist);
252 280 : ReleaseCatCacheList(oprlist);
253 280 : ReleaseSysCache(classtup);
254 :
255 280 : return result;
256 : }
257 :
258 :
259 : /*
260 : * Prechecking function for adding operators/functions to a hash opfamily.
261 : */
262 : void
263 156 : hashadjustmembers(Oid opfamilyoid,
264 : Oid opclassoid,
265 : List *operators,
266 : List *functions)
267 : {
268 : Oid opcintype;
269 : ListCell *lc;
270 :
271 : /*
272 : * Hash operators and required support functions are always "loose"
273 : * members of the opfamily if they are cross-type. If they are not
274 : * cross-type, we prefer to tie them to the appropriate opclass ... but if
275 : * the user hasn't created one, we can't do that, and must fall back to
276 : * using the opfamily dependency. (We mustn't force creation of an
277 : * opclass in such a case, as leaving an incomplete opclass laying about
278 : * would be bad. Throwing an error is another undesirable alternative.)
279 : *
280 : * This behavior results in a bit of a dump/reload hazard, in that the
281 : * order of restoring objects could affect what dependencies we end up
282 : * with. pg_dump's existing behavior will preserve the dependency choices
283 : * in most cases, but not if a cross-type operator has been bound tightly
284 : * into an opclass. That's a mistake anyway, so silently "fixing" it
285 : * isn't awful.
286 : *
287 : * Optional support functions are always "loose" family members.
288 : *
289 : * To avoid repeated lookups, we remember the most recently used opclass's
290 : * input type.
291 : */
292 156 : if (OidIsValid(opclassoid))
293 : {
294 : /* During CREATE OPERATOR CLASS, need CCI to see the pg_opclass row */
295 104 : CommandCounterIncrement();
296 104 : opcintype = get_opclass_input_type(opclassoid);
297 : }
298 : else
299 52 : opcintype = InvalidOid;
300 :
301 : /*
302 : * We handle operators and support functions almost identically, so rather
303 : * than duplicate this code block, just join the lists.
304 : */
305 384 : foreach(lc, list_concat_copy(operators, functions))
306 : {
307 228 : OpFamilyMember *op = (OpFamilyMember *) lfirst(lc);
308 :
309 228 : if (op->is_func && op->number != HASHSTANDARD_PROC)
310 : {
311 : /* Optional support proc, so always a soft family dependency */
312 52 : op->ref_is_hard = false;
313 52 : op->ref_is_family = true;
314 52 : op->refobjid = opfamilyoid;
315 : }
316 176 : else if (op->lefttype != op->righttype)
317 : {
318 : /* Cross-type, so always a soft family dependency */
319 46 : op->ref_is_hard = false;
320 46 : op->ref_is_family = true;
321 46 : op->refobjid = opfamilyoid;
322 : }
323 : else
324 : {
325 : /* Not cross-type; is there a suitable opclass? */
326 130 : if (op->lefttype != opcintype)
327 : {
328 : /* Avoid repeating this expensive lookup, even if it fails */
329 6 : opcintype = op->lefttype;
330 6 : opclassoid = opclass_for_family_datatype(HASH_AM_OID,
331 : opfamilyoid,
332 : opcintype);
333 : }
334 130 : if (OidIsValid(opclassoid))
335 : {
336 : /* Hard dependency on opclass */
337 124 : op->ref_is_hard = true;
338 124 : op->ref_is_family = false;
339 124 : op->refobjid = opclassoid;
340 : }
341 : else
342 : {
343 : /* We're stuck, so make a soft dependency on the opfamily */
344 6 : op->ref_is_hard = false;
345 6 : op->ref_is_family = true;
346 6 : op->refobjid = opfamilyoid;
347 : }
348 : }
349 : }
350 156 : }
|