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