Removing pg_pltemplate and creating "trustable" extensions

Поиск
Список
Период
Сортировка
От Tom Lane
Тема Removing pg_pltemplate and creating "trustable" extensions
Дата
Msg-id 5889.1566415762@sss.pgh.pa.us
обсуждение исходный текст
Ответы Re: Removing pg_pltemplate and creating "trustable" extensions  (Peter Eisentraut <peter.eisentraut@2ndquadrant.com>)
Список pgsql-hackers
We've repeatedly kicked around the idea of getting rid of the
pg_pltemplate catalog in favor of keeping that information directly in
the languages' extension files [1][2][3][4].  The primary abstract
argument for that is that it removes a way in which our in-tree PLs
are special compared to out-of-tree PLs, which can't have entries in
pg_pltemplate.  A concrete argument for it is that it might simplify
fixing the python-2-vs-python-3 mess, since one of the issues there
is that pg_pltemplate has hard-wired knowledge that "plpythonu" is
Python 2.  Accordingly, attached is a patch series that ends by
removing that catalog.

As I noted in [2], the main stumbling block to doing this is that
the code associated with pg_pltemplate provides a privilege override
mechanism that allows non-superuser database owners to install trusted
PLs.  For backwards compatibility if nothing else, we probably want to
keep that ability, though it'd be nice if it weren't such a hard-wired
behavior.

Patch 0001 below addresses this problem by inventing a concept of
"trustable" (not necessarily trusted) extensions.  An extension that
would normally require superuser permissions (e.g., because it creates
C functions) can now be installed by a non-superuser if (a) it is
marked trustable in the extension's control file, AND (b) it is
listed as trusted in one of two new GUCs, trusted_extensions_dba and
trusted_extensions_anyone.  (These names could stand a visit to the
bikeshed, no doubt.)  Extensions matching trusted_extensions_dba can
be installed by a database owner, while extensions matching
trusted_extensions_anyone can be installed by anybody.  The default
settings of these GUCs provide backwards-compatible behavior, but
they can be adjusted to provide more or less ability to install
extensions.  (This design is basically what Andres advocated in [2].)

In this patch series, I've only marked the trusted-PL extensions as
trustable, but we should probably make most of the contrib extensions
trustable --- not, say, adminpack, but surely most of the datatype
and transform modules could be marked trustable.  (Maybe we could
make the default GUC settings more permissive, too.)

As coded, the two GUCs are not lists of extension names but rather
regexes.  You could use them as lists, eg "^plperl$|^plpgsql$|^pltcl$"
but that's a bit tedious, especially if someone wants to trust most
or all of contrib.  I am a tad worried about user-friendliness of
this notation, but I think we need something with wild-cards, and
that's the only wild-card-capable matching engine we have available
at a low level.

You might wonder why bother with the trustable flag rather than just
relying on the GUCs.  The answer is mostly paranoia: I'm worried about
somebody writing e.g. "plperl" with no anchors and not realizing that
that will match "plperlu" as well.  Anyway, since we're talking about
potential escalation-to-superuser security problems, I think having
both belt and suspenders protection on untrusted languages is wise.

There are no regression tests for this functionality in 0001,
but I added one in 0002.

Patch 0002 converts all the in-tree PLs to use fully specified
CREATE LANGUAGE and not rely on pg_pltemplate.

I had a better idea about how to manage permissions than what was
discussed in [3]; we can just give ownership of the language
object to the user calling CREATE EXTENSION.  Doing it that way
means that we end up with exactly the same catalog state as we
do in existing releases.  And that should mean that we don't have
to treat this as an extension version upgrade.  So I just modified
the 1.0 scripts in-place instead of adding 1.0--1.1 scripts.  It
looks to me like there's no need to touch the from-unpackaged
scripts, either.  And by the same token this isn't really an issue
for pg_upgrade.

(I noticed while testing this that pg_upgrade fails to preserve
ownership on extensions, but that's not new; this patch is not
making that situation any better or worse than it was.  Still,
maybe we oughta try to fix that sometime soon too.)

Patch 0003 removes CREATE LANGUAGE's reliance on pg_pltemplate.
CREATE LANGUAGE without parameters is now interpreted as
CREATE EXTENSION, thus providing a forward compatibility path
for old dump files.

Note: this won't help for *really* old dump files, ie those containing
CREATE LANGUAGE commands that do have parameters but the parameters are
wrong according to modern usage.  This is a hazard for dumps coming
from 8.0 or older servers; we invented pg_pltemplate in 8.1 primarily
as a way of cleaning up such dumps [5].  I think that that's far enough
back that we don't have to worry about how convenient it will be to go
from 8.0-or-older to v13-or-newer in one jump.

Finally, patch 0004 removes the now-unused catalog and cleans up some
incidental comments referring to it.

Once this is in, we could start thinking about whether we actually
want to change anything about plpython in the near future.

            regards, tom lane

[1] https://www.postgresql.org/message-id/flat/763f2fe4-743f-d530-8831-20811edd3d6a%402ndquadrant.com
[2] https://www.postgresql.org/message-id/flat/7495.1524861244%40sss.pgh.pa.us
[3] https://www.postgresql.org/message-id/flat/5351890.TdMePpdHBD%40nb.usersys.redhat.com
[4] https://www.postgresql.org/message-id/flat/CAKmB1PGDAy9mXxSTqUchYEi4iJAA6NKVj4P5BtAzvQ9wSDUwJw@mail.gmail.com
[5] https://www.postgresql.org/message-id/flat/5088.1125525412@sss.pgh.pa.us

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 5e71a2e..4c32fd8 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -8520,7 +8520,15 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
      <row>
       <entry><structfield>superuser</structfield></entry>
       <entry><type>bool</type></entry>
-      <entry>True if only superusers are allowed to install this extension</entry>
+      <entry>True if only superusers are allowed to install this extension
+       (but see <structfield>trustable</structfield>)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>trustable</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry>True if the extension can be installed by non-superusers when
+       configuration settings permit</entry>
      </row>

      <row>
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 89284dc..ff4520b 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -8447,6 +8447,78 @@ dynamic_library_path = 'C:\tools\postgresql;H:\my_project\lib;$libdir'
       </listitem>
      </varlistentry>

