Line data Source code
1 : /*-------------------------------------------------------------------------
2 : *
3 : * spgvalidate.c
4 : * Opclass validator for SP-GiST.
5 : *
6 : * Portions Copyright (c) 1996-2026, 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 23 : spgvalidate(Oid opclassoid)
39 : {
40 23 : 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 23 : Oid configOutLefttype = InvalidOid;
57 23 : Oid configOutRighttype = InvalidOid;
58 23 : Oid configOutLeafType = InvalidOid;
59 :
60 : /* Fetch opclass information */
61 23 : classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid));
62 23 : if (!HeapTupleIsValid(classtup))
63 0 : elog(ERROR, "cache lookup failed for operator class %u", opclassoid);
64 23 : classform = (Form_pg_opclass) GETSTRUCT(classtup);
65 :
66 23 : opfamilyoid = classform->opcfamily;
67 23 : opcintype = classform->opcintype;
68 23 : opckeytype = classform->opckeytype;
69 23 : opclassname = NameStr(classform->opcname);
70 :
71 : /* Fetch opfamily information */
72 23 : opfamilyname = get_opfamily_name(opfamilyoid, false);
73 :
74 : /* Fetch all operators and support functions of the opfamily */
75 23 : oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid));
76 23 : proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid));
77 23 : grouplist = identify_opfamily_groups(oprlist, proclist);
78 :
79 : /* Check individual support functions */
80 143 : for (i = 0; i < proclist->n_members; i++)
81 : {
82 120 : HeapTuple proctup = &proclist->members[i]->tuple;
83 120 : 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 120 : 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 120 : switch (procform->amprocnum)
102 : {
103 23 : case SPGIST_CONFIG_PROC:
104 23 : ok = check_amproc_signature(procform->amproc, VOIDOID, true,
105 : 2, 2, INTERNALOID, INTERNALOID);
106 23 : configIn.attType = procform->amproclefttype;
107 23 : memset(&configOut, 0, sizeof(configOut));
108 :
109 23 : OidFunctionCall2(procform->amproc,
110 : PointerGetDatum(&configIn),
111 : PointerGetDatum(&configOut));
112 :
113 23 : configOutLefttype = procform->amproclefttype;
114 23 : configOutRighttype = procform->amprocrighttype;
115 :
116 : /* Default leaf type is opckeytype or input type */
117 23 : if (OidIsValid(opckeytype))
118 4 : configOutLeafType = opckeytype;
119 : else
120 19 : configOutLeafType = procform->amproclefttype;
121 :
122 : /* If some other leaf datum type is specified, warn */
123 23 : if (OidIsValid(configOut.leafType) &&
124 5 : configOutLeafType != configOut.leafType)
125 : {
126 1 : 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 1 : result = false;
132 1 : 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 23 : if (configOutLeafType == configIn.attType)
141 : {
142 24 : foreach(lc, grouplist)
143 : {
144 24 : OpFamilyOpFuncGroup *group = lfirst(lc);
145 :
146 24 : if (group->lefttype == procform->amproclefttype &&
147 24 : group->righttype == procform->amprocrighttype)
148 : {
149 18 : group->functionset |=
150 : ((uint64) 1) << SPGIST_COMPRESS_PROC;
151 18 : break;
152 : }
153 : }
154 : }
155 23 : break;
156 69 : case SPGIST_CHOOSE_PROC:
157 : case SPGIST_PICKSPLIT_PROC:
158 : case SPGIST_INNER_CONSISTENT_PROC:
159 69 : ok = check_amproc_signature(procform->amproc, VOIDOID, true,
160 : 2, 2, INTERNALOID, INTERNALOID);
161 69 : break;
162 23 : case SPGIST_LEAF_CONSISTENT_PROC:
163 23 : ok = check_amproc_signature(procform->amproc, BOOLOID, true,
164 : 2, 2, INTERNALOID, INTERNALOID);
165 23 : break;
166 5 : case SPGIST_COMPRESS_PROC:
167 5 : if (configOutLefttype != procform->amproclefttype ||
168 5 : configOutRighttype != procform->amprocrighttype)
169 0 : ok = false;
170 : else
171 5 : ok = check_amproc_signature(procform->amproc,
172 : configOutLeafType, true,
173 : 1, 1, procform->amproclefttype);
174 5 : 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 120 : 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 258 : for (i = 0; i < oprlist->n_members; i++)
203 : {
204 235 : HeapTuple oprtup = &oprlist->members[i]->tuple;
205 235 : Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
206 : Oid op_rettype;
207 :
208 : /* TODO: Check that only allowed strategy numbers exist */
209 235 : 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 235 : if (oprform->amoppurpose != AMOP_SEARCH)
222 : {
223 : /* ... and operator result must match the claimed btree opfamily */
224 12 : op_rettype = get_op_rettype(oprform->amopopr);
225 12 : 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 223 : op_rettype = BOOLOID;
237 :
238 : /* Check operator signature --- same for all spgist strategies */
239 235 : 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 23 : opclassgroup = NULL;
254 61 : foreach(lc, grouplist)
255 : {
256 38 : OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc);
257 :
258 : /* Remember the group exactly matching the test opclass */
259 38 : if (thisgroup->lefttype == opcintype &&
260 38 : thisgroup->righttype == opcintype)
261 23 : 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 38 : 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 38 : if (thisgroup->lefttype != thisgroup->righttype)
284 15 : continue;
285 :
286 184 : for (i = 1; i <= SPGISTNProc; i++)
287 : {
288 161 : if ((thisgroup->functionset & (((uint64) 1) << i)) != 0)
289 138 : continue; /* got it */
290 23 : if (i == SPGIST_OPTIONS_PROC)
291 23 : 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 23 : 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 23 : ReleaseCatCacheList(proclist);
313 23 : ReleaseCatCacheList(oprlist);
314 23 : ReleaseSysCache(classtup);
315 :
316 23 : return result;
317 : }
318 :
319 : /*
320 : * Prechecking function for adding operators/functions to an SP-GiST opfamily.
321 : */
322 : void
323 2 : 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 12 : foreach(lc, operators)
339 : {
340 10 : OpFamilyMember *op = (OpFamilyMember *) lfirst(lc);
341 :
342 10 : op->ref_is_hard = false;
343 10 : op->ref_is_family = true;
344 10 : 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 14 : foreach(lc, functions)
355 : {
356 12 : OpFamilyMember *op = (OpFamilyMember *) lfirst(lc);
357 :
358 12 : switch (op->number)
359 : {
360 10 : 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 10 : op->ref_is_hard = true;
367 10 : break;
368 2 : case SPGIST_COMPRESS_PROC:
369 : case SPGIST_OPTIONS_PROC:
370 : /* Optional, so force it to be a soft family dependency */
371 2 : op->ref_is_hard = false;
372 2 : op->ref_is_family = true;
373 2 : op->refobjid = opfamilyoid;
374 2 : 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 2 : }
|