Re: Removing pg_pltemplate and creating "trustable" extensions - Mailing list pgsql-hackers

From Tom Lane
Subject Re: Removing pg_pltemplate and creating "trustable" extensions
Date
Msg-id 28867.1580321965@sss.pgh.pa.us
Whole thread Raw
In response to Re: Removing pg_pltemplate and creating "trustable" extensions  (Tom Lane <tgl@sss.pgh.pa.us>)
List pgsql-hackers
I wrote:
> Stephen Frost <sfrost@snowman.net> writes:
>> On Tue, Jan 28, 2020 at 16:17 Tom Lane <tgl@sss.pgh.pa.us> wrote:
>>> On the other hand, there's the point that lots of people have probably
>>> given out schema-CREATE privilege to users whom they wouldn't necessarily
>>> wish to trust with INSTALL privilege.  Schema-CREATE is a pretty harmless
>>> privilege, INSTALL much less so.

>> CREATE doesn't just control the ability to create schemas these days- it
>> was extended to cover publications also not that long ago.

> Oh really ... hm, that does make it a much bigger deal than I was
> thinking.  Given that, I don't think there's any huge objection to
> attaching this to CREATE, at least till we get around to a more
> significant redesign.

Here's a v5 that drops the new predefined role and allows
trusted-extension installation when you have CREATE on the current
database.  There's no other changes except a bit of documentation
wordsmithing.

Barring further complaints, I'm going to push this fairly soon.

            regards, tom lane

diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 85ac79f..a10b665 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -8519,7 +8519,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>trusted</structfield>)</entry>
+     </row>
+
+     <row>
+      <entry><structfield>trusted</structfield></entry>
+      <entry><type>bool</type></entry>
+      <entry>True if the extension can be installed by non-superusers
+       with appropriate privileges</entry>
      </row>

      <row>
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 3546e39..8d3a0d1 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -1742,6 +1742,7 @@ REVOKE ALL ON accounts FROM PUBLIC;
      <listitem>
       <para>
        For databases, allows new schemas and publications to be created within
+       the database, and allows trusted extensions to be installed within
        the database.
       </para>
       <para>
@@ -1753,8 +1754,11 @@ REVOKE ALL ON accounts FROM PUBLIC;
       <para>
        For tablespaces, allows tables, indexes, and temporary files to be
        created within the tablespace, and allows databases to be created that
-       have the tablespace as their default tablespace.  (Note that revoking
-       this privilege will not alter the placement of existing objects.)
+       have the tablespace as their default tablespace.
+      </para>
+      <para>
+       Note that revoking this privilege will not alter the existence or
+       location of existing objects.
       </para>
      </listitem>
     </varlistentry>
diff --git a/doc/src/sgml/extend.sgml b/doc/src/sgml/extend.sgml
index a3046f2..ffe068b 100644
--- a/doc/src/sgml/extend.sgml
+++ b/doc/src/sgml/extend.sgml
@@ -576,6 +576,31 @@
         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>trusted</varname> (<type>boolean</type>)</term>
+      <listitem>
+       <para>
+        This parameter, if set to <literal>true</literal> (which is not the
+        default), allows some non-superusers to install an extension that
+        has <varname>superuser</varname> set to <literal>true</literal>.
+        Specifically, installation will be permitted for anyone who has
+        <literal>CREATE</literal> privilege on the current database.
+        When the user executing <command>CREATE EXTENSION</command> is not
+        a superuser but is allowed to install by virtue of this parameter,
+        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 could
+        allow access to otherwise-superuser-only abilities, such as
+        filesystem access.
        </para>
       </listitem>
      </varlistentry>
@@ -642,6 +667,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 trusted 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..d76ac3e 100644
--- a/doc/src/sgml/ref/create_extension.sgml
+++ b/doc/src/sgml/ref/create_extension.sgml
@@ -47,14 +47,25 @@ 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.
    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>
+   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.
+   However, if the extension is marked <firstterm>trusted</firstterm> in
+   its control file, then it can be installed by any user who has
+   <literal>CREATE</literal> privilege on the current database.
+   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 c9e75f4..c9e6060 100644
--- a/src/backend/catalog/system_views.sql
+++ b/src/backend/catalog/system_views.sql
@@ -317,7 +317,8 @@ CREATE VIEW pg_available_extensions AS

 CREATE VIEW pg_available_extension_versions AS
     SELECT E.name, E.version, (X.extname IS NOT NULL) AS installed,
-           E.superuser, E.relocatable, E.schema, E.requires, E.comment
+           E.superuser, E.trusted, E.relocatable,
+           E.schema, E.requires, E.comment
       FROM pg_available_extension_versions() AS E
            LEFT JOIN pg_extension AS X
              ON E.name = X.extname AND E.version = X.extversion;
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 01de398..ddd46f4 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -40,6 +40,7 @@
 #include "catalog/indexing.h"
 #include "catalog/namespace.h"
 #include "catalog/objectaccess.h"
+#include "catalog/pg_authid.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_depend.h"
 #include "catalog/pg_extension.h"