+     <varlistentry id="guc-trusted-extensions-dba" xreflabel="trusted_extensions_dba">
+      <term><varname>trusted_extensions_dba</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>trusted_extensions_dba</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        This parameter is a regular expression that matches the names of
+        trustable extensions that database owners are allowed to install
+        into their databases
+        (that is, run <xref linkend="sql-createextension"/> on; the
+        extension's files must already be present in the installation).
+        See <xref linkend="extend-extensions"/> for more information.
+        Regular expressions are explained in
+        <xref linkend="posix-syntax-details"/>.
+       </para>
+
+       <para>
+        The default value for this parameter is
+        <literal>'^pl'</literal>, which matches any extension whose name
+        begins with <literal>pl</literal>.  This allows database owners to
+        install trusted procedural languages, which was the hard-wired
+        behavior in older <productname>PostgreSQL</productname> versions.
+       </para>
+
+       <para>
+        This parameter can be changed at run time by superusers, but a
+        setting done that way will only persist until the end of the
+        client connection, so this method should be reserved for
+        development purposes. The recommended way to set this parameter
+        is in the <filename>postgresql.conf</filename> configuration
+        file.
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry id="guc-trusted-extensions-anyone" xreflabel="trusted_extensions_anyone">
+      <term><varname>trusted_extensions_anyone</varname> (<type>string</type>)
+      <indexterm>
+       <primary><varname>trusted_extensions_anyone</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        This parameter is a regular expression that matches the names of
+        trustable extensions that any SQL user is allowed to install
+        (that is, run <xref linkend="sql-createextension"/> on; the
+        extension's files must already be present in the installation).
+        See <xref linkend="extend-extensions"/> for more information.
+        Regular expressions are explained in
+        <xref linkend="posix-syntax-details"/>.
+       </para>
+
+       <para>
+        The default value for this parameter is
+        <literal>'^$'</literal>, which matches no extension names, thus
+        forbidding unprivileged users from installing any extensions that
+        require superuser privileges to install.
+       </para>
+
+       <para>
+        This parameter can be changed at run time by superusers, but a
+        setting done that way will only persist until the end of the
+        client connection, so this method should be reserved for
+        development purposes. The recommended way to set this parameter
+        is in the <filename>postgresql.conf</filename> configuration
+        file.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-gin-fuzzy-search-limit" xreflabel="gin_fuzzy_search_limit">
       <term><varname>gin_fuzzy_search_limit</varname> (<type>integer</type>)
       <indexterm>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index b5e59d5..8049934 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -576,6 +576,33 @@
         version.  If it is set to <literal>false</literal>, just the privileges
         required to execute the commands in the installation or update script
         are required.
+        This should normally be set to <literal>true</literal> if any of the
+        script commands require superuser privileges.  (Such commands would
+        fail anyway, but it's more user-friendly to give the error up front.)
+       </para>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><varname>trustable</varname> (<type>boolean</type>)</term>
+      <listitem>
+       <para>
+        This parameter, if set to <literal>true</literal> (which is not the
+        default), allows non-superusers to install an extension that
+        has <varname>superuser</varname> set to <literal>true</literal>.
+        The extension must <emphasis>also</emphasis> be considered
+        trustable by the installation's configuration parameters (see
+        <xref linkend="guc-trusted-extensions-dba"/> and
+        <xref linkend="guc-trusted-extensions-anyone"/>) before this is
+        allowed.  If both requirements hold and the user
+        executing <command>CREATE EXTENSION</command> is not a superuser,
+        then the installation or update script is run as the bootstrap
+        superuser, not as the calling user.
+        This parameter is irrelevant if <varname>superuser</varname> is
+        <literal>false</literal>.
+        Generally, this should not be set true for extensions that would
+        allow access to otherwise-superuser-only abilities, such as
+        untrusted procedural languages.
        </para>
       </listitem>
      </varlistentry>
@@ -642,6 +669,18 @@
     </para>

     <para>
+     If the extension script contains the
+     string <literal>@extowner@</literal>, that string is replaced with the
+     (suitably quoted) name of the user calling <command>CREATE
+     EXTENSION</command> or <command>ALTER EXTENSION</command>.  Typically
+     this feature is used by extensions that are marked trustable to assign
+     ownership of selected objects to the calling user rather than the
+     bootstrap superuser.  (One should be careful about doing so, however.
+     For example, assigning ownership of a C-language function to a
+     non-superuser would create a privilege escalation path for that user.)
+    </para>
+
+    <para>
      While the script files can contain any characters allowed by the specified
      encoding, control files should contain only plain ASCII, because there
      is no way for <productname>PostgreSQL</productname> to know what encoding a
diff --git a/doc/src/sgml/ref/create_extension.sgml b/doc/src/sgml/ref/create_extension.sgml
index 36837f9..ee59bfe 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -47,14 +47,28 @@ CREATE EXTENSION [ IF NOT EXISTS ] <replaceable class="parameter">extension_name
   </para>

   <para>
-   Loading an extension requires the same privileges that would be
-   required to create its component objects.  For most extensions this
-   means superuser or database owner privileges are needed.
+   Loading an extension ordinarily requires the same privileges that would be
+   required to create its component objects.  For many extensions this
+   means superuser privileges are needed.
    The user who runs <command>CREATE EXTENSION</command> becomes the
    owner of the extension for purposes of later privilege checks, as well
    as the owner of any objects created by the extension's script.
   </para>

+  <para>
+   However, if the extension is marked <firstterm>trustable</firstterm> in
+   its control file, and it is trusted by the local installation according
+   to the relevant configuration parameter
+   (<xref linkend="guc-trusted-extensions-dba"/> or
+   <xref linkend="guc-trusted-extensions-anyone"/>),
+   then it can be installed by a non-superuser.  In this case the extension
+   object itself will be owned by the calling user, but the contained
+   objects will be owned by the bootstrap superuser (unless the
+   extension's script explicitly assigns them to the calling user).
+   This configuration gives the calling user the right to drop the
+   extension, but not to modify individual objects within it.
+  </para>
+
  </refsect1>

  <refsect1>
diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql
index ea4c85e..f3e4d43 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.trustable, 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 f7202cc..257b4fc 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"
@@ -54,6 +55,7 @@
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
+#include "regex/regex.h"
 #include "storage/fd.h"
 #include "tcop/utility.h"
 #include "utils/acl.h"
@@ -66,6 +68,10 @@
 #include "utils/varlena.h"


+/* GUC settings */
+char       *trusted_extensions_dba;
+char       *trusted_extensions_anyone;
+
 /* Globally visible state variables */
 bool        creating_extension = false;
 Oid            CurrentExtensionObject = InvalidOid;
@@ -84,6 +90,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        trustable;        /* allow becoming superuser on the fly? */
     int            encoding;        /* encoding of the script file, or -1 */
     List       *requires;        /* names of prerequisite extensions */
 } ExtensionControlFile;
@@ -558,6 +565,14 @@ parse_extension_control_file(ExtensionControlFile *control,
                          errmsg("parameter \"%s\" requires a Boolean value",
                                 item->name)));
         }
