/src/postgres/src/backend/commands/proclang.c
Line | Count | Source |
1 | | /*------------------------------------------------------------------------- |
2 | | * |
3 | | * proclang.c |
4 | | * PostgreSQL LANGUAGE support code. |
5 | | * |
6 | | * Portions Copyright (c) 1996-2026, PostgreSQL Global Development Group |
7 | | * Portions Copyright (c) 1994, Regents of the University of California |
8 | | * |
9 | | * IDENTIFICATION |
10 | | * src/backend/commands/proclang.c |
11 | | * |
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_language.h" |
23 | | #include "catalog/pg_proc.h" |
24 | | #include "catalog/pg_type.h" |
25 | | #include "commands/proclang.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 | | /* |
35 | | * CREATE LANGUAGE |
36 | | */ |
37 | | ObjectAddress |
38 | | CreateProceduralLanguage(CreatePLangStmt *stmt) |
39 | 0 | { |
40 | 0 | const char *languageName = stmt->plname; |
41 | 0 | Oid languageOwner = GetUserId(); |
42 | 0 | Oid handlerOid, |
43 | 0 | inlineOid, |
44 | 0 | valOid; |
45 | 0 | Oid funcrettype; |
46 | 0 | Oid funcargtypes[1]; |
47 | 0 | Relation rel; |
48 | 0 | TupleDesc tupDesc; |
49 | 0 | Datum values[Natts_pg_language]; |
50 | 0 | bool nulls[Natts_pg_language]; |
51 | 0 | bool replaces[Natts_pg_language]; |
52 | 0 | NameData langname; |
53 | 0 | HeapTuple oldtup; |
54 | 0 | HeapTuple tup; |
55 | 0 | Oid langoid; |
56 | 0 | bool is_update; |
57 | 0 | ObjectAddress myself, |
58 | 0 | referenced; |
59 | 0 | ObjectAddresses *addrs; |
60 | | |
61 | | /* |
62 | | * Check permission |
63 | | */ |
64 | 0 | if (!superuser()) |
65 | 0 | ereport(ERROR, |
66 | 0 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
67 | 0 | errmsg("must be superuser to create custom procedural language"))); |
68 | | |
69 | | /* |
70 | | * Lookup the PL handler function and check that it is of the expected |
71 | | * return type |
72 | | */ |
73 | 0 | Assert(stmt->plhandler); |
74 | 0 | handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false); |
75 | 0 | funcrettype = get_func_rettype(handlerOid); |
76 | 0 | if (funcrettype != LANGUAGE_HANDLEROID) |
77 | 0 | ereport(ERROR, |
78 | 0 | (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
79 | 0 | errmsg("function %s must return type %s", |
80 | 0 | NameListToString(stmt->plhandler), "language_handler"))); |
81 | | |
82 | | /* validate the inline function */ |
83 | 0 | if (stmt->plinline) |
84 | 0 | { |
85 | 0 | funcargtypes[0] = INTERNALOID; |
86 | 0 | inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false); |
87 | | /* return value is ignored, so we don't check the type */ |
88 | 0 | } |
89 | 0 | else |
90 | 0 | inlineOid = InvalidOid; |
91 | | |
92 | | /* validate the validator function */ |
93 | 0 | if (stmt->plvalidator) |
94 | 0 | { |
95 | 0 | funcargtypes[0] = OIDOID; |
96 | 0 | valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false); |
97 | | /* return value is ignored, so we don't check the type */ |
98 | 0 | } |
99 | 0 | else |
100 | 0 | valOid = InvalidOid; |
101 | | |
102 | | /* ok to create it */ |
103 | 0 | rel = table_open(LanguageRelationId, RowExclusiveLock); |
104 | 0 | tupDesc = RelationGetDescr(rel); |
105 | | |
106 | | /* Prepare data to be inserted */ |
107 | 0 | memset(values, 0, sizeof(values)); |
108 | 0 | memset(nulls, false, sizeof(nulls)); |
109 | 0 | memset(replaces, true, sizeof(replaces)); |
110 | |
|
111 | 0 | namestrcpy(&langname, languageName); |
112 | 0 | values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname); |
113 | 0 | values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner); |
114 | 0 | values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true); |
115 | 0 | values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(stmt->pltrusted); |
116 | 0 | values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid); |
117 | 0 | values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid); |
118 | 0 | values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid); |
119 | 0 | nulls[Anum_pg_language_lanacl - 1] = true; |
120 | | |
121 | | /* Check for pre-existing definition */ |
122 | 0 | oldtup = SearchSysCache1(LANGNAME, PointerGetDatum(languageName)); |
123 | |
|
124 | 0 | if (HeapTupleIsValid(oldtup)) |
125 | 0 | { |
126 | 0 | Form_pg_language oldform = (Form_pg_language) GETSTRUCT(oldtup); |
127 | | |
128 | | /* There is one; okay to replace it? */ |
129 | 0 | if (!stmt->replace) |
130 | 0 | ereport(ERROR, |
131 | 0 | (errcode(ERRCODE_DUPLICATE_OBJECT), |
132 | 0 | errmsg("language \"%s\" already exists", languageName))); |
133 | | |
134 | | /* This is currently pointless, since we already checked superuser */ |
135 | | #ifdef NOT_USED |
136 | | if (!object_ownercheck(LanguageRelationId, oldform->oid, languageOwner)) |
137 | | aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE, |
138 | | languageName); |
139 | | #endif |
140 | | |
141 | | /* |
142 | | * Do not change existing oid, ownership or permissions. Note |
143 | | * dependency-update code below has to agree with this decision. |
144 | | */ |
145 | 0 | replaces[Anum_pg_language_oid - 1] = false; |
146 | 0 | replaces[Anum_pg_language_lanowner - 1] = false; |
147 | 0 | replaces[Anum_pg_language_lanacl - 1] = false; |
148 | | |
149 | | /* Okay, do it... */ |
150 | 0 | tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces); |
151 | 0 | CatalogTupleUpdate(rel, &tup->t_self, tup); |
152 | |
|
153 | 0 | langoid = oldform->oid; |
154 | 0 | ReleaseSysCache(oldtup); |
155 | 0 | is_update = true; |
156 | 0 | } |
157 | 0 | else |
158 | 0 | { |
159 | | /* Creating a new language */ |
160 | 0 | langoid = GetNewOidWithIndex(rel, LanguageOidIndexId, |
161 | 0 | Anum_pg_language_oid); |
162 | 0 | values[Anum_pg_language_oid - 1] = ObjectIdGetDatum(langoid); |
163 | 0 | tup = heap_form_tuple(tupDesc, values, nulls); |
164 | 0 | CatalogTupleInsert(rel, tup); |
165 | 0 | is_update = false; |
166 | 0 | } |
167 | | |
168 | | /* |
169 | | * Create dependencies for the new language. If we are updating an |
170 | | * existing language, first delete any existing pg_depend entries. |
171 | | * (However, since we are not changing ownership or permissions, the |
172 | | * shared dependencies do *not* need to change, and we leave them alone.) |
173 | | */ |
174 | 0 | myself.classId = LanguageRelationId; |
175 | 0 | myself.objectId = langoid; |
176 | 0 | myself.objectSubId = 0; |
177 | |
|
178 | 0 | if (is_update) |
179 | 0 | deleteDependencyRecordsFor(myself.classId, myself.objectId, true); |
180 | | |
181 | | /* dependency on owner of language */ |
182 | 0 | if (!is_update) |
183 | 0 | recordDependencyOnOwner(myself.classId, myself.objectId, |
184 | 0 | languageOwner); |
185 | | |
186 | | /* dependency on extension */ |
187 | 0 | recordDependencyOnCurrentExtension(&myself, is_update); |
188 | |
|
189 | 0 | addrs = new_object_addresses(); |
190 | | |
191 | | /* dependency on the PL handler function */ |
192 | 0 | ObjectAddressSet(referenced, ProcedureRelationId, handlerOid); |
193 | 0 | add_exact_object_address(&referenced, addrs); |
194 | | |
195 | | /* dependency on the inline handler function, if any */ |
196 | 0 | if (OidIsValid(inlineOid)) |
197 | 0 | { |
198 | 0 | ObjectAddressSet(referenced, ProcedureRelationId, inlineOid); |
199 | 0 | add_exact_object_address(&referenced, addrs); |
200 | 0 | } |
201 | | |
202 | | /* dependency on the validator function, if any */ |
203 | 0 | if (OidIsValid(valOid)) |
204 | 0 | { |
205 | 0 | ObjectAddressSet(referenced, ProcedureRelationId, valOid); |
206 | 0 | add_exact_object_address(&referenced, addrs); |
207 | 0 | } |
208 | |
|
209 | 0 | record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); |
210 | 0 | free_object_addresses(addrs); |
211 | | |
212 | | /* Post creation hook for new procedural language */ |
213 | 0 | InvokeObjectPostCreateHook(LanguageRelationId, myself.objectId, 0); |
214 | |
|
215 | 0 | table_close(rel, RowExclusiveLock); |
216 | |
|
217 | 0 | return myself; |
218 | 0 | } |
219 | | |
220 | | /* |
221 | | * get_language_oid - given a language name, look up the OID |
222 | | * |
223 | | * If missing_ok is false, throw an error if language name not found. If |
224 | | * true, just return InvalidOid. |
225 | | */ |
226 | | Oid |
227 | | get_language_oid(const char *langname, bool missing_ok) |
228 | 0 | { |
229 | 0 | Oid oid; |
230 | |
|
231 | 0 | oid = GetSysCacheOid1(LANGNAME, Anum_pg_language_oid, |
232 | 0 | CStringGetDatum(langname)); |
233 | 0 | if (!OidIsValid(oid) && !missing_ok) |
234 | 0 | ereport(ERROR, |
235 | 0 | (errcode(ERRCODE_UNDEFINED_OBJECT), |
236 | 0 | errmsg("language \"%s\" does not exist", langname))); |
237 | 0 | return oid; |
238 | 0 | } |