@@ -84,6 +85,7 @@ typedef struct ExtensionControlFile
     char       *schema;            /* target schema (allowed if !relocatable) */
     bool        relocatable;    /* is ALTER EXTENSION SET SCHEMA supported? */
     bool        superuser;        /* must be superuser to install? */
+    bool        trusted;        /* allow becoming superuser on the fly? */
     int            encoding;        /* encoding of the script file, or -1 */
     List       *requires;        /* names of prerequisite extensions */
 } ExtensionControlFile;
@@ -558,6 +560,14 @@ parse_extension_control_file(ExtensionControlFile *control,
                          errmsg("parameter \"%s\" requires a Boolean value",
                                 item->name)));
         }
+        else if (strcmp(item->name, "trusted") == 0)
+        {
+            if (!parse_bool(item->value, &control->trusted))
+                ereport(ERROR,
+                        (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+                         errmsg("parameter \"%s\" requires a Boolean value",
+                                item->name)));
+        }
         else if (strcmp(item->name, "encoding") == 0)
         {
             control->encoding = pg_valid_server_encoding(item->value);
@@ -614,6 +624,7 @@ read_extension_control_file(const char *extname)
     control->name = pstrdup(extname);
     control->relocatable = false;
     control->superuser = true;
+    control->trusted = false;
     control->encoding = -1;

     /*
@@ -795,6 +806,27 @@ execute_sql_string(const char *sql)
 }

 /*
+ * Policy function: is the given extension trusted for installation by a
+ * non-superuser?
+ *
+ * (Update the errhint logic below if you change this.)
+ */
+static bool
+extension_is_trusted(ExtensionControlFile *control)
+{
+    AclResult    aclresult;
+
+    /* Never trust unless extension's control file says it's okay */
+    if (!control->trusted)
+        return false;
+    /* Allow if user has CREATE privilege on current database */
+    aclresult = pg_database_aclcheck(MyDatabaseId, GetUserId(), ACL_CREATE);
+    if (aclresult == ACLCHECK_OK)
+        return true;
+    return false;
+}
+
+/*
  * Execute the appropriate script file for installing or updating the extension
  *
  * If from_version isn't NULL, it's an update
@@ -806,35 +838,56 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
                          List *requiredSchemas,
                          const char *schemaName, Oid schemaOid)
 {
+    bool        switch_to_superuser = false;
     char       *filename;
+    Oid            save_userid = 0;
+    int            save_sec_context = 0;
     int            save_nestlevel;
     StringInfoData pathbuf;
     ListCell   *lc;

     /*
-     * Enforce superuser-ness if appropriate.  We postpone this check until
-     * here so that the flag is correctly associated with the right script(s)
-     * if it's set in secondary control files.
+     * Enforce superuser-ness if appropriate.  We postpone these checks until
+     * here so that the control flags are correctly associated with the right
+     * script(s) if they happen to be set in secondary control files.
      */
     if (control->superuser && !superuser())
     {
-        if (from_version == NULL)
+        if (extension_is_trusted(control))
+            switch_to_superuser = true;
+        else if (from_version == NULL)
             ereport(ERROR,
                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                      errmsg("permission denied to create extension \"%s\"",
                             control->name),
-                     errhint("Must be superuser to create this extension.")));
+                     control->trusted
+                     ? errhint("Must have CREATE privilege on current database to create this extension.")
+                     : errhint("Must be superuser to create this extension.")));
         else
             ereport(ERROR,
                     (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
                      errmsg("permission denied to update extension \"%s\"",
                             control->name),
-                     errhint("Must be superuser to update this extension.")));
+                     control->trusted
+                     ? errhint("Must have CREATE privilege on current database to update this extension.")
+                     : errhint("Must be superuser to update this extension.")));
     }

     filename = get_extension_script_filename(control, from_version, version);

     /*
+     * If installing a trusted extension on behalf of a non-superuser, become
+     * the bootstrap superuser.  (This switch will be cleaned up automatically
+     * if the transaction aborts, as will the GUC changes below.)
+     */
+    if (switch_to_superuser)
+    {
+        GetUserIdAndSecContext(&save_userid, &save_sec_context);
+        SetUserIdAndSecContext(BOOTSTRAP_SUPERUSERID,
+                               save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
+    }
+
+    /*
      * Force client_min_messages and log_min_messages to be at least WARNING,
      * so that we won't spam the user with useless NOTICE messages from common
      * script actions like creating shell types.
@@ -907,6 +960,22 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
                                         CStringGetTextDatum("ng"));

         /*
+         * If the script uses @extowner@, substitute the calling username.
+         */
+        if (strstr(c_sql, "@extowner@"))
+        {
+            Oid            uid = switch_to_superuser ? save_userid : GetUserId();
+            const char *userName = GetUserNameFromId(uid, false);
+            const char *qUserName = quote_identifier(userName);
+
+            t_sql = DirectFunctionCall3Coll(replace_text,
+                                            C_COLLATION_OID,
+                                            t_sql,
+                                            CStringGetTextDatum("@extowner@"),
+                                            CStringGetTextDatum(qUserName));
+        }
+
+        /*
          * If it's not relocatable, substitute the target schema name for
          * occurrences of @extschema@.
          *
@@ -953,6 +1022,12 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
      * Restore the GUC variables we set above.
      */
     AtEOXact_GUC(true, save_nestlevel);
+
+    /*
+     * Restore authentication state if needed.
+     */
+    if (switch_to_superuser)
+        SetUserIdAndSecContext(save_userid, save_sec_context);
 }

 /*
@@ -2111,8 +2186,8 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
     {
         ExtensionVersionInfo *evi = (ExtensionVersionInfo *) lfirst(lc);
         ExtensionControlFile *control;
-        Datum        values[7];
-        bool        nulls[7];
+        Datum        values[8];
+        bool        nulls[8];
         ListCell   *lc2;

         if (!evi->installable)
@@ -2133,24 +2208,26 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
         values[1] = CStringGetTextDatum(evi->name);
         /* superuser */
         values[2] = BoolGetDatum(control->superuser);
