Age Owner Branch data TLA 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
3520 tgl@sss.pgh.pa.us 38 :CBC 23 : spgvalidate(Oid opclassoid)
39 : : {
3516 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;
2815 teodor@sigaev.ru 56 : 23 : Oid configOutLefttype = InvalidOid;
57 : 23 : Oid configOutRighttype = InvalidOid;
1616 tgl@sss.pgh.pa.us 58 : 23 : Oid configOutLeafType = InvalidOid;
59 : :
60 : : /* Fetch opclass information */
3520 61 : 23 : classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid));
62 [ - + ]: 23 : if (!HeapTupleIsValid(classtup))
3520 tgl@sss.pgh.pa.us 63 [ # # ]:UBC 0 : elog(ERROR, "cache lookup failed for operator class %u", opclassoid);
3520 tgl@sss.pgh.pa.us 64 :CBC 23 : classform = (Form_pg_opclass) GETSTRUCT(classtup);
65 : :
66 : 23 : opfamilyoid = classform->opcfamily;
67 : 23 : opcintype = classform->opcintype;
1616 68 : 23 : opckeytype = classform->opckeytype;
3516 69 : 23 : opclassname = NameStr(classform->opcname);
70 : :
71 : : /* Fetch opfamily information */
225 peter@eisentraut.org 72 : 23 : opfamilyname = get_opfamily_name(opfamilyoid, false);
73 : :
74 : : /* Fetch all operators and support functions of the opfamily */
3520 tgl@sss.pgh.pa.us 75 : 23 : oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid));
76 : 23 : proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid));
2815 teodor@sigaev.ru 77 : 23 : grouplist = identify_opfamily_groups(oprlist, proclist);
78 : :
79 : : /* Check individual support functions */
3520 tgl@sss.pgh.pa.us 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 : : */
3516 90 [ - + ]: 120 : if (procform->amproclefttype != procform->amprocrighttype)
91 : : {
3516 tgl@sss.pgh.pa.us 92 [ # # ]:UBC 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 */
3516 tgl@sss.pgh.pa.us 101 [ + + + + :CBC 120 : switch (procform->amprocnum)
- - ]
102 : : {
103 : 23 : case SPGIST_CONFIG_PROC:
2815 teodor@sigaev.ru 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 */
1616 tgl@sss.pgh.pa.us 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 : : {
2815 teodor@sigaev.ru 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;
3516 tgl@sss.pgh.pa.us 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;
2815 teodor@sigaev.ru 166 : 5 : case SPGIST_COMPRESS_PROC:
167 [ + - ]: 5 : if (configOutLefttype != procform->amproclefttype ||
168 [ - + ]: 5 : configOutRighttype != procform->amprocrighttype)
2815 teodor@sigaev.ru 169 :UBC 0 : ok = false;
170 : : else
2815 teodor@sigaev.ru 171 :CBC 5 : ok = check_amproc_signature(procform->amproc,
172 : : configOutLeafType, true,
173 : : 1, 1, procform->amproclefttype);
174 : 5 : break;
1986 akorotkov@postgresql 175 :UBC 0 : case SPGIST_OPTIONS_PROC:
176 : 0 : ok = check_amoptsproc_signature(procform->amproc);
177 : 0 : break;
3516 tgl@sss.pgh.pa.us 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 : :
3516 tgl@sss.pgh.pa.us 189 [ - + ]:CBC 120 : if (!ok)
190 : : {
3516 tgl@sss.pgh.pa.us 191 [ # # ]:UBC 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 */
3520 tgl@sss.pgh.pa.us 202 [ + + ]:CBC 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 */
3516 209 [ + - - + ]: 235 : if (oprform->amopstrategy < 1 || oprform->amopstrategy > 63)
210 : : {
3516 tgl@sss.pgh.pa.us 211 [ # # ]:UBC 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 */
2544 akorotkov@postgresql 221 [ + + ]:CBC 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 : : {
2544 akorotkov@postgresql 227 [ # # ]:UBC 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
2544 akorotkov@postgresql 236 :CBC 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 : : {
3516 tgl@sss.pgh.pa.us 243 [ # # ]:UBC 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 */
3516 tgl@sss.pgh.pa.us 253 :CBC 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 : : {
3516 tgl@sss.pgh.pa.us 270 [ # # ]:UBC 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 : : */
3516 tgl@sss.pgh.pa.us 283 [ + + ]:CBC 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 */
1986 akorotkov@postgresql 290 [ + - ]: 23 : if (i == SPGIST_OPTIONS_PROC)
1941 tgl@sss.pgh.pa.us 291 : 23 : continue; /* optional method */
3516 tgl@sss.pgh.pa.us 292 [ # # ]:UBC 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) */
3516 tgl@sss.pgh.pa.us 303 [ - + ]:CBC 23 : if (!opclassgroup)
304 : : {
3516 tgl@sss.pgh.pa.us 305 [ # # ]:UBC 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 : :
3520 tgl@sss.pgh.pa.us 312 :CBC 23 : ReleaseCatCacheList(proclist);
313 : 23 : ReleaseCatCacheList(oprlist);
3516 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
1862 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;
1862 tgl@sss.pgh.pa.us 375 :UBC 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 : : }
1862 tgl@sss.pgh.pa.us 383 :CBC 2 : }
|