Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * spgvalidate.c
4 : * Opclass validator for SP-GiST.
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/spgist/spgvalidate.c
11 : *
12 : *-------------------------------------------------------------------------
13 : */
14 : #include "postgres.h"
15 :
16 : #include "access/amvalidate.h"
17 : #include "access/htup_details.h"
18 : #include "access/spgist.h"
19 : #include "catalog/pg_amop.h"
20 : #include "catalog/pg_amproc.h"
21 : #include "catalog/pg_opclass.h"
22 : #include "catalog/pg_type.h"
23 : #include "utils/builtins.h"
24 : #include "utils/lsyscache.h"
25 : #include "utils/regproc.h"
26 : #include "utils/syscache.h"
27 :
28 :
29 : /*
30 : * Validator for an SP-GiST opclass.
31 : *
32 : * Some of the checks done here cover the whole opfamily, and therefore are
33 : * redundant when checking each opclass in a family. But they don't run long
34 : * enough to be much of a problem, so we accept the duplication rather than
35 : * complicate the amvalidate API.
36 : */
37 : bool
38 46 : spgvalidate(Oid opclassoid)
39 : {
40 46 : bool result = true;
41 : HeapTuple classtup;
42 : Form_pg_opclass classform;
43 : Oid opfamilyoid;
44 : Oid opcintype;
45 : Oid opckeytype;
46 : char *opclassname;
47 : char *opfamilyname;
48 : CatCList *proclist,
49 : *oprlist;
50 : List *grouplist;
51 : OpFamilyOpFuncGroup *opclassgroup;
52 : int i;
53 : ListCell *lc;
54 : spgConfigIn configIn;
55 : spgConfigOut configOut;
56 46 : Oid configOutLefttype = InvalidOid;
57 46 : Oid configOutRighttype = InvalidOid;
58 46 : Oid configOutLeafType = InvalidOid;
59 :
60 : /* Fetch opclass information */
61 46 : classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid));
62 46 : if (!HeapTupleIsValid(classtup))
63 0 : elog(ERROR, "cache lookup failed for operator class %u", opclassoid);
64 46 : classform = (Form_pg_opclass) GETSTRUCT(classtup);
65 :
66 46 : opfamilyoid = classform->opcfamily;
67 46 : opcintype = classform->opcintype;
68 46 : opckeytype = classform->opckeytype;
69 46 : opclassname = NameStr(classform->opcname);
70 :
71 : /* Fetch opfamily information */
72 46 : opfamilyname = get_opfamily_name(opfamilyoid, false);
73 :
74 : /* Fetch all operators and support functions of the opfamily */
75 46 : oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid));
76 46 : proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid));
77 46 : grouplist = identify_opfamily_groups(oprlist, proclist);
78 :
79 : /* Check individual support functions */
80 286 : for (i = 0; i < proclist->n_members; i++)
81 : {
82 240 : HeapTuple proctup = &proclist->members[i]->tuple;
83 240 : Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup);
84 : bool ok;
85 :
86 : /*
87 : * All SP-GiST support functions should be registered with matching
88 : * left/right types
89 : */
90 240 : if (procform->amproclefttype != procform->amprocrighttype)
91 : {
92 0 : ereport(INFO,
93 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
94 : errmsg("operator family \"%s\" of access method %s contains support function %s with different left and right input types",
95 : opfamilyname, "spgist",
96 : format_procedure(procform->amproc))));
97 0 : result = false;
98 : }
99 :
100 : /* Check procedure numbers and function signatures */
101 240 : switch (procform->amprocnum)
102 : {
103 46 : case SPGIST_CONFIG_PROC:
104 46 : ok = check_amproc_signature(procform->amproc, VOIDOID, true,
105 : 2, 2, INTERNALOID, INTERNALOID);
106 46 : configIn.attType = procform->amproclefttype;
107 46 : memset(&configOut, 0, sizeof(configOut));
108 :
109 46 : OidFunctionCall2(procform->amproc,
110 : PointerGetDatum(&configIn),
111 : PointerGetDatum(&configOut));
112 :
113 46 : configOutLefttype = procform->amproclefttype;
114 46 : configOutRighttype = procform->amprocrighttype;
115 :
116 : /* Default leaf type is opckeytype or input type */
117 46 : if (OidIsValid(opckeytype))
118 8 : configOutLeafType = opckeytype;
119 : else
120 38 : configOutLeafType = procform->amproclefttype;
121 :
122 : /* If some other leaf datum type is specified, warn */
123 46 : if (OidIsValid(configOut.leafType) &&
124 10 : configOutLeafType != configOut.leafType)
125 : {
126 2 : ereport(INFO,
127 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
128 : errmsg("SP-GiST leaf data type %s does not match declared type %s",
129 : format_type_be(configOut.leafType),
130 : format_type_be(configOutLeafType))));
131 2 : result = false;
132 2 : configOutLeafType = configOut.leafType;
133 : }
134 :
135 : /*
136 : * When leaf and attribute types are the same, compress
137 : * function is not required and we set corresponding bit in
138 : * functionset for later group consistency check.
139 : */
140 46 : if (configOutLeafType == configIn.attType)
141 : {
142 48 : foreach(lc, grouplist)
143 : {
144 48 : OpFamilyOpFuncGroup *group = lfirst(lc);
145 :
146 48 : if (group->lefttype == procform->amproclefttype &&
147 48 : group->righttype == procform->amprocrighttype)
148 : {
149 36 : group->functionset |=
150 : ((uint64) 1) << SPGIST_COMPRESS_PROC;
151 36 : break;
152 : }
153 : }
154 : }
155 46 : break;
156 138 : case SPGIST_CHOOSE_PROC:
157 : case SPGIST_PICKSPLIT_PROC:
158 : case SPGIST_INNER_CONSISTENT_PROC:
159 138 : ok = check_amproc_signature(procform->amproc, VOIDOID, true,
160 : 2, 2, INTERNALOID, INTERNALOID);
161 138 : break;
162 46 : case SPGIST_LEAF_CONSISTENT_PROC:
163 46 : ok = check_amproc_signature(procform->amproc, BOOLOID, true,
164 : 2, 2, INTERNALOID, INTERNALOID);
165 46 : break;
166 10 : case SPGIST_COMPRESS_PROC:
167 10 : if (configOutLefttype != procform->amproclefttype ||
168 10 : configOutRighttype != procform->amprocrighttype)
169 0 : ok = false;
170 : else
171 10 : ok = check_amproc_signature(procform->amproc,
172 : configOutLeafType, true,
173 : 1, 1, procform->amproclefttype);
174 10 : break;
175 0 : case SPGIST_OPTIONS_PROC:
176 0 : ok = check_amoptsproc_signature(procform->amproc);
177 0 : break;
178 0 : default:
179 0 : ereport(INFO,
180 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
181 : errmsg("operator family \"%s\" of access method %s contains function %s with invalid support number %d",
182 : opfamilyname, "spgist",
183 : format_procedure(procform->amproc),
184 : procform->amprocnum)));
185 0 : result = false;
186 0 : continue; /* don't want additional message */
187 : }
188 :
189 240 : if (!ok)
190 : {
191 0 : ereport(INFO,
192 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
193 : errmsg("operator family \"%s\" of access method %s contains function %s with wrong signature for support number %d",
194 : opfamilyname, "spgist",
195 : format_procedure(procform->amproc),
196 : procform->amprocnum)));
197 0 : result = false;
198 : }
199 : }
200 :
201 : /* Check individual operators */
202 516 : for (i = 0; i < oprlist->n_members; i++)
203 : {
204 470 : HeapTuple oprtup = &oprlist->members[i]->tuple;
205 470 : Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
206 : Oid op_rettype;
207 :
208 : /* TODO: Check that only allowed strategy numbers exist */
209 470 : if (oprform->amopstrategy < 1 || oprform->amopstrategy > 63)
210 : {
211 0 : ereport(INFO,
212 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
213 : errmsg("operator family \"%s\" of access method %s contains operator %s with invalid strategy number %d",
214 : opfamilyname, "spgist",
215 : format_operator(oprform->amopopr),
216 : oprform->amopstrategy)));
217 0 : result = false;
218 : }
219 :
220 : /* spgist supports ORDER BY operators */
221 470 : if (oprform->amoppurpose != AMOP_SEARCH)
222 : {
223 : /* ... and operator result must match the claimed btree opfamily */
224 24 : op_rettype = get_op_rettype(oprform->amopopr);
225 24 : if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype))
226 : {
227 0 : ereport(INFO,
228 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
229 : errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
230 : opfamilyname, "spgist",
231 : format_operator(oprform->amopopr))));
232 0 : result = false;
233 : }
234 : }
235 : else
236 446 : op_rettype = BOOLOID;
237 :
238 : /* Check operator signature --- same for all spgist strategies */
239 470 : if (!check_amop_signature(oprform->amopopr, op_rettype,
240 : oprform->amoplefttype,
241 : oprform->amoprighttype))
242 : {
243 0 : ereport(INFO,
244 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
245 : errmsg("operator family \"%s\" of access method %s contains operator %s with wrong signature",
246 : opfamilyname, "spgist",
247 : format_operator(oprform->amopopr))));
248 0 : result = false;
249 : }
250 : }
251 :
252 : /* Now check for inconsistent groups of operators/functions */
253 46 : opclassgroup = NULL;
254 122 : foreach(lc, grouplist)
255 : {
256 76 : OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc);
257 :
258 : /* Remember the group exactly matching the test opclass */
259 76 : if (thisgroup->lefttype == opcintype &&
260 76 : thisgroup->righttype == opcintype)
261 46 : opclassgroup = thisgroup;
262 :
263 : /*
264 : * Complain if there are any datatype pairs with functions but no
265 : * operators. This is about the best we can do for now to detect
266 : * missing operators.
267 : */
268 76 : if (thisgroup->operatorset == 0)
269 : {
270 0 : ereport(INFO,
271 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
272 : errmsg("operator family \"%s\" of access method %s is missing operator(s) for types %s and %s",
273 : opfamilyname, "spgist",
274 : format_type_be(thisgroup->lefttype),
275 : format_type_be(thisgroup->righttype))));
276 0 : result = false;
277 : }
278 :
279 : /*
280 : * Complain if we're missing functions for any datatype, remembering
281 : * that SP-GiST doesn't use cross-type support functions.
282 : */
283 76 : if (thisgroup->lefttype != thisgroup->righttype)
284 30 : continue;
285 :
286 368 : for (i = 1; i <= SPGISTNProc; i++)
287 : {
288 322 : if ((thisgroup->functionset & (((uint64) 1) << i)) != 0)
289 276 : continue; /* got it */
290 46 : if (i == SPGIST_OPTIONS_PROC)
291 46 : continue; /* optional method */
292 0 : ereport(INFO,
293 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
294 : errmsg("operator family \"%s\" of access method %s is missing support function %d for type %s",
295 : opfamilyname, "spgist", i,
296 : format_type_be(thisgroup->lefttype))));
297 0 : result = false;
298 : }
299 : }
300 :
301 : /* Check that the originally-named opclass is supported */
302 : /* (if group is there, we already checked it adequately above) */
303 46 : if (!opclassgroup)
304 : {
305 0 : ereport(INFO,
306 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
307 : errmsg("operator class \"%s\" of access method %s is missing operator(s)",
308 : opclassname, "spgist")));
309 0 : result = false;
310 : }
311 :
312 46 : ReleaseCatCacheList(proclist);
313 46 : ReleaseCatCacheList(oprlist);
314 46 : ReleaseSysCache(classtup);
315 :
316 46 : return result;
317 : }
318 :
319 : /*
320 : * Prechecking function for adding operators/functions to an SP-GiST opfamily.
321 : */
322 : void
323 4 : spgadjustmembers(Oid opfamilyoid,
324 : Oid opclassoid,
325 : List *operators,
326 : List *functions)
327 : {
328 : ListCell *lc;
329 :
330 : /*
331 : * Operator members of an SP-GiST opfamily should never have hard
332 : * dependencies, since their connection to the opfamily depends only on
333 : * what the support functions think, and that can be altered. For
334 : * consistency, we make all soft dependencies point to the opfamily,
335 : * though a soft dependency on the opclass would work as well in the
336 : * CREATE OPERATOR CLASS case.
337 : */
338 24 : foreach(lc, operators)
339 : {
340 20 : OpFamilyMember *op = (OpFamilyMember *) lfirst(lc);
341 :
342 20 : op->ref_is_hard = false;
343 20 : op->ref_is_family = true;
344 20 : op->refobjid = opfamilyoid;
345 : }
346 :
347 : /*
348 : * Required support functions should have hard dependencies. Preferably
349 : * those are just dependencies on the opclass, but if we're in ALTER
350 : * OPERATOR FAMILY, we leave the dependency pointing at the whole
351 : * opfamily. (Given that SP-GiST opclasses generally don't share
352 : * opfamilies, it seems unlikely to be worth working harder.)
353 : */
354 28 : foreach(lc, functions)
355 : {
356 24 : OpFamilyMember *op = (OpFamilyMember *) lfirst(lc);
357 :
358 24 : switch (op->number)
359 : {
360 20 : case SPGIST_CONFIG_PROC:
361 : case SPGIST_CHOOSE_PROC:
362 : case SPGIST_PICKSPLIT_PROC:
363 : case SPGIST_INNER_CONSISTENT_PROC:
364 : case SPGIST_LEAF_CONSISTENT_PROC:
365 : /* Required support function */
366 20 : op->ref_is_hard = true;
367 20 : break;
368 4 : case SPGIST_COMPRESS_PROC:
369 : case SPGIST_OPTIONS_PROC:
370 : /* Optional, so force it to be a soft family dependency */
371 4 : op->ref_is_hard = false;
372 4 : op->ref_is_family = true;
373 4 : op->refobjid = opfamilyoid;
374 4 : break;
375 0 : default:
376 0 : ereport(ERROR,
377 : (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
378 : errmsg("support function number %d is invalid for access method %s",
379 : op->number, "spgist")));
380 : break;
381 : }
382 : }
383 4 : }
|