diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/catalog/Makefile | 4 | ||||
-rw-r--r-- | src/backend/catalog/catalog.c | 5 | ||||
-rw-r--r-- | src/backend/catalog/system_views.sql | 3 | ||||
-rw-r--r-- | src/backend/commands/extension.c | 175 | ||||
-rw-r--r-- | src/backend/commands/functioncmds.c | 5 | ||||
-rw-r--r-- | src/backend/commands/proclang.c | 422 | ||||
-rw-r--r-- | src/backend/parser/gram.y | 19 |
7 files changed, 239 insertions, 394 deletions
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile index d5da81c8016..f8f0b4841c3 100644 --- a/src/backend/catalog/Makefile +++ b/src/backend/catalog/Makefile @@ -58,7 +58,7 @@ CATALOG_HEADERS := \ pg_statistic_ext.h pg_statistic_ext_data.h \ pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \ pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \ - pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \ + pg_database.h pg_db_role_setting.h pg_tablespace.h \ pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ pg_ts_parser.h pg_ts_template.h pg_extension.h \ @@ -84,7 +84,7 @@ POSTGRES_BKI_DATA = $(addprefix $(top_srcdir)/src/include/catalog/,\ pg_cast.dat pg_class.dat pg_collation.dat pg_conversion.dat \ pg_database.dat pg_language.dat \ pg_namespace.dat pg_opclass.dat pg_operator.dat pg_opfamily.dat \ - pg_pltemplate.dat pg_proc.dat pg_range.dat pg_tablespace.dat \ + pg_proc.dat pg_range.dat pg_tablespace.dat \ pg_ts_config.dat pg_ts_config_map.dat pg_ts_dict.dat pg_ts_parser.dat \ pg_ts_template.dat pg_type.dat \ ) diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c index 16cb6d88714..7d6acaed92c 100644 --- a/src/backend/catalog/catalog.c +++ b/src/backend/catalog/catalog.c @@ -33,7 +33,6 @@ #include "catalog/pg_database.h" #include "catalog/pg_db_role_setting.h" #include "catalog/pg_namespace.h" -#include "catalog/pg_pltemplate.h" #include "catalog/pg_replication_origin.h" #include "catalog/pg_shdepend.h" #include "catalog/pg_shdescription.h" @@ -242,7 +241,6 @@ IsSharedRelation(Oid relationId) if (relationId == AuthIdRelationId || relationId == AuthMemRelationId || relationId == DatabaseRelationId || - relationId == PLTemplateRelationId || relationId == SharedDescriptionRelationId || relationId == SharedDependRelationId || relationId == SharedSecLabelRelationId || @@ -258,7 +256,6 @@ IsSharedRelation(Oid relationId) relationId == AuthMemMemRoleIndexId || relationId == DatabaseNameIndexId || relationId == DatabaseOidIndexId || - relationId == PLTemplateNameIndexId || relationId == SharedDescriptionObjIndexId || relationId == SharedDependDependerIndexId || relationId == SharedDependReferenceIndexId || @@ -278,8 +275,6 @@ IsSharedRelation(Oid relationId) relationId == PgDatabaseToastIndex || relationId == PgDbRoleSettingToastTable || relationId == PgDbRoleSettingToastIndex || - relationId == PgPlTemplateToastTable || - relationId == PgPlTemplateToastIndex || relationId == PgReplicationOriginToastTable || relationId == PgReplicationOriginToastIndex || relationId == PgShdescriptionToastTable || diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index c9e75f43705..c9e60600357 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -317,7 +317,8 @@ CREATE VIEW pg_available_extensions AS CREATE VIEW pg_available_extension_versions AS SELECT E.name, E.version, (X.extname IS NOT NULL) AS installed, - E.superuser, E.relocatable, E.schema, E.requires, E.comment + E.superuser, E.trusted, E.relocatable, + E.schema, E.requires, E.comment FROM pg_available_extension_versions() AS E LEFT JOIN pg_extension AS X ON E.name = X.extname AND E.version = X.extversion; diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c index 01de398dcb3..ddd46f4e2f5 100644 --- a/src/backend/commands/extension.c +++ b/src/backend/commands/extension.c @@ -40,6 +40,7 @@ #include "catalog/indexing.h" #include "catalog/namespace.h" #include "catalog/objectaccess.h" +#include "catalog/pg_authid.h" #include "catalog/pg_collation.h" #include "catalog/pg_depend.h" #include "catalog/pg_extension.h" @@ -84,6 +85,7 @@ typedef struct ExtensionControlFile char *schema; /* target schema (allowed if !relocatable) */ bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */ bool superuser; /* must be superuser to install? */ + bool trusted; /* allow becoming superuser on the fly? */ int encoding; /* encoding of the script file, or -1 */ List *requires; /* names of prerequisite extensions */ } ExtensionControlFile; @@ -558,6 +560,14 @@ parse_extension_control_file(ExtensionControlFile *control, errmsg("parameter \"%s\" requires a Boolean value", item->name))); } + else if (strcmp(item->name, "trusted") == 0) + { + if (!parse_bool(item->value, &control->trusted)) + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"%s\" requires a Boolean value", + item->name))); + } else if (strcmp(item->name, "encoding") == 0) { control->encoding = pg_valid_server_encoding(item->value); @@ -614,6 +624,7 @@ read_extension_control_file(const char *extname) control->name = pstrdup(extname); control->relocatable = false; control->superuser = true; + control->trusted = false; control->encoding = -1; /* @@ -795,6 +806,27 @@ execute_sql_string(const char *sql) } /* + * Policy function: is the given extension trusted for installation by a + * non-superuser? + * + * (Update the errhint logic below if you change this.) + */ +static bool +extension_is_trusted(ExtensionControlFile *control) +{ + AclResult aclresult; + + /* Never trust unless extension's control file says it's okay */ + if (!control->trusted) + return false; + /* Allow if user has CREATE privilege on current database */ + aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE); + if (aclresult == ACLCHECK_OK) + return true; + return false; +} + +/* * Execute the appropriate script file for installing or updating the extension * * If from_version isn't NULL, it's an update @@ -806,35 +838,56 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, List *requiredSchemas, const char *schemaName, Oid schemaOid) { + bool switch_to_superuser = false; char *filename; + Oid save_userid = 0; + int save_sec_context = 0; int save_nestlevel; StringInfoData pathbuf; ListCell *lc; /* - * Enforce superuser-ness if appropriate. We postpone this check until - * here so that the flag is correctly associated with the right script(s) - * if it's set in secondary control files. + * Enforce superuser-ness if appropriate. We postpone these checks until + * here so that the control flags are correctly associated with the right + * script(s) if they happen to be set in secondary control files. */ if (control->superuser && !superuser()) { - if (from_version == NULL) + if (extension_is_trusted(control)) + switch_to_superuser = true; + else if (from_version == NULL) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to create extension \"%s\"", control->name), - errhint("Must be superuser to create this extension."))); + control->trusted + ? errhint("Must have CREATE privilege on current database to create this extension.") + : errhint("Must be superuser to create this extension."))); else ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to update extension \"%s\"", control->name), - errhint("Must be superuser to update this extension."))); + control->trusted + ? errhint("Must have CREATE privilege on current database to update this extension.") + : errhint("Must be superuser to update this extension."))); } filename = get_extension_script_filename(control, from_version, version); /* + * If installing a trusted extension on behalf of a non-superuser, become + * the bootstrap superuser. (This switch will be cleaned up automatically + * if the transaction aborts, as will the GUC changes below.) + */ + if (switch_to_superuser) + { + GetUserIdAndSecContext(&save_userid, &save_sec_context); + SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID, + save_sec_context | SECURITY_LOCAL_USERID_CHANGE); + } + + /* * Force client_min_messages and log_min_messages to be at least WARNING, * so that we won't spam the user with useless NOTICE messages from common * script actions like creating shell types. @@ -907,6 +960,22 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, CStringGetTextDatum("ng")); /* + * If the script uses @extowner@, substitute the calling username. + */ + if (strstr(c_sql, "@extowner@")) + { + Oid uid = switch_to_superuser ? save_userid : GetUserId(); + const char *userName = GetUserNameFromId(uid, false); + const char *qUserName = quote_identifier(userName); + + t_sql = DirectFunctionCall3Coll(replace_text, + C_COLLATION_OID, + t_sql, + CStringGetTextDatum("@extowner@"), + CStringGetTextDatum(qUserName)); + } + + /* * If it's not relocatable, substitute the target schema name for * occurrences of @extschema@. * @@ -953,6 +1022,12 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control, * Restore the GUC variables we set above. */ AtEOXact_GUC(true, save_nestlevel); + + /* + * Restore authentication state if needed. + */ + if (switch_to_superuser) + SetUserIdAndSecContext(save_userid, save_sec_context); } /* @@ -2111,8 +2186,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol, { ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc); ExtensionControlFile *control; - Datum values[7]; - bool nulls[7]; + Datum values[8]; + bool nulls[8]; ListCell *lc2; if (!evi->installable) @@ -2133,24 +2208,26 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol, values[1] = CStringGetTextDatum(evi->name); /* superuser */ values[2] = BoolGetDatum(control->superuser); + /* trusted */ + values[3] = BoolGetDatum(control->trusted); /* relocatable */ - values[3] = BoolGetDatum(control->relocatable); + values[4] = BoolGetDatum(control->relocatable); /* schema */ if (control->schema == NULL) - nulls[4] = true; + nulls[5] = true; else - values[4] = DirectFunctionCall1(namein, + values[5] = DirectFunctionCall1(namein, CStringGetDatum(control->schema)); /* requires */ if (control->requires == NIL) - nulls[5] = true; + nulls[6] = true; else - values[5] = convert_requires_to_datum(control->requires); + values[6] = convert_requires_to_datum(control->requires); /* comment */ if (control->comment == NULL) - nulls[6] = true; + nulls[7] = true; else - values[6] = CStringGetTextDatum(control->comment); + values[7] = CStringGetTextDatum(control->comment); tuplestore_putvalues(tupstore, tupdesc, values, nulls); @@ -2178,16 +2255,18 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol, values[1] = CStringGetTextDatum(evi2->name); /* superuser */ values[2] = BoolGetDatum(control->superuser); + /* trusted */ + values[3] = BoolGetDatum(control->trusted); /* relocatable */ - values[3] = BoolGetDatum(control->relocatable); + values[4] = BoolGetDatum(control->relocatable); /* schema stays the same */ /* requires */ if (control->requires == NIL) - nulls[5] = true; + nulls[6] = true; else { - values[5] = convert_requires_to_datum(control->requires); - nulls[5] = false; + values[6] = convert_requires_to_datum(control->requires); + nulls[6] = false; } /* comment stays the same */ @@ -2198,6 +2277,64 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol, } /* + * Test whether the given extension exists (not whether it's installed) + * + * This checks for the existence of a matching control file in the extension + * directory. That's not a bulletproof check, since the file might be + * invalid, but this is only used for hints so it doesn't have to be 100% + * right. + */ +bool +extension_file_exists(const char *extensionName) +{ + bool result = false; + char *location; + DIR *dir; + struct dirent *de; + + location = get_extension_control_directory(); + dir = AllocateDir(location); + + /* + * If the control directory doesn't exist, we want to silently return + * false. Any other error will be reported by ReadDir. + */ + if (dir == NULL && errno == ENOENT) + { + /* do nothing */ + } + else + { + while ((de = ReadDir(dir, location)) != NULL) + { + char *extname; + + if (!is_extension_control_filename(de->d_name)) + continue; + + /* extract extension name from 'name.control' filename */ + extname = pstrdup(de->d_name); + *strrchr(extname, '.') = '\0'; + + /* ignore it if it's an auxiliary control file */ + if (strstr(extname, "--")) + continue; + + /* done if it matches request */ + if (strcmp(extname, extensionName) == 0) + { + result = true; + break; + } + } + + FreeDir(dir); + } + + return result; +} + +/* * Convert a list of extension names to a name[] Datum */ static Datum diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index c31c57e5e9a..0f40c9ee27b 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -49,6 +49,7 @@ #include "catalog/pg_type.h" #include "commands/alter.h" #include "commands/defrem.h" +#include "commands/extension.h" #include "commands/proclang.h" #include "executor/execdesc.h" #include "executor/executor.h" @@ -991,7 +992,7 @@ CreateFunction(ParseState *pstate, CreateFunctionStmt *stmt) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("language \"%s\" does not exist", language), - (PLTemplateExists(language) ? + (extension_file_exists(language) ? errhint("Use CREATE EXTENSION to load the language into the database.") : 0))); languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); @@ -2225,7 +2226,7 @@ ExecuteDoStmt(DoStmt *stmt, bool atomic) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("language \"%s\" does not exist", language), - (PLTemplateExists(language) ? + (extension_file_exists(language) ? errhint("Use CREATE EXTENSION to load the language into the database.") : 0))); languageStruct = (Form_pg_language) GETSTRUCT(languageTuple); diff --git a/src/backend/commands/proclang.c b/src/backend/commands/proclang.c index cdff43d3ce7..9d72edbfec5 100644 --- a/src/backend/commands/proclang.c +++ b/src/backend/commands/proclang.c @@ -13,329 +13,110 @@ */ #include "postgres.h" -#include "access/genam.h" -#include "access/htup_details.h" #include "access/table.h" #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/indexing.h" #include "catalog/objectaccess.h" -#include "catalog/pg_authid.h" #include "catalog/pg_language.h" #include "catalog/pg_namespace.h" -#include "catalog/pg_pltemplate.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" -#include "commands/dbcommands.h" #include "commands/defrem.h" #include "commands/proclang.h" #include "miscadmin.h" #include "parser/parse_func.h" -#include "parser/parser.h" -#include "utils/acl.h" #include "utils/builtins.h" -#include "utils/fmgroids.h" #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" -typedef struct -{ - bool tmpltrusted; /* trusted? */ - bool tmpldbacreate; /* db owner allowed to create? */ - char *tmplhandler; /* name of handler function */ - char *tmplinline; /* name of anonymous-block handler, or NULL */ - char *tmplvalidator; /* name of validator function, or NULL */ - char *tmpllibrary; /* path of shared library */ -} PLTemplate; - -static ObjectAddress create_proc_lang(const char *languageName, bool replace, - Oid languageOwner, Oid handlerOid, Oid inlineOid, - Oid valOid, bool trusted); -static PLTemplate *find_language_template(const char *languageName); - /* * CREATE LANGUAGE */ ObjectAddress CreateProceduralLanguage(CreatePLangStmt *stmt) { - PLTemplate *pltemplate; - ObjectAddress tmpAddr; + 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; /* - * If we have template information for the language, ignore the supplied - * parameters (if any) and use the template information. + * Check permission */ - if ((pltemplate = find_language_template(stmt->plname)) != NULL) - { - List *funcname; - - /* - * Give a notice if we are ignoring supplied parameters. - */ - if (stmt->plhandler) - ereport(NOTICE, - (errmsg("using pg_pltemplate information instead of CREATE LANGUAGE parameters"))); - - /* - * Check permission - */ - if (!superuser()) - { - if (!pltemplate->tmpldbacreate) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("must be superuser to create procedural language \"%s\"", - stmt->plname))); - if (!pg_database_ownercheck(MyDatabaseId, GetUserId())) - aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE, - get_database_name(MyDatabaseId)); - } - - /* - * Find or create the handler function, which we force to be in the - * pg_catalog schema. If already present, it must have the correct - * return type. - */ - funcname = SystemFuncName(pltemplate->tmplhandler); - handlerOid = LookupFuncName(funcname, 0, NULL, true); - if (OidIsValid(handlerOid)) - { - funcrettype = get_func_rettype(handlerOid); - if (funcrettype != LANGUAGE_HANDLEROID) - ereport(ERROR, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("function %s must return type %s", - NameListToString(funcname), "language_handler"))); - } - else - { - tmpAddr = ProcedureCreate(pltemplate->tmplhandler, - PG_CATALOG_NAMESPACE, - false, /* replace */ - false, /* returnsSet */ - LANGUAGE_HANDLEROID, - BOOTSTRAP_SUPERUSERID, - ClanguageId, - F_FMGR_C_VALIDATOR, - pltemplate->tmplhandler, - pltemplate->tmpllibrary, - PROKIND_FUNCTION, - false, /* security_definer */ - false, /* isLeakProof */ - false, /* isStrict */ - PROVOLATILE_VOLATILE, - PROPARALLEL_UNSAFE, - buildoidvector(funcargtypes, 0), - PointerGetDatum(NULL), - PointerGetDatum(NULL), - PointerGetDatum(NULL), - NIL, - PointerGetDatum(NULL), - PointerGetDatum(NULL), - InvalidOid, - 1, - 0); - handlerOid = tmpAddr.objectId; - } - - /* - * Likewise for the anonymous block handler, if required; but we don't - * care about its return type. - */ - if (pltemplate->tmplinline) - { - funcname = SystemFuncName(pltemplate->tmplinline); - funcargtypes[0] = INTERNALOID; - inlineOid = LookupFuncName(funcname, 1, funcargtypes, true); - if (!OidIsValid(inlineOid)) - { - tmpAddr = ProcedureCreate(pltemplate->tmplinline, - PG_CATALOG_NAMESPACE, - false, /* replace */ - false, /* returnsSet */ - VOIDOID, - BOOTSTRAP_SUPERUSERID, - ClanguageId, - F_FMGR_C_VALIDATOR, - pltemplate->tmplinline, - pltemplate->tmpllibrary, - PROKIND_FUNCTION, - false, /* security_definer */ - false, /* isLeakProof */ - true, /* isStrict */ - PROVOLATILE_VOLATILE, - PROPARALLEL_UNSAFE, - buildoidvector(funcargtypes, 1), - PointerGetDatum(NULL), - PointerGetDatum(NULL), - PointerGetDatum(NULL), - NIL, - PointerGetDatum(NULL), - PointerGetDatum(NULL), - InvalidOid, - 1, - 0); - inlineOid = tmpAddr.objectId; - } - } - else - inlineOid = InvalidOid; + 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) + { /* - * Likewise for the validator, if required; but we don't care about - * its return type. + * We allow OPAQUE just so we can load old dump files. When we see a + * handler function declared OPAQUE, change it to LANGUAGE_HANDLER. + * (This is probably obsolete and removable?) */ - if (pltemplate->tmplvalidator) + if (funcrettype == OPAQUEOID) { - funcname = SystemFuncName(pltemplate->tmplvalidator); - funcargtypes[0] = OIDOID; - valOid = LookupFuncName(funcname, 1, funcargtypes, true); - if (!OidIsValid(valOid)) - { - tmpAddr = ProcedureCreate(pltemplate->tmplvalidator, - PG_CATALOG_NAMESPACE, - false, /* replace */ - false, /* returnsSet */ - VOIDOID, - BOOTSTRAP_SUPERUSERID, - ClanguageId, - F_FMGR_C_VALIDATOR, - pltemplate->tmplvalidator, - pltemplate->tmpllibrary, - PROKIND_FUNCTION, - false, /* security_definer */ - false, /* isLeakProof */ - true, /* isStrict */ - PROVOLATILE_VOLATILE, - PROPARALLEL_UNSAFE, - buildoidvector(funcargtypes, 1), - PointerGetDatum(NULL), - PointerGetDatum(NULL), - PointerGetDatum(NULL), - NIL, - PointerGetDatum(NULL), - PointerGetDatum(NULL), - InvalidOid, - 1, - 0); - valOid = tmpAddr.objectId; - } + ereport(WARNING, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("changing return type of function %s from %s to %s", + NameListToString(stmt->plhandler), + "opaque", "language_handler"))); + SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID); } else - valOid = InvalidOid; + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("function %s must return type %s", + NameListToString(stmt->plhandler), "language_handler"))); + } - /* ok, create it */ - return create_proc_lang(stmt->plname, stmt->replace, GetUserId(), - handlerOid, inlineOid, - valOid, pltemplate->tmpltrusted); + /* 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 - { - /* - * No template, so use the provided information. If there's no - * handler clause, the user is trying to rely on a template that we - * don't have, so complain accordingly. - */ - if (!stmt->plhandler) - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("unsupported language \"%s\"", - stmt->plname), - errhint("The supported languages are listed in the pg_pltemplate system catalog."))); + inlineOid = InvalidOid; - /* - * 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 - */ - handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false); - funcrettype = get_func_rettype(handlerOid); - if (funcrettype != LANGUAGE_HANDLEROID) - { - /* - * We allow OPAQUE just so we can load old dump files. When we - * see a handler function declared OPAQUE, change it to - * LANGUAGE_HANDLER. (This is probably obsolete and removable?) - */ - if (funcrettype == OPAQUEOID) - { - ereport(WARNING, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("changing return type of function %s from %s to %s", - NameListToString(stmt->plhandler), - "opaque", "language_handler"))); - SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID); - } - else - 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, create it */ - return create_proc_lang(stmt->plname, stmt->replace, GetUserId(), - handlerOid, inlineOid, - valOid, stmt->pltrusted); + /* 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 */ } -} - -/* - * Guts of language creation. - */ -static ObjectAddress -create_proc_lang(const char *languageName, bool replace, - Oid languageOwner, Oid handlerOid, Oid inlineOid, - Oid valOid, bool trusted) -{ - 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; + else + valOid = InvalidOid; + /* ok to create it */ rel = table_open(LanguageRelationId, RowExclusiveLock); tupDesc = RelationGetDescr(rel); @@ -348,7 +129,7 @@ create_proc_lang(const char *languageName, bool replace, 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(trusted); + 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); @@ -362,13 +143,17 @@ create_proc_lang(const char *languageName, bool replace, Form_pg_language oldform = (Form_pg_language) GETSTRUCT(oldtup); /* There is one; okay to replace it? */ - if (!replace) + 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 (!pg_language_ownercheck(oldform->oid, languageOwner)) aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE, languageName); +#endif /* * Do not change existing oid, ownership or permissions. Note @@ -451,83 +236,6 @@ create_proc_lang(const char *languageName, bool replace, } /* - * Look to see if we have template information for the given language name. - */ -static PLTemplate * -find_language_template(const char *languageName) -{ - PLTemplate *result; - Relation rel; - SysScanDesc scan; - ScanKeyData key; - HeapTuple tup; - - rel = table_open(PLTemplateRelationId, AccessShareLock); - - ScanKeyInit(&key, - Anum_pg_pltemplate_tmplname, - BTEqualStrategyNumber, F_NAMEEQ, - CStringGetDatum(languageName)); - scan = systable_beginscan(rel, PLTemplateNameIndexId, true, - NULL, 1, &key); - - tup = systable_getnext(scan); - if (HeapTupleIsValid(tup)) - { - Form_pg_pltemplate tmpl = (Form_pg_pltemplate) GETSTRUCT(tup); - Datum datum; - bool isnull; - - result = (PLTemplate *) palloc0(sizeof(PLTemplate)); - result->tmpltrusted = tmpl->tmpltrusted; - result->tmpldbacreate = tmpl->tmpldbacreate; - - /* Remaining fields are variable-width so we need heap_getattr */ - datum = heap_getattr(tup, Anum_pg_pltemplate_tmplhandler, - RelationGetDescr(rel), &isnull); - if (!isnull) - result->tmplhandler = TextDatumGetCString(datum); - - datum = heap_getattr(tup, Anum_pg_pltemplate_tmplinline, - RelationGetDescr(rel), &isnull); - if (!isnull) - result->tmplinline = TextDatumGetCString(datum); - - datum = heap_getattr(tup, Anum_pg_pltemplate_tmplvalidator, - RelationGetDescr(rel), &isnull); - if (!isnull) - result->tmplvalidator = TextDatumGetCString(datum); - - datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary, - RelationGetDescr(rel), &isnull); - if (!isnull) - result->tmpllibrary = TextDatumGetCString(datum); - - /* Ignore template if handler or library info is missing */ - if (!result->tmplhandler || !result->tmpllibrary) - result = NULL; - } - else - result = NULL; - - systable_endscan(scan); - - table_close(rel, AccessShareLock); - - return result; -} - - -/* - * This just returns true if we have a valid template for a given language - */ -bool -PLTemplateExists(const char *languageName) -{ - return (find_language_template(languageName) != NULL); -} - -/* * Guts of language dropping. */ void diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y index ba5916b4d27..1b0edf5d3d1 100644 --- a/src/backend/parser/gram.y +++ b/src/backend/parser/gram.y @@ -4324,14 +4324,17 @@ NumericOnly_list: NumericOnly { $$ = list_make1($1); } CreatePLangStmt: CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst { - CreatePLangStmt *n = makeNode(CreatePLangStmt); - n->replace = $2; - n->plname = $6; - /* parameters are all to be supplied by system */ - n->plhandler = NIL; - n->plinline = NIL; - n->plvalidator = NIL; - n->pltrusted = false; + /* + * We now interpret parameterless CREATE LANGUAGE as + * CREATE EXTENSION. "OR REPLACE" is silently translated + * to "IF NOT EXISTS", which isn't quite the same, but + * seems more useful than throwing an error. We just + * ignore TRUSTED, as the previous code would have too. + */ + CreateExtensionStmt *n = makeNode(CreateExtensionStmt); + n->if_not_exists = $2; + n->extname = $6; + n->options = NIL; $$ = (Node *)n; } | CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst |