Age Owner Branch data TLA 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
3520 tgl@sss.pgh.pa.us 40 :CBC 140 : hashvalidate(Oid opclassoid)
41 : : {
3516 42 : 140 : 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 : 140 : List *hashabletypes = NIL;
54 : : int i;
55 : : ListCell *lc;
56 : :
57 : : /* Fetch opclass information */
3520 58 : 140 : classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid));
59 [ - + ]: 140 : if (!HeapTupleIsValid(classtup))
3520 tgl@sss.pgh.pa.us 60 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for operator class %u", opclassoid);
3520 tgl@sss.pgh.pa.us 61 :CBC 140 : classform = (Form_pg_opclass) GETSTRUCT(classtup);
62 : :
63 : 140 : opfamilyoid = classform->opcfamily;
64 : 140 : opcintype = classform->opcintype;
3516 65 : 140 : opclassname = NameStr(classform->opcname);
66 : :
67 : : /* Fetch opfamily information */
225 peter@eisentraut.org 68 : 140 : opfamilyname = get_opfamily_name(opfamilyoid, false);
69 : :
70 : : /* Fetch all operators and support functions of the opfamily */
3520 tgl@sss.pgh.pa.us 71 : 140 : oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid));
72 : 140 : proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid));
73 : :
74 : : /* Check individual support functions */
75 [ + + ]: 528 : for (i = 0; i < proclist->n_members; i++)
76 : : {
77 : 388 : HeapTuple proctup = &proclist->members[i]->tuple;
78 : 388 : 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 : : */
3516 85 [ - + ]: 388 : if (procform->amproclefttype != procform->amprocrighttype)
86 : : {
3516 tgl@sss.pgh.pa.us 87 [ # # ]:UBC 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 */
3516 tgl@sss.pgh.pa.us 96 [ + + - - ]:CBC 388 : switch (procform->amprocnum)
97 : : {
2928 rhaas@postgresql.org 98 : 223 : case HASHSTANDARD_PROC:
359 peter@eisentraut.org 99 : 223 : ok = check_amproc_signature(procform->amproc, INT4OID, true,
100 : : 1, 1, procform->amproclefttype);
101 : 223 : break;
2928 rhaas@postgresql.org 102 : 165 : case HASHEXTENDED_PROC:
359 peter@eisentraut.org 103 : 165 : ok = check_amproc_signature(procform->amproc, INT8OID, true,
104 : : 2, 2, procform->amproclefttype, INT8OID);
3516 tgl@sss.pgh.pa.us 105 : 165 : break;
1986 akorotkov@postgresql 106 :UBC 0 : case HASHOPTIONS_PROC:
359 peter@eisentraut.org 107 : 0 : ok = check_amoptsproc_signature(procform->amproc);
1986 akorotkov@postgresql 108 : 0 : break;
3516 tgl@sss.pgh.pa.us 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;
359 peter@eisentraut.org 117 : 0 : continue; /* don't want additional message */
118 : : }
119 : :
359 peter@eisentraut.org 120 [ - + ]:CBC 388 : if (!ok)
121 : : {
359 peter@eisentraut.org 122 [ # # ]:UBC 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 */
359 peter@eisentraut.org 132 [ + - + + :CBC 388 : if (ok && (procform->amprocnum == HASHSTANDARD_PROC || procform->amprocnum == HASHEXTENDED_PROC))
+ - ]
133 : : {
134 : 388 : hashabletypes = list_append_unique_oid(hashabletypes, procform->amproclefttype);
135 : : }
136 : : }
137 : :
138 : : /* Check individual operators */
3520 tgl@sss.pgh.pa.us 139 [ + + ]: 613 : for (i = 0; i < oprlist->n_members; i++)
140 : : {
141 : 473 : HeapTuple oprtup = &oprlist->members[i]->tuple;
142 : 473 : Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
143 : :
144 : : /* Check that only allowed strategy numbers exist */
145 [ + - ]: 473 : if (oprform->amopstrategy < 1 ||
146 [ - + ]: 473 : oprform->amopstrategy > HTMaxStrategyNumber)
147 : : {
3516 tgl@sss.pgh.pa.us 148 [ # # ]:UBC 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 */
3516 tgl@sss.pgh.pa.us 158 [ + - ]:CBC 473 : if (oprform->amoppurpose != AMOP_SEARCH ||
159 [ - + ]: 473 : OidIsValid(oprform->amopsortfamily))
160 : : {
3516 tgl@sss.pgh.pa.us 161 [ # # ]:UBC 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 */
3516 tgl@sss.pgh.pa.us 170 [ - + ]:CBC 473 : if (!check_amop_signature(oprform->amopopr, BOOLOID,
171 : : oprform->amoplefttype,
172 : : oprform->amoprighttype))
173 : : {
3516 tgl@sss.pgh.pa.us 174 [ # # ]:UBC 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 */
3516 tgl@sss.pgh.pa.us 183 [ + - ]:CBC 473 : if (!list_member_oid(hashabletypes, oprform->amoplefttype) ||
184 [ - + ]: 473 : !list_member_oid(hashabletypes, oprform->amoprighttype))
185 : : {
3516 tgl@sss.pgh.pa.us 186 [ # # ]:UBC 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 */
3516 tgl@sss.pgh.pa.us 196 :CBC 140 : grouplist = identify_opfamily_groups(oprlist, proclist);
197 : 140 : opclassgroup = NULL;
198 [ + - + + : 613 : foreach(lc, grouplist)
+ + ]
199 : : {
200 : 473 : OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc);
201 : :
202 : : /* Remember the group exactly matching the test opclass */
203 [ + + ]: 473 : if (thisgroup->lefttype == opcintype &&
204 [ + + ]: 193 : thisgroup->righttype == opcintype)
205 : 140 : 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 [ - + ]: 473 : if (thisgroup->operatorset != (1 << HTEqualStrategyNumber))
213 : : {
3516 tgl@sss.pgh.pa.us 214 [ # # ]:UBC 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) */
3516 tgl@sss.pgh.pa.us 226 [ - + ]:CBC 140 : if (!opclassgroup)
227 : : {
3516 tgl@sss.pgh.pa.us 228 [ # # ]:UBC 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 : : */
3516 tgl@sss.pgh.pa.us 241 :CBC 140 : if (list_length(grouplist) !=
242 [ + + ]: 140 : list_length(hashabletypes) * list_length(hashabletypes))
243 : : {
244 [ + - ]: 8 : 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 : 8 : result = false;
249 : : }
250 : :
3520 251 : 140 : ReleaseCatCacheList(proclist);
252 : 140 : ReleaseCatCacheList(oprlist);
3516 253 : 140 : ReleaseSysCache(classtup);
254 : :
255 : 140 : return result;
256 : : }
257 : :
258 : :
259 : : /*
260 : : * Prechecking function for adding operators/functions to a hash opfamily.
261 : : */
262 : : void
1862 263 : 81 : 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 [ + + ]: 81 : if (OidIsValid(opclassoid))
293 : : {
294 : : /* During CREATE OPERATOR CLASS, need CCI to see the pg_opclass row */
295 : 55 : CommandCounterIncrement();
296 : 55 : opcintype = get_opclass_input_type(opclassoid);
297 : : }
298 : : else
299 : 26 : 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 [ + + + + : 198 : foreach(lc, list_concat_copy(operators, functions))
+ + ]
306 : : {
307 : 117 : OpFamilyMember *op = (OpFamilyMember *) lfirst(lc);
308 : :
309 [ + + + + ]: 117 : if (op->is_func && op->number != HASHSTANDARD_PROC)
310 : : {
311 : : /* Optional support proc, so always a soft family dependency */
312 : 29 : op->ref_is_hard = false;
313 : 29 : op->ref_is_family = true;
314 : 29 : op->refobjid = opfamilyoid;
315 : : }
316 [ + + ]: 88 : else if (op->lefttype != op->righttype)
317 : : {
318 : : /* Cross-type, so always a soft family dependency */
319 : 23 : op->ref_is_hard = false;
320 : 23 : op->ref_is_family = true;
321 : 23 : op->refobjid = opfamilyoid;
322 : : }
323 : : else
324 : : {
325 : : /* Not cross-type; is there a suitable opclass? */
326 [ + + ]: 65 : if (op->lefttype != opcintype)
327 : : {
328 : : /* Avoid repeating this expensive lookup, even if it fails */
329 : 3 : opcintype = op->lefttype;
330 : 3 : opclassoid = opclass_for_family_datatype(HASH_AM_OID,
331 : : opfamilyoid,
332 : : opcintype);
333 : : }
334 [ + + ]: 65 : if (OidIsValid(opclassoid))
335 : : {
336 : : /* Hard dependency on opclass */
337 : 62 : op->ref_is_hard = true;
338 : 62 : op->ref_is_family = false;
339 : 62 : op->refobjid = opclassoid;
340 : : }
341 : : else
342 : : {
343 : : /* We're stuck, so make a soft dependency on the opfamily */
344 : 3 : op->ref_is_hard = false;
345 : 3 : op->ref_is_family = true;
346 : 3 : op->refobjid = opfamilyoid;
347 : : }
348 : : }
349 : : }
350 : 81 : }
|