+        else if (strcmp(item->name, "trustable") == 0)
+        {
+            if (!parse_bool(item->value, &control->trustable))
+                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 +629,7 @@ read_extension_control_file(const char *extname)
     control->name = pstrdup(extname);
     control->relocatable = false;
     control->superuser = true;
+    control->trustable = false;
     control->encoding = -1;

     /*
@@ -795,6 +811,76 @@ execute_sql_string(const char *sql)
 }

 /*
+ * Check if "str" matches the regular expression "pattern".
+ *
+ * XXX Perhaps we should put this somewhere else?
+ */
+static bool
+string_matches_regex(const char *str, const char *pattern)
+{
+    int            r;
+    pg_wchar   *wstr;
+    int            wlen;
+    regex_t        re;
+    char        errstr[100];
+
+    /* The regex library wants to deal in wchars not chars */
+    wstr = palloc((strlen(pattern) + 1) * sizeof(pg_wchar));
+    wlen = pg_mb2wchar_with_len(pattern, wstr, strlen(pattern));
+
+    r = pg_regcomp(&re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
+    if (r)
+    {
+        /* This shouldn't really happen, since guc.c checked the value */
+        pg_regerror(r, &re, errstr, sizeof(errstr));
+        /* no need for pg_regfree here */
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+                 errmsg("invalid regular expression \"%s\": %s",
+                        pattern, errstr)));
+    }
+    pfree(wstr);
+
+    wstr = palloc((strlen(str) + 1) * sizeof(pg_wchar));
+    wlen = pg_mb2wchar_with_len(str, wstr, strlen(str));
+
+    r = pg_regexec(&re, wstr, wlen, 0, NULL, 0, NULL, 0);
+    if (r != REG_OKAY && r != REG_NOMATCH)
+    {
+        pg_regerror(r, &re, errstr, sizeof(errstr));
+        pg_regfree(&re);
+        ereport(ERROR,
+                (errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+                 errmsg("regular expression match for \"%s\" failed: %s",
+                        pattern, errstr)));
+    }
+    pfree(wstr);
+
+    pg_regfree(&re);
+    return (r == REG_OKAY);
+}
+
+/*
+ * Policy function: is the given extension trusted for installation by a
+ * non-superuser?
+ */
+static bool
+extension_is_trusted(ExtensionControlFile *control)
+{
+    /* Never trust unless extension's control file says it's okay */
+    if (!control->trustable)
+        return false;
+    /* Database owner can install, if it matches appropriate GUC */
+    if (pg_database_ownercheck(MyDatabaseId, GetUserId()) &&
+        string_matches_regex(control->name, trusted_extensions_dba))
+        return true;
+    /* Anyone can install, if it matches that GUC */
+    if (string_matches_regex(control->name, trusted_extensions_anyone))
+        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,19 +892,24 @@ 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\"",
@@ -835,6 +926,18 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
     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 +1010,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@.
          *
@@ -957,6 +1076,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);
 }

 /*
@@ -2117,8 +2242,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)
@@ -2139,24 +2264,26 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
         values[1] = CStringGetTextDatum(evi->name);
         /* superuser */
         values[2] = BoolGetDatum(control->superuser);
+        /* trustable */
+        values[3] = BoolGetDatum(control->trustable);
         /* 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);

@@ -2184,16 +2311,18 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
                 values[1] = CStringGetTextDatum(evi2->name);
                 /* superuser */
                 values[2] = BoolGetDatum(control->superuser);
+                /* trustable */
+                values[3] = BoolGetDatum(control->trustable);
                 /* 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 */

diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 90ffd89..614d993 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -36,7 +36,9 @@
 #include "access/xlog_internal.h"
 #include "catalog/namespace.h"
 #include "catalog/pg_authid.h"
+#include "catalog/pg_collation.h"
 #include "commands/async.h"
+#include "commands/extension.h"
 #include "commands/prepare.h"
 #include "commands/user.h"
 #include "commands/vacuum.h"
@@ -65,6 +67,7 @@
 #include "postmaster/postmaster.h"
 #include "postmaster/syslogger.h"
 #include "postmaster/walwriter.h"
+#include "regex/regex.h"
 #include "replication/logicallauncher.h"
 #include "replication/slot.h"
 #include "replication/syncrep.h"
@@ -175,6 +178,7 @@ static bool check_ssl(bool *newval, void **extra, GucSource source);
 static bool check_stage_log_stats(bool *newval, void **extra, GucSource source);
 static bool check_log_stats(bool *newval, void **extra, GucSource source);
 static bool check_canonical_path(char **newval, void **extra, GucSource source);
+static bool check_regular_expression(char **newval, void **extra, GucSource source);
 static bool check_timezone_abbreviations(char **newval, void **extra, GucSource source);
 static void assign_timezone_abbreviations(const char *newval, void *extra);
 static void pg_timezone_abbrev_initialize(void);
