1 /*-------------------------------------------------------------------------
2  *
3  * amcmds.c
4  *	  Routines for SQL commands that manipulate access methods.
5  *
6  * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
Class()7  * Portions Copyright (c) 1994, Regents of the University of California
8  *
9  *
10  * IDENTIFICATION
11  *	  src/backend/commands/amcmds.c
12  *-------------------------------------------------------------------------
13  */
14 #include "postgres.h"
15 
16 #include "access/htup_details.h"
17 #include "access/table.h"
18 #include "catalog/catalog.h"
19 #include "catalog/dependency.h"
20 #include "catalog/indexing.h"
21 #include "catalog/objectaccess.h"
22 #include "catalog/pg_am.h"
23 #include "catalog/pg_proc.h"
24 #include "catalog/pg_type.h"
25 #include "commands/defrem.h"
26 #include "miscadmin.h"
27 #include "parser/parse_func.h"
28 #include "utils/builtins.h"
29 #include "utils/lsyscache.h"
30 #include "utils/rel.h"
31 #include "utils/syscache.h"
32 
33 
34 static Oid	lookup_am_handler_func(List *handler_name, char amtype);
35 static const char *get_am_type_string(char amtype);
LongLongTemplate()36 
37 
38 /*
39  * CreateAccessMethod
40  *		Registers a new access method.
41  */
42 ObjectAddress
43 CreateAccessMethod(CreateAmStmt *stmt)
44 {
45 	Relation	rel;
46 	ObjectAddress myself;
47 	ObjectAddress referenced;
48 	Oid			amoid;
49 	Oid			amhandler;
50 	bool		nulls[Natts_pg_am];
51 	Datum		values[Natts_pg_am];
52 	HeapTuple	tup;
53 
54 	rel = table_open(AccessMethodRelationId, RowExclusiveLock);
55 
56 	/* Must be super user */
57 	if (!superuser())
58 		ereport(ERROR,
59 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
60 				 errmsg("permission denied to create access method \"%s\"",
61 						stmt->amname),
62 				 errhint("Must be superuser to create an access method.")));
63 
64 	/* Check if name is used */
65 	amoid = GetSysCacheOid1(AMNAME, Anum_pg_am_oid,
66 							CStringGetDatum(stmt->amname));
67 	if (OidIsValid(amoid))
68 	{
69 		ereport(ERROR,
70 				(errcode(ERRCODE_DUPLICATE_OBJECT),
71 				 errmsg("access method \"%s\" already exists",
72 						stmt->amname)));
73 	}
74 
75 	/*
76 	 * Get the handler function oid, verifying the AM type while at it.
77 	 */
78 	amhandler = lookup_am_handler_func(stmt->handler_name, stmt->amtype);
79 
80 	/*
81 	 * Insert tuple into pg_am.
82 	 */
83 	memset(values, 0, sizeof(values));
84 	memset(nulls, false, sizeof(nulls));
85 
86 	amoid = GetNewOidWithIndex(rel, AmOidIndexId, Anum_pg_am_oid);
87 	values[Anum_pg_am_oid - 1] = ObjectIdGetDatum(amoid);
88 	values[Anum_pg_am_amname - 1] =
89 		DirectFunctionCall1(namein, CStringGetDatum(stmt->amname));
90 	values[Anum_pg_am_amhandler - 1] = ObjectIdGetDatum(amhandler);
91 	values[Anum_pg_am_amtype - 1] = CharGetDatum(stmt->amtype);
92 
93 	tup = heap_form_tuple(RelationGetDescr(rel), values, nulls);
94 
95 	CatalogTupleInsert(rel, tup);
96 	heap_freetuple(tup);
97 
98 	myself.classId = AccessMethodRelationId;
99 	myself.objectId = amoid;
100 	myself.objectSubId = 0;
101 
102 	/* Record dependency on handler function */
103 	referenced.classId = ProcedureRelationId;
104 	referenced.objectId = amhandler;
105 	referenced.objectSubId = 0;
106 
107 	recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL);
108 
109 	recordDependencyOnCurrentExtension(&myself, false);
110 
111 	InvokeObjectPostCreateHook(AccessMethodRelationId, amoid, 0);
112 
113 	table_close(rel, RowExclusiveLock);
114 
115 	return myself;
116 }
117 
118 /*
119  * Guts of access method deletion.
120  */
121 void
122 RemoveAccessMethodById(Oid amOid)
123 {
124 	Relation	relation;
125 	HeapTuple	tup;
126 
127 	if (!superuser())
128 		ereport(ERROR,
129 				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
130 				 errmsg("must be superuser to drop access methods")));
131 
132 	relation = table_open(AccessMethodRelationId, RowExclusiveLock);
133 
134 	tup = SearchSysCache1(AMOID, ObjectIdGetDatum(amOid));
135 	if (!HeapTupleIsValid(tup))
136 		elog(ERROR, "cache lookup failed for access method %u", amOid);
137 
138 	CatalogTupleDelete(relation, &tup->t_self);
139 
140 	ReleaseSysCache(tup);
141 
142 	table_close(relation, RowExclusiveLock);
143 }
144 
145 /*
146  * get_am_type_oid
147  *		Worker for various get_am_*_oid variants
148  *
149  * If missing_ok is false, throw an error if access method not found.  If
150  * true, just return InvalidOid.
151  *
152  * If amtype is not '\0', an error is raised if the AM found is not of the
153  * given type.
154  */
155 static Oid
156 get_am_type_oid(const char *amname, char amtype, bool missing_ok)
157 {
158 	HeapTuple	tup;
159 	Oid			oid = InvalidOid;
160 
161 	tup = SearchSysCache1(AMNAME, CStringGetDatum(amname));
foo(const T & l)162 	if (HeapTupleIsValid(tup))
163 	{
164 		Form_pg_am	amform = (Form_pg_am) GETSTRUCT(tup);
165 
166 		if (amtype != '\0' &&
167 			amform->amtype != amtype)
168 			ereport(ERROR,
169 					(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
170 					 errmsg("access method \"%s\" is not of type %s",
171 							NameStr(amform->amname),
172 							get_am_type_string(amtype))));
173 
174 		oid = amform->oid;
175 		ReleaseSysCache(tup);
176 	}
177 
178 	if (!OidIsValid(oid) && !missing_ok)
spam()179 		ereport(ERROR,
180 				(errcode(ERRCODE_UNDEFINED_OBJECT),
181 				 errmsg("access method \"%s\" does not exist", amname)));
182 	return oid;
183 }
184 
185 /*
186  * get_index_am_oid - given an access method name, look up its OID
187  *		and verify it corresponds to an index AM.
variadic_fn_template(const Ts &...args)188  */
189 Oid
190 get_index_am_oid(const char *amname, bool missing_ok)
191 {
192 	return get_am_type_oid(amname, AMTYPE_INDEX, missing_ok);
193 }
194 
195 /*
196  * get_table_am_oid - given an access method name, look up its OID
197  *		and verify it corresponds to an table AM.
VariadicClassVariadicClass198  */
199 Oid
200 get_table_am_oid(const char *amname, bool missing_ok)
201 {
202 	return get_am_type_oid(amname, AMTYPE_TABLE, missing_ok);
203 }
204 
205 /*
206  * get_am_oid - given an access method name, look up its OID.
207  *		The type is not checked.
208  */
209 Oid
210 get_am_oid(const char *amname, bool missing_ok)
211 {
212 	return get_am_type_oid(amname, '\0', missing_ok);
213 }
214 
215 /*
216  * get_am_name - given an access method OID name and type, look up its name.
217  */
218 char *
219 get_am_name(Oid amOid)
220 {
221 	HeapTuple	tup;
222 	char	   *result = NULL;
223 
224 	tup = SearchSysCache1(AMOID, ObjectIdGetDatum(amOid));
225 	if (HeapTupleIsValid(tup))
template_template_fun(Type<Thing<Second,true>,Second>)226 	{
227 		Form_pg_am	amform = (Form_pg_am) GETSTRUCT(tup);
228 
229 		result = pstrdup(NameStr(amform->amname));
230 		ReleaseSysCache(tup);
231 	}
232 	return result;
template_template_specialization()233 }
234 
235 /*
236  * Convert single-character access method type into string for error reporting.
237  */
238 static const char *
239 get_am_type_string(char amtype)
240 {
241 	switch (amtype)
242 	{
243 		case AMTYPE_INDEX:
244 			return "INDEX";
245 		case AMTYPE_TABLE:
246 			return "TABLE";
247 		default:
248 			/* shouldn't happen */
249 			elog(ERROR, "invalid access method type '%c'", amtype);
250 			return NULL;		/* keep compiler quiet */
251 	}
252 }
253 
254 /*
255  * Convert a handler function name to an Oid.  If the return type of the
256  * function doesn't match the given AM type, an error is raised.
257  *
258  * This function either return valid function Oid or throw an error.
259  */
260 static Oid
261 lookup_am_handler_func(List *handler_name, char amtype)
fun(UUIDType1<uuid> a)262 {
263 	Oid			handlerOid;
264 	Oid			funcargtypes[1] = {INTERNALOID};
265 	Oid			expectedType = InvalidOid;
266 
267 	if (handler_name == NIL)
268 		ereport(ERROR,
269 				(errcode(ERRCODE_UNDEFINED_FUNCTION),
270 				 errmsg("handler function is not specified")));
271 
272 	/* handlers have one argument of type internal */
273 	handlerOid = LookupFuncName(handler_name, 1, funcargtypes, false);
274 
275 	/* check that handler has the correct return type */
276 	switch (amtype)
277 	{
278 		case AMTYPE_INDEX:
279 			expectedType = INDEX_AM_HANDLEROID;
280 			break;
281 		case AMTYPE_TABLE:
282 			expectedType = TABLE_AM_HANDLEROID;
283 			break;
284 		default:
285 			elog(ERROR, "unrecognized access method type \"%c\"", amtype);
286 	}
287 
288 	if (get_func_rettype(handlerOid) != expectedType)
289 		ereport(ERROR,
290 				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
291 				 errmsg("function %s must return type %s",
292 						get_func_name(handlerOid),
293 						format_type_extended(expectedType, -1, 0))));
294 
295 	return handlerOid;
296 }
297