1 /*-------------------------------------------------------------------------
2  *
3  * spgvalidate.c
4  *	  Opclass validator for SP-GiST.
5  *
6  * Portions Copyright (c) 1996-2017, 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_private.h"
19 #include "catalog/pg_amop.h"
20 #include "catalog/pg_amproc.h"
21 #include "catalog/pg_opclass.h"
22 #include "catalog/pg_opfamily.h"
23 #include "catalog/pg_type.h"
24 #include "utils/builtins.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
spgvalidate(Oid opclassoid)38 spgvalidate(Oid opclassoid)
39 {
40 	bool		result = true;
41 	HeapTuple	classtup;
42 	Form_pg_opclass classform;
43 	Oid			opfamilyoid;
44 	Oid			opcintype;
45 	char	   *opclassname;
46 	HeapTuple	familytup;
47 	Form_pg_opfamily familyform;
48 	char	   *opfamilyname;
49 	CatCList   *proclist,
50 			   *oprlist;
51 	List	   *grouplist;
52 	OpFamilyOpFuncGroup *opclassgroup;
53 	int			i;
54 	ListCell   *lc;
55 
56 	/* Fetch opclass information */
57 	classtup = SearchSysCache1(CLAOID, ObjectIdGetDatum(opclassoid));
58 	if (!HeapTupleIsValid(classtup))
59 		elog(ERROR, "cache lookup failed for operator class %u", opclassoid);
60 	classform = (Form_pg_opclass) GETSTRUCT(classtup);
61 
62 	opfamilyoid = classform->opcfamily;
63 	opcintype = classform->opcintype;
64 	opclassname = NameStr(classform->opcname);
65 
66 	/* Fetch opfamily information */
67 	familytup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(opfamilyoid));
68 	if (!HeapTupleIsValid(familytup))
69 		elog(ERROR, "cache lookup failed for operator family %u", opfamilyoid);
70 	familyform = (Form_pg_opfamily) GETSTRUCT(familytup);
71 
72 	opfamilyname = NameStr(familyform->opfname);
73 
74 	/* Fetch all operators and support functions of the opfamily */
75 	oprlist = SearchSysCacheList1(AMOPSTRATEGY, ObjectIdGetDatum(opfamilyoid));
76 	proclist = SearchSysCacheList1(AMPROCNUM, ObjectIdGetDatum(opfamilyoid));
77 
78 	/* Check individual support functions */
79 	for (i = 0; i < proclist->n_members; i++)
80 	{
81 		HeapTuple	proctup = &proclist->members[i]->tuple;
82 		Form_pg_amproc procform = (Form_pg_amproc) GETSTRUCT(proctup);
83 		bool		ok;
84 
85 		/*
86 		 * All SP-GiST support functions should be registered with matching
87 		 * left/right types
88 		 */
89 		if (procform->amproclefttype != procform->amprocrighttype)
90 		{
91 			ereport(INFO,
92 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
93 					 errmsg("operator family \"%s\" of access method %s contains support procedure %s with different left and right input types",
94 							opfamilyname, "spgist",
95 							format_procedure(procform->amproc))));
96 			result = false;
97 		}
98 
99 		/* Check procedure numbers and function signatures */
100 		switch (procform->amprocnum)
101 		{
102 			case SPGIST_CONFIG_PROC:
103 			case SPGIST_CHOOSE_PROC:
104 			case SPGIST_PICKSPLIT_PROC:
105 			case SPGIST_INNER_CONSISTENT_PROC:
106 				ok = check_amproc_signature(procform->amproc, VOIDOID, true,
107 											2, 2, INTERNALOID, INTERNALOID);
108 				break;
109 			case SPGIST_LEAF_CONSISTENT_PROC:
110 				ok = check_amproc_signature(procform->amproc, BOOLOID, true,
111 											2, 2, INTERNALOID, INTERNALOID);
112 				break;
113 			default:
114 				ereport(INFO,
115 						(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
116 						 errmsg("operator family \"%s\" of access method %s contains function %s with invalid support number %d",
117 								opfamilyname, "spgist",
118 								format_procedure(procform->amproc),
119 								procform->amprocnum)));
120 				result = false;
121 				continue;		/* don't want additional message */
122 		}
123 
124 		if (!ok)
125 		{
126 			ereport(INFO,
127 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
128 					 errmsg("operator family \"%s\" of access method %s contains function %s with wrong signature for support number %d",
129 							opfamilyname, "spgist",
130 							format_procedure(procform->amproc),
131 							procform->amprocnum)));
132 			result = false;
133 		}
134 	}
135 
136 	/* Check individual operators */
137 	for (i = 0; i < oprlist->n_members; i++)
138 	{
139 		HeapTuple	oprtup = &oprlist->members[i]->tuple;
140 		Form_pg_amop oprform = (Form_pg_amop) GETSTRUCT(oprtup);
141 
142 		/* TODO: Check that only allowed strategy numbers exist */
143 		if (oprform->amopstrategy < 1 || oprform->amopstrategy > 63)
144 		{
145 			ereport(INFO,
146 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
147 					 errmsg("operator family \"%s\" of access method %s contains operator %s with invalid strategy number %d",
148 							opfamilyname, "spgist",
149 							format_operator(oprform->amopopr),
150 							oprform->amopstrategy)));
151 			result = false;
152 		}
153 
154 		/* spgist doesn't support ORDER BY operators */
155 		if (oprform->amoppurpose != AMOP_SEARCH ||
156 			OidIsValid(oprform->amopsortfamily))
157 		{
158 			ereport(INFO,
159 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
160 					 errmsg("operator family \"%s\" of access method %s contains invalid ORDER BY specification for operator %s",
161 							opfamilyname, "spgist",
162 							format_operator(oprform->amopopr))));
163 			result = false;
164 		}
165 
166 		/* Check operator signature --- same for all spgist strategies */
167 		if (!check_amop_signature(oprform->amopopr, BOOLOID,
168 								  oprform->amoplefttype,
169 								  oprform->amoprighttype))
170 		{
171 			ereport(INFO,
172 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
173 					 errmsg("operator family \"%s\" of access method %s contains operator %s with wrong signature",
174 							opfamilyname, "spgist",
175 							format_operator(oprform->amopopr))));
176 			result = false;
177 		}
178 	}
179 
180 	/* Now check for inconsistent groups of operators/functions */
181 	grouplist = identify_opfamily_groups(oprlist, proclist);
182 	opclassgroup = NULL;
183 	foreach(lc, grouplist)
184 	{
185 		OpFamilyOpFuncGroup *thisgroup = (OpFamilyOpFuncGroup *) lfirst(lc);
186 
187 		/* Remember the group exactly matching the test opclass */
188 		if (thisgroup->lefttype == opcintype &&
189 			thisgroup->righttype == opcintype)
190 			opclassgroup = thisgroup;
191 
192 		/*
193 		 * Complain if there are any datatype pairs with functions but no
194 		 * operators.  This is about the best we can do for now to detect
195 		 * missing operators.
196 		 */
197 		if (thisgroup->operatorset == 0)
198 		{
199 			ereport(INFO,
200 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
201 					 errmsg("operator family \"%s\" of access method %s is missing operator(s) for types %s and %s",
202 							opfamilyname, "spgist",
203 							format_type_be(thisgroup->lefttype),
204 							format_type_be(thisgroup->righttype))));
205 			result = false;
206 		}
207 
208 		/*
209 		 * Complain if we're missing functions for any datatype, remembering
210 		 * that SP-GiST doesn't use cross-type support functions.
211 		 */
212 		if (thisgroup->lefttype != thisgroup->righttype)
213 			continue;
214 
215 		for (i = 1; i <= SPGISTNProc; i++)
216 		{
217 			if ((thisgroup->functionset & (((uint64) 1) << i)) != 0)
218 				continue;		/* got it */
219 			ereport(INFO,
220 					(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
221 					 errmsg("operator family \"%s\" of access method %s is missing support function %d for type %s",
222 							opfamilyname, "spgist", i,
223 							format_type_be(thisgroup->lefttype))));
224 			result = false;
225 		}
226 	}
227 
228 	/* Check that the originally-named opclass is supported */
229 	/* (if group is there, we already checked it adequately above) */
230 	if (!opclassgroup)
231 	{
232 		ereport(INFO,
233 				(errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
234 				 errmsg("operator class \"%s\" of access method %s is missing operator(s)",
235 						opclassname, "spgist")));
236 		result = false;
237 	}
238 
239 	ReleaseCatCacheList(proclist);
240 	ReleaseCatCacheList(oprlist);
241 	ReleaseSysCache(familytup);
242 	ReleaseSysCache(classtup);
243 
244 	return result;
245 }
246