/src/postgres/src/backend/access/gist/gistvalidate.c
Line | Count | Source (jump to first uncovered line) |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * gistvalidate.c |
4 | | * Opclass validator for 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/gist/gistvalidate.c |
11 | | * |
12 | | *------------------------------------------------------------------------- |
13 | | */ |
14 | | #include "postgres.h" |
15 | | |
16 | | #include "access/amvalidate.h" |
17 | | #include "access/gist_private.h" |
18 | | #include "access/htup_details.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/lsyscache.h" |
24 | | #include "utils/regproc.h" |
25 | | #include "utils/syscache.h" |
26 | | |
27 | | |
28 | | /* |
29 | | * Validator for a GiST opclass. |
30 | | */ |
31 | | bool |
32 | | gistvalidate(Oid opclassoid) |
33 | 0 | { |
34 | 0 | bool result = true; |
35 | 0 | HeapTuple classtup; |
36 | 0 | Form_pg_opclass classform; |
37 | 0 | Oid opfamilyoid; |
38 | 0 | Oid opcintype; |
39 | 0 | Oid opckeytype; |
40 | 0 | char *opclassname; |
41 | 0 | char *opfamilyname; |
42 | 0 | CatCList *proclist, |
43 | 0 | *oprlist; |
44 | 0 | List *grouplist; |
45 | 0 | OpFamilyOpFuncGroup *opclassgroup; |
46 | 0 | int i; |
47 | 0 | ListCell *lc; |
48 | | |
49 | | /* Fetch opclass information */ |
50 | 0 | classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid)); |
51 | 0 | if (!HeapTupleIsValid(classtup)) |
52 | 0 | elog(ERROR, "cache lookup failed for operator class %u", opclassoid); |
53 | 0 | classform = (Form_pg_opclass) GETSTRUCT(classtup); |
54 | |
|
55 | 0 | opfamilyoid = classform->opcfamily; |
56 | 0 | opcintype = classform->opcintype; |
57 | 0 | opckeytype = classform->opckeytype; |
58 | 0 | if (!OidIsValid(opckeytype)) |
59 | 0 | opckeytype = opcintype; |
60 | 0 | opclassname = NameStr(classform->opcname); |
61 | | |
62 | | /* Fetch opfamily information */ |
63 | 0 | opfamilyname = get_opfamily_name(opfamilyoid, false); |
64 | | |
65 | | /* Fetch all operators and support functions of the opfamily */ |
66 | 0 | oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid)); |
67 | 0 | proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid)); |
68 | | |
69 | | /* Check individual support functions */ |
70 | 0 | for (i = 0; i < proclist->n_members; i++) |
71 | 0 | { |
72 | 0 | HeapTuple proctup = &proclist->members[i]->tuple; |
73 | 0 | Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup); |
74 | 0 | bool ok; |
75 | | |
76 | | /* |
77 | | * All GiST support functions should be registered with matching |
78 | | * left/right types |
79 | | */ |
80 | 0 | if (procform->amproclefttype != procform->amprocrighttype) |
81 | 0 | { |
82 | 0 | ereport(INFO, |
83 | 0 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
84 | 0 | errmsg("operator family \"%s\" of access method %s contains support function %s with different left and right input types", |
85 | 0 | opfamilyname, "gist", |
86 | 0 | format_procedure(procform->amproc)))); |
87 | 0 | result = false; |
88 | 0 | } |
89 | | |
90 | | /* |
91 | | * We can't check signatures except within the specific opclass, since |
92 | | * we need to know the associated opckeytype in many cases. |
93 | | */ |
94 | 0 | if (procform->amproclefttype != opcintype) |
95 | 0 | continue; |
96 | | |
97 | | /* Check procedure numbers and function signatures */ |
98 | 0 | switch (procform->amprocnum) |
99 | 0 | { |
100 | 0 | case GIST_CONSISTENT_PROC: |
101 | 0 | ok = check_amproc_signature(procform->amproc, BOOLOID, false, |
102 | 0 | 5, 5, INTERNALOID, opcintype, |
103 | 0 | INT2OID, OIDOID, INTERNALOID); |
104 | 0 | break; |
105 | 0 | case GIST_UNION_PROC: |
106 | 0 | ok = check_amproc_signature(procform->amproc, opckeytype, false, |
107 | 0 | 2, 2, INTERNALOID, INTERNALOID); |
108 | 0 | break; |
109 | 0 | case GIST_COMPRESS_PROC: |
110 | 0 | case GIST_DECOMPRESS_PROC: |
111 | 0 | case GIST_FETCH_PROC: |
112 | 0 | ok = check_amproc_signature(procform->amproc, INTERNALOID, true, |
113 | 0 | 1, 1, INTERNALOID); |
114 | 0 | break; |
115 | 0 | case GIST_PENALTY_PROC: |
116 | 0 | ok = check_amproc_signature(procform->amproc, INTERNALOID, true, |
117 | 0 | 3, 3, INTERNALOID, |
118 | 0 | INTERNALOID, INTERNALOID); |
119 | 0 | break; |
120 | 0 | case GIST_PICKSPLIT_PROC: |
121 | 0 | ok = check_amproc_signature(procform->amproc, INTERNALOID, true, |
122 | 0 | 2, 2, INTERNALOID, INTERNALOID); |
123 | 0 | break; |
124 | 0 | case GIST_EQUAL_PROC: |
125 | 0 | ok = check_amproc_signature(procform->amproc, INTERNALOID, false, |
126 | 0 | 3, 3, opckeytype, opckeytype, |
127 | 0 | INTERNALOID); |
128 | 0 | break; |
129 | 0 | case GIST_DISTANCE_PROC: |
130 | 0 | ok = check_amproc_signature(procform->amproc, FLOAT8OID, false, |
131 | 0 | 5, 5, INTERNALOID, opcintype, |
132 | 0 | INT2OID, OIDOID, INTERNALOID); |
133 | 0 | break; |
134 | 0 | case GIST_OPTIONS_PROC: |
135 | 0 | ok = check_amoptsproc_signature(procform->amproc); |
136 | 0 | break; |
137 | 0 | case GIST_SORTSUPPORT_PROC: |
138 | 0 | ok = check_amproc_signature(procform->amproc, VOIDOID, true, |
139 | 0 | 1, 1, INTERNALOID); |
140 | 0 | break; |
141 | 0 | case GIST_TRANSLATE_CMPTYPE_PROC: |
142 | 0 | ok = check_amproc_signature(procform->amproc, INT2OID, true, |
143 | 0 | 1, 1, INT4OID) && |
144 | 0 | procform->amproclefttype == ANYOID && |
145 | 0 | procform->amprocrighttype == ANYOID; |
146 | 0 | break; |
147 | 0 | default: |
148 | 0 | ereport(INFO, |
149 | 0 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
150 | 0 | errmsg("operator family \"%s\" of access method %s contains function %s with invalid support number %d", |
151 | 0 | opfamilyname, "gist", |
152 | 0 | format_procedure(procform->amproc), |
153 | 0 | procform->amprocnum))); |
154 | 0 | result = false; |
155 | 0 | continue; /* don't want additional message */ |
156 | 0 | } |
157 | | |
158 | 0 | if (!ok) |
159 | 0 | { |
160 | 0 | ereport(INFO, |
161 | 0 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
162 | 0 | errmsg("operator family \"%s\" of access method %s contains function %s with wrong signature for support number %d", |
163 | 0 | opfamilyname, "gist", |
164 | 0 | format_procedure(procform->amproc), |
165 | 0 | procform->amprocnum))); |
166 | 0 | result = false; |
167 | 0 | } |
168 | 0 | } |
169 | | |
170 | | /* Check individual operators */ |
171 | 0 | for (i = 0; i < oprlist->n_members; i++) |
172 | 0 | { |
173 | 0 | HeapTuple oprtup = &oprlist->members[i]->tuple; |
174 | 0 | Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); |
175 | 0 | Oid op_rettype; |
176 | | |
177 | | /* TODO: Check that only allowed strategy numbers exist */ |
178 | 0 | if (oprform->amopstrategy < 1) |
179 | 0 | { |
180 | 0 | ereport(INFO, |
181 | 0 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
182 | 0 | errmsg("operator family \"%s\" of access method %s contains operator %s with invalid strategy number %d", |
183 | 0 | opfamilyname, "gist", |
184 | 0 | format_operator(oprform->amopopr), |
185 | 0 | oprform->amopstrategy))); |
186 | 0 | result = false; |
187 | 0 | } |
188 | | |
189 | | /* GiST supports ORDER BY operators */ |
190 | 0 | if (oprform->amoppurpose != AMOP_SEARCH) |
191 | 0 | { |
192 | | /* ... but must have matching distance proc */ |
193 | 0 | if (!OidIsValid(get_opfamily_proc(opfamilyoid, |
194 | 0 | oprform->amoplefttype, |
195 | 0 | oprform->amoplefttype, |
196 | 0 | GIST_DISTANCE_PROC))) |
197 | 0 | { |
198 | 0 | ereport(INFO, |
199 | 0 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
200 | 0 | errmsg("operator family \"%s\" of access method %s contains unsupported ORDER BY specification for operator %s", |
201 | 0 | opfamilyname, "gist", |
202 | 0 | format_operator(oprform->amopopr)))); |
203 | 0 | result = false; |
204 | 0 | } |
205 | | /* ... and operator result must match the claimed btree opfamily */ |
206 | 0 | op_rettype = get_op_rettype(oprform->amopopr); |
207 | 0 | if (!opfamily_can_sort_type(oprform->amopsortfamily, op_rettype)) |
208 | 0 | { |
209 | 0 | ereport(INFO, |
210 | 0 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
211 | 0 | errmsg("operator family \"%s\" of access method %s contains incorrect ORDER BY opfamily specification for operator %s", |
212 | 0 | opfamilyname, "gist", |
213 | 0 | format_operator(oprform->amopopr)))); |
214 | 0 | result = false; |
215 | 0 | } |
216 | 0 | } |
217 | 0 | else |
218 | 0 | { |
219 | | /* Search operators must always return bool */ |
220 | 0 | op_rettype = BOOLOID; |
221 | 0 | } |
222 | | |
223 | | /* Check operator signature */ |
224 | 0 | if (!check_amop_signature(oprform->amopopr, op_rettype, |
225 | 0 | oprform->amoplefttype, |
226 | 0 | oprform->amoprighttype)) |
227 | 0 | { |
228 | 0 | ereport(INFO, |
229 | 0 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
230 | 0 | errmsg("operator family \"%s\" of access method %s contains operator %s with wrong signature", |
231 | 0 | opfamilyname, "gist", |
232 | 0 | format_operator(oprform->amopopr)))); |
233 | 0 | result = false; |
234 | 0 | } |
235 | 0 | } |
236 | | |
237 | | /* Now check for inconsistent groups of operators/functions */ |
238 | 0 | grouplist = identify_opfamily_groups(oprlist, proclist); |
239 | 0 | opclassgroup = NULL; |
240 | 0 | foreach(lc, grouplist) |
241 | 0 | { |
242 | 0 | OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc); |
243 | | |
244 | | /* Remember the group exactly matching the test opclass */ |
245 | 0 | if (thisgroup->lefttype == opcintype && |
246 | 0 | thisgroup->righttype == opcintype) |
247 | 0 | opclassgroup = thisgroup; |
248 | | |
249 | | /* |
250 | | * There is not a lot we can do to check the operator sets, since each |
251 | | * GiST opclass is more or less a law unto itself, and some contain |
252 | | * only operators that are binary-compatible with the opclass datatype |
253 | | * (meaning that empty operator sets can be OK). That case also means |
254 | | * that we shouldn't insist on nonempty function sets except for the |
255 | | * opclass's own group. |
256 | | */ |
257 | 0 | } |
258 | | |
259 | | /* Check that the originally-named opclass is complete */ |
260 | 0 | for (i = 1; i <= GISTNProcs; i++) |
261 | 0 | { |
262 | 0 | if (opclassgroup && |
263 | 0 | (opclassgroup->functionset & (((uint64) 1) << i)) != 0) |
264 | 0 | continue; /* got it */ |
265 | 0 | if (i == GIST_DISTANCE_PROC || i == GIST_FETCH_PROC || |
266 | 0 | i == GIST_COMPRESS_PROC || i == GIST_DECOMPRESS_PROC || |
267 | 0 | i == GIST_OPTIONS_PROC || i == GIST_SORTSUPPORT_PROC || |
268 | 0 | i == GIST_TRANSLATE_CMPTYPE_PROC) |
269 | 0 | continue; /* optional methods */ |
270 | 0 | ereport(INFO, |
271 | 0 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
272 | 0 | errmsg("operator class \"%s\" of access method %s is missing support function %d", |
273 | 0 | opclassname, "gist", i))); |
274 | 0 | result = false; |
275 | 0 | } |
276 | | |
277 | 0 | ReleaseCatCacheList(proclist); |
278 | 0 | ReleaseCatCacheList(oprlist); |
279 | 0 | ReleaseSysCache(classtup); |
280 | |
|
281 | 0 | return result; |
282 | 0 | } |
283 | | |
284 | | /* |
285 | | * Prechecking function for adding operators/functions to a GiST opfamily. |
286 | | */ |
287 | | void |
288 | | gistadjustmembers(Oid opfamilyoid, |
289 | | Oid opclassoid, |
290 | | List *operators, |
291 | | List *functions) |
292 | 0 | { |
293 | 0 | ListCell *lc; |
294 | | |
295 | | /* |
296 | | * Operator members of a GiST opfamily should never have hard |
297 | | * dependencies, since their connection to the opfamily depends only on |
298 | | * what the support functions think, and that can be altered. For |
299 | | * consistency, we make all soft dependencies point to the opfamily, |
300 | | * though a soft dependency on the opclass would work as well in the |
301 | | * CREATE OPERATOR CLASS case. |
302 | | */ |
303 | 0 | foreach(lc, operators) |
304 | 0 | { |
305 | 0 | OpFamilyMember *op = (OpFamilyMember *) lfirst(lc); |
306 | |
|
307 | 0 | op->ref_is_hard = false; |
308 | 0 | op->ref_is_family = true; |
309 | 0 | op->refobjid = opfamilyoid; |
310 | 0 | } |
311 | | |
312 | | /* |
313 | | * Required support functions should have hard dependencies. Preferably |
314 | | * those are just dependencies on the opclass, but if we're in ALTER |
315 | | * OPERATOR FAMILY, we leave the dependency pointing at the whole |
316 | | * opfamily. (Given that GiST opclasses generally don't share opfamilies, |
317 | | * it seems unlikely to be worth working harder.) |
318 | | */ |
319 | 0 | foreach(lc, functions) |
320 | 0 | { |
321 | 0 | OpFamilyMember *op = (OpFamilyMember *) lfirst(lc); |
322 | |
|
323 | 0 | switch (op->number) |
324 | 0 | { |
325 | 0 | case GIST_CONSISTENT_PROC: |
326 | 0 | case GIST_UNION_PROC: |
327 | 0 | case GIST_PENALTY_PROC: |
328 | 0 | case GIST_PICKSPLIT_PROC: |
329 | 0 | case GIST_EQUAL_PROC: |
330 | | /* Required support function */ |
331 | 0 | op->ref_is_hard = true; |
332 | 0 | break; |
333 | 0 | case GIST_COMPRESS_PROC: |
334 | 0 | case GIST_DECOMPRESS_PROC: |
335 | 0 | case GIST_DISTANCE_PROC: |
336 | 0 | case GIST_FETCH_PROC: |
337 | 0 | case GIST_OPTIONS_PROC: |
338 | 0 | case GIST_SORTSUPPORT_PROC: |
339 | 0 | case GIST_TRANSLATE_CMPTYPE_PROC: |
340 | | /* Optional, so force it to be a soft family dependency */ |
341 | 0 | op->ref_is_hard = false; |
342 | 0 | op->ref_is_family = true; |
343 | 0 | op->refobjid = opfamilyoid; |
344 | 0 | break; |
345 | 0 | default: |
346 | 0 | ereport(ERROR, |
347 | 0 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
348 | 0 | errmsg("support function number %d is invalid for access method %s", |
349 | 0 | op->number, "gist"))); |
350 | 0 | break; |
351 | 0 | } |
352 | 0 | } |
353 | 0 | } |