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
33static Oid lookup_am_handler_func(List *handler_name, char amtype);
34static const char *get_am_type_string(char amtype);
35
36
37/*
38 * CreateAccessMethod
39 * Registers a new access method.
40 */
41ObjectAddress
42CreateAccessMethod(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 */
118void
119RemoveAccessMethodById(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 */
152static Oid
153get_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 */
186Oid
187get_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 */
196Oid
197get_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 */
206Oid
207get_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 */
215char *
216get_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 */
235static const char *
236get_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 */
257static Oid
258lookup_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