@@ -4178,6 +4182,26 @@ static struct config_string ConfigureNamesString[] =
     },

     {
+        {"trusted_extensions_dba", PGC_SUSET, CLIENT_CONN_OTHER,
+            gettext_noop("Selects which trustable extensions may be installed by database owners."),
+            NULL
+        },
+        &trusted_extensions_dba,
+        "^pl",
+        check_regular_expression, NULL, NULL
+    },
+
+    {
+        {"trusted_extensions_anyone", PGC_SUSET, CLIENT_CONN_OTHER,
+            gettext_noop("Selects which trustable extensions may be installed by anyone."),
+            NULL
+        },
+        &trusted_extensions_anyone,
+        "^$",
+        check_regular_expression, NULL, NULL
+    },
+
+    {
         {"wal_consistency_checking", PGC_SUSET, DEVELOPER_OPTIONS,
             gettext_noop("Sets the WAL resource managers for which WAL consistency checks are done."),
             gettext_noop("Full-page images will be logged for all data blocks and cross-checked against the results of
WALreplay."), 
@@ -11114,6 +11138,37 @@ check_canonical_path(char **newval, void **extra, GucSource source)
     return true;
 }

+/* Check that the GUC's value is a valid regular expression. */
+static bool
+check_regular_expression(char **newval, void **extra, GucSource source)
+{
+    int            r;
+    pg_wchar   *wstr;
+    int            wlen;
+    regex_t        re;
+
+    if (!*newval)
+        return false;
+
+    /* The regex library wants to deal in wchars not chars */
+    wstr = palloc((strlen(*newval) + 1) * sizeof(pg_wchar));
+    wlen = pg_mb2wchar_with_len(*newval, wstr, strlen(*newval));
+
+    r = pg_regcomp(&re, wstr, wlen, REG_ADVANCED, C_COLLATION_OID);
+    if (r)
+    {
+        char        errstr[100];
+
+        pg_regerror(r, &re, errstr, sizeof(errstr));
+        GUC_check_errdetail("invalid regular expression: %s", errstr);
+        pfree(wstr);
+        return false;
+    }
+    pg_regfree(&re);
+    pfree(wstr);
+    return true;
+}
+
 static bool
 check_timezone_abbreviations(char **newval, void **extra, GucSource source)
 {
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 39fc787..5caeb76 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -682,6 +682,9 @@

 #dynamic_library_path = '$libdir'

+#trusted_extensions_dba = '^pl'        # exts installable by database owner
+#trusted_extensions_anyone = '^$'    # exts installable by anyone
+

 #------------------------------------------------------------------------------
 # LOCK MANAGEMENT
diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index b88e886..f2551e1 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9431,9 +9431,9 @@
   proname => 'pg_available_extension_versions', procost => '10',
   prorows => '100', proretset => 't', provolatile => 's',
   prorettype => 'record', proargtypes => '',
-  proallargtypes => '{name,text,bool,bool,name,_name,text}',
-  proargmodes => '{o,o,o,o,o,o,o}',
-  proargnames => '{name,version,superuser,relocatable,schema,requires,comment}',
+  proallargtypes => '{name,text,bool,bool,bool,name,_name,text}',
+  proargmodes => '{o,o,o,o,o,o,o,o}',
+  proargnames => '{name,version,superuser,trustable,relocatable,schema,requires,comment}',
   prosrc => 'pg_available_extension_versions' },
 { oid => '3084', descr => 'list an extension\'s version update paths',
   proname => 'pg_extension_update_paths', procost => '10', prorows => '100',
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 02fc17d..3a155eaf 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -18,6 +18,10 @@
 #include "nodes/parsenodes.h"


+/* GUC settings */
+extern char *trusted_extensions_dba;
+extern char *trusted_extensions_anyone;
+
 /*
  * creating_extension is only true while running a CREATE EXTENSION or ALTER
  * EXTENSION UPDATE command.  It instructs recordDependencyOnCurrentExtension()
diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 210e9cd..56b20f6 100644
--- a/src/test/regress/expected/rules.out
+++ b/src/test/regress/expected/rules.out
@@ -1311,11 +1311,12 @@ pg_available_extension_versions| SELECT e.name,
     e.version,
     (x.extname IS NOT NULL) AS installed,
     e.superuser,
+    e.trustable,
     e.relocatable,
     e.schema,
     e.requires,
     e.comment
-   FROM (pg_available_extension_versions() e(name, version, superuser, relocatable, schema, requires, comment)
+   FROM (pg_available_extension_versions() e(name, version, superuser, trustable, relocatable, schema, requires,
comment)
      LEFT JOIN pg_extension x ON (((e.name = x.extname) AND (e.version = x.extversion))));
 pg_available_extensions| SELECT e.name,
     e.default_version,
diff --git a/src/pl/plperl/GNUmakefile b/src/pl/plperl/GNUmakefile
index 9b1c514..e4d0a0b 100644
--- a/src/pl/plperl/GNUmakefile
+++ b/src/pl/plperl/GNUmakefile
@@ -55,8 +55,10 @@ endif # win32

 SHLIB_LINK = $(perl_embed_ldflags)

-REGRESS_OPTS = --dbname=$(PL_TESTDB) --load-extension=plperl  --load-extension=plperlu
-REGRESS = plperl plperl_lc plperl_trigger plperl_shared plperl_elog plperl_util plperl_init plperlu plperl_array
plperl_callplperl_transaction 
+REGRESS_OPTS = --dbname=$(PL_TESTDB)
+REGRESS = plperl_setup plperl plperl_lc plperl_trigger plperl_shared \
+    plperl_elog plperl_util plperl_init plperlu plperl_array \
+    plperl_call plperl_transaction
 # if Perl can support two interpreters in one backend,
 # test plperl-and-plperlu cases
 ifneq ($(PERL),)
diff --git a/src/pl/plperl/expected/plperl_setup.out b/src/pl/plperl/expected/plperl_setup.out
new file mode 100644
index 0000000..05cf5ac
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_setup.out
@@ -0,0 +1,63 @@
+--
+-- Install the plperl and plperlu extensions
+--
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- the trusted-extensions configuration permits.
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+SET trusted_extensions_anyone = '^$';
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;  -- fail
+ERROR:  permission denied to create extension "plperl"
+HINT:  Must be superuser to create this extension.
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+SET trusted_extensions_anyone = '^plperl';  -- fail
+ERROR:  permission denied to set parameter "trusted_extensions_anyone"
+RESET ROLE;
+SET trusted_extensions_anyone = '^plperl';
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+ foo1
+------
+    1
+(1 row)
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';  -- fail
+ERROR:  permission denied for language plperl
+SET ROLE regress_user1;
+grant usage on language plperl to regress_user2;
+SET ROLE regress_user2;
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+ foo2
+------
+    2
+(1 row)
+
+SET ROLE regress_user1;
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+ERROR:  cannot drop language plperl because extension plperl requires it
+HINT:  You can drop extension plperl instead.
+DROP EXTENSION plperl CASCADE;
+NOTICE:  drop cascades to 2 other objects
+DETAIL:  drop cascades to function foo1()
+drop cascades to function foo2()
+-- Clean up
+RESET ROLE;
+DROP USER regress_user1;
+DROP USER regress_user2;
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plperl/plperl--1.0.sql b/src/pl/plperl/plperl--1.0.sql
index f716ba1..5ff31e7 100644
--- a/src/pl/plperl/plperl--1.0.sql
+++ b/src/pl/plperl/plperl--1.0.sql
@@ -1,11 +1,20 @@
 /* src/pl/plperl/plperl--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plperl_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plperl;
+CREATE FUNCTION plperl_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperl_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plperl
+  HANDLER plperl_call_handler
+  INLINE plperl_inline_handler
+  VALIDATOR plperl_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plperl OWNER TO @extowner@;

 COMMENT ON LANGUAGE plperl IS 'PL/Perl procedural language';
diff --git a/src/pl/plperl/plperl.control b/src/pl/plperl/plperl.control
index 6faace1..1fb7e7e 100644
--- a/src/pl/plperl/plperl.control
+++ b/src/pl/plperl/plperl.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/plperl'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trustable = true
diff --git a/src/pl/plperl/plperlu--1.0.sql b/src/pl/plperl/plperlu--1.0.sql
index 7efb4fb..10d7594 100644
--- a/src/pl/plperl/plperlu--1.0.sql
+++ b/src/pl/plperl/plperlu--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plperl/plperlu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plperlu_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plperlu;
+CREATE FUNCTION plperlu_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plperlu_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plperlu
+  HANDLER plperlu_call_handler
+  INLINE plperlu_inline_handler
+  VALIDATOR plperlu_validator;

 COMMENT ON LANGUAGE plperlu IS 'PL/PerlU untrusted procedural language';
diff --git a/src/pl/plperl/sql/plperl_setup.sql b/src/pl/plperl/sql/plperl_setup.sql
new file mode 100644
index 0000000..dd292fb
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_setup.sql
@@ -0,0 +1,62 @@
+--
+-- Install the plperl and plperlu extensions
+--
+
+-- Before going ahead with the to-be-tested installations, verify that
+-- a non-superuser is allowed to install plperl (but not plperlu) when
+-- the trusted-extensions configuration permits.
+
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+
+SET trusted_extensions_anyone = '^$';
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;  -- fail
+CREATE EXTENSION plperlu;  -- fail
+
+SET trusted_extensions_anyone = '^plperl';  -- fail
+
+RESET ROLE;
+
+SET trusted_extensions_anyone = '^plperl';
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;  -- fail
+
+CREATE FUNCTION foo1() returns int language plperl as '1;';
+SELECT foo1();
+
+-- Should be able to change privileges on the language
+revoke all on language plperl from public;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';  -- fail
+
+SET ROLE regress_user1;
+
+grant usage on language plperl to regress_user2;
+
+SET ROLE regress_user2;
+
+CREATE FUNCTION foo2() returns int language plperl as '2;';
+SELECT foo2();
+
+SET ROLE regress_user1;
+
+-- Should be able to drop the extension, but not the language per se
+DROP LANGUAGE plperl CASCADE;
+DROP EXTENSION plperl CASCADE;
+
+-- Clean up
+RESET ROLE;
+DROP USER regress_user1;
+DROP USER regress_user2;
+
+-- Now install the versions that will be used by subsequent test scripts.
+CREATE EXTENSION plperl;
+CREATE EXTENSION plperlu;
diff --git a/src/pl/plpgsql/src/plpgsql--1.0.sql b/src/pl/plpgsql/src/plpgsql--1.0.sql
index ab6fa84..6e5b990 100644
--- a/src/pl/plpgsql/src/plpgsql--1.0.sql
+++ b/src/pl/plpgsql/src/plpgsql--1.0.sql
@@ -1,11 +1,20 @@
 /* src/pl/plpgsql/src/plpgsql--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpgsql_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpgsql;
+CREATE FUNCTION plpgsql_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpgsql_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE TRUSTED LANGUAGE plpgsql
+  HANDLER plpgsql_call_handler
+  INLINE plpgsql_inline_handler
+  VALIDATOR plpgsql_validator;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE plpgsql OWNER TO @extowner@;

 COMMENT ON LANGUAGE plpgsql IS 'PL/pgSQL procedural language';
diff --git a/src/pl/plpgsql/src/plpgsql.control b/src/pl/plpgsql/src/plpgsql.control
index b320227..c4373c8 100644
--- a/src/pl/plpgsql/src/plpgsql.control
+++ b/src/pl/plpgsql/src/plpgsql.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/plpgsql'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trustable = true
diff --git a/src/pl/plpython/plpython2u--1.0.sql b/src/pl/plpython/plpython2u--1.0.sql
index 661cc66..69f7477 100644
--- a/src/pl/plpython/plpython2u--1.0.sql
+++ b/src/pl/plpython/plpython2u--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpython2u--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython2_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpython2u;
+CREATE FUNCTION plpython2_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython2_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython2u
+  HANDLER plpython2_call_handler
+  INLINE plpython2_inline_handler
+  VALIDATOR plpython2_validator;

 COMMENT ON LANGUAGE plpython2u IS 'PL/Python2U untrusted procedural language';
diff --git a/src/pl/plpython/plpython3u--1.0.sql b/src/pl/plpython/plpython3u--1.0.sql
index c0d6ea8..ba2e6ac 100644
--- a/src/pl/plpython/plpython3u--1.0.sql
+++ b/src/pl/plpython/plpython3u--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpython3u--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython3_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpython3u;
+CREATE FUNCTION plpython3_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython3_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpython3u
+  HANDLER plpython3_call_handler
+  INLINE plpython3_inline_handler
+  VALIDATOR plpython3_validator;

 COMMENT ON LANGUAGE plpython3u IS 'PL/Python3U untrusted procedural language';
diff --git a/src/pl/plpython/plpythonu--1.0.sql b/src/pl/plpython/plpythonu--1.0.sql
index 4a3e64a..4c6f7c3 100644
--- a/src/pl/plpython/plpythonu--1.0.sql
+++ b/src/pl/plpython/plpythonu--1.0.sql
@@ -1,11 +1,17 @@
 /* src/pl/plpython/plpythonu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION plpython_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE plpythonu;
+CREATE FUNCTION plpython_inline_handler(internal) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE FUNCTION plpython_validator(oid) RETURNS void
+  STRICT LANGUAGE c AS 'MODULE_PATHNAME';
+
+CREATE LANGUAGE plpythonu
+  HANDLER plpython_call_handler
+  INLINE plpython_inline_handler
+  VALIDATOR plpython_validator;

 COMMENT ON LANGUAGE plpythonu IS 'PL/PythonU untrusted procedural language';
diff --git a/src/pl/tcl/pltcl--1.0.sql b/src/pl/tcl/pltcl--1.0.sql
index 34a68c8..2ed2b92 100644
--- a/src/pl/tcl/pltcl--1.0.sql
+++ b/src/pl/tcl/pltcl--1.0.sql
@@ -1,11 +1,12 @@
 /* src/pl/tcl/pltcl--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION pltcl_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE pltcl;
+CREATE TRUSTED LANGUAGE pltcl
+  HANDLER pltcl_call_handler;
+
+-- The language object, but not the functions, can be owned by a non-superuser.
+ALTER LANGUAGE pltcl OWNER TO @extowner@;

 COMMENT ON LANGUAGE pltcl IS 'PL/Tcl procedural language';
diff --git a/src/pl/tcl/pltcl.control b/src/pl/tcl/pltcl.control
index b9dc1b8..fb242c4 100644
--- a/src/pl/tcl/pltcl.control
+++ b/src/pl/tcl/pltcl.control
@@ -4,4 +4,5 @@ default_version = '1.0'
 module_pathname = '$libdir/pltcl'
 relocatable = false
 schema = pg_catalog
-superuser = false
+superuser = true
+trustable = true
diff --git a/src/pl/tcl/pltclu--1.0.sql b/src/pl/tcl/pltclu--1.0.sql
index e05b470..fca869f 100644
--- a/src/pl/tcl/pltclu--1.0.sql
+++ b/src/pl/tcl/pltclu--1.0.sql
@@ -1,11 +1,9 @@
 /* src/pl/tcl/pltclu--1.0.sql */

-/*
- * Currently, all the interesting stuff is done by CREATE LANGUAGE.
- * Later we will probably "dumb down" that command and put more of the
- * knowledge into this script.
- */
+CREATE FUNCTION pltclu_call_handler() RETURNS language_handler
+  LANGUAGE c AS 'MODULE_PATHNAME';

-CREATE LANGUAGE pltclu;
+CREATE LANGUAGE pltclu
+  HANDLER pltclu_call_handler;

 COMMENT ON LANGUAGE pltclu IS 'PL/TclU untrusted procedural language';
diff --git a/doc/src/sgml/ref/create_language.sgml b/doc/src/sgml/ref/create_language.sgml
index 13b28b1..2a9c7e8 100644
--- a/doc/src/sgml/ref/create_language.sgml
+++ b/doc/src/sgml/ref/create_language.sgml
@@ -21,9 +21,9 @@ PostgreSQL documentation

  <refsynopsisdiv>
 <synopsis>
-CREATE [ OR REPLACE ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
     HANDLER <replaceable class="parameter">call_handler</replaceable> [ INLINE <replaceable
class="parameter">inline_handler</replaceable>] [ VALIDATOR <replaceable>valfunction</replaceable> ] 
+CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="parameter">name</replaceable>
 </synopsis>
  </refsynopsisdiv>

@@ -37,21 +37,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
    defined in this new language.
   </para>

-  <note>
-   <para>
-    As of <productname>PostgreSQL</productname> 9.1, most procedural
-    languages have been made into <quote>extensions</quote>, and should
-    therefore be installed with <xref linkend="sql-createextension"/>
-    not <command>CREATE LANGUAGE</command>.  Direct use of
-    <command>CREATE LANGUAGE</command> should now be confined to
-    extension installation scripts.  If you have a <quote>bare</quote>
-    language in your database, perhaps as a result of an upgrade,
-    you can convert it to an extension using
-    <literal>CREATE EXTENSION <replaceable>langname</replaceable> FROM
-    unpackaged</literal>.
-   </para>
-  </note>
-
   <para>
    <command>CREATE LANGUAGE</command> effectively associates the
    language name with handler function(s) that are responsible for executing
@@ -60,53 +45,32 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>

   <para>
-   There are two forms of the <command>CREATE LANGUAGE</command> command.
-   In the first form, the user supplies just the name of the desired
-   language, and the <productname>PostgreSQL</productname> server consults
-   the <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
-   system catalog to determine the correct parameters.  In the second form,
-   the user supplies the language parameters along with the language name.
-   The second form can be used to create a language that is not defined in
-   <structname>pg_pltemplate</structname>, but this approach is considered obsolescent.
-  </para>
-
-  <para>
-   When the server finds an entry in the <structname>pg_pltemplate</structname> catalog
-   for the given language name, it will use the catalog data even if the
-   command includes language parameters.  This behavior simplifies loading of
-   old dump files, which are likely to contain out-of-date information
-   about language support functions.
-  </para>
-
-  <para>
-   Ordinarily, the user must have the
-   <productname>PostgreSQL</productname> superuser privilege to
-   register a new language.  However, the owner of a database can register
-   a new language within that database if the language is listed in
-   the <structname>pg_pltemplate</structname> catalog and is marked
-   as allowed to be created by database owners (<structfield>tmpldbacreate</structfield>
-   is true).  The default is that trusted languages can be created
-   by database owners, but this can be adjusted by superusers by modifying
-   the contents of <structname>pg_pltemplate</structname>.
-   The creator of a language becomes its owner and can later
-   drop it, rename it, or assign it to a new owner.
+   The form of <command>CREATE LANGUAGE</command> that does not supply
+   any handler function is obsolete.  For backwards compatibility with
+   old dump files, it is interpreted as <command>CREATE EXTENSION</command>.
+   That will work if the language has been packaged into an extension of
+   the same name, which is the conventional way to set up procedural
+   languages.
   </para>

   <para>
    <command>CREATE OR REPLACE LANGUAGE</command> will either create a
    new language, or replace an existing definition.  If the language
-   already exists, its parameters are updated according to the values
-   specified or taken from <structname>pg_pltemplate</structname>,
+   already exists, its parameters are updated according to the command,
    but the language's ownership and permissions settings do not change,
    and any existing functions written in the language are assumed to still
-   be valid.  In addition to the normal privilege requirements for creating
-   a language, the user must be superuser or owner of the existing language.
-   The <literal>REPLACE</literal> case is mainly meant to be used to
-   ensure that the language exists.  If the language has a
-   <structname>pg_pltemplate</structname> entry then <literal>REPLACE</literal>
-   will not actually change anything about an existing definition, except in
-   the unusual case where the <structname>pg_pltemplate</structname> entry
-   has been modified since the language was created.
+   be valid.
+  </para>
+
+  <para>
+   One must have the
+   <productname>PostgreSQL</productname> superuser privilege to
+   register a new language or change an existing language's parameters.
+   However, once the language is created it is valid to assign ownership of
+   it to a non-superuser, who may then drop it, change its permissions,
+   rename it, or assign it to a new owner.  (Do not, however, assign
+   ownership of the underlying C functions to a non-superuser; that would
+   create a privilege escalation path for that user.)
   </para>
  </refsect1>

@@ -218,12 +182,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
      </listitem>
     </varlistentry>
    </variablelist>
-
-  <para>
-   The <literal>TRUSTED</literal> option and the support function name(s) are
-   ignored if the server has an entry for the specified language
-   name in <structname>pg_pltemplate</structname>.
-  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-notes">
@@ -255,18 +213,6 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   </para>

   <para>
-   The call handler function, the inline handler function (if any),
-   and the validator function (if any)
-   must already exist if the server does not have an entry for the language
-   in <structname>pg_pltemplate</structname>.  But when there is an entry,
-   the functions need not already exist;
-   they will be automatically defined if not present in the database.
-   (This might result in <command>CREATE LANGUAGE</command> failing, if the
-   shared library that implements the language is not available in
-   the installation.)
-  </para>
-
-  <para>
    In <productname>PostgreSQL</productname> versions before 7.3, it was
    necessary to declare handler functions as returning the placeholder
    type <type>opaque</type>, rather than <type>language_handler</type>.
@@ -281,23 +227,20 @@ CREATE [ OR REPLACE ] [ TRUSTED ] [ PROCEDURAL ] LANGUAGE <replaceable class="pa
   <title>Examples</title>

   <para>
-   The preferred way of creating any of the standard procedural languages
-   is just:
-<programlisting>
-CREATE LANGUAGE plperl;
-</programlisting>
-  </para>
-
-  <para>
-   For a language not known in the <structname>pg_pltemplate</structname> catalog, a
-   sequence such as this is needed:
+   A minimal sequence for creating a new procedural language is:
 <programlisting>
 CREATE FUNCTION plsample_call_handler() RETURNS language_handler
     AS '$libdir/plsample'
     LANGUAGE C;
 CREATE LANGUAGE plsample
     HANDLER plsample_call_handler;
-</programlisting></para>
+</programlisting>
+   Typically that would be written in an extension's creation script,
+   and users would do this to install the extension:
+<programlisting>
+CREATE EXTENSION plsample;
+</programlisting>
+  </para>
  </refsect1>

  <refsect1 id="sql-createlanguage-compat">
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 257b4fc..8fcd98b 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2333,6 +2333,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 40f1f9a..aa408b8 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 343cd1d..520a603 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, funcargtypes, 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, funcargtypes, 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, funcargtypes, 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 c97bb36..d09b4a5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4273,14 +4273,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
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 3a155eaf..b4e3c65 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -51,6 +51,7 @@ extern ObjectAddress ExecAlterExtensionContentsStmt(AlterExtensionContentsStmt *

 extern Oid    get_extension_oid(const char *extname, bool missing_ok);
 extern char *get_extension_name(Oid ext_oid);
+extern bool extension_file_exists(const char *extensionName);

 extern ObjectAddress AlterExtensionNamespace(const char *extensionName, const char *newschema,
                                              Oid *oldschema);
diff --git a/src/include/commands/proclang.h b/src/include/commands/proclang.h
index 9a4bc75..d7b0ab5 100644
--- a/src/include/commands/proclang.h
+++ b/src/include/commands/proclang.h
@@ -1,11 +1,12 @@
-/*
- * src/include/commands/proclang.h
- *
- *-------------------------------------------------------------------------
+/*-------------------------------------------------------------------------
  *
  * proclang.h
  *      prototypes for proclang.c.
  *
+ * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/proclang.h
  *
  *-------------------------------------------------------------------------
  */
@@ -17,7 +18,7 @@

 extern ObjectAddress CreateProceduralLanguage(CreatePLangStmt *stmt);
 extern void DropProceduralLanguageById(Oid langOid);
-extern bool PLTemplateExists(const char *languageName);
+
 extern Oid    get_language_oid(const char *langname, bool missing_ok);

 #endif                            /* PROCLANG_H */
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 4c32fd8..4de7075 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -226,11 +226,6 @@
      </row>

      <row>
-      <entry><link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link></entry>
-      <entry>template data for procedural languages</entry>
-     </row>
-
-     <row>
       <entry><link linkend="catalog-pg-policy"><structname>pg_policy</structname></link></entry>
       <entry>row-security policies</entry>
      </row>
@@ -4911,113 +4906,6 @@ SCRAM-SHA-256$<replaceable><iteration count></replaceable>:<replaceable>&l
  </sect1>


- <sect1 id="catalog-pg-pltemplate">
-  <title><structname>pg_pltemplate</structname></title>
-
-  <indexterm zone="catalog-pg-pltemplate">
-   <primary>pg_pltemplate</primary>
-  </indexterm>
-
-  <para>
-   The catalog <structname>pg_pltemplate</structname> stores
-   <quote>template</quote> information for procedural languages.
-   A template for a language allows the language to be created in a
-   particular database by a simple <command>CREATE LANGUAGE</command> command,
-   with no need to specify implementation details.
-  </para>
-
-  <para>
-   Unlike most system catalogs, <structname>pg_pltemplate</structname>
-   is shared across all databases of a cluster: there is only one
-   copy of <structname>pg_pltemplate</structname> per cluster, not
-   one per database.  This allows the information to be accessible in
-   each database as it is needed.
-  </para>
-
-  <table>
-   <title><structname>pg_pltemplate</structname> Columns</title>
-
-   <tgroup cols="3">
-    <thead>
-     <row>
-      <entry>Name</entry>
-      <entry>Type</entry>
-      <entry>Description</entry>
-     </row>
-    </thead>
-
-    <tbody>
-     <row>
-      <entry><structfield>tmplname</structfield></entry>
-      <entry><type>name</type></entry>
-      <entry>Name of the language this template is for</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpltrusted</structfield></entry>
-      <entry><type>boolean</type></entry>
-      <entry>True if language is considered trusted</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpldbacreate</structfield></entry>
-      <entry><type>boolean</type></entry>
-      <entry>True if language may be created by a database owner</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplhandler</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of call handler function</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplinline</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of anonymous-block handler function, or null if none</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplvalidator</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Name of validator function, or null if none</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmpllibrary</structfield></entry>
-      <entry><type>text</type></entry>
-      <entry>Path of shared library that implements language</entry>
-     </row>
-
-     <row>
-      <entry><structfield>tmplacl</structfield></entry>
-      <entry><type>aclitem[]</type></entry>
-      <entry>Access privileges for template (not actually used)</entry>
-     </row>
-
-    </tbody>
-   </tgroup>
-  </table>
-
-  <para>
-   There are not currently any commands that manipulate procedural language
-   templates; to change the built-in information, a superuser must modify
-   the table using ordinary <command>INSERT</command>, <command>DELETE</command>,
-   or <command>UPDATE</command> commands.
-  </para>
-
-  <note>
-   <para>
-    It is likely that <structname>pg_pltemplate</structname> will be removed in some
-    future release of <productname>PostgreSQL</productname>, in favor of
-    keeping this knowledge about procedural languages in their respective
-    extension installation scripts.
-   </para>
-  </note>
-
- </sect1>
-
-
  <sect1 id="catalog-pg-policy">
   <title><structname>pg_policy</structname></title>

diff --git a/doc/src/sgml/plpython.sgml b/doc/src/sgml/plpython.sgml
index 60a5907..b5109f0 100644
--- a/doc/src/sgml/plpython.sgml
+++ b/doc/src/sgml/plpython.sgml
@@ -153,7 +153,7 @@
      <para>
       Daredevils, who want to build a Python-3-only operating system
       environment, can change the contents of
-      <link linkend="catalog-pg-pltemplate"><structname>pg_pltemplate</structname></link>
+      <literal>plpythonu</literal>'s extension control and script files
       to make <literal>plpythonu</literal> be equivalent
       to <literal>plpython3u</literal>, keeping in mind that this
       would make their installation incompatible with most of the rest
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index 8bece07..0299ee0 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -37,7 +37,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 \
@@ -63,7 +63,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 1af31c2..8d424de 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -32,7 +32,6 @@
 #include "catalog/pg_authid.h"
 #include "catalog/pg_database.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_shdepend.h"
@@ -243,7 +242,6 @@ IsSharedRelation(Oid relationId)
     if (relationId == AuthIdRelationId ||
         relationId == AuthMemRelationId ||
         relationId == DatabaseRelationId ||
-        relationId == PLTemplateRelationId ||
         relationId == SharedDescriptionRelationId ||
         relationId == SharedDependRelationId ||
         relationId == SharedSecLabelRelationId ||
@@ -259,7 +257,6 @@ IsSharedRelation(Oid relationId)
         relationId == AuthMemMemRoleIndexId ||
         relationId == DatabaseNameIndexId ||
         relationId == DatabaseOidIndexId ||
-        relationId == PLTemplateNameIndexId ||
         relationId == SharedDescriptionObjIndexId ||
         relationId == SharedDependDependerIndexId ||
         relationId == SharedDependReferenceIndexId ||
@@ -279,8 +276,6 @@ IsSharedRelation(Oid relationId)
         relationId == PgDatabaseToastIndex ||
         relationId == PgDbRoleSettingToastTable ||
         relationId == PgDbRoleSettingToastIndex ||
-        relationId == PgPlTemplateToastTable ||
-        relationId == PgPlTemplateToastIndex ||
         relationId == PgReplicationOriginToastTable ||
         relationId == PgReplicationOriginToastIndex ||
         relationId == PgShdescriptionToastTable ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 3498140..778000d 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11352,9 +11352,9 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
     }

     /*
-     * If the functions are dumpable then emit a traditional CREATE LANGUAGE
-     * with parameters.  Otherwise, we'll write a parameterless command, which
-     * will rely on data from pg_pltemplate.
+     * If the functions are dumpable then emit a complete CREATE LANGUAGE with
+     * parameters.  Otherwise, we'll write a parameterless command, which will
+     * be interpreted as CREATE EXTENSION.
      */
     useParams = (funcInfo != NULL &&
                  (inlineInfo != NULL || !OidIsValid(plang->laninline)) &&
@@ -11387,11 +11387,11 @@ dumpProcLang(Archive *fout, ProcLangInfo *plang)
         /*
          * If not dumping parameters, then use CREATE OR REPLACE so that the
          * command will not fail if the language is preinstalled in the target
-         * database.  We restrict the use of REPLACE to this case so as to
-         * eliminate the risk of replacing a language with incompatible
-         * parameter settings: this command will only succeed at all if there
-         * is a pg_pltemplate entry, and if there is one, the existing entry
-         * must match it too.
+         * database.
+         *
+         * Modern servers will interpret this as CREATE EXTENSION IF NOT
+         * EXISTS; perhaps we should emit that instead?  But it might just add
+         * confusion.
          */
         appendPQExpBuffer(defqry, "CREATE OR REPLACE PROCEDURAL LANGUAGE %s",
                           qlanname);
diff --git a/src/bin/pg_upgrade/function.c b/src/bin/pg_upgrade/function.c
index 0c66d1c..d00d748 100644
--- a/src/bin/pg_upgrade/function.c
+++ b/src/bin/pg_upgrade/function.c
@@ -216,13 +216,9 @@ check_loadable_libraries(void)
              * plpython2u language was created with library name plpython2.so
              * as a symbolic link to plpython.so.  In Postgres 9.1, only the
              * plpython2.so library was created, and both plpythonu and
-             * plpython2u pointing to it.  For this reason, any reference to
+             * plpython2u point to it.  For this reason, any reference to
              * library name "plpython" in an old PG <= 9.1 cluster must look
              * for "plpython2" in the new cluster.
-             *
-             * For this case, we could check pg_pltemplate, but that only
-             * works for languages, and does not help with function shared
-             * objects, so we just do a general fix.
              */
             if (GET_MAJOR_VERSION(old_cluster.major_version) < 901 &&
                 strcmp(lib, "$libdir/plpython") == 0)
diff --git a/src/include/catalog/indexing.h b/src/include/catalog/indexing.h
index ef4445b..7294d59 100644
--- a/src/include/catalog/indexing.h
+++ b/src/include/catalog/indexing.h
@@ -206,9 +206,6 @@ DECLARE_UNIQUE_INDEX(pg_opfamily_am_name_nsp_index, 2754, on pg_opfamily using b
 DECLARE_UNIQUE_INDEX(pg_opfamily_oid_index, 2755, on pg_opfamily using btree(oid oid_ops));
 #define OpfamilyOidIndexId    2755

-DECLARE_UNIQUE_INDEX(pg_pltemplate_name_index, 1137, on pg_pltemplate using btree(tmplname name_ops));
-#define PLTemplateNameIndexId  1137
-
 DECLARE_UNIQUE_INDEX(pg_proc_oid_index, 2690, on pg_proc using btree(oid oid_ops));
 #define ProcedureOidIndexId  2690
 DECLARE_UNIQUE_INDEX(pg_proc_proname_args_nsp_index, 2691, on pg_proc using btree(proname name_ops, proargtypes
oidvector_ops,pronamespace oid_ops)); 
diff --git a/src/include/catalog/pg_pltemplate.dat b/src/include/catalog/pg_pltemplate.dat
deleted file mode 100644
index 6f6d167..0000000
--- a/src/include/catalog/pg_pltemplate.dat
+++ /dev/null
@@ -1,51 +0,0 @@
-#----------------------------------------------------------------------
-#
-# pg_pltemplate.dat
-#    Initial contents of the pg_pltemplate system catalog.
-#
-# Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
-# Portions Copyright (c) 1994, Regents of the University of California
-#
-# src/include/catalog/pg_pltemplate.dat
-#
-#----------------------------------------------------------------------
-
-[
-
-{ tmplname => 'plpgsql', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'plpgsql_call_handler', tmplinline => 'plpgsql_inline_handler',
-  tmplvalidator => 'plpgsql_validator', tmpllibrary => '$libdir/plpgsql',
-  tmplacl => '_null_' },
-{ tmplname => 'pltcl', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'pltcl_call_handler', tmplinline => '_null_',
-  tmplvalidator => '_null_', tmpllibrary => '$libdir/pltcl',
-  tmplacl => '_null_' },
-{ tmplname => 'pltclu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'pltclu_call_handler', tmplinline => '_null_',
-  tmplvalidator => '_null_', tmpllibrary => '$libdir/pltcl',
-  tmplacl => '_null_' },
-{ tmplname => 'plperl', tmpltrusted => 't', tmpldbacreate => 't',
-  tmplhandler => 'plperl_call_handler', tmplinline => 'plperl_inline_handler',
-  tmplvalidator => 'plperl_validator', tmpllibrary => '$libdir/plperl',
-  tmplacl => '_null_' },
-{ tmplname => 'plperlu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plperlu_call_handler', tmplinline => 'plperlu_inline_handler',
-  tmplvalidator => 'plperlu_validator', tmpllibrary => '$libdir/plperl',
-  tmplacl => '_null_' },
-{ tmplname => 'plpythonu', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython_call_handler',
-  tmplinline => 'plpython_inline_handler',
-  tmplvalidator => 'plpython_validator', tmpllibrary => '$libdir/plpython2',
-  tmplacl => '_null_' },
-{ tmplname => 'plpython2u', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython2_call_handler',
-  tmplinline => 'plpython2_inline_handler',
-  tmplvalidator => 'plpython2_validator', tmpllibrary => '$libdir/plpython2',
-  tmplacl => '_null_' },
-{ tmplname => 'plpython3u', tmpltrusted => 'f', tmpldbacreate => 'f',
-  tmplhandler => 'plpython3_call_handler',
-  tmplinline => 'plpython3_inline_handler',
-  tmplvalidator => 'plpython3_validator', tmpllibrary => '$libdir/plpython3',
-  tmplacl => '_null_' },
-
-]
diff --git a/src/include/catalog/pg_pltemplate.h b/src/include/catalog/pg_pltemplate.h
deleted file mode 100644
index ce89000..0000000
--- a/src/include/catalog/pg_pltemplate.h
+++ /dev/null
@@ -1,52 +0,0 @@
-/*-------------------------------------------------------------------------
- *
- * pg_pltemplate.h
- *      definition of the "PL template" system catalog (pg_pltemplate)
- *
- *
- * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group
- * Portions Copyright (c) 1994, Regents of the University of California
- *
- * src/include/catalog/pg_pltemplate.h
- *
- * NOTES
- *      The Catalog.pm module reads this file and derives schema
- *      information.
- *
- *-------------------------------------------------------------------------
- */
-#ifndef PG_PLTEMPLATE_H
-#define PG_PLTEMPLATE_H
-
-#include "catalog/genbki.h"
-#include "catalog/pg_pltemplate_d.h"
-
-/* ----------------
- *        pg_pltemplate definition.  cpp turns this into
- *        typedef struct FormData_pg_pltemplate
- * ----------------
- */
-CATALOG(pg_pltemplate,1136,PLTemplateRelationId) BKI_SHARED_RELATION
-{
-    NameData    tmplname;        /* name of PL */
-    bool        tmpltrusted;    /* PL is trusted? */
-    bool        tmpldbacreate;    /* PL is installable by db owner? */
-
-#ifdef CATALOG_VARLEN            /* variable-length fields start here */
-    text        tmplhandler BKI_FORCE_NOT_NULL; /* name of call handler
-                                                 * function */
-    text        tmplinline;        /* name of anonymous-block handler, or NULL */
-    text        tmplvalidator;    /* name of validator function, or NULL */
-    text        tmpllibrary BKI_FORCE_NOT_NULL; /* path of shared library */
-    aclitem        tmplacl[1];        /* access privileges for template */
-#endif
-} FormData_pg_pltemplate;
-
-/* ----------------
- *        Form_pg_pltemplate corresponds to a pointer to a row with
- *        the format of pg_pltemplate relation.
- * ----------------
- */
-typedef FormData_pg_pltemplate *Form_pg_pltemplate;
-
-#endif                            /* PG_PLTEMPLATE_H */
diff --git a/src/include/catalog/toasting.h b/src/include/catalog/toasting.h
index cc5dfed..5a047fc 100644
--- a/src/include/catalog/toasting.h
+++ b/src/include/catalog/toasting.h
@@ -86,9 +86,6 @@ DECLARE_TOAST(pg_database, 4177, 4178);
 DECLARE_TOAST(pg_db_role_setting, 2966, 2967);
 #define PgDbRoleSettingToastTable 2966
 #define PgDbRoleSettingToastIndex 2967
-DECLARE_TOAST(pg_pltemplate, 4179, 4180);
-#define PgPlTemplateToastTable 4179
-#define PgPlTemplateToastIndex 4180
 DECLARE_TOAST(pg_replication_origin, 4181, 4182);
 #define PgReplicationOriginToastTable 4181
 #define PgReplicationOriginToastIndex 4182
diff --git a/src/pl/plpython/plpy_main.c b/src/pl/plpython/plpy_main.c
index 6edfcf2..0d9564e 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -33,7 +33,7 @@
  */

 #if PY_MAJOR_VERSION >= 3
-/* Use separate names to avoid clash in pg_pltemplate */
+/* Use separate names to reduce confusion */
 #define plpython_validator plpython3_validator
 #define plpython_call_handler plpython3_call_handler
 #define plpython_inline_handler plpython3_inline_handler
diff --git a/src/test/regress/expected/sanity_check.out b/src/test/regress/expected/sanity_check.out
index d6e75ff..9bdba7b 100644
--- a/src/test/regress/expected/sanity_check.out
+++ b/src/test/regress/expected/sanity_check.out
@@ -134,7 +134,6 @@ pg_opclass|t
 pg_operator|t
 pg_opfamily|t
 pg_partitioned_table|t
-pg_pltemplate|t
 pg_policy|t
 pg_proc|t
 pg_publication|t

В списке pgsql-hackers по дате отправления:

Предыдущее
От: Alvaro Herrera
Дата:
Сообщение: Re: Cleanup isolation specs from unused steps
Следующее
От: Andrew Dunstan
Дата:
Сообщение: Re: "ago" times on buildfarm status page