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