/src/postgres/src/backend/access/gin/ginvalidate.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * ginvalidate.c |
4 | | * Opclass validator for GIN. |
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/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 | | ginvalidate(Oid opclassoid) |
32 | | { |
33 | | 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 | | classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid)); |
50 | | if (!HeapTupleIsValid(classtup)) |
51 | | elog(ERROR, "cache lookup failed for operator class %u", opclassoid); |
52 | | classform = (Form_pg_opclass) GETSTRUCT(classtup); |
53 | | |
54 | | opfamilyoid = classform->opcfamily; |
55 | | opcintype = classform->opcintype; |
56 | | opckeytype = classform->opckeytype; |
57 | | if (!OidIsValid(opckeytype)) |
58 | | opckeytype = opcintype; |
59 | | opclassname = NameStr(classform->opcname); |
60 | | |
61 | | /* Fetch opfamily information */ |
62 | | opfamilyname = get_opfamily_name(opfamilyoid, false); |
63 | | |
64 | | /* Fetch all operators and support functions of the opfamily */ |
65 | | oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid)); |
66 | | proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid)); |
67 | | |
68 | | /* Check individual support functions */ |
69 | | for (i = 0; i < proclist->n_members; i++) |
70 | | { |
71 | | HeapTuple proctup = &proclist->members[i]->tuple; |
72 | | 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 | | if (procform->amproclefttype != procform->amprocrighttype) |
80 | | { |
81 | | 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 | | 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 | | if (procform->amproclefttype != opcintype) |
94 | | continue; |
95 | | |
96 | | /* Check procedure numbers and function signatures */ |
97 | | switch (procform->amprocnum) |
98 | | { |
99 | | case GIN_COMPARE_PROC: |
100 | | ok = check_amproc_signature(procform->amproc, INT4OID, false, |
101 | | 2, 2, opckeytype, opckeytype); |
102 | | break; |
103 | | case GIN_EXTRACTVALUE_PROC: |
104 | | /* Some opclasses omit nullFlags */ |
105 | | ok = check_amproc_signature(procform->amproc, INTERNALOID, false, |
106 | | 2, 3, opcintype, INTERNALOID, |
107 | | INTERNALOID); |
108 | | break; |
109 | | case GIN_EXTRACTQUERY_PROC: |
110 | | /* Some opclasses omit nullFlags and searchMode */ |
111 | | ok = check_amproc_signature(procform->amproc, INTERNALOID, false, |
112 | | 5, 7, opcintype, INTERNALOID, |
113 | | INT2OID, INTERNALOID, INTERNALOID, |
114 | | INTERNALOID, INTERNALOID); |
115 | | break; |
116 | | case GIN_CONSISTENT_PROC: |
117 | | /* Some opclasses omit queryKeys and nullFlags */ |
118 | | ok = check_amproc_signature(procform->amproc, BOOLOID, false, |
119 | | 6, 8, INTERNALOID, INT2OID, |
120 | | opcintype, INT4OID, |
121 | | INTERNALOID, INTERNALOID, |
122 | | INTERNALOID, INTERNALOID); |
123 | | break; |
124 | | case GIN_COMPARE_PARTIAL_PROC: |
125 | | ok = check_amproc_signature(procform->amproc, INT4OID, false, |
126 | | 4, 4, opckeytype, opckeytype, |
127 | | INT2OID, INTERNALOID); |
128 | | break; |
129 | | case GIN_TRICONSISTENT_PROC: |
130 | | ok = check_amproc_signature(procform->amproc, CHAROID, false, |
131 | | 7, 7, INTERNALOID, INT2OID, |
132 | | opcintype, INT4OID, |
133 | | INTERNALOID, INTERNALOID, |
134 | | INTERNALOID); |
135 | | break; |
136 | | case GIN_OPTIONS_PROC: |
137 | | ok = check_amoptsproc_signature(procform->amproc); |
138 | | break; |
139 | | default: |
140 | | 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 | | result = false; |
147 | | continue; /* don't want additional message */ |
148 | | } |
149 | | |
150 | | if (!ok) |
151 | | { |
152 | | 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 | | result = false; |
159 | | } |
160 | | } |
161 | | |
162 | | /* Check individual operators */ |
163 | | for (i = 0; i < oprlist->n_members; i++) |
164 | | { |
165 | | HeapTuple oprtup = &oprlist->members[i]->tuple; |
166 | | Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup); |
167 | | |
168 | | /* TODO: Check that only allowed strategy numbers exist */ |
169 | | if (oprform->amopstrategy < 1 || oprform->amopstrategy > 63) |
170 | | { |
171 | | 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 | | result = false; |
178 | | } |
179 | | |
180 | | /* gin doesn't support ORDER BY operators */ |
181 | | if (oprform->amoppurpose != AMOP_SEARCH || |
182 | | OidIsValid(oprform->amopsortfamily)) |
183 | | { |
184 | | 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 | | result = false; |
190 | | } |
191 | | |
192 | | /* Check operator signature --- same for all gin strategies */ |
193 | | if (!check_amop_signature(oprform->amopopr, BOOLOID, |
194 | | oprform->amoplefttype, |
195 | | oprform->amoprighttype)) |
196 | | { |
197 | | 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 | | result = false; |
203 | | } |
204 | | } |
205 | | |
206 | | /* Now check for inconsistent groups of operators/functions */ |
207 | | grouplist = identify_opfamily_groups(oprlist, proclist); |
208 | | opclassgroup = NULL; |
209 | | foreach(lc, grouplist) |
210 | | { |
211 | | OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc); |
212 | | |
213 | | /* Remember the group exactly matching the test opclass */ |
214 | | if (thisgroup->lefttype == opcintype && |
215 | | thisgroup->righttype == opcintype) |
216 | | 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 | | for (i = 1; i <= GINNProcs; i++) |
230 | | { |
231 | | if (opclassgroup && |
232 | | (opclassgroup->functionset & (((uint64) 1) << i)) != 0) |
233 | | continue; /* got it */ |
234 | | if (i == GIN_COMPARE_PROC || i == GIN_COMPARE_PARTIAL_PROC || |
235 | | i == GIN_OPTIONS_PROC) |
236 | | continue; /* optional method */ |
237 | | if (i == GIN_CONSISTENT_PROC || i == GIN_TRICONSISTENT_PROC) |
238 | | continue; /* don't need both, see check below loop */ |
239 | | 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 | | result = false; |
244 | | } |
245 | | if (!opclassgroup || |
246 | | ((opclassgroup->functionset & (1 << GIN_CONSISTENT_PROC)) == 0 && |
247 | | (opclassgroup->functionset & (1 << GIN_TRICONSISTENT_PROC)) == 0)) |
248 | | { |
249 | | 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 | | result = false; |
255 | | } |
256 | | |
257 | | |
258 | | ReleaseCatCacheList(proclist); |
259 | | ReleaseCatCacheList(oprlist); |
260 | | ReleaseSysCache(classtup); |
261 | | |
262 | | return result; |
263 | | } |
264 | | |
265 | | /* |
266 | | * Prechecking function for adding operators/functions to a GIN opfamily. |
267 | | */ |
268 | | void |
269 | | ginadjustmembers(Oid opfamilyoid, |
270 | | Oid opclassoid, |
271 | | List *operators, |
272 | | List *functions) |
273 | 0 | { |
274 | 0 | 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 | 0 | foreach(lc, operators) |
284 | 0 | { |
285 | 0 | OpFamilyMember *op = (OpFamilyMember *) lfirst(lc); |
286 | |
|
287 | 0 | op->ref_is_hard = false; |
288 | 0 | op->ref_is_family = true; |
289 | 0 | op->refobjid = opfamilyoid; |
290 | 0 | } |
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 | 0 | foreach(lc, functions) |
300 | 0 | { |
301 | 0 | OpFamilyMember *op = (OpFamilyMember *) lfirst(lc); |
302 | |
|
303 | 0 | switch (op->number) |
304 | 0 | { |
305 | 0 | case GIN_EXTRACTVALUE_PROC: |
306 | 0 | case GIN_EXTRACTQUERY_PROC: |
307 | | /* Required support function */ |
308 | 0 | op->ref_is_hard = true; |
309 | 0 | break; |
310 | 0 | case GIN_COMPARE_PROC: |
311 | 0 | case GIN_CONSISTENT_PROC: |
312 | 0 | case GIN_COMPARE_PARTIAL_PROC: |
313 | 0 | case GIN_TRICONSISTENT_PROC: |
314 | 0 | case GIN_OPTIONS_PROC: |
315 | | /* Optional, so force it to be a soft family dependency */ |
316 | 0 | op->ref_is_hard = false; |
317 | 0 | op->ref_is_family = true; |
318 | 0 | op->refobjid = opfamilyoid; |
319 | 0 | break; |
320 | 0 | default: |
321 | 0 | ereport(ERROR, |
322 | 0 | (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), |
323 | 0 | errmsg("support function number %d is invalid for access method %s", |
324 | 0 | op->number, "gin"))); |
325 | 0 | break; |
326 | 0 | } |
327 | 0 | } |
328 | 0 | } |