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 |
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 |
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 |
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 |
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 |
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 |
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 * |
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 * |
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 |
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 | |