+        /* trusted */
+        values[3] = BoolGetDatum(control->trusted);
         /* relocatable */
-        values[3] = BoolGetDatum(control->relocatable);
+        values[4] = BoolGetDatum(control->relocatable);
         /* schema */
         if (control->schema == NULL)
-            nulls[4] = true;
+            nulls[5] = true;
         else
-            values[4] = DirectFunctionCall1(namein,
+            values[5] = DirectFunctionCall1(namein,
                                             CStringGetDatum(control->schema));
         /* requires */
         if (control->requires == NIL)
-            nulls[5] = true;
+            nulls[6] = true;
         else
-            values[5] = convert_requires_to_datum(control->requires);
+            values[6] = convert_requires_to_datum(control->requires);
         /* comment */
         if (control->comment == NULL)
-            nulls[6] = true;
+            nulls[7] = true;
         else
-            values[6] = CStringGetTextDatum(control->comment);
+            values[7] = CStringGetTextDatum(control->comment);

         tuplestore_putvalues(tupstore, tupdesc, values, nulls);

@@ -2178,16 +2255,18 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
                 values[1] = CStringGetTextDatum(evi2->name);
                 /* superuser */
                 values[2] = BoolGetDatum(control->superuser);
+                /* trusted */
+                values[3] = BoolGetDatum(control->trusted);
                 /* relocatable */
-                values[3] = BoolGetDatum(control->relocatable);
+                values[4] = BoolGetDatum(control->relocatable);
                 /* schema stays the same */
                 /* requires */
                 if (control->requires == NIL)
-                    nulls[5] = true;
+                    nulls[6] = true;
                 else
                 {
-                    values[5] = convert_requires_to_datum(control->requires);
-                    nulls[5] = false;
+                    values[6] = convert_requires_to_datum(control->requires);
+                    nulls[6] = false;
                 }
                 /* comment stays the same */

diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat
index bef50c7..2228256 100644
--- a/src/include/catalog/pg_proc.dat
+++ b/src/include/catalog/pg_proc.dat
@@ -9496,9 +9496,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,trusted,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/test/regress/expected/rules.out b/src/test/regress/expected/rules.out
index 70e1e2f..2ab2115 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.trusted,
     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, trusted, 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..faeb645
--- /dev/null
+++ b/src/pl/plperl/expected/plperl_setup.out
@@ -0,0 +1,66 @@
+--
+-- 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
+-- suitable permissions have been granted.
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+SET ROLE regress_user1;
+CREATE EXTENSION plperl;  -- fail
+ERROR:  permission denied to create extension "plperl"
+HINT:  Must have CREATE privilege on current database to create this extension.
+CREATE EXTENSION plperlu;  -- fail
+ERROR:  permission denied to create extension "plperlu"
+HINT:  Must be superuser to create this extension.
+RESET ROLE;
+DO $$
+begin
+  execute format('grant create on database %I to regress_user1',
+                 current_database());
+end;
+$$;
+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 OWNED BY regress_user1;
+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..3a2230a 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
+trusted = 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..ae48fea
--- /dev/null
+++ b/src/pl/plperl/sql/plperl_setup.sql
@@ -0,0 +1,64 @@
+--
+-- 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
+-- suitable permissions have been granted.
+
+CREATE USER regress_user1;
+CREATE USER regress_user2;
+
+SET ROLE regress_user1;
+
+CREATE EXTENSION plperl;  -- fail
+CREATE EXTENSION plperlu;  -- fail
+
+RESET ROLE;
+
+DO $$
+begin
+  execute format('grant create on database %I to regress_user1',
+                 current_database());
+end;
+$$;
+
+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 OWNED BY regress_user1;
+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..42e764b 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
+trusted = 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..1568c17 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
+trusted = 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..af9d115 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.
+   <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 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.
   </para>

   <para>
-   Ordinarily, the user must have the
+   One 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.
+   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>

   <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>,
-   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.
+   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>
  </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 01de398..ddd46f4 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -2277,6 +2277,64 @@ get_available_versions_for_extension(ExtensionControlFile *pcontrol,
 }

 /*
+ * Test whether the given extension exists (not whether it's installed)
+ *
+ * This checks for the existence of a matching control file in the extension
+ * directory.  That's not a bulletproof check, since the file might be
+ * invalid, but this is only used for hints so it doesn't have to be 100%
+ * right.
+ */
+bool
+extension_file_exists(const char *extensionName)
+{
+    bool        result = false;
+    char       *location;
+    DIR           *dir;
+    struct dirent *de;
+
+    location = get_extension_control_directory();
+    dir = AllocateDir(location);
+
+    /*
+     * If the control directory doesn't exist, we want to silently return
+     * false.  Any other error will be reported by ReadDir.
+     */
+    if (dir == NULL && errno == ENOENT)
+    {
+        /* do nothing */
+    }
+    else
+    {
+        while ((de = ReadDir(dir, location)) != NULL)
+        {
+            char       *extname;
+
+            if (!is_extension_control_filename(de->d_name))
+                continue;
+
+            /* extract extension name from 'name.control' filename */
+            extname = pstrdup(de->d_name);
+            *strrchr(extname, '.') = '\0';
+
+            /* ignore it if it's an auxiliary control file */
+            if (strstr(extname, "--"))
+                continue;
+
+            /* done if it matches request */
+            if (strcmp(extname, extensionName) == 0)
+            {
+                result = true;
+                break;
+            }
+        }
+
+        FreeDir(dir);
+    }
+
+    return result;
+}
+
+/*
  * Convert a list of extension names to a name[] Datum
  */
 static Datum
diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c
index c31c57e..0f40c9e 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 cdff43d..9d72edb 100644
--- a/src/backend/commands/proclang.c
+++ b/src/backend/commands/proclang.c
@@ -13,329 +13,110 @@
  */
 #include "postgres.h"

-#include "access/genam.h"
-#include "access/htup_details.h"
 #include "access/table.h"
 #include "catalog/catalog.h"
 #include "catalog/dependency.h"
 #include "catalog/indexing.h"
 #include "catalog/objectaccess.h"
-#include "catalog/pg_authid.h"
 #include "catalog/pg_language.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_proc.h"
 #include "catalog/pg_type.h"
-#include "commands/dbcommands.h"
 #include "commands/defrem.h"
 #include "commands/proclang.h"
 #include "miscadmin.h"
 #include "parser/parse_func.h"
-#include "parser/parser.h"
-#include "utils/acl.h"
 #include "utils/builtins.h"
-#include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"


-typedef struct
-{
-    bool        tmpltrusted;    /* trusted? */
-    bool        tmpldbacreate;    /* db owner allowed to create? */
-    char       *tmplhandler;    /* name of handler function */
-    char       *tmplinline;        /* name of anonymous-block handler, or NULL */
-    char       *tmplvalidator;    /* name of validator function, or NULL */
-    char       *tmpllibrary;    /* path of shared library */
-} PLTemplate;
-
-static ObjectAddress create_proc_lang(const char *languageName, bool replace,
-                                      Oid languageOwner, Oid handlerOid, Oid inlineOid,
-                                      Oid valOid, bool trusted);
-static PLTemplate *find_language_template(const char *languageName);
-
 /*
  * CREATE LANGUAGE
  */
 ObjectAddress
 CreateProceduralLanguage(CreatePLangStmt *stmt)
 {
-    PLTemplate *pltemplate;
-    ObjectAddress tmpAddr;
+    const char *languageName = stmt->plname;
+    Oid            languageOwner = GetUserId();
     Oid            handlerOid,
                 inlineOid,
                 valOid;
     Oid            funcrettype;
     Oid            funcargtypes[1];
+    Relation    rel;
+    TupleDesc    tupDesc;
+    Datum        values[Natts_pg_language];
+    bool        nulls[Natts_pg_language];
+    bool        replaces[Natts_pg_language];
+    NameData    langname;
+    HeapTuple    oldtup;
+    HeapTuple    tup;
+    Oid            langoid;
+    bool        is_update;
+    ObjectAddress myself,
+                referenced;

     /*
-     * If we have template information for the language, ignore the supplied
-     * parameters (if any) and use the template information.
+     * Check permission
      */
-    if ((pltemplate = find_language_template(stmt->plname)) != NULL)
-    {
-        List       *funcname;
-
-        /*
-         * Give a notice if we are ignoring supplied parameters.
-         */
-        if (stmt->plhandler)
-            ereport(NOTICE,
-                    (errmsg("using pg_pltemplate information instead of CREATE LANGUAGE parameters")));
-
-        /*
-         * Check permission
-         */
-        if (!superuser())
-        {
-            if (!pltemplate->tmpldbacreate)
-                ereport(ERROR,
-                        (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                         errmsg("must be superuser to create procedural language \"%s\"",
-                                stmt->plname)));
-            if (!pg_database_ownercheck(MyDatabaseId, GetUserId()))
-                aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_DATABASE,
-                               get_database_name(MyDatabaseId));
-        }
-
-        /*
-         * Find or create the handler function, which we force to be in the
-         * pg_catalog schema.  If already present, it must have the correct
-         * return type.
-         */
-        funcname = SystemFuncName(pltemplate->tmplhandler);
-        handlerOid = LookupFuncName(funcname, 0, NULL, true);
-        if (OidIsValid(handlerOid))
-        {
-            funcrettype = get_func_rettype(handlerOid);
-            if (funcrettype != LANGUAGE_HANDLEROID)
-                ereport(ERROR,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("function %s must return type %s",
-                                NameListToString(funcname), "language_handler")));
-        }
-        else
-        {
-            tmpAddr = ProcedureCreate(pltemplate->tmplhandler,
-                                      PG_CATALOG_NAMESPACE,
-                                      false,    /* replace */
-                                      false,    /* returnsSet */
-                                      LANGUAGE_HANDLEROID,
-                                      BOOTSTRAP_SUPERUSERID,
-                                      ClanguageId,
-                                      F_FMGR_C_VALIDATOR,
-                                      pltemplate->tmplhandler,
-                                      pltemplate->tmpllibrary,
-                                      PROKIND_FUNCTION,
-                                      false,    /* security_definer */
-                                      false,    /* isLeakProof */
-                                      false,    /* isStrict */
-                                      PROVOLATILE_VOLATILE,
-                                      PROPARALLEL_UNSAFE,
-                                      buildoidvector(funcargtypes, 0),
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      NIL,
-                                      PointerGetDatum(NULL),
-                                      PointerGetDatum(NULL),
-                                      InvalidOid,
-                                      1,
-                                      0);
-            handlerOid = tmpAddr.objectId;
-        }
-
-        /*
-         * Likewise for the anonymous block handler, if required; but we don't
-         * care about its return type.
-         */
-        if (pltemplate->tmplinline)
-        {
-            funcname = SystemFuncName(pltemplate->tmplinline);
-            funcargtypes[0] = INTERNALOID;
-            inlineOid = LookupFuncName(funcname, 1, funcargtypes, true);
-            if (!OidIsValid(inlineOid))
-            {
-                tmpAddr = ProcedureCreate(pltemplate->tmplinline,
-                                          PG_CATALOG_NAMESPACE,
-                                          false,    /* replace */
-                                          false,    /* returnsSet */
-                                          VOIDOID,
-                                          BOOTSTRAP_SUPERUSERID,
-                                          ClanguageId,
-                                          F_FMGR_C_VALIDATOR,
-                                          pltemplate->tmplinline,
-                                          pltemplate->tmpllibrary,
-                                          PROKIND_FUNCTION,
-                                          false,    /* security_definer */
-                                          false,    /* isLeakProof */
-                                          true, /* isStrict */
-                                          PROVOLATILE_VOLATILE,
-                                          PROPARALLEL_UNSAFE,
-                                          buildoidvector(funcargtypes, 1),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          NIL,
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          InvalidOid,
-                                          1,
-                                          0);
-                inlineOid = tmpAddr.objectId;
-            }
-        }
-        else
-            inlineOid = InvalidOid;
+    if (!superuser())
+        ereport(ERROR,
+                (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+                 errmsg("must be superuser to create custom procedural language")));

+    /*
+     * Lookup the PL handler function and check that it is of the expected
+     * return type
+     */
+    Assert(stmt->plhandler);
+    handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false);
+    funcrettype = get_func_rettype(handlerOid);
+    if (funcrettype != LANGUAGE_HANDLEROID)
+    {
         /*
-         * Likewise for the validator, if required; but we don't care about
-         * its return type.
+         * We allow OPAQUE just so we can load old dump files.  When we see a
+         * handler function declared OPAQUE, change it to LANGUAGE_HANDLER.
+         * (This is probably obsolete and removable?)
          */
-        if (pltemplate->tmplvalidator)
+        if (funcrettype == OPAQUEOID)
         {
-            funcname = SystemFuncName(pltemplate->tmplvalidator);
-            funcargtypes[0] = OIDOID;
-            valOid = LookupFuncName(funcname, 1, funcargtypes, true);
-            if (!OidIsValid(valOid))
-            {
-                tmpAddr = ProcedureCreate(pltemplate->tmplvalidator,
-                                          PG_CATALOG_NAMESPACE,
-                                          false,    /* replace */
-                                          false,    /* returnsSet */
-                                          VOIDOID,
-                                          BOOTSTRAP_SUPERUSERID,
-                                          ClanguageId,
-                                          F_FMGR_C_VALIDATOR,
-                                          pltemplate->tmplvalidator,
-                                          pltemplate->tmpllibrary,
-                                          PROKIND_FUNCTION,
-                                          false,    /* security_definer */
-                                          false,    /* isLeakProof */
-                                          true, /* isStrict */
-                                          PROVOLATILE_VOLATILE,
-                                          PROPARALLEL_UNSAFE,
-                                          buildoidvector(funcargtypes, 1),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          NIL,
-                                          PointerGetDatum(NULL),
-                                          PointerGetDatum(NULL),
-                                          InvalidOid,
-                                          1,
-                                          0);
-                valOid = tmpAddr.objectId;
-            }
+            ereport(WARNING,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                     errmsg("changing return type of function %s from %s to %s",
+                            NameListToString(stmt->plhandler),
+                            "opaque", "language_handler")));
+            SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
         }
         else
