/*------------------------------------------------------------------------- * * proclang.c * PostgreSQL LANGUAGE support code. * * Portions Copyright (c) 1996-2023, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * IDENTIFICATION * src/backend/commands/proclang.c * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/table.h" #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/objectaccess.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "commands/defrem.h" #include "commands/proclang.h" #include "miscadmin.h" #include "parser/parse_func.h" #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" /* * CREATE LANGUAGE */ ObjectAddress CreateProceduralLanguage(CreatePLangStmt *stmt) { const char *languageName = stmt->plname; Oid languageOwner = GetUserId(); Oid handlerOid, inlineOid, valOid; Oid funcrettype; Oid funcargtypes[1]; Relation rel; TupleDesc tupDesc; Datum values[Natts_pg_language]; bool nulls[Natts_pg_language]; bool replaces[Natts_pg_language]; NameData langname; HeapTuple oldtup; HeapTuple tup; Oid langoid; bool is_update; ObjectAddress myself, referenced; ObjectAddresses *addrs; /* * Check permission */ if (!superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to create custom procedural language"))); /* * Lookup the PL handler function and check that it is of the expected * return type */ Assert(stmt->plhandler); handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false); funcrettype = get_func_rettype(handlerOid); if (funcrettype != LANGUAGE_HANDLEROID) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("function %s must return type %s", NameListToString(stmt->plhandler), "language_handler"))); /* validate the inline function */ if (stmt->plinline) { funcargtypes[0] = INTERNALOID; inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false); /* return value is ignored, so we don't check the type */ } else inlineOid = InvalidOid; /* validate the validator function */ if (stmt->plvalidator) { funcargtypes[0] = OIDOID; valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false); /* return value is ignored, so we don't check the type */ } else valOid = InvalidOid; /* ok to create it */ rel = table_open(LanguageRelationId, RowExclusiveLock); tupDesc = RelationGetDescr(rel); /* Prepare data to be inserted */ memset(values, 0, sizeof(values)); memset(nulls, false, sizeof(nulls)); memset(replaces, true, sizeof(replaces)); namestrcpy(&langname, languageName); values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname); values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner); values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true); values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(stmt->pltrusted); values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid); values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid); values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid); nulls[Anum_pg_language_lanacl - 1] = true; /* Check for pre-existing definition */ oldtup = SearchSysCache1(LANGNAME, PointerGetDatum(languageName)); if (HeapTupleIsValid(oldtup)) { Form_pg_language oldform = (Form_pg_language) GETSTRUCT(oldtup); /* There is one; okay to replace it? */ if (!stmt->replace) ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("language \"%s\" already exists", languageName))); /* This is currently pointless, since we already checked superuser */ #ifdef NOT_USED if (!object_ownercheck(LanguageRelationId, oldform->oid, languageOwner)) aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE, languageName); #endif /* * Do not change existing oid, ownership or permissions. Note * dependency-update code below has to agree with this decision. */ replaces[Anum_pg_language_oid - 1] = false; replaces[Anum_pg_language_lanowner - 1] = false; replaces[Anum_pg_language_lanacl - 1] = false; /* Okay, do it... */ tup = heap_modify_tuple(oldtup, tupDesc, values, nulls, replaces); CatalogTupleUpdate(rel, &tup->t_self, tup); langoid = oldform->oid; ReleaseSysCache(oldtup); is_update = true; } else { /* Creating a new language */ langoid = GetNewOidWithIndex(rel, LanguageOidIndexId, Anum_pg_language_oid); values[Anum_pg_language_oid - 1] = ObjectIdGetDatum(langoid); tup = heap_form_tuple(tupDesc, values, nulls); CatalogTupleInsert(rel, tup); is_update = false; } /* * Create dependencies for the new language. If we are updating an * existing language, first delete any existing pg_depend entries. * (However, since we are not changing ownership or permissions, the * shared dependencies do *not* need to change, and we leave them alone.) */ myself.classId = LanguageRelationId; myself.objectId = langoid; myself.objectSubId = 0; if (is_update) deleteDependencyRecordsFor(myself.classId, myself.objectId, true); /* dependency on owner of language */ if (!is_update) recordDependencyOnOwner(myself.classId, myself.objectId, languageOwner); /* dependency on extension */ recordDependencyOnCurrentExtension(&myself, is_update); addrs = new_object_addresses(); /* dependency on the PL handler function */ ObjectAddressSet(referenced, ProcedureRelationId, handlerOid); add_exact_object_address(&referenced, addrs); /* dependency on the inline handler function, if any */ if (OidIsValid(inlineOid)) { ObjectAddressSet(referenced, ProcedureRelationId, inlineOid); add_exact_object_address(&referenced, addrs); } /* dependency on the validator function, if any */ if (OidIsValid(valOid)) { ObjectAddressSet(referenced, ProcedureRelationId, valOid); add_exact_object_address(&referenced, addrs); } record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); free_object_addresses(addrs); /* Post creation hook for new procedural language */ InvokeObjectPostCreateHook(LanguageRelationId, myself.objectId, 0); table_close(rel, RowExclusiveLock); return myself; } /* * get_language_oid - given a language name, look up the OID * * If missing_ok is false, throw an error if language name not found. If * true, just return InvalidOid. */ Oid get_language_oid(const char *langname, bool missing_ok) { Oid oid; oid = GetSysCacheOid1(LANGNAME, Anum_pg_language_oid, CStringGetDatum(langname)); if (!OidIsValid(oid) && !missing_ok) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("language \"%s\" does not exist", langname))); return oid; }