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