-            valOid = InvalidOid;
+            ereport(ERROR,
+                    (errcode(ERRCODE_WRONG_OBJECT_TYPE),
+                     errmsg("function %s must return type %s",
+                            NameListToString(stmt->plhandler), "language_handler")));
+    }

-        /* ok, create it */
-        return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-                                handlerOid, inlineOid,
-                                valOid, pltemplate->tmpltrusted);
+    /* validate the inline function */
+    if (stmt->plinline)
+    {
+        funcargtypes[0] = INTERNALOID;
+        inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
+        /* return value is ignored, so we don't check the type */
     }
     else
-    {
-        /*
-         * No template, so use the provided information.  If there's no
-         * handler clause, the user is trying to rely on a template that we
-         * don't have, so complain accordingly.
-         */
-        if (!stmt->plhandler)
-            ereport(ERROR,
-                    (errcode(ERRCODE_UNDEFINED_OBJECT),
-                     errmsg("unsupported language \"%s\"",
-                            stmt->plname),
-                     errhint("The supported languages are listed in the pg_pltemplate system catalog.")));
+        inlineOid = InvalidOid;

-        /*
-         * Check permission
-         */
-        if (!superuser())
-            ereport(ERROR,
-                    (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
-                     errmsg("must be superuser to create custom procedural language")));
-
-        /*
-         * Lookup the PL handler function and check that it is of the expected
-         * return type
-         */
-        handlerOid = LookupFuncName(stmt->plhandler, 0, NULL, false);
-        funcrettype = get_func_rettype(handlerOid);
-        if (funcrettype != LANGUAGE_HANDLEROID)
-        {
-            /*
-             * We allow OPAQUE just so we can load old dump files.  When we
-             * see a handler function declared OPAQUE, change it to
-             * LANGUAGE_HANDLER.  (This is probably obsolete and removable?)
-             */
-            if (funcrettype == OPAQUEOID)
-            {
-                ereport(WARNING,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("changing return type of function %s from %s to %s",
-                                NameListToString(stmt->plhandler),
-                                "opaque", "language_handler")));
-                SetFunctionReturnType(handlerOid, LANGUAGE_HANDLEROID);
-            }
-            else
-                ereport(ERROR,
-                        (errcode(ERRCODE_WRONG_OBJECT_TYPE),
-                         errmsg("function %s must return type %s",
-                                NameListToString(stmt->plhandler), "language_handler")));
-        }
-
-        /* validate the inline function */
-        if (stmt->plinline)
-        {
-            funcargtypes[0] = INTERNALOID;
-            inlineOid = LookupFuncName(stmt->plinline, 1, funcargtypes, false);
-            /* return value is ignored, so we don't check the type */
-        }
-        else
-            inlineOid = InvalidOid;
-
-        /* validate the validator function */
-        if (stmt->plvalidator)
-        {
-            funcargtypes[0] = OIDOID;
-            valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
-            /* return value is ignored, so we don't check the type */
-        }
-        else
-            valOid = InvalidOid;
-
-        /* ok, create it */
-        return create_proc_lang(stmt->plname, stmt->replace, GetUserId(),
-                                handlerOid, inlineOid,
-                                valOid, stmt->pltrusted);
+    /* validate the validator function */
+    if (stmt->plvalidator)
+    {
+        funcargtypes[0] = OIDOID;
+        valOid = LookupFuncName(stmt->plvalidator, 1, funcargtypes, false);
+        /* return value is ignored, so we don't check the type */
     }
-}
-
-/*
- * Guts of language creation.
- */
-static ObjectAddress
-create_proc_lang(const char *languageName, bool replace,
-                 Oid languageOwner, Oid handlerOid, Oid inlineOid,
-                 Oid valOid, bool trusted)
-{
-    Relation    rel;
-    TupleDesc    tupDesc;
-    Datum        values[Natts_pg_language];
-    bool        nulls[Natts_pg_language];
-    bool        replaces[Natts_pg_language];
-    NameData    langname;
-    HeapTuple    oldtup;
-    HeapTuple    tup;
-    Oid            langoid;
-    bool        is_update;
-    ObjectAddress myself,
-                referenced;
+    else
+        valOid = InvalidOid;

+    /* ok to create it */
     rel = table_open(LanguageRelationId, RowExclusiveLock);
     tupDesc = RelationGetDescr(rel);

@@ -348,7 +129,7 @@ create_proc_lang(const char *languageName, bool replace,
     values[Anum_pg_language_lanname - 1] = NameGetDatum(&langname);
     values[Anum_pg_language_lanowner - 1] = ObjectIdGetDatum(languageOwner);
     values[Anum_pg_language_lanispl - 1] = BoolGetDatum(true);
-    values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(trusted);
+    values[Anum_pg_language_lanpltrusted - 1] = BoolGetDatum(stmt->pltrusted);
     values[Anum_pg_language_lanplcallfoid - 1] = ObjectIdGetDatum(handlerOid);
     values[Anum_pg_language_laninline - 1] = ObjectIdGetDatum(inlineOid);
     values[Anum_pg_language_lanvalidator - 1] = ObjectIdGetDatum(valOid);
@@ -362,13 +143,17 @@ create_proc_lang(const char *languageName, bool replace,
         Form_pg_language oldform = (Form_pg_language) GETSTRUCT(oldtup);

         /* There is one; okay to replace it? */
-        if (!replace)
+        if (!stmt->replace)
             ereport(ERROR,
                     (errcode(ERRCODE_DUPLICATE_OBJECT),
                      errmsg("language \"%s\" already exists", languageName)));
+
+        /* This is currently pointless, since we already checked superuser */
+#ifdef NOT_USED
         if (!pg_language_ownercheck(oldform->oid, languageOwner))
             aclcheck_error(ACLCHECK_NOT_OWNER, OBJECT_LANGUAGE,
                            languageName);
+#endif

         /*
          * Do not change existing oid, ownership or permissions.  Note
@@ -451,83 +236,6 @@ create_proc_lang(const char *languageName, bool replace,
 }

 /*
- * Look to see if we have template information for the given language name.
- */
-static PLTemplate *
-find_language_template(const char *languageName)
-{
-    PLTemplate *result;
-    Relation    rel;
-    SysScanDesc scan;
-    ScanKeyData key;
-    HeapTuple    tup;
-
-    rel = table_open(PLTemplateRelationId, AccessShareLock);
-
-    ScanKeyInit(&key,
-                Anum_pg_pltemplate_tmplname,
-                BTEqualStrategyNumber, F_NAMEEQ,
-                CStringGetDatum(languageName));
-    scan = systable_beginscan(rel, PLTemplateNameIndexId, true,
-                              NULL, 1, &key);
-
-    tup = systable_getnext(scan);
-    if (HeapTupleIsValid(tup))
-    {
-        Form_pg_pltemplate tmpl = (Form_pg_pltemplate) GETSTRUCT(tup);
-        Datum        datum;
-        bool        isnull;
-
-        result = (PLTemplate *) palloc0(sizeof(PLTemplate));
-        result->tmpltrusted = tmpl->tmpltrusted;
-        result->tmpldbacreate = tmpl->tmpldbacreate;
-
-        /* Remaining fields are variable-width so we need heap_getattr */
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplhandler,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplhandler = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplinline,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplinline = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmplvalidator,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmplvalidator = TextDatumGetCString(datum);
-
-        datum = heap_getattr(tup, Anum_pg_pltemplate_tmpllibrary,
-                             RelationGetDescr(rel), &isnull);
-        if (!isnull)
-            result->tmpllibrary = TextDatumGetCString(datum);
-
-        /* Ignore template if handler or library info is missing */
-        if (!result->tmplhandler || !result->tmpllibrary)
-            result = NULL;
-    }
-    else
-        result = NULL;
-
-    systable_endscan(scan);
-
-    table_close(rel, AccessShareLock);
-
-    return result;
-}
-
-
-/*
- * This just returns true if we have a valid template for a given language
- */
-bool
-PLTemplateExists(const char *languageName)
-{
-    return (find_language_template(languageName) != NULL);
-}
-
-/*
  * Guts of language dropping.
  */
 void
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index ba5916b..1b0edf5 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4324,14 +4324,17 @@ NumericOnly_list:    NumericOnly                        { $$ = list_make1($1); }
 CreatePLangStmt:
             CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
             {
-                CreatePLangStmt *n = makeNode(CreatePLangStmt);
-                n->replace = $2;
-                n->plname = $6;
-                /* parameters are all to be supplied by system */
-                n->plhandler = NIL;
-                n->plinline = NIL;
-                n->plvalidator = NIL;
-                n->pltrusted = false;
+                /*
+                 * We now interpret parameterless CREATE LANGUAGE as
+                 * CREATE EXTENSION.  "OR REPLACE" is silently translated
+                 * to "IF NOT EXISTS", which isn't quite the same, but
+                 * seems more useful than throwing an error.  We just
+                 * ignore TRUSTED, as the previous code would have too.
+                 */
+                CreateExtensionStmt *n = makeNode(CreateExtensionStmt);
+                n->if_not_exists = $2;
+                n->extname = $6;
+                n->options = NIL;
                 $$ = (Node *)n;
             }
             | CREATE opt_or_replace opt_trusted opt_procedural LANGUAGE NonReservedWord_or_Sconst
diff --git a/src/include/commands/extension.h b/src/include/commands/extension.h
index 6f10707..7923cdc 100644
--- a/src/include/commands/extension.h
+++ b/src/include/commands/extension.h
@@ -47,6 +47,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..c70f8ec 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-2020, 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 85ac79f..a10b665 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 7bdaf76..1921915 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/doc/src/sgml/xplang.sgml b/doc/src/sgml/xplang.sgml
index 60e0430..e647e2d 100644
--- a/doc/src/sgml/xplang.sgml
+++ b/doc/src/sgml/xplang.sgml
@@ -190,7 +190,7 @@ CREATE FUNCTION plperl_call_handler() RETURNS language_handler AS

 <programlisting>
 CREATE FUNCTION plperl_inline_handler(internal) RETURNS void AS
-    '$libdir/plperl' LANGUAGE C;
+    '$libdir/plperl' LANGUAGE C STRICT;

 CREATE FUNCTION plperl_validator(oid) RETURNS void AS
     '$libdir/plperl' LANGUAGE C STRICT;
diff --git a/src/backend/catalog/Makefile b/src/backend/catalog/Makefile
index d5da81c..f8f0b48 100644
--- a/src/backend/catalog/Makefile
+++ b/src/backend/catalog/Makefile
@@ -58,7 +58,7 @@ CATALOG_HEADERS := \
     pg_statistic_ext.h pg_statistic_ext_data.h \
     pg_statistic.h pg_rewrite.h pg_trigger.h pg_event_trigger.h pg_description.h \
     pg_cast.h pg_enum.h pg_namespace.h pg_conversion.h pg_depend.h \
-    pg_database.h pg_db_role_setting.h pg_tablespace.h pg_pltemplate.h \
+    pg_database.h pg_db_role_setting.h pg_tablespace.h \
     pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
     pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
     pg_ts_parser.h pg_ts_template.h pg_extension.h \
@@ -84,7 +84,7 @@ POSTGRES_BKI_DATA = $(addprefix $(top_srcdir)/src/include/catalog/,\
     pg_cast.dat pg_class.dat pg_collation.dat pg_conversion.dat \
     pg_database.dat pg_language.dat \
     pg_namespace.dat pg_opclass.dat pg_operator.dat pg_opfamily.dat \
-    pg_pltemplate.dat pg_proc.dat pg_range.dat pg_tablespace.dat \
+    pg_proc.dat pg_range.dat pg_tablespace.dat \
     pg_ts_config.dat pg_ts_config_map.dat pg_ts_dict.dat pg_ts_parser.dat \
     pg_ts_template.dat pg_type.dat \
     )
diff --git a/src/backend/catalog/catalog.c b/src/backend/catalog/catalog.c
index 16cb6d8..7d6acae 100644
--- a/src/backend/catalog/catalog.c
+++ b/src/backend/catalog/catalog.c
@@ -33,7 +33,6 @@
 #include "catalog/pg_database.h"
 #include "catalog/pg_db_role_setting.h"
 #include "catalog/pg_namespace.h"
-#include "catalog/pg_pltemplate.h"
 #include "catalog/pg_replication_origin.h"
 #include "catalog/pg_shdepend.h"
 #include "catalog/pg_shdescription.h"
@@ -242,7 +241,6 @@ IsSharedRelation(Oid relationId)
     if (relationId == AuthIdRelationId ||
         relationId == AuthMemRelationId ||
         relationId == DatabaseRelationId ||
-        relationId == PLTemplateRelationId ||
         relationId == SharedDescriptionRelationId ||
         relationId == SharedDependRelationId ||
         relationId == SharedSecLabelRelationId ||
@@ -258,7 +256,6 @@ IsSharedRelation(Oid relationId)
         relationId == AuthMemMemRoleIndexId ||
         relationId == DatabaseNameIndexId ||
         relationId == DatabaseOidIndexId ||
-        relationId == PLTemplateNameIndexId ||
         relationId == SharedDescriptionObjIndexId ||
         relationId == SharedDependDependerIndexId ||
         relationId == SharedDependReferenceIndexId ||
@@ -278,8 +275,6 @@ IsSharedRelation(Oid relationId)
         relationId == PgDatabaseToastIndex ||
         relationId == PgDbRoleSettingToastTable ||
         relationId == PgDbRoleSettingToastIndex ||
-        relationId == PgPlTemplateToastTable ||
-        relationId == PgPlTemplateToastIndex ||
         relationId == PgReplicationOriginToastTable ||
         relationId == PgReplicationOriginToastIndex ||
         relationId == PgShdescriptionToastTable ||
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 799b698..ec3e2c6 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -11396,9 +11396,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)) &&
@@ -11431,11 +11431,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 28638e9..d163cb2 100644
--- a/src/bin/pg_upgrade/function.c
+++ b/src/bin/pg_upgrade/function.c
@@ -214,13 +214,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 13e07fc..8be3038 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 1c96b30..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-2020, 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 b930730..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-2020, 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 3281dbd..51491c4 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 faaec55..882d69e 100644
--- a/src/pl/plpython/plpy_main.c
+++ b/src/pl/plpython/plpy_main.c
@@ -29,7 +29,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 070de78..f0cd40b 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 by date:

Previous
From: Robert Haas
Date:
Subject: Re: [Proposal] Global temporary tables
Next
From: Robert Haas
Date:
Subject: Re: Enabling B-Tree deduplication by default