*** a/contrib/pg_upgrade_support/pg_upgrade_support.c
--- b/contrib/pg_upgrade_support/pg_upgrade_support.c
***************
*** 190,196 **** create_empty_extension(PG_FUNCTION_ARGS)
text_to_cstring(extVersion),
extConfig,
extCondition,
! requiredExtensions);
PG_RETURN_VOID();
}
--- 190,197 ----
text_to_cstring(extVersion),
extConfig,
extCondition,
! requiredExtensions,
! InvalidOid);
PG_RETURN_VOID();
}
*** a/doc/src/sgml/extend.sgml
--- b/doc/src/sgml/extend.sgml
***************
*** 350,355 ****
--- 350,363 ----
+ When an extension only uses SQL definitions (and does not need to ship
+ compiled binary code, usually from C source), then it can use
+ the template> facility in order to upload the necessary
+ script to the PostgreSQL> server, all using the usual
+ clients and protocol.
+
+
+
The kinds of SQL objects that can be members of an extension are shown in
the description of . Notably, objects
that are database-cluster-wide, such as databases, roles, and tablespaces,
***************
*** 368,373 ****
--- 376,416 ----
+ Extension Templates
+
+
+ template for extension
+
+
+
+ The command allows a
+ superuser to create an Extension's Template>, that users
+ will then be able to use as the source for their script and control
+ parameters. Upgrade scripts can be provided with the same command, and
+ those upgrade scripts can include parameters update too, as would a
+ secondary control file.
+
+
+
+ Here's a very simple example at using a template for an extension:
+
+ create template for extension myextension version '1.0' with (nosuperuser) as
+ $script$
+ create function identity(i int)
+ returns int
+ language sql
+ as $$
+ select $1;
+ $$;
+ $script$;
+
+ create extension myextension;
+
+
+
+
+
+
Extension Files
***************
*** 429,434 ****
--- 472,492 ----
+ default_full_version (string)
+
+
+ This option allows an extension author to avoid shiping all versions
+ scripts when shipping an extension. When a version is requested and
+ the matching script does not exist on disk,
+ set default_full_version to the first
+ script you still ship and PostgreSQL will apply the intermediate
+ upgrade script as per the ALTER EXTENSION UPDATE
+ command.
+
+
+
+
+
comment (string)
*** a/doc/src/sgml/ref/allfiles.sgml
--- b/doc/src/sgml/ref/allfiles.sgml
***************
*** 32,37 **** Complete list of usable sgml source files in this directory.
--- 32,38 ----
+
***************
*** 76,81 **** Complete list of usable sgml source files in this directory.
--- 77,83 ----
+
***************
*** 116,121 **** Complete list of usable sgml source files in this directory.
--- 118,124 ----
+
*** /dev/null
--- b/doc/src/sgml/ref/alter_extension_template.sgml
***************
*** 0 ****
--- 1,149 ----
+
+
+
+
+ ALTER TEMPLATE FOR EXTENSION
+ 7
+ SQL - Language Statements
+
+
+
+ ALTER TEMPLATE FOR EXTENSION
+ change the definition of a template for an extension
+
+
+
+ ALTER TEMPLATE FOR EXTENSION
+
+
+
+
+ ALTER TEMPLATE FOR EXTENSION name SET DEFAULT VERSION version
+ ALTER TEMPLATE FOR EXTENSION name SET DEFAULT FULL VERSION full_version
+ ALTER TEMPLATE FOR EXTENSION name VERSION version WITH [ ([ control_parameter ] [, ... ]) ]
+ ALTER TEMPLATE FOR EXTENSION name VERSION version AS script
+ ALTER TEMPLATE FOR EXTENSION name FOR UPDATE FROM from_version TO to_version AS script
+ ALTER TEMPLATE FOR EXTENSION name OWNER TO new_owner
+ ALTER TEMPLATE FOR EXTENSION name RENAME TO new_name
+
+ where control_parameter is one of:
+
+ SCHEMA schema_name
+ COMMENT comment
+ SUPERUSER
+ NOSUPERUSER
+ RELOCATABLE
+ NORELOCATABLE
+ REQUIRES requirements
+
+
+
+
+ Description
+
+
+ ALTER TEMPLATE FOR EXTENSION changes the definition of
+ an extension template.
+
+
+
+
+ Parameters
+
+
+
+ name
+
+
+ The name of an extension that already has templates.
+
+
+
+
+
+ version
+
+
+ The version of the extension we want to install by default when using
+ its template.
+
+
+
+
+
+ full_version
+
+
+ The version of the extension we want to install from by default when
+ using its template. For example, if you have an extension installation
+ scipt for version 1.0 and an upgrade script
+ for 1.0--1.1, you can set the
+ default full_version to 1.0 so
+ that PostgreSQL knows to install
+ version 1.1 by first using
+ the 1.0 creation script then
+ the 1.1 upgrade script.
+
+
+
+
+
+ script
+
+
+ The script to run when installing this version of the extension.
+
+
+
+
+
+ control_parameters
+
+
+ For details about the control parameters meaning, please refer
+ to .
+
+
+
+
+
+ new_name
+
+
+ The new name of the aggregate function.
+
+
+
+
+
+ new_owner
+
+
+ The new owner of the aggregate function.
+
+
+
+
+
+
+
+ Compatibility
+
+
+ There is no ALTER TEMPLATE FOR EXTENSION statement in
+ the SQL standard.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
*** /dev/null
--- b/doc/src/sgml/ref/create_extension_template.sgml
***************
*** 0 ****
--- 1,211 ----
+
+
+
+
+ CREATE TEMPLATE FOR EXTENSION
+ 7
+ SQL - Language Statements
+
+
+
+ CREATE TEMPLATE FOR EXTENSION
+ define a new template for extension
+
+
+
+ CREATE TEMPLATE FOR EXTENSION
+
+
+
+
+ CREATE TEMPLATE FOR EXTENSION name
+ [ DEFAULT ] VERSION version
+ [ WITH ( [ control_parameter ] [, ... ] ) ]
+ AS script
+
+ CREATE TEMPLATE FOR EXTENSION name
+ FOR UPDATE FROM old_version TO new_version
+ [ WITH ( [ control_parameter ] [, ... ] ) ]
+ AS script
+
+ where control_parameter is one of:
+
+ SCHEMA schema_name
+ COMMENT comment
+ SUPERUSER
+ NOSUPERUSER
+ RELOCATABLE
+ NORELOCATABLE
+ REQUIRES requirements
+
+
+
+
+
+ Description
+
+
+ CREATE TEMPLATE FOR EXTENSION creates a new template
+ for creating the extension of the same name. It allows tools and users to
+ upload an extension script and control file without needing to access the
+ file system of the server which is running
+ the PostgreSQL service.
+
+
+
+ Using the CREATE TEMPLATE FOR EXTENSION command you
+ can upload script to be run at CREATE EXTENSION time
+ and at ALTER EXTENSION ... UPDATE time.
+
+
+
+ Refer to for further information.
+
+
+
+
+ Control Parameters
+
+
+ For details about the control parameters meaning, please refer
+ to .
+
+
+
+
+ COMMENT (string)
+
+
+ See the comment> control file parameter
+ in .
+
+
+
+
+
+ REQUIRES (string)
+
+
+ See the requires> control file parameter
+ in .
+
+
+
+
+
+ SUPERUSER (boolean)
+
+
+ Sets the superuser> control file parameter
+ to true>, with meaning as explained
+ in .
+
+
+
+
+
+ NOSUPERUSER (boolean)
+
+
+ Sets the superuser> control file parameter
+ to false>, with meaning as explained
+ in .
+
+
+
+
+
+ RELOCATABLE (boolean)
+
+
+ Sets the relocatable> control file parameter
+ to true>, with meaning as explained
+ in .
+
+
+
+
+
+ NORELOCATABLE (boolean)
+
+
+ Sets the relocatable> control file parameter
+ to false>, with meaning as explained
+ in .
+
+
+
+
+
+ schema (string)
+
+
+ See the schema> control file parameter
+ in .
+
+
+
+
+
+
+ The arguments can appear in any order, not only the one shown above.
+
+
+
+
+ Examples
+
+
+ Create a template for an extension pair, which is a kind of a key-value datatype:
+
+
+ CREATE TEMPLATE
+ FOR EXTENSION pair DEFAULT VERSION '1.0'
+ WITH (nosuperuser, relocatable, schema public)
+ AS $$
+
+ CREATE TYPE pair AS ( k text, v text );
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;';
+
+ CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = anyelement, PROCEDURE = pair);
+ CREATE OPERATOR ~> (LEFTARG = anyelement, RIGHTARG = text, PROCEDURE = pair);
+ CREATE OPERATOR ~> (LEFTARG = anyelement, RIGHTARG = anyelement, PROCEDURE = pair);
+ CREATE OPERATOR ~> (LEFTARG = text, RIGHTARG = text, PROCEDURE = pair);
+ $$;
+
+
+
+
+
+ Compatibility
+
+
+ There is no
+ CREATE TEMPLATE FOR EXTENSION statement in the SQL
+ standard.
+
+
+
+
+ See Also
+
+
+
+
+
+
+
*** /dev/null
--- b/doc/src/sgml/ref/drop_extension_template.sgml
***************
*** 0 ****
--- 1,81 ----
+
+
+
+
+ DROP TEMPLATE FOR EXTENSION
+ 7
+ SQL - Language Statements
+
+
+
+ DROP TEMPLATE FOR EXTENSION
+ remove a template for an extension
+
+
+
+ DROP TEMPLATE FOR EXTENSION
+
+
+
+
+ DROP TEMPLATE FOR EXTENSION name VERSION version [ CASCADE | RESTRICT ]
+ DROP TEMPLATE FOR EXTENSION name FOR UPDATE FROM old_version TO new_version [ CASCADE | RESTRICT ]
+
+
+
+
+ Description
+
+
+ DROP TEMPLATE FOR EXTENSION drops an existing template
+ for named extension
+
+
+
+
+ Parameters
+
+
+
+ name
+
+
+ The name of an existing text search template.
+
+
+
+
+
+ CASCADE
+
+
+ Automatically drop objects that depend on the template.
+
+
+
+
+
+ RESTRICT
+
+
+ Refuse to drop the template if any objects depend on it. This is the
+ default.
+
+
+
+
+
+
+
+ See Also
+
+
+
+
+
+
+
+
*** a/doc/src/sgml/reference.sgml
--- b/doc/src/sgml/reference.sgml
***************
*** 60,65 ****
--- 60,66 ----
&alterServer;
&alterTable;
&alterTableSpace;
+ &alterTemplateForExtension;
&alterTSConfig;
&alterTSDictionary;
&alterTSParser;
***************
*** 104,109 ****
--- 105,111 ----
&createTable;
&createTableAs;
&createTableSpace;
+ &createTemplateForExtension;
&createTSConfig;
&createTSDictionary;
&createTSParser;
***************
*** 144,149 ****
--- 146,152 ----
&dropServer;
&dropTable;
&dropTableSpace;
+ &dropTemplateForExtension;
&dropTSConfig;
&dropTSDictionary;
&dropTSParser;
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
***************
*** 38,43 **** POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
--- 38,44 ----
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 \
+ pg_extension_control.h pg_extension_template.h pg_extension_uptmpl.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
***************
*** 33,38 ****
--- 33,41 ----
#include "catalog/pg_default_acl.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
+ #include "catalog/pg_extension_control.h"
+ #include "catalog/pg_extension_template.h"
+ #include "catalog/pg_extension_uptmpl.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_language.h"
***************
*** 5051,5056 **** pg_extension_ownercheck(Oid ext_oid, Oid roleid)
--- 5054,5191 ----
}
/*
+ * Ownership check for an extension control (specified by OID).
+ */
+ bool
+ pg_extension_control_ownercheck(Oid ext_control_oid, Oid roleid)
+ {
+ Relation pg_extension_control;
+ ScanKeyData entry[1];
+ SysScanDesc scan;
+ HeapTuple tuple;
+ Oid ownerId;
+
+ /* Superusers bypass all permission checking. */
+ if (superuser_arg(roleid))
+ return true;
+
+ /* There's no syscache for pg_extension_control, so do it the hard way */
+ pg_extension_control =
+ heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ext_control_oid));
+
+ scan = systable_beginscan(pg_extension_control,
+ ExtensionControlOidIndexId, true,
+ NULL, 1, entry);
+
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension control with OID %u does not exist",
+ ext_control_oid)));
+
+ ownerId = ((Form_pg_extension_control) GETSTRUCT(tuple))->ctlowner;
+
+ systable_endscan(scan);
+ heap_close(pg_extension_control, AccessShareLock);
+
+ return has_privs_of_role(roleid, ownerId);
+ }
+
+ /*
+ * Ownership check for an extension template (specified by OID).
+ */
+ bool
+ pg_extension_template_ownercheck(Oid ext_template_oid, Oid roleid)
+ {
+ Relation pg_extension_template;
+ ScanKeyData entry[1];
+ SysScanDesc scan;
+ HeapTuple tuple;
+ Oid ownerId;
+
+ /* Superusers bypass all permission checking. */
+ if (superuser_arg(roleid))
+ return true;
+
+ /* There's no syscache for pg_extension_template, so do it the hard way */
+ pg_extension_template =
+ heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ext_template_oid));
+
+ scan = systable_beginscan(pg_extension_template,
+ ExtensionTemplateOidIndexId, true,
+ NULL, 1, entry);
+
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension template with OID %u does not exist",
+ ext_template_oid)));
+
+ ownerId = ((Form_pg_extension_template) GETSTRUCT(tuple))->tplowner;
+
+ systable_endscan(scan);
+ heap_close(pg_extension_template, AccessShareLock);
+
+ return has_privs_of_role(roleid, ownerId);
+ }
+
+ /*
+ * Ownership check for an extension update template (specified by OID).
+ */
+ bool
+ pg_extension_uptmpl_ownercheck(Oid ext_uptmpl_oid, Oid roleid)
+ {
+ Relation pg_extension_uptmpl;
+ ScanKeyData entry[1];
+ SysScanDesc scan;
+ HeapTuple tuple;
+ Oid ownerId;
+
+ /* Superusers bypass all permission checking. */
+ if (superuser_arg(roleid))
+ return true;
+
+ /* There's no syscache for pg_extension_uptmpl, so do it the hard way */
+ pg_extension_uptmpl =
+ heap_open(ExtensionUpTmplRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ext_uptmpl_oid));
+
+ scan = systable_beginscan(pg_extension_uptmpl,
+ ExtensionUpTmplOidIndexId, true,
+ NULL, 1, entry);
+
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension template for update with OID %u does not exist",
+ ext_uptmpl_oid)));
+
+ ownerId = ((Form_pg_extension_uptmpl) GETSTRUCT(tuple))->uptowner;
+
+ systable_endscan(scan);
+ heap_close(pg_extension_uptmpl, AccessShareLock);
+
+ return has_privs_of_role(roleid, ownerId);
+ }
+
+ /*
* Check whether specified role has CREATEROLE privilege (or is a superuser)
*
* Note: roles do not have owners per se; instead we use this test in
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 35,40 ****
--- 35,43 ----
#include "catalog/pg_depend.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
+ #include "catalog/pg_extension_control.h"
+ #include "catalog/pg_extension_template.h"
+ #include "catalog/pg_extension_uptmpl.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_language.h"
***************
*** 60,65 ****
--- 63,69 ----
#include "commands/proclang.h"
#include "commands/schemacmds.h"
#include "commands/seclabel.h"
+ #include "commands/template.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "nodes/nodeFuncs.h"
***************
*** 1245,1250 **** doDeletion(const ObjectAddress *object, int flags)
--- 1249,1266 ----
RemoveExtensionById(object->objectId);
break;
+ case OCLASS_EXTENSION_CONTROL:
+ RemoveExtensionControlById(object->objectId);
+ break;
+
+ case OCLASS_EXTENSION_TEMPLATE:
+ RemoveExtensionTemplateById(object->objectId);
+ break;
+
+ case OCLASS_EXTENSION_UPTMPL:
+ RemoveExtensionUpTmplById(object->objectId);
+ break;
+
case OCLASS_EVENT_TRIGGER:
RemoveEventTriggerById(object->objectId);
break;
***************
*** 2306,2311 **** getObjectClass(const ObjectAddress *object)
--- 2322,2336 ----
case ExtensionRelationId:
return OCLASS_EXTENSION;
+ case ExtensionControlRelationId:
+ return OCLASS_EXTENSION_CONTROL;
+
+ case ExtensionTemplateRelationId:
+ return OCLASS_EXTENSION_TEMPLATE;
+
+ case ExtensionUpTmplRelationId:
+ return OCLASS_EXTENSION_UPTMPL;
+
case EventTriggerRelationId:
return OCLASS_EVENT_TRIGGER;
}
*** a/src/backend/catalog/objectaddress.c
--- b/src/backend/catalog/objectaddress.c
***************
*** 32,37 ****
--- 32,40 ----
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
#include "catalog/pg_extension.h"
+ #include "catalog/pg_extension_control.h"
+ #include "catalog/pg_extension_template.h"
+ #include "catalog/pg_extension_uptmpl.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_language.h"
***************
*** 57,62 ****
--- 60,66 ----
#include "commands/extension.h"
#include "commands/proclang.h"
#include "commands/tablespace.h"
+ #include "commands/template.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
#include "funcapi.h"
***************
*** 176,181 **** static ObjectPropertyType ObjectProperty[] =
--- 180,218 ----
true
},
{
+ ExtensionControlRelationId,
+ ExtensionControlOidIndexId,
+ -1,
+ -1,
+ Anum_pg_extension_control_ctlname,
+ InvalidAttrNumber, /* extension doesn't belong to extnamespace */
+ Anum_pg_extension_control_ctlowner,
+ InvalidAttrNumber,
+ ACL_KIND_EXTCONTROL
+ },
+ {
+ ExtensionTemplateRelationId,
+ ExtensionTemplateOidIndexId,
+ -1,
+ -1,
+ Anum_pg_extension_template_tplname,
+ InvalidAttrNumber, /* extension doesn't belong to extnamespace */
+ Anum_pg_extension_template_tplowner,
+ InvalidAttrNumber,
+ ACL_KIND_EXTTEMPLATE
+ },
+ {
+ ExtensionUpTmplRelationId,
+ ExtensionUpTmplOidIndexId,
+ -1,
+ -1,
+ Anum_pg_extension_uptmpl_uptname,
+ InvalidAttrNumber, /* extension doesn't belong to extnamespace */
+ Anum_pg_extension_uptmpl_uptowner,
+ InvalidAttrNumber,
+ ACL_KIND_EXTUPTMPL
+ },
+ {
ForeignDataWrapperRelationId,
ForeignDataWrapperOidIndexId,
FOREIGNDATAWRAPPEROID,
***************
*** 431,436 **** static ObjectAddress get_object_address_type(ObjectType objtype,
--- 468,475 ----
List *objname, bool missing_ok);
static ObjectAddress get_object_address_opcf(ObjectType objtype, List *objname,
List *objargs, bool missing_ok);
+ static ObjectAddress get_object_address_tmpl(ObjectType objtype,
+ List *objname, List *objargs, bool missing_ok);
static ObjectPropertyType *get_object_property_data(Oid class_id);
static void getRelationDescription(StringInfo buffer, Oid relid);
***************
*** 521,526 **** get_object_address(ObjectType objtype, List *objname, List *objargs,
--- 560,570 ----
address = get_object_address_unqualified(objtype,
objname, missing_ok);
break;
+ case OBJECT_EXTENSION_TEMPLATE:
+ case OBJECT_EXTENSION_UPTMPL:
+ address = get_object_address_tmpl(objtype,
+ objname, objargs, missing_ok);
+ break;
case OBJECT_TYPE:
case OBJECT_DOMAIN:
address = get_object_address_type(objtype, objname, missing_ok);
***************
*** 1115,1120 **** get_object_address_opcf(ObjectType objtype,
--- 1159,1240 ----
}
/*
+ * Find the ObjectAddress for an extension template, control or update
+ * template.
+ */
+ static ObjectAddress
+ get_object_address_tmpl(ObjectType objtype,
+ List *objname, List *objargs, bool missing_ok)
+ {
+ const char *name;
+ ObjectAddress address;
+
+ /*
+ * The types of names handled by this function are not permitted to be
+ * schema-qualified or catalog-qualified.
+ */
+ if (list_length(objname) != 1)
+ {
+ const char *msg;
+
+ switch (objtype)
+ {
+ case OBJECT_EXTENSION_TEMPLATE:
+ msg = gettext_noop("extension template name cannot be qualified");
+ break;
+ case OBJECT_EXTENSION_UPTMPL:
+ msg = gettext_noop("extension update template name cannot be qualified");
+ break;
+ default:
+ elog(ERROR, "unrecognized objtype: %d", (int) objtype);
+ msg = NULL; /* placate compiler */
+ }
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("%s", _(msg))));
+ }
+
+ name = strVal(linitial(objname));
+
+ switch (objtype)
+ {
+ case OBJECT_EXTENSION_TEMPLATE:
+ {
+ const char *version;
+
+ Assert(list_length(objargs) == 1);
+ version = strVal(linitial(objargs));
+
+ address.classId = ExtensionTemplateRelationId;
+ address.objectId = get_template_oid(name, version, missing_ok);
+ address.objectSubId = 0;
+ break;
+ }
+ case OBJECT_EXTENSION_UPTMPL:
+ {
+ const char *from, *to;
+
+ Assert(list_length(objargs) == 2);
+
+ from = strVal(linitial(objargs));
+ to = strVal(lsecond(objargs));
+
+ address.classId = ExtensionUpTmplRelationId;
+ address.objectId = get_uptmpl_oid(name, from, to, missing_ok);
+ address.objectSubId = 0;
+ break;
+ }
+ default:
+ elog(ERROR, "unrecognized objtype: %d", (int) objtype);
+ /* placate compiler, which doesn't know elog won't return */
+ address.classId = InvalidOid;
+ address.objectId = InvalidOid;
+ address.objectSubId = 0;
+ }
+ return address;
+ }
+
+ /*
* Check ownership of an object previously identified by get_object_address.
*/
void
***************
*** 1179,1184 **** check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
--- 1299,1314 ----
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTENSION,
NameListToString(objname));
break;
+ case OBJECT_EXTENSION_TEMPLATE:
+ if (!pg_extension_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTTEMPLATE,
+ NameListToString(objname));
+ break;
+ case OBJECT_EXTENSION_UPTMPL:
+ if (!pg_extension_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTUPTMPL,
+ NameListToString(objname));
+ break;
case OBJECT_FDW:
if (!pg_foreign_data_wrapper_ownercheck(address.objectId, roleid))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FDW,
***************
*** 2143,2148 **** getObjectDescription(const ObjectAddress *object)
--- 2273,2314 ----
break;
}
+ case OCLASS_EXTENSION_CONTROL:
+ {
+ char *extname;
+
+ extname = get_extension_control_name(object->objectId);
+ if (!extname)
+ elog(ERROR, "cache lookup failed for control template for extension %u",
+ object->objectId);
+ appendStringInfo(&buffer, _("control template for extension %s"), extname);
+ break;
+ }
+
+ case OCLASS_EXTENSION_TEMPLATE:
+ {
+ char *extname;
+
+ extname = get_extension_template_name(object->objectId);
+ if (!extname)
+ elog(ERROR, "cache lookup failed for create template for extension %u",
+ object->objectId);
+ appendStringInfo(&buffer, _("create template for extension %s"), extname);
+ break;
+ }
+
+ case OCLASS_EXTENSION_UPTMPL:
+ {
+ char *extname;
+
+ extname = get_extension_uptmpl_name(object->objectId);
+ if (!extname)
+ elog(ERROR, "cache lookup failed for update template for extension %u",
+ object->objectId);
+ appendStringInfo(&buffer, _("update template for extension %s"), extname);
+ break;
+ }
+
default:
appendStringInfo(&buffer, "unrecognized object %u %u %d",
object->classId,
*** a/src/backend/commands/Makefile
--- b/src/backend/commands/Makefile
***************
*** 19,25 **** OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
portalcmds.o prepare.o proclang.o \
schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
! tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
variable.o view.o
include $(top_srcdir)/src/backend/common.mk
--- 19,25 ----
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
portalcmds.o prepare.o proclang.o \
schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
! template.o tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
variable.o view.o
include $(top_srcdir)/src/backend/common.mk
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 47,52 ****
--- 47,53 ----
#include "commands/schemacmds.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+ #include "commands/template.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "commands/user.h"
***************
*** 147,157 **** report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
* objectId: OID of object to be renamed
* new_name: CString representation of new name
*/
! static void
AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
{
Oid classId = RelationGetRelid(rel);
- int oidCacheId = get_object_catcache_oid(classId);
int nameCacheId = get_object_catcache_name(classId);
AttrNumber Anum_name = get_object_attnum_name(classId);
AttrNumber Anum_namespace = get_object_attnum_namespace(classId);
--- 148,157 ----
* objectId: OID of object to be renamed
* new_name: CString representation of new name
*/
! void
AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
{
Oid classId = RelationGetRelid(rel);
int nameCacheId = get_object_catcache_name(classId);
AttrNumber Anum_name = get_object_attnum_name(classId);
AttrNumber Anum_namespace = get_object_attnum_namespace(classId);
***************
*** 170,177 **** AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
bool *replaces;
NameData nameattrdata;
! oldtup = SearchSysCache1(oidCacheId, ObjectIdGetDatum(objectId));
! if (!HeapTupleIsValid(oldtup))
elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
objectId, RelationGetRelationName(rel));
--- 170,177 ----
bool *replaces;
NameData nameattrdata;
! oldtup = get_catalog_object_by_oid(rel, objectId);
! if (oldtup == NULL)
elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
objectId, RelationGetRelationName(rel));
***************
*** 291,298 **** AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
pfree(nulls);
pfree(replaces);
heap_freetuple(newtup);
-
- ReleaseSysCache(oldtup);
}
/*
--- 291,296 ----
***************
*** 342,347 **** ExecRenameStmt(RenameStmt *stmt)
--- 340,348 ----
case OBJECT_TYPE:
return RenameType(stmt);
+ case OBJECT_EXTENSION_TEMPLATE:
+ return AlterExtensionTemplateRename(stmt->subname, stmt->newname);
+
case OBJECT_AGGREGATE:
case OBJECT_COLLATION:
case OBJECT_CONVERSION:
***************
*** 701,707 **** ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
return AlterEventTriggerOwner(strVal(linitial(stmt->object)),
newowner);
! /* Generic cases */
case OBJECT_AGGREGATE:
case OBJECT_COLLATION:
case OBJECT_CONVERSION:
--- 702,712 ----
return AlterEventTriggerOwner(strVal(linitial(stmt->object)),
newowner);
! case OBJECT_EXTENSION_TEMPLATE:
! return AlterExtensionTemplateOwner(strVal(linitial(stmt->object)),
! newowner);
!
! /* Generic cases */
case OBJECT_AGGREGATE:
case OBJECT_COLLATION:
case OBJECT_CONVERSION:
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
***************
*** 92,97 **** static event_trigger_support_data event_trigger_support[] = {
--- 92,98 ----
{"SERVER", true},
{"TABLE", true},
{"TABLESPACE", false},
+ {"TEMPLATE FOR EXTENSION", true},
{"TRIGGER", true},
{"TEXT SEARCH CONFIGURATION", true},
{"TEXT SEARCH DICTIONARY", true},
***************
*** 922,927 **** EventTriggerSupportsObjectType(ObjectType obtype)
--- 923,930 ----
case OBJECT_CONVERSION:
case OBJECT_DOMAIN:
case OBJECT_EXTENSION:
+ case OBJECT_EXTENSION_TEMPLATE:
+ case OBJECT_EXTENSION_UPTMPL:
case OBJECT_FDW:
case OBJECT_FOREIGN_SERVER:
case OBJECT_FOREIGN_TABLE:
***************
*** 992,997 **** EventTriggerSupportsObjectClass(ObjectClass objclass)
--- 995,1003 ----
case OCLASS_USER_MAPPING:
case OCLASS_DEFACL:
case OCLASS_EXTENSION:
+ case OCLASS_EXTENSION_CONTROL:
+ case OCLASS_EXTENSION_TEMPLATE:
+ case OCLASS_EXTENSION_UPTMPL:
return true;
case MAX_OCLASS:
*** a/src/backend/commands/extension.c
--- b/src/backend/commands/extension.c
***************
*** 37,48 ****
--- 37,50 ----
#include "catalog/pg_collation.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_extension.h"
+ #include "catalog/pg_extension_control.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_type.h"
#include "commands/alter.h"
#include "commands/comment.h"
#include "commands/extension.h"
#include "commands/schemacmds.h"
+ #include "commands/template.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
***************
*** 61,83 **** bool creating_extension = false;
Oid CurrentExtensionObject = InvalidOid;
/*
- * Internal data structure to hold the results of parsing a control file
- */
- typedef struct ExtensionControlFile
- {
- char *name; /* name of the extension */
- char *directory; /* directory for script files */
- char *default_version; /* default install target version, if any */
- char *module_pathname; /* string to substitute for MODULE_PATHNAME */
- char *comment; /* comment, if any */
- char *schema; /* target schema (allowed if !relocatable) */
- bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */
- bool superuser; /* must be superuser to install? */
- int encoding; /* encoding of the script file, or -1 */
- List *requires; /* names of prerequisite extensions */
- } ExtensionControlFile;
-
- /*
* Internal data structure for update path information
*/
typedef struct ExtensionVersionInfo
--- 63,68 ----
***************
*** 96,106 **** static List *find_update_path(List *evi_list,
ExtensionVersionInfo *evi_start,
ExtensionVersionInfo *evi_target,
bool reinitialize);
! static void get_available_versions_for_extension(ExtensionControlFile *pcontrol,
Tuplestorestate *tupstore,
TupleDesc tupdesc);
static void ApplyExtensionUpdates(Oid extensionOid,
! ExtensionControlFile *pcontrol,
const char *initialVersion,
List *updateVersions);
--- 81,93 ----
ExtensionVersionInfo *evi_start,
ExtensionVersionInfo *evi_target,
bool reinitialize);
! static void get_available_versions_for_extension(ExtensionControl *pcontrol,
Tuplestorestate *tupstore,
TupleDesc tupdesc);
+ static void get_available_versions_for_extension_templates(Tuplestorestate *tupstore,
+ TupleDesc tupdesc);
static void ApplyExtensionUpdates(Oid extensionOid,
! ExtensionControl *pcontrol,
const char *initialVersion,
List *updateVersions);
***************
*** 232,238 **** get_extension_schema(Oid ext_oid)
/*
* Utility functions to check validity of extension and version names
*/
! static void
check_valid_extension_name(const char *extensionname)
{
int namelen = strlen(extensionname);
--- 219,225 ----
/*
* Utility functions to check validity of extension and version names
*/
! void
check_valid_extension_name(const char *extensionname)
{
int namelen = strlen(extensionname);
***************
*** 355,361 **** get_extension_control_directory(void)
return result;
}
! static char *
get_extension_control_filename(const char *extname)
{
char sharepath[MAXPGPATH];
--- 342,351 ----
return result;
}
! /*
! * We need that function in template.c
! */
! char *
get_extension_control_filename(const char *extname)
{
char sharepath[MAXPGPATH];
***************
*** 370,376 **** get_extension_control_filename(const char *extname)
}
static char *
! get_extension_script_directory(ExtensionControlFile *control)
{
char sharepath[MAXPGPATH];
char *result;
--- 360,366 ----
}
static char *
! get_extension_script_directory(ExtensionControl *control)
{
char sharepath[MAXPGPATH];
char *result;
***************
*** 393,399 **** get_extension_script_directory(ExtensionControlFile *control)
}
static char *
! get_extension_aux_control_filename(ExtensionControlFile *control,
const char *version)
{
char *result;
--- 383,389 ----
}
static char *
! get_extension_aux_control_filename(ExtensionControl *control,
const char *version)
{
char *result;
***************
*** 411,417 **** get_extension_aux_control_filename(ExtensionControlFile *control,
}
static char *
! get_extension_script_filename(ExtensionControlFile *control,
const char *from_version, const char *version)
{
char *result;
--- 401,407 ----
}
static char *
! get_extension_script_filename(ExtensionControl *control,
const char *from_version, const char *version)
{
char *result;
***************
*** 432,437 **** get_extension_script_filename(ExtensionControlFile *control,
--- 422,440 ----
return result;
}
+ /*
+ * An extension version is said to be "full" when it has a full install script,
+ * so that we know we don't need any update sequences dances either from
+ * "unpackaged" or from "default_major_version".
+ */
+ static bool
+ extension_version_is_full(ExtensionControl *control, const char *version)
+ {
+ char *filename = get_extension_script_filename(control, NULL, version);
+
+ return access(filename, F_OK) == 0
+ || OidIsValid(get_template_oid(control->name, version, true));
+ }
/*
* Parse contents of primary or auxiliary control file, and fill in
***************
*** 443,449 **** get_extension_script_filename(ExtensionControlFile *control,
* worry about what encoding it's in; all values are expected to be ASCII.
*/
static void
! parse_extension_control_file(ExtensionControlFile *control,
const char *version)
{
char *filename;
--- 446,452 ----
* worry about what encoding it's in; all values are expected to be ASCII.
*/
static void
! parse_extension_control_file(ExtensionControl *control,
const char *version)
{
char *filename;
***************
*** 483,489 **** parse_extension_control_file(ExtensionControlFile *control,
FreeFile(file);
/*
! * Convert the ConfigVariable list into ExtensionControlFile entries.
*/
for (item = head; item != NULL; item = item->next)
{
--- 486,492 ----
FreeFile(file);
/*
! * Convert the ConfigVariable list into ExtensionControl entries.
*/
for (item = head; item != NULL; item = item->next)
{
***************
*** 507,512 **** parse_extension_control_file(ExtensionControlFile *control,
--- 510,519 ----
control->default_version = pstrdup(item->value);
}
+ else if (strcmp(item->name, "default_full_version") == 0)
+ {
+ control->default_full_version = pstrdup(item->value);
+ }
else if (strcmp(item->name, "module_pathname") == 0)
{
control->module_pathname = pstrdup(item->value);
***************
*** 579,594 **** parse_extension_control_file(ExtensionControlFile *control,
/*
* Read the primary control file for the specified extension.
*/
! static ExtensionControlFile *
read_extension_control_file(const char *extname)
{
! ExtensionControlFile *control;
/*
* Set up default values. Pointer fields are initially null.
*/
! control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile));
control->name = pstrdup(extname);
control->relocatable = false;
control->superuser = true;
control->encoding = -1;
--- 586,603 ----
/*
* Read the primary control file for the specified extension.
*/
! ExtensionControl *
read_extension_control_file(const char *extname)
{
! ExtensionControl *control;
/*
* Set up default values. Pointer fields are initially null.
*/
! control = (ExtensionControl *) palloc0(sizeof(ExtensionControl));
! control->ctrlOid = InvalidOid;
control->name = pstrdup(extname);
+ control->is_template = false;
control->relocatable = false;
control->superuser = true;
control->encoding = -1;
***************
*** 604,623 **** read_extension_control_file(const char *extname)
/*
* Read the auxiliary control file for the specified extension and version.
*
! * Returns a new modified ExtensionControlFile struct; the original struct
* (reflecting just the primary control file) is not modified.
*/
! static ExtensionControlFile *
! read_extension_aux_control_file(const ExtensionControlFile *pcontrol,
const char *version)
{
! ExtensionControlFile *acontrol;
/*
* Flat-copy the struct. Pointer fields share values with original.
*/
! acontrol = (ExtensionControlFile *) palloc(sizeof(ExtensionControlFile));
! memcpy(acontrol, pcontrol, sizeof(ExtensionControlFile));
/*
* Parse the auxiliary control file, overwriting struct fields
--- 613,632 ----
/*
* Read the auxiliary control file for the specified extension and version.
*
! * Returns a new modified ExtensionControl struct; the original struct
* (reflecting just the primary control file) is not modified.
*/
! static ExtensionControl *
! read_extension_aux_control_file(const ExtensionControl *pcontrol,
const char *version)
{
! ExtensionControl *acontrol;
/*
* Flat-copy the struct. Pointer fields share values with original.
*/
! acontrol = (ExtensionControl *) palloc(sizeof(ExtensionControl));
! memcpy(acontrol, pcontrol, sizeof(ExtensionControl));
/*
* Parse the auxiliary control file, overwriting struct fields
***************
*** 628,637 **** read_extension_aux_control_file(const ExtensionControlFile *pcontrol,
}
/*
* Read an SQL script file into a string, and convert to database encoding
*/
static char *
! read_extension_script_file(const ExtensionControlFile *control,
const char *filename)
{
int src_encoding;
--- 637,724 ----
}
/*
+ * Read the control properties for given extension, either from a file on the
+ * file system or if it does not exists there, from a template catalog in
+ * pg_extension_control, if it exists.
+ *
+ * In the file system case, we get the default properties for the extension and
+ * one of them is the default_version property that allows us to know which
+ * version to install. Knowing that we can then read the right auxilliary
+ * control file to override some defaults if needs be.
+ *
+ * When reading from the catalogs, we have in pg_extension_control at most a
+ * row per version, with the whole set of properties we need to apply. So once
+ * we found the current default version to install, we don't need to read and
+ * another set of properties and override them.
+ *
+ * In both cases we return the structure ExtensionControl.
+ */
+ static ExtensionControl *
+ read_extension_control(const char *extname)
+ {
+ char *filename;
+
+ filename = get_extension_control_filename(extname);
+
+ if (access(filename, F_OK) == -1 && errno == ENOENT)
+ {
+ /* ENOENT: let's look at the control templates
+ *
+ * We pass in missing_ok as true so as to be able to offer a better
+ * error message.
+ */
+ ExtensionControl *c = find_default_pg_extension_control(extname, true);
+
+ if (c == NULL)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("Extension \"%s\" is not available from \"%s\" nor as a template",
+ extname, get_extension_control_directory())));
+ return NULL;
+ }
+ return c;
+ }
+ else
+ /* we let the file specific routines deal with any other error */
+ return read_extension_control_file(extname);
+ }
+
+ static ExtensionControl *
+ read_extension_aux_control(const ExtensionControl *pcontrol,
+ const char *version)
+ {
+ if (pcontrol->is_template)
+ {
+ /* we might already have read the right version */
+ if (strcmp(pcontrol->default_version, version) != 0)
+ {
+ ExtensionControl *control;
+ /*
+ * While read_extension_aux_control() override pcontrol with the
+ * auxilliary control file properties, in the case when we read
+ * from the catalogs, the overriding has been done already at
+ * CREATE TEMPLATE time, so we only need to load a single row from
+ * pg_extension_control at any time.
+ */
+ control = find_pg_extension_control(pcontrol->name, version, true);
+
+ return control ? control : (ExtensionControl *)pcontrol;
+ }
+ else
+ /* pcontrol is the control file for the right version. */
+ return (ExtensionControl *)pcontrol;
+ }
+ else
+ /* read ExtensionControl from files */
+ return read_extension_aux_control_file(pcontrol, version);
+ }
+
+ /*
* Read an SQL script file into a string, and convert to database encoding
*/
static char *
! read_extension_script_file(const ExtensionControl *control,
const char *filename)
{
int src_encoding;
***************
*** 674,681 **** read_extension_script_file(const ExtensionControlFile *control,
/*
* Execute given SQL string.
*
- * filename is used only to report errors.
- *
* Note: it's tempting to just use SPI to execute the string, but that does
* not work very well. The really serious problem is that SPI will parse,
* analyze, and plan the whole string before executing any of it; of course
--- 761,766 ----
***************
*** 685,691 **** read_extension_script_file(const ExtensionControlFile *control,
* could be very long.
*/
static void
! execute_sql_string(const char *sql, const char *filename)
{
List *raw_parsetree_list;
DestReceiver *dest;
--- 770,776 ----
* could be very long.
*/
static void
! execute_sql_string(const char *sql)
{
List *raw_parsetree_list;
DestReceiver *dest;
***************
*** 770,782 **** execute_sql_string(const char *sql, const char *filename)
* If from_version isn't NULL, it's an update
*/
static void
! execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
const char *from_version,
const char *version,
List *requiredSchemas,
const char *schemaName, Oid schemaOid)
{
- char *filename;
int save_nestlevel;
StringInfoData pathbuf;
ListCell *lc;
--- 855,866 ----
* If from_version isn't NULL, it's an update
*/
static void
! execute_extension_script(Oid extensionOid, ExtensionControl *control,
const char *from_version,
const char *version,
List *requiredSchemas,
const char *schemaName, Oid schemaOid)
{
int save_nestlevel;
StringInfoData pathbuf;
ListCell *lc;
***************
*** 802,809 **** execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
errhint("Must be superuser to update this extension.")));
}
- filename = get_extension_script_filename(control, from_version, version);
-
/*
* 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
--- 886,891 ----
***************
*** 858,865 **** execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
CurrentExtensionObject = extensionOid;
PG_TRY();
{
! char *c_sql = read_extension_script_file(control, filename);
! Datum t_sql;
/* We use various functions that want to operate on text datums */
t_sql = CStringGetTextDatum(c_sql);
--- 940,962 ----
CurrentExtensionObject = extensionOid;
PG_TRY();
{
! char *c_sql;
! Datum t_sql;
!
! if (control->is_template)
! {
! c_sql = read_extension_template_script(control->name,
! from_version,
! version);
! }
! else
! {
! char *filename = get_extension_script_filename(control,
! from_version,
! version);
!
! c_sql = read_extension_script_file(control, filename);
! }
/* We use various functions that want to operate on text datums */
t_sql = CStringGetTextDatum(c_sql);
***************
*** 908,914 **** execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
/* And now back to C string */
c_sql = text_to_cstring(DatumGetTextPP(t_sql));
! execute_sql_string(c_sql, filename);
}
PG_CATCH();
{
--- 1005,1011 ----
/* And now back to C string */
c_sql = text_to_cstring(DatumGetTextPP(t_sql));
! execute_sql_string(c_sql);
}
PG_CATCH();
{
***************
*** 997,1003 **** get_nearest_unprocessed_vertex(List *evi_list)
* the versions that can be reached in one step from that version.
*/
static List *
! get_ext_ver_list(ExtensionControlFile *control)
{
List *evi_list = NIL;
int extnamelen = strlen(control->name);
--- 1094,1100 ----
* the versions that can be reached in one step from that version.
*/
static List *
! get_ext_ver_list_from_files(ExtensionControl *control)
{
List *evi_list = NIL;
int extnamelen = strlen(control->name);
***************
*** 1053,1058 **** get_ext_ver_list(ExtensionControlFile *control)
--- 1150,1208 ----
}
/*
+ * We scan pg_extension_template for all install scripts of given extension,
+ * then pg_extension_uptmpl for all update scripts of same extension.
+ */
+ static List *
+ get_ext_ver_list_from_catalog(ExtensionControl *control)
+ {
+ List *evi_list = NIL;
+ List *installable, *direct_update_paths;
+ ListCell *lc;
+
+ /* pg_extension_template contains install scripts */
+ installable = list_pg_extension_template_versions(control->name);
+
+ foreach(lc, installable)
+ {
+ ExtensionVersionInfo *evi;
+ char *vername = (char *) lfirst(lc);
+
+ evi = get_ext_ver_info(vername, &evi_list);
+ (void) evi; /* silence a compiler warning */
+ }
+
+ /* pg_extension_uptmpl contains upgrade scripts */
+ direct_update_paths = list_pg_extension_update_versions(control->name);
+
+ foreach(lc, direct_update_paths)
+ {
+ ExtensionVersionInfo *evi, *evi2;
+ char *vername = (char *) linitial(lfirst(lc));
+ char *vername2 = (char *) lsecond(lfirst(lc));
+
+ evi = get_ext_ver_info(vername, &evi_list);
+ evi2 = get_ext_ver_info(vername2, &evi_list);
+ evi->reachable = lappend(evi->reachable, evi2);
+ }
+ return evi_list;
+ }
+
+ /*
+ * We have to implement that function twice. The first implementation deals
+ * with control files and sql scripts on the file system while the second one
+ * deals with the catalogs pg_extension_template and pg_extension_uptmpl.
+ */
+ static List *
+ get_ext_ver_list(ExtensionControl *control)
+ {
+ if (control->is_template)
+ return get_ext_ver_list_from_catalog(control);
+ else
+ return get_ext_ver_list_from_files(control);
+ }
+
+ /*
* Given an initial and final version name, identify the sequence of update
* scripts that have to be applied to perform that update.
*
***************
*** 1060,1066 **** get_ext_ver_list(ExtensionControlFile *control)
* version is *not* included).
*/
static List *
! identify_update_path(ExtensionControlFile *control,
const char *oldVersion, const char *newVersion)
{
List *result;
--- 1210,1216 ----
* version is *not* included).
*/
static List *
! identify_update_path(ExtensionControl *control,
const char *oldVersion, const char *newVersion)
{
List *result;
***************
*** 1172,1177 **** find_update_path(List *evi_list,
--- 1322,1349 ----
}
/*
+ * Add a dependency from the extension on a given pg_extension_control entry.
+ */
+ static void
+ record_control_dependency(Oid extensionOid, Oid ctrlOid)
+ {
+ if (OidIsValid(ctrlOid))
+ {
+ ObjectAddress myself, pg_extension_control;
+
+ myself.classId = ExtensionRelationId;
+ myself.objectId = extensionOid;
+ myself.objectSubId = 0;
+
+ pg_extension_control.classId = ExtensionControlRelationId;
+ pg_extension_control.objectId = ctrlOid;
+ pg_extension_control.objectSubId = 0;
+
+ recordDependencyOn(&myself, &pg_extension_control, DEPENDENCY_NORMAL);
+ }
+ }
+
+ /*
* CREATE EXTENSION
*/
Oid
***************
*** 1185,1197 **** CreateExtension(CreateExtensionStmt *stmt)
char *versionName;
char *oldVersionName;
Oid extowner = GetUserId();
! ExtensionControlFile *pcontrol;
! ExtensionControlFile *control;
List *updateVersions;
List *requiredExtensions;
List *requiredSchemas;
Oid extensionOid;
ListCell *lc;
/* Check extension name validity before any filesystem access */
check_valid_extension_name(stmt->extname);
--- 1357,1370 ----
char *versionName;
char *oldVersionName;
Oid extowner = GetUserId();
! ExtensionControl *pcontrol;
! ExtensionControl *control;
List *updateVersions;
List *requiredExtensions;
List *requiredSchemas;
Oid extensionOid;
ListCell *lc;
+ bool unpackaged = false, target_version_is_full = false;
/* Check extension name validity before any filesystem access */
check_valid_extension_name(stmt->extname);
***************
*** 1233,1239 **** CreateExtension(CreateExtensionStmt *stmt)
* any non-ASCII data, so there is no need to worry about encoding at this
* point.
*/
! pcontrol = read_extension_control_file(stmt->extname);
/*
* Read the statement option list
--- 1406,1412 ----
* any non-ASCII data, so there is no need to worry about encoding at this
* point.
*/
! pcontrol = read_extension_control(stmt->extname);
/*
* Read the statement option list
***************
*** 1272,1277 **** CreateExtension(CreateExtensionStmt *stmt)
--- 1445,1455 ----
/*
* Determine the version to install
+ *
+ * Note that in the case when we install an extension from a template, and
+ * when the target version to install is given in the SQL command, we could
+ * arrange the code to only scan pg_extension_control once: there's no need
+ * to read any primary control row in that case. There's no harm doing so.
*/
if (d_new_version && d_new_version->arg)
versionName = strVal(d_new_version->arg);
***************
*** 1287,1328 **** CreateExtension(CreateExtensionStmt *stmt)
check_valid_version_name(versionName);
/*
* Determine the (unpackaged) version to update from, if any, and then
* figure out what sequence of update scripts we need to apply.
*/
! if (d_old_version && d_old_version->arg)
{
! oldVersionName = strVal(d_old_version->arg);
! check_valid_version_name(oldVersionName);
! if (strcmp(oldVersionName, versionName) == 0)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! errmsg("FROM version must be different from installation target version \"%s\"",
! versionName)));
! updateVersions = identify_update_path(pcontrol,
! oldVersionName,
! versionName);
! if (list_length(updateVersions) == 1)
{
! /*
! * Simple case where there's just one update script to run. We
! * will not need any follow-on update steps.
! */
! Assert(strcmp((char *) linitial(updateVersions), versionName) == 0);
! updateVersions = NIL;
}
else
{
! /*
! * Multi-step sequence. We treat this as installing the version
! * that is the target of the first script, followed by successive
! * updates to the later versions.
! */
! versionName = (char *) linitial(updateVersions);
! updateVersions = list_delete_first(updateVersions);
}
}
else
--- 1465,1552 ----
check_valid_version_name(versionName);
/*
+ * If we have a full script for the target version (or a create template),
+ * we don't need to care about unpackaged or default_major_version, nor
+ * about upgrade sequences.
+ */
+ if (extension_version_is_full(pcontrol, versionName))
+ {
+ target_version_is_full = true;
+ oldVersionName = NULL;
+ updateVersions = NIL;
+ }
+ /*
* Determine the (unpackaged) version to update from, if any, and then
* figure out what sequence of update scripts we need to apply.
+ *
+ * When we have a default_full_version and the target is different from it,
+ * apply the same algorithm to find a sequence of updates. If the user did
+ * ask for a target version that happens to be the same as the
+ * default_full_version, just install that one directly.
*/
! else if ((d_old_version && d_old_version->arg) || pcontrol->default_full_version)
{
! unpackaged = (d_old_version && d_old_version->arg);
! if (unpackaged)
! oldVersionName = strVal(d_old_version->arg);
! else
! oldVersionName = pcontrol->default_full_version;
! check_valid_version_name(oldVersionName);
! if (strcmp(oldVersionName, versionName) == 0)
{
! if (unpackaged)
! {
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! errmsg("FROM version must be different from installation target version \"%s\"",
! versionName)));
! }
! else
! {
! /*
! * CREATE EXTENSION ... VERSION = default_full_version, just
! * pretend we don't have a default_full_version for the
! * remaining of the code here, as that's the behavior we want
! * to see happening.
! */
! pcontrol->default_full_version = NULL;
! oldVersionName = NULL;
! updateVersions = NIL;
! }
}
else
{
! /* oldVersionName != versionName */
! updateVersions = identify_update_path(pcontrol,
! oldVersionName,
! versionName);
! }
!
! /* in the create from unpackaged case, reduce the update list */
! if (unpackaged)
! {
! if (list_length(updateVersions) == 1)
! {
! /*
! * Simple case where there's just one update script to run. We
! * will not need any follow-on update steps.
! */
! Assert(strcmp((char *) linitial(updateVersions), versionName) == 0);
! updateVersions = NIL;
! }
! else
! {
! /*
! * Multi-step sequence. We treat this as installing the version
! * that is the target of the first script, followed by successive
! * updates to the later versions.
! */
! versionName = (char *) linitial(updateVersions);
! updateVersions = list_delete_first(updateVersions);
! }
}
}
else
***************
*** 1334,1340 **** CreateExtension(CreateExtensionStmt *stmt)
/*
* Fetch control parameters for installation target version
*/
! control = read_extension_aux_control_file(pcontrol, versionName);
/*
* Determine the target schema to install the extension into
--- 1558,1564 ----
/*
* Fetch control parameters for installation target version
*/
! control = read_extension_aux_control(pcontrol, versionName);
/*
* Determine the target schema to install the extension into
***************
*** 1452,1458 **** CreateExtension(CreateExtensionStmt *stmt)
versionName,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
! requiredExtensions);
/*
* Apply any control-file comment on extension
--- 1676,1683 ----
versionName,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
! requiredExtensions,
! control->ctrlOid);
/*
* Apply any control-file comment on extension
***************
*** 1462,1480 **** CreateExtension(CreateExtensionStmt *stmt)
/*
* Execute the installation script file
! */
! execute_extension_script(extensionOid, control,
! oldVersionName, versionName,
! requiredSchemas,
! schemaName, schemaOid);
!
! /*
* If additional update scripts have to be executed, apply the updates as
* though a series of ALTER EXTENSION UPDATE commands were given
*/
! ApplyExtensionUpdates(extensionOid, pcontrol,
! versionName, updateVersions);
return extensionOid;
}
--- 1687,1742 ----
/*
* Execute the installation script file
! *
* If additional update scripts have to be executed, apply the updates as
* though a series of ALTER EXTENSION UPDATE commands were given
*/
! if (target_version_is_full)
! {
! /* InsertExtensionTuple set the template control dependency */
! execute_extension_script(extensionOid, control,
! NULL, versionName,
! requiredSchemas,
! schemaName, schemaOid);
! }
! else if (pcontrol->default_full_version && !unpackaged)
! {
! /* Set the pg_extension_control dependency, when relevant. */
! if (pcontrol->is_template)
! {
! ExtensionControl *old =
! find_pg_extension_control(stmt->extname,
! pcontrol->default_full_version,
! false);
+ record_control_dependency(extensionOid, old->ctrlOid);
+ }
+ execute_extension_script(extensionOid, control,
+ NULL, oldVersionName,
+ requiredSchemas,
+ schemaName, schemaOid);
+
+ ApplyExtensionUpdates(extensionOid, pcontrol,
+ oldVersionName, updateVersions);
+ }
+ else
+ {
+ /* Set the pg_extension_control dependency, when relevant. */
+ if (pcontrol->is_template && oldVersionName)
+ {
+ ExtensionControl *old =
+ find_pg_extension_control(stmt->extname, oldVersionName, false);
+
+ record_control_dependency(extensionOid, old->ctrlOid);
+ }
+ execute_extension_script(extensionOid, control,
+ oldVersionName, versionName,
+ requiredSchemas,
+ schemaName, schemaOid);
+
+ ApplyExtensionUpdates(extensionOid, pcontrol,
+ versionName, updateVersions);
+ }
return extensionOid;
}
***************
*** 1495,1501 **** Oid
InsertExtensionTuple(const char *extName, Oid extOwner,
Oid schemaOid, bool relocatable, const char *extVersion,
Datum extConfig, Datum extCondition,
! List *requiredExtensions)
{
Oid extensionOid;
Relation rel;
--- 1757,1763 ----
InsertExtensionTuple(const char *extName, Oid extOwner,
Oid schemaOid, bool relocatable, const char *extVersion,
Datum extConfig, Datum extCondition,
! List *requiredExtensions, Oid ctrlOid)
{
Oid extensionOid;
Relation rel;
***************
*** 1565,1570 **** InsertExtensionTuple(const char *extName, Oid extOwner,
--- 1827,1836 ----
recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
}
+
+ /* Record dependency on pg_extension_control, if created from a template */
+ record_control_dependency(extensionOid, ctrlOid);
+
/* Post creation hook for new extension */
InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
***************
*** 1635,1647 **** Datum
pg_available_extensions(PG_FUNCTION_ARGS)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
! TupleDesc tupdesc;
! Tuplestorestate *tupstore;
! MemoryContext per_query_ctx;
! MemoryContext oldcontext;
! char *location;
! DIR *dir;
! struct dirent *de;
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
--- 1901,1915 ----
pg_available_extensions(PG_FUNCTION_ARGS)
{
ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
! TupleDesc tupdesc;
! Tuplestorestate *tupstore;
! MemoryContext per_query_ctx;
! MemoryContext oldcontext;
! char *location;
! DIR *dir;
! struct dirent *de;
! List *templates;
! ListCell *lc;
/* check to see if caller supports us returning a tuplestore */
if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
***************
*** 1684,1690 **** pg_available_extensions(PG_FUNCTION_ARGS)
{
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControlFile *control;
char *extname;
Datum values[3];
bool nulls[3];
--- 1952,1958 ----
{
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControl *control;
char *extname;
Datum values[3];
bool nulls[3];
***************
*** 1725,1730 **** pg_available_extensions(PG_FUNCTION_ARGS)
--- 1993,2025 ----
FreeDir(dir);
}
+ /* add in the extension we can install from a template */
+ templates = pg_extension_default_controls();
+
+ foreach(lc, templates)
+ {
+ char *name = (char *)linitial(lfirst(lc));
+ char *vers = (char *)lsecond(lfirst(lc));
+ char *comm = (char *)lthird(lfirst(lc));
+ Datum values[3];
+ bool nulls[3];
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ /* name */
+ values[0] = DirectFunctionCall1(namein, CStringGetDatum(name));
+ /* default_version */
+ values[1] = CStringGetTextDatum(vers);
+ /* comment */
+ if (comm)
+ values[2] = CStringGetTextDatum(comm);
+ else
+ nulls[2] = true;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
/* clean up and return the tuplestore */
tuplestore_donestoring(tupstore);
***************
*** 1793,1799 **** pg_available_extension_versions(PG_FUNCTION_ARGS)
{
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControlFile *control;
char *extname;
if (!is_extension_control_filename(de->d_name))
--- 2088,2094 ----
{
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControl *control;
char *extname;
if (!is_extension_control_filename(de->d_name))
***************
*** 1817,1822 **** pg_available_extension_versions(PG_FUNCTION_ARGS)
--- 2112,2119 ----
FreeDir(dir);
}
+ get_available_versions_for_extension_templates(tupstore, tupdesc);
+
/* clean up and return the tuplestore */
tuplestore_donestoring(tupstore);
***************
*** 1824,1834 **** pg_available_extension_versions(PG_FUNCTION_ARGS)
}
/*
* Inner loop for pg_available_extension_versions:
* read versions of one extension, add rows to tupstore
*/
static void
! get_available_versions_for_extension(ExtensionControlFile *pcontrol,
Tuplestorestate *tupstore,
TupleDesc tupdesc)
{
--- 2121,2194 ----
}
/*
+ * Helper function to fill in the pg_available_extension_versions tuplestore.
+ */
+ static void
+ tuplestore_put_extension_control(ExtensionControl *control,
+ Tuplestorestate *tupstore,
+ TupleDesc tupdesc)
+ {
+ Datum values[7];
+ bool nulls[7];
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ /* name */
+ values[0] = DirectFunctionCall1(namein,
+ CStringGetDatum(control->name));
+ /* version */
+ values[1] = CStringGetTextDatum(control->version);
+ /* superuser */
+ values[2] = BoolGetDatum(control->superuser);
+ /* relocatable */
+ values[3] = BoolGetDatum(control->relocatable);
+ /* schema */
+ if (control->schema == NULL)
+ nulls[4] = true;
+ else
+ values[4] = DirectFunctionCall1(namein,
+ CStringGetDatum(control->schema));
+ /* requires */
+ if (control->requires == NIL)
+ nulls[5] = true;
+ else
+ {
+ Datum *datums;
+ int ndatums;
+ ArrayType *a;
+ ListCell *lc;
+
+ ndatums = list_length(control->requires);
+ datums = (Datum *) palloc(ndatums * sizeof(Datum));
+ ndatums = 0;
+ foreach(lc, control->requires)
+ {
+ char *curreq = (char *) lfirst(lc);
+
+ datums[ndatums++] =
+ DirectFunctionCall1(namein, CStringGetDatum(curreq));
+ }
+ a = construct_array(datums, ndatums,
+ NAMEOID,
+ NAMEDATALEN, false, 'c');
+ values[5] = PointerGetDatum(a);
+ }
+ /* comment */
+ if (control->comment == NULL)
+ nulls[6] = true;
+ else
+ values[6] = CStringGetTextDatum(control->comment);
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
+ /*
* Inner loop for pg_available_extension_versions:
* read versions of one extension, add rows to tupstore
*/
static void
! get_available_versions_for_extension(ExtensionControl *pcontrol,
Tuplestorestate *tupstore,
TupleDesc tupdesc)
{
***************
*** 1842,1851 **** get_available_versions_for_extension(ExtensionControlFile *pcontrol,
/* Note this will fail if script directory doesn't exist */
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControlFile *control;
char *vername;
- Datum values[7];
- bool nulls[7];
/* must be a .sql file ... */
if (!is_extension_script_filename(de->d_name))
--- 2202,2209 ----
/* Note this will fail if script directory doesn't exist */
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControl *control;
char *vername;
/* must be a .sql file ... */
if (!is_extension_script_filename(de->d_name))
***************
*** 1869,1928 **** get_available_versions_for_extension(ExtensionControlFile *pcontrol,
* Fetch parameters for specific version (pcontrol is not changed)
*/
control = read_extension_aux_control_file(pcontrol, vername);
! memset(values, 0, sizeof(values));
! memset(nulls, 0, sizeof(nulls));
! /* name */
! values[0] = DirectFunctionCall1(namein,
! CStringGetDatum(control->name));
! /* version */
! values[1] = CStringGetTextDatum(vername);
! /* superuser */
! values[2] = BoolGetDatum(control->superuser);
! /* relocatable */
! values[3] = BoolGetDatum(control->relocatable);
! /* schema */
! if (control->schema == NULL)
! nulls[4] = true;
! else
! values[4] = DirectFunctionCall1(namein,
! CStringGetDatum(control->schema));
! /* requires */
! if (control->requires == NIL)
! nulls[5] = true;
! else
! {
! Datum *datums;
! int ndatums;
! ArrayType *a;
! ListCell *lc;
!
! ndatums = list_length(control->requires);
! datums = (Datum *) palloc(ndatums * sizeof(Datum));
! ndatums = 0;
! foreach(lc, control->requires)
! {
! char *curreq = (char *) lfirst(lc);
! datums[ndatums++] =
! DirectFunctionCall1(namein, CStringGetDatum(curreq));
! }
! a = construct_array(datums, ndatums,
! NAMEOID,
! NAMEDATALEN, false, 'c');
! values[5] = PointerGetDatum(a);
! }
! /* comment */
! if (control->comment == NULL)
! nulls[6] = true;
! else
! values[6] = CStringGetTextDatum(control->comment);
! tuplestore_putvalues(tupstore, tupdesc, values, nulls);
! }
! FreeDir(dir);
}
/*
--- 2227,2263 ----
* Fetch parameters for specific version (pcontrol is not changed)
*/
control = read_extension_aux_control_file(pcontrol, vername);
+ control->version = pstrdup(vername);
! tuplestore_put_extension_control(control, tupstore, tupdesc);
! }
! FreeDir(dir);
! }
! /*
! * Inner loop for pg_available_extension_versions:
! * read versions of one extension from templates, add rows to tupstore
! */
! static void
! get_available_versions_for_extension_templates(Tuplestorestate *tupstore,
! TupleDesc tupdesc)
! {
! List *controls;
! ListCell *lc;
! controls = pg_extension_controls();
! foreach(lc, controls)
! {
! ExtensionControl *control = (ExtensionControl *)lfirst(lc);
!
! /* add-in the comment */
! control->comment = GetComment(control->ctrlOid,
! ExtensionControlRelationId, 0);
!
! tuplestore_put_extension_control(control, tupstore, tupdesc);
! }
}
/*
***************
*** 1939,1945 **** pg_extension_update_paths(PG_FUNCTION_ARGS)
MemoryContext per_query_ctx;
MemoryContext oldcontext;
List *evi_list;
! ExtensionControlFile *control;
ListCell *lc1;
/* Check extension name validity before any filesystem access */
--- 2274,2280 ----
MemoryContext per_query_ctx;
MemoryContext oldcontext;
List *evi_list;
! ExtensionControl *control;
ListCell *lc1;
/* Check extension name validity before any filesystem access */
***************
*** 1972,1978 **** pg_extension_update_paths(PG_FUNCTION_ARGS)
MemoryContextSwitchTo(oldcontext);
/* Read the extension's control file */
! control = read_extension_control_file(NameStr(*extname));
/* Extract the version update graph from the script directory */
evi_list = get_ext_ver_list(control);
--- 2307,2313 ----
MemoryContextSwitchTo(oldcontext);
/* Read the extension's control file */
! control = read_extension_control(NameStr(*extname));
/* Extract the version update graph from the script directory */
evi_list = get_ext_ver_list(control);
***************
*** 2591,2597 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
DefElem *d_new_version = NULL;
char *versionName;
char *oldVersionName;
! ExtensionControlFile *control;
Oid extensionOid;
Relation extRel;
ScanKeyData key[1];
--- 2926,2932 ----
DefElem *d_new_version = NULL;
char *versionName;
char *oldVersionName;
! ExtensionControl *control;
Oid extensionOid;
Relation extRel;
ScanKeyData key[1];
***************
*** 2657,2663 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
* any non-ASCII data, so there is no need to worry about encoding at this
* point.
*/
! control = read_extension_control_file(stmt->extname);
/*
* Read the statement option list
--- 2992,2998 ----
* any non-ASCII data, so there is no need to worry about encoding at this
* point.
*/
! control = read_extension_control(stmt->extname);
/*
* Read the statement option list
***************
*** 2732,2738 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
*/
static void
ApplyExtensionUpdates(Oid extensionOid,
! ExtensionControlFile *pcontrol,
const char *initialVersion,
List *updateVersions)
{
--- 3067,3073 ----
*/
static void
ApplyExtensionUpdates(Oid extensionOid,
! ExtensionControl *pcontrol,
const char *initialVersion,
List *updateVersions)
{
***************
*** 2742,2748 **** ApplyExtensionUpdates(Oid extensionOid,
foreach(lcv, updateVersions)
{
char *versionName = (char *) lfirst(lcv);
! ExtensionControlFile *control;
char *schemaName;
Oid schemaOid;
List *requiredExtensions;
--- 3077,3083 ----
foreach(lcv, updateVersions)
{
char *versionName = (char *) lfirst(lcv);
! ExtensionControl *control;
char *schemaName;
Oid schemaOid;
List *requiredExtensions;
***************
*** 2761,2767 **** ApplyExtensionUpdates(Oid extensionOid,
/*
* Fetch parameters for specific version (pcontrol is not changed)
*/
! control = read_extension_aux_control_file(pcontrol, versionName);
/* Find the pg_extension tuple */
extRel = heap_open(ExtensionRelationId, RowExclusiveLock);
--- 3096,3102 ----
/*
* Fetch parameters for specific version (pcontrol is not changed)
*/
! control = read_extension_aux_control(pcontrol, versionName);
/* Find the pg_extension tuple */
extRel = heap_open(ExtensionRelationId, RowExclusiveLock);
***************
*** 2862,2867 **** ApplyExtensionUpdates(Oid extensionOid,
--- 3197,3205 ----
recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
}
+ /* Record dependency on pg_extension_control, if created from a template */
+ record_control_dependency(extensionOid, control->ctrlOid);
+
InvokeObjectPostAlterHook(ExtensionRelationId, extensionOid, 0);
/*
*** /dev/null
--- b/src/backend/commands/template.c
***************
*** 0 ****
--- 1,2606 ----
+ /*-------------------------------------------------------------------------
+ *
+ * template.c
+ * Commands to manipulate templates
+ *
+ * Extension Templates in PostgreSQL allow creation of Extension from the
+ * protocol only.
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ *
+ * IDENTIFICATION
+ * src/backend/commands/template.c
+ *
+ *-------------------------------------------------------------------------
+ */
+ #include "postgres.h"
+
+ #include
+
+ #include "access/heapam.h"
+ #include "access/htup_details.h"
+ #include "access/sysattr.h"
+ #include "access/xact.h"
+ #include "catalog/dependency.h"
+ #include "catalog/indexing.h"
+ #include "catalog/namespace.h"
+ #include "catalog/objectaccess.h"
+ #include "catalog/pg_depend.h"
+ #include "catalog/pg_extension.h"
+ #include "catalog/pg_extension_control.h"
+ #include "catalog/pg_extension_template.h"
+ #include "catalog/pg_extension_uptmpl.h"
+ #include "catalog/pg_namespace.h"
+ #include "catalog/pg_type.h"
+ #include "commands/alter.h"
+ #include "commands/comment.h"
+ #include "commands/extension.h"
+ #include "commands/template.h"
+ #include "funcapi.h"
+ #include "miscadmin.h"
+ #include "tcop/utility.h"
+ #include "utils/acl.h"
+ #include "utils/builtins.h"
+ #include "utils/fmgroids.h"
+ #include "utils/lsyscache.h"
+ #include "utils/rel.h"
+ #include "utils/snapmgr.h"
+ #include "utils/tqual.h"
+
+ static Oid InsertExtensionControlTuple(Oid owner,
+ ExtensionControl *control,
+ const char *version);
+
+ static Oid InsertExtensionTemplateTuple(Oid owner,
+ ExtensionControl *control,
+ const char *version,
+ const char *script);
+
+ static Oid InsertExtensionUpTmplTuple(Oid owner,
+ const char *extname,
+ ExtensionControl *control,
+ const char *from,
+ const char *to,
+ const char *script);
+
+ static Oid AlterTemplateSetDefault(const char *extname, const char *version);
+ static Oid AlterTemplateSetDefaultFull(const char *extname,
+ const char *version);
+ static Oid AlterTemplateSetControl(const char *extname,
+ const char *version,
+ List *options);
+
+ static Oid AlterTemplateSetScript(const char *extname,
+ const char *version, const char *script);
+ static Oid AlterUpTpmlSetScript(const char *extname,
+ const char *from,
+ const char *to,
+ const char *script);
+
+ static Oid modify_pg_extension_control_default(const char *extname,
+ const char *version,
+ bool value);
+
+ static Oid modify_pg_extension_control_default_full(const char *extname,
+ const char *version,
+ bool value);
+
+ static ExtensionControl *read_pg_extension_control(const char *extname,
+ Relation rel,
+ HeapTuple tuple);
+
+ /*
+ * The grammar accumulates control properties into a DefElem list that we have
+ * to process in multiple places.
+ */
+ static void
+ parse_statement_control_defelems(ExtensionControl *control, List *defelems)
+ {
+ ListCell *lc;
+ DefElem *d_schema = NULL;
+ DefElem *d_comment = NULL;
+ DefElem *d_superuser = NULL;
+ DefElem *d_relocatable = NULL;
+ DefElem *d_requires = NULL;
+
+ /*
+ * Read the statement option list
+ */
+ foreach(lc, defelems)
+ {
+ DefElem *defel = (DefElem *) lfirst(lc);
+
+ if (strcmp(defel->defname, "schema") == 0)
+ {
+ if (d_schema)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ d_schema = defel;
+
+ control->schema = strVal(d_schema->arg);
+ }
+ else if (strcmp(defel->defname, "comment") == 0)
+ {
+ if (d_comment)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ d_comment = defel;
+
+ control->comment = strVal(d_comment->arg);
+ }
+ else if (strcmp(defel->defname, "superuser") == 0)
+ {
+ if (d_superuser)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ d_superuser = defel;
+
+ control->superuser = intVal(d_superuser->arg) != 0;
+ }
+ else if (strcmp(defel->defname, "relocatable") == 0)
+ {
+ if (d_relocatable)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ d_relocatable = defel;
+
+ control->relocatable = intVal(d_relocatable->arg) != 0;
+ }
+ else if (strcmp(defel->defname, "requires") == 0)
+ {
+ if (d_requires)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("conflicting or redundant options")));
+ d_requires = defel;
+
+ if (!SplitIdentifierString(pstrdup(strVal(d_requires->arg)),
+ ',',
+ &control->requires))
+ {
+ /* syntax error in name list */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"requires\" must be a list of extension names")));
+ }
+ }
+ else
+ elog(ERROR, "unrecognized option: %s", defel->defname);
+ }
+ }
+
+ /*
+ * Check that no other extension is available on the system or as a template in
+ * the catalogs. When that's the case, we ereport() an ERROR to the user.
+ */
+ static bool
+ CheckExtensionAvailability(const char *extname, const char *version,
+ bool if_not_exists)
+ {
+ if (version)
+ {
+ /*
+ * Check for duplicate template for given extension and version. The
+ * unique index on pg_extension_template(extname, version) would catch
+ * this anyway, and serves as a backstop in case of race conditions;
+ * but this is a friendlier error message.
+ */
+ if (get_template_oid(extname, version, true) != InvalidOid)
+ {
+ if (if_not_exists)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("template for extension \"%s\" version \"%s\" already exists, skipping",
+ extname, version)));
+ return false;
+ }
+ else
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("template for extension \"%s\" version \"%s\" already exists",
+ extname, version)));
+ }
+ }
+ }
+ else
+ {
+ /*
+ * Version is NULL here, meaning we're checking for a RENAME of the
+ * extension, and we want to search for any pre-existing version in the
+ * catalogs. As we have setup an invariant that we always have a single
+ * default version, that's the lookup we make here.
+ */
+ ExtensionControl *default_version =
+ find_default_pg_extension_control(extname, true);
+
+ if (default_version)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("template for extension \"%s\" version \"%s\" already exists",
+ extname, default_version->default_version)));
+ }
+
+ /*
+ * Check that no control file of the same extension's name is already
+ * available on disk, as a friendliness service to our users. Between
+ * CREATE TEMPLATE FOR EXTENSION and CREATE EXTENSION time, some new file
+ * might have been added to the file-system and would then be prefered, but
+ * at least we tried to be as nice as we possibly can.
+ */
+ if (access(get_extension_control_filename(extname), F_OK) == 0)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("extension \"%s\" is already available", extname)));
+ }
+
+ return true;
+ }
+
+ /*
+ * CREATE TEMPLATE FOR EXTENSION
+ *
+ * Routing function, the statement can be either about a template for creating
+ * an extension or a template for updating and extension.
+ */
+ Oid
+ CreateTemplate(CreateExtTemplateStmt *stmt)
+ {
+ switch (stmt->tmpltype)
+ {
+ case TEMPLATE_CREATE_EXTENSION:
+ return CreateExtensionTemplate(stmt);
+
+ case TEMPLATE_UPDATE_EXTENSION:
+ return CreateExtensionUpdateTemplate(stmt);
+ }
+ /* keep compiler happy */
+ return InvalidOid;
+ }
+
+ /*
+ * CREATE TEMPLATE FOR EXTENSION
+ *
+ * Create a template for an extension's given version.
+ */
+ Oid
+ CreateExtensionTemplate(CreateExtTemplateStmt *stmt)
+ {
+ ExtensionControl *default_version;
+ Oid extTemplateOid;
+ Oid owner = GetUserId();
+ ExtensionControl *control;
+
+ /*
+ * It would be nice to allow database owners or even regular users to do
+ * this, but then an evil user could create his own template for a known
+ * extension and then provide malicious features if an extension was
+ * created from that template.
+ */
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to create template for extension \"%s\"",
+ stmt->extname),
+ errhint("Must be superuser to create a template for an extension.")));
+
+ /* Check extension name validity before any filesystem access */
+ check_valid_extension_name(stmt->extname);
+
+ /* Check that we don't already have an extension of this name available. */
+ if (!CheckExtensionAvailability(stmt->extname, stmt->version,
+ stmt->if_not_exists))
+ /* Messages have already been sent to the client */
+ return InvalidOid;
+
+ /* Now read the control properties from the statement */
+ control = (ExtensionControl *) palloc0(sizeof(ExtensionControl));
+ control->ctrlOid = InvalidOid;
+ control->name = pstrdup(stmt->extname);
+
+ parse_statement_control_defelems(control, stmt->control);
+
+ if (control->schema == NULL)
+ {
+ /*
+ * Else, use the current default creation namespace, which is the
+ * first explicit entry in the search_path.
+ */
+ Oid schemaOid;
+ List *search_path = fetch_search_path(false);
+
+ if (search_path == NIL) /* nothing valid in search_path? */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected to create in")));
+ schemaOid = linitial_oid(search_path);
+ control->schema = get_namespace_name(schemaOid);
+ if (control->schema == NULL) /* recently-deleted namespace? */
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_SCHEMA),
+ errmsg("no schema has been selected to create in")));
+
+ list_free(search_path);
+ }
+
+ /*
+ * Check that there's no other pg_extension_control row already claiming to
+ * be the default for this extension, when the statement claims to be the
+ * default.
+ */
+ default_version = find_default_pg_extension_control(control->name, true);
+
+ if (stmt->default_version)
+ {
+ if (default_version)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("extension \"%s\" already has a default control template",
+ control->name),
+ errdetail("default version is \"%s\"",
+ default_version->default_version)));
+
+ /* no pre-existing */
+ control->default_version = pstrdup(stmt->version);
+ }
+ else
+ {
+ /*
+ * No explicit default has been given in the command, and we didn't
+ * find one in the catalogs (it must be the first time we hear about
+ * that very extension): we maintain our invariant that we must have a
+ * single line per extension in pg_extension_control where ctldefault
+ * is true.
+ */
+ if (default_version == NULL)
+ control->default_version = pstrdup(stmt->version);
+ }
+
+ /*
+ * In the control structure, find_default_pg_extension_control() has
+ * stuffed the current default full version of the extension, which might
+ * be different from the default version.
+ *
+ * When creating the first template for an extension, we don't have a
+ * default_full_version set yet. To maintain our invariant that we always
+ * have a single version of the extension templates always as the default
+ * full version, if no default_full_version has been found, forcibly set it
+ * now.
+ */
+ if (default_version == NULL ||
+ default_version->default_full_version == NULL)
+ {
+ control->default_full_version = stmt->version;
+ }
+
+ extTemplateOid = InsertExtensionTemplateTuple(owner,
+ control,
+ stmt->version,
+ stmt->script);
+
+ /* Check that we have a default version target now */
+ CommandCounterIncrement();
+ find_default_pg_extension_control(stmt->extname, false);
+
+ return extTemplateOid;
+ }
+
+ /*
+ * CREATE TEMPLATE FOR UPDATE OF EXTENSION
+ */
+ Oid
+ CreateExtensionUpdateTemplate(CreateExtTemplateStmt *stmt)
+ {
+ Oid owner = GetUserId();
+ ExtensionControl *control;
+
+ if (!superuser())
+ ereport(ERROR,
+ (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
+ errmsg("permission denied to create template for extension \"%s\"",
+ stmt->extname),
+ errhint("Must be superuser to create a template for an extension.")));
+
+ /* Check extension name validity before any filesystem access */
+ check_valid_extension_name(stmt->extname);
+
+ /*
+ * Check that a template for installing extension already exists in the
+ * catalogs. Do not enforce that we have a complete path upgrade path at
+ * template creation time, that will get checked at CREATE EXTENSION time.
+ */
+ (void) can_create_extension_from_template(stmt->extname, false);
+
+ /*
+ * Check for duplicate template for given extension and versions. The
+ * unique index on pg_extension_uptmpl(uptname, uptfrom, uptto) would catch
+ * this anyway, and serves as a backstop in case of race conditions; but
+ * this is a friendlier error message, and besides we need a check to
+ * support IF NOT EXISTS.
+ */
+ if (get_uptmpl_oid(stmt->extname, stmt->from, stmt->to, true) != InvalidOid)
+ {
+ if (stmt->if_not_exists)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("template for extension \"%s\" update from version \"%s\" to version \"%s\" already exists, skipping",
+ stmt->extname, stmt->from, stmt->to)));
+ return InvalidOid;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("template for extension \"%s\" update from version \"%s\" to version \"%s\" already exists",
+ stmt->extname, stmt->from, stmt->to)));
+ }
+
+ /*
+ * Check that no control file of the same extension's name is already
+ * available on disk, as a friendliness service to our users. Between
+ * CREATE TEMPLATE FOR EXTENSION and CREATE EXTENSION time, some new file
+ * might have been added to the file-system and would then be prefered, but
+ * at least we tried to be as nice as we possibly can.
+ */
+ if (access(get_extension_control_filename(stmt->extname), F_OK) == 0)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("extension \"%s\" is already available", stmt->extname)));
+ }
+
+ /*
+ * The update template can change any control properties of the extension,
+ * so first duplicate the properties of the version we are upgrading from
+ * and then override that with the properties given in the command, if any.
+ */
+ control = find_pg_extension_control(stmt->extname, stmt->from, false);
+
+ /* Now reset ctldefault and defaultfull, don't blindly copy them */
+ control->default_version = NULL;
+ control->default_full_version = NULL;
+
+ /* Now read the (optional) control properties from the statement */
+ if (stmt->control)
+ parse_statement_control_defelems(control, stmt->control);
+
+ return InsertExtensionUpTmplTuple(owner, stmt->extname, control,
+ stmt->from, stmt->to, stmt->script);
+ }
+
+ /*
+ * Utility function to build a text[] from the List *requires option.
+ */
+ static Datum
+ construct_control_requires_datum(List *requires)
+ {
+ Datum *datums;
+ int ndatums;
+ ArrayType *a;
+ ListCell *lc;
+
+ ndatums = list_length(requires);
+ datums = (Datum *) palloc(ndatums * sizeof(Datum));
+ ndatums = 0;
+ foreach(lc, requires)
+ {
+ char *curreq = (char *) lfirst(lc);
+
+ datums[ndatums++] =
+ DirectFunctionCall1(namein, CStringGetDatum(curreq));
+ }
+ a = construct_array(datums, ndatums,
+ NAMEOID,
+ NAMEDATALEN, false, 'c');
+
+ return PointerGetDatum(a);
+ }
+
+ /*
+ * Utility function to check control parameters conflicts when providing
+ * another path to get to an extension's version (e.g. adding a upgrade
+ * script).
+ *
+ * In details, we allow to create a template for version '1.2' of an extension
+ * even if we already had one for '1.1' and an upgrade script from '1.1' to
+ * '1.2', but we insist on the setup (control properties) for '1.2' to not be
+ * changed in that case. If you want to also change them, use an ALTER command
+ * first, then install the new template.
+ */
+ static Oid
+ check_for_control_conflicts(const ExtensionControl *new_control,
+ const char *version)
+ {
+ ExtensionControl *old_control;
+
+ old_control = find_pg_extension_control(new_control->name, version, true);
+
+ if (old_control)
+ {
+ /*
+ * It must be possible to change default_version and
+ * default_full_version when installing a full script install for an
+ * extension already having a upgrade path to that version.
+ */
+
+ if (strcmp(new_control->schema,
+ old_control->schema) != 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid setting for \"schema\""),
+ errdetail("Template for extension \"%s\" version \"%s\" is set already with \"schema\" = \"%s\".",
+ new_control->name, version,
+ old_control->schema)));
+
+ if (new_control->relocatable != old_control->relocatable)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid setting for \"relocatable\""),
+ errdetail("Template for extension \"%s\" version \"%s\" is already set with \"relocatable\" = \"%s\".",
+ new_control->name, version,
+ old_control->relocatable ? "true" : "false")));
+
+ if (new_control->superuser != old_control->superuser)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid setting for \"superuser\""),
+ errdetail("Template for extension \"%s\" version \"%s\" is already set with \"superuser\" = \"%s\".",
+ new_control->name, version,
+ old_control->superuser ? "true" : "false")));
+
+ /*
+ * control->requires is a List * of char * extension names.
+ * It's usually empty, or very short.
+ */
+ if ((new_control->requires == NIL && old_control->requires != NIL)
+ || (new_control->requires != NIL && old_control->requires == NIL)
+ || (list_length(new_control->requires)
+ != list_length(old_control->requires)))
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid setting for \"requires\""),
+ errdetail("Template for extension \"%s\" version \"%s\" is already set with a different \"requires\" list.",
+ new_control->name, version)));
+
+ else if (new_control->requires != NIL && old_control->requires != NIL)
+ {
+ /* we have to compare two non empty lists of the same size */
+ ListCell *lc1, *lc2;
+
+ foreach(lc1, new_control->requires)
+ {
+ char *req1 = (char *) lfirst(lc1);
+ bool found = false;
+
+ foreach(lc2, old_control->requires)
+ {
+ char *req2 = (char *) lfirst(lc2);
+
+ if (strcmp(req1, req2) != 0)
+ {
+ found = true;
+ break;
+ }
+ }
+ if (!found)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("invalid setting for \"requires\""),
+ errdetail("Template for extension \"%s\" version \"%s\" is already set with a different \"requires\" list.",
+ new_control->name, version)));
+ }
+ /*
+ * As lists are of the same size, there's no need to check about
+ * elements in the second list not present in the first one, which
+ * means we're done now.
+ */
+ }
+
+ return old_control->ctrlOid;
+ }
+ return InvalidOid;
+ }
+
+ /*
+ * InsertExtensionControlTuple
+ *
+ * Insert the new pg_extension_control row and register its dependency to its
+ * owner. Return the OID assigned to the new row.
+ */
+ static Oid
+ InsertExtensionControlTuple(Oid owner,
+ ExtensionControl *control,
+ const char *version)
+ {
+ Oid extControlOid;
+ Relation rel;
+ Datum values[Natts_pg_extension_control];
+ bool nulls[Natts_pg_extension_control];
+ HeapTuple tuple;
+ ObjectAddress myself;
+
+ /*
+ * Build and insert the pg_extension_control tuple
+ */
+ rel = heap_open(ExtensionControlRelationId, RowExclusiveLock);
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ values[Anum_pg_extension_control_ctlname - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(control->name));
+
+ values[Anum_pg_extension_control_ctlowner - 1] =
+ ObjectIdGetDatum(owner);
+
+ values[Anum_pg_extension_control_ctlrelocatable - 1] =
+ BoolGetDatum(control->relocatable);
+
+ values[Anum_pg_extension_control_ctlsuperuser - 1] =
+ BoolGetDatum(control->superuser);
+
+ if (control->schema == NULL)
+ nulls[Anum_pg_extension_control_ctlnamespace - 1] = true;
+ else
+ values[Anum_pg_extension_control_ctlnamespace - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum((control->schema)));
+
+ values[Anum_pg_extension_control_ctlversion - 1] =
+ CStringGetTextDatum(version);
+
+ /*
+ * We only register that this pg_extension_control row is the default for
+ * the given extension. Necessary controls must have been made before.
+ */
+ if (control->default_version == NULL)
+ values[Anum_pg_extension_control_ctldefault - 1] = false;
+ else
+ values[Anum_pg_extension_control_ctldefault - 1] = true;
+
+ if (control->default_full_version == NULL)
+ values[Anum_pg_extension_control_ctldefaultfull - 1] = false;
+ else
+ values[Anum_pg_extension_control_ctldefaultfull - 1] = true;
+
+ if (control->requires == NULL)
+ nulls[Anum_pg_extension_control_ctlrequires - 1] = true;
+ else
+ values[Anum_pg_extension_control_ctlrequires - 1] =
+ construct_control_requires_datum(control->requires);
+
+ tuple = heap_form_tuple(rel->rd_att, values, nulls);
+
+ extControlOid = simple_heap_insert(rel, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, RowExclusiveLock);
+
+ /*
+ * Record dependencies on owner.
+ *
+ * When we create the extension template and control file, the target
+ * extension, its schema and requirements usually do not exist in the
+ * database. Don't even think about registering a dependency from the
+ * template.
+ */
+ recordDependencyOnOwner(ExtensionControlRelationId, extControlOid, owner);
+
+ /* if created from within an extension script, register dependency */
+ myself.classId = ExtensionControlRelationId;
+ myself.objectId = extControlOid;
+ myself.objectSubId = 0;
+
+ recordDependencyOnCurrentExtension(&myself, false);
+
+ /* Post creation hook for new extension control */
+ InvokeObjectPostCreateHook(ExtensionControlRelationId, extControlOid, 0);
+
+ /*
+ * Apply any control-file comment on extension control
+ */
+ if (control->comment != NULL)
+ CreateComments(extControlOid, ExtensionControlRelationId, 0,
+ control->comment);
+
+ return extControlOid;
+ }
+
+ /*
+ * InsertExtensionTemplateTuple
+ *
+ * Insert the new pg_extension_template row and register its dependencies.
+ * Return the OID assigned to the new row.
+ */
+ static Oid
+ InsertExtensionTemplateTuple(Oid owner, ExtensionControl *control,
+ const char *version, const char *script)
+ {
+ Oid extControlOid, extTemplateOid;
+ Relation rel;
+ Datum values[Natts_pg_extension_template];
+ bool nulls[Natts_pg_extension_template];
+ HeapTuple tuple;
+ ObjectAddress myself, ctrl;
+
+ /*
+ * Check that no pre-existing control entry exists for our version. That
+ * happens when adding a new full script for a version where you already
+ * have an upgrade path from a previous version.
+ */
+ extControlOid = check_for_control_conflicts(control, version);
+
+ if (!OidIsValid(extControlOid))
+ /* Then create the companion extension control entry */
+ extControlOid = InsertExtensionControlTuple(owner, control, version);
+
+ /*
+ * Build and insert the pg_extension_template tuple
+ */
+ rel = heap_open(ExtensionTemplateRelationId, RowExclusiveLock);
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ values[Anum_pg_extension_template_tplname - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(control->name));
+
+ values[Anum_pg_extension_template_tplowner - 1] =
+ ObjectIdGetDatum(owner);
+
+ values[Anum_pg_extension_template_tplversion - 1] =
+ CStringGetTextDatum(version);
+
+ values[Anum_pg_extension_template_tplscript - 1] =
+ CStringGetTextDatum(script);
+
+ tuple = heap_form_tuple(rel->rd_att, values, nulls);
+
+ extTemplateOid = simple_heap_insert(rel, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, RowExclusiveLock);
+
+ /*
+ * Record dependencies on owner only.
+ *
+ * When we create the extension template and control file, the target
+ * extension, its schema and requirements usually do not exist in the
+ * database. Don't even think about registering a dependency from the
+ * template.
+ */
+ recordDependencyOnOwner(ExtensionTemplateRelationId, extTemplateOid, owner);
+
+ myself.classId = ExtensionTemplateRelationId;
+ myself.objectId = extTemplateOid;
+ myself.objectSubId = 0;
+
+ /* record he dependency between the control row and the template row */
+ ctrl.classId = ExtensionControlRelationId;
+ ctrl.objectId = extControlOid;
+ ctrl.objectSubId = 0;
+
+ recordDependencyOn(&ctrl, &myself, DEPENDENCY_INTERNAL);
+
+ /* if created from within an extension script, register dependency */
+ recordDependencyOnCurrentExtension(&myself, false);
+
+ /* Post creation hook for new extension control */
+ InvokeObjectPostCreateHook(ExtensionTemplateRelationId, extTemplateOid, 0);
+
+ return extTemplateOid;
+ }
+
+ /*
+ * InsertExtensionUpTmplTuple
+ *
+ * Insert the new pg_extension_uptmpl row and register its dependencies.
+ * Return the OID assigned to the new row.
+ */
+ static Oid
+ InsertExtensionUpTmplTuple(Oid owner,
+ const char *extname,
+ ExtensionControl *control,
+ const char *from,
+ const char *to,
+ const char *script)
+ {
+ Oid extControlOid, extUpTmplOid;
+ Relation rel;
+ Datum values[Natts_pg_extension_uptmpl];
+ bool nulls[Natts_pg_extension_uptmpl];
+ HeapTuple tuple;
+ ObjectAddress myself, ctrl;
+
+ /*
+ * First create the companion extension control entry. In the case of an
+ * Update Template the companion control entry is similar in scope to a
+ * secondary control file, and is attached to the target version.
+ *
+ * Check that no pre-existing control entry exists for the target version
+ * of the upgrade script.
+ */
+ extControlOid = check_for_control_conflicts(control, to);
+
+ if (!OidIsValid(extControlOid))
+ /* Then create the companion extension control entry */
+ extControlOid = InsertExtensionControlTuple(owner, control, to);
+
+ /*
+ * Build and insert the pg_extension_uptmpl tuple
+ */
+ rel = heap_open(ExtensionUpTmplRelationId, RowExclusiveLock);
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ values[Anum_pg_extension_uptmpl_uptname - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(extname));
+ values[Anum_pg_extension_uptmpl_uptowner - 1] = ObjectIdGetDatum(owner);
+ values[Anum_pg_extension_uptmpl_uptfrom - 1] = CStringGetTextDatum(from);
+ values[Anum_pg_extension_uptmpl_uptto - 1] = CStringGetTextDatum(to);
+ values[Anum_pg_extension_uptmpl_uptscript - 1] = CStringGetTextDatum(script);
+
+ tuple = heap_form_tuple(rel->rd_att, values, nulls);
+
+ extUpTmplOid = simple_heap_insert(rel, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, RowExclusiveLock);
+
+ /*
+ * Record dependencies on owner only.
+ *
+ * When we create the extension template and control file, the target
+ * extension, its schema and requirements usually do not exist in the
+ * database. Don't even think about registering a dependency from the
+ * template.
+ */
+ recordDependencyOnOwner(ExtensionUpTmplRelationId, extUpTmplOid, owner);
+
+ myself.classId = ExtensionUpTmplRelationId;
+ myself.objectId = extUpTmplOid;
+ myself.objectSubId = 0;
+
+ /* record he dependency between the control row and the template row */
+ ctrl.classId = ExtensionControlRelationId;
+ ctrl.objectId = extControlOid;
+ ctrl.objectSubId = 0;
+
+ recordDependencyOn(&ctrl, &myself, DEPENDENCY_INTERNAL);
+
+ /* if created from within an extension script, register dependency */
+ recordDependencyOnCurrentExtension(&myself, false);
+
+ /* Post creation hook for new extension control */
+ InvokeObjectPostCreateHook(ExtensionUpTmplRelationId, extUpTmplOid, 0);
+
+ return extUpTmplOid;
+ }
+
+ /*
+ * Lookup functions
+ */
+ char *
+ get_extension_control_name(Oid ctrlOid)
+ {
+ char *result;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ctrlOid));
+
+ scandesc = systable_beginscan(rel, ExtensionControlOidIndexId, true,
+ NULL, 1, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ result = pstrdup(
+ NameStr(((Form_pg_extension_control) GETSTRUCT(tuple))->ctlname));
+ else
+ result = NULL;
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return result;
+ }
+
+ char *
+ get_extension_template_name(Oid tmplOid)
+ {
+ char *result;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(tmplOid));
+
+ scandesc = systable_beginscan(rel, ExtensionTemplateOidIndexId, true,
+ NULL, 1, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ result = pstrdup(
+ NameStr(((Form_pg_extension_template) GETSTRUCT(tuple))->tplname));
+ else
+ result = NULL;
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return result;
+ }
+
+ char *
+ get_extension_uptmpl_name(Oid tmplOid)
+ {
+ char *result;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionUpTmplRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(tmplOid));
+
+ scandesc = systable_beginscan(rel, ExtensionUpTmplOidIndexId, true,
+ NULL, 1, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ result = pstrdup(
+ NameStr(((Form_pg_extension_uptmpl) GETSTRUCT(tuple))->uptname));
+ else
+ result = NULL;
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return result;
+ }
+
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION name VERSION version
+ *
+ * This implements high level routing for sub commands.
+ */
+ Oid
+ AlterTemplate(AlterExtTemplateStmt *stmt)
+ {
+ switch (stmt->tmpltype)
+ {
+ case TEMPLATE_CREATE_EXTENSION:
+ return AlterExtensionTemplate(stmt);
+
+ case TEMPLATE_UPDATE_EXTENSION:
+ return AlterExtensionUpdateTemplate(stmt);
+ }
+ /* keep compiler happy */
+ return InvalidOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION routing
+ */
+ Oid
+ AlterExtensionTemplate(AlterExtTemplateStmt *stmt)
+ {
+ switch (stmt->cmdtype)
+ {
+ case AET_SET_DEFAULT:
+ return AlterTemplateSetDefault(stmt->extname, stmt->version);
+
+ case AET_SET_DEFAULT_FULL:
+ return AlterTemplateSetDefaultFull(stmt->extname, stmt->version);
+
+ case AET_SET_SCRIPT:
+ return AlterTemplateSetScript(stmt->extname,
+ stmt->version,
+ stmt->script);
+
+ case AET_UPDATE_CONTROL:
+ return AlterTemplateSetControl(stmt->extname,
+ stmt->version,
+ stmt->control);
+ }
+ /* make compiler happy */
+ return InvalidOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION UPDATE routing
+ */
+ Oid
+ AlterExtensionUpdateTemplate(AlterExtTemplateStmt *stmt)
+ {
+ switch (stmt->cmdtype)
+ {
+ case AET_SET_DEFAULT:
+ case AET_SET_DEFAULT_FULL:
+ /* shouldn't happen */
+ elog(ERROR, "pg_extension_control is associated to a specific version of an extension, not an update script.");
+ break;
+
+ case AET_UPDATE_CONTROL:
+ /* shouldn't happen */
+ elog(ERROR, "pg_extension_control is associated to a specific version of an extension, not an update script.");
+ break;
+
+ case AET_SET_SCRIPT:
+ return AlterUpTpmlSetScript(stmt->extname,
+ stmt->from,
+ stmt->to,
+ stmt->script);
+
+ }
+ /* make compiler happy */
+ return InvalidOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION ... OWNER TO ...
+ *
+ * In fact we are going to change the owner of all the templates objects
+ * related to the extension name given: pg_extension_control entries,
+ * pg_extension_template entries and also pg_extension_uptmpl entries.
+ *
+ * There's no reason to be able to change the owner of only a part of an
+ * extension's template (the control but not the template, or just the upgrade
+ * script).
+ */
+ Oid
+ AlterExtensionTemplateOwner(const char *extname, Oid newOwnerId)
+ {
+ int controls = 0;
+ ListCell *lc;
+
+ /* Alter owner of all pg_extension_control entries for extname */
+ foreach(lc, list_pg_extension_control_oids_for(extname))
+ {
+ Relation catalog;
+ Oid objectId = lfirst_oid(lc);
+
+ controls++;
+ elog(DEBUG1, "alter owner of pg_extension_control %u", objectId);
+
+ catalog = heap_open(ExtensionControlRelationId, RowExclusiveLock);
+ AlterObjectOwner_internal(catalog, objectId, newOwnerId);
+ heap_close(catalog, RowExclusiveLock);
+ }
+
+ if (controls == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("template for extension \"%s\" does not exist", extname)));
+
+ /* Alter owner of all pg_extension_template entries for extname */
+ foreach(lc, list_pg_extension_template_oids_for(extname))
+ {
+ Relation catalog;
+ Oid objectId = lfirst_oid(lc);
+
+ elog(DEBUG1, "alter owner of pg_extension_template %u", objectId);
+
+ catalog = heap_open(ExtensionTemplateRelationId, RowExclusiveLock);
+ AlterObjectOwner_internal(catalog, objectId, newOwnerId);
+ heap_close(catalog, RowExclusiveLock);
+ }
+
+ /* Alter owner of all pg_extension_uptmpl entries for extname */
+ foreach(lc, list_pg_extension_uptmpl_oids_for(extname))
+ {
+ Relation catalog;
+ Oid objectId = lfirst_oid(lc);
+
+ elog(DEBUG1, "alter owner of pg_extension_uptmpl %u", objectId);
+
+ catalog = heap_open(ExtensionUpTmplRelationId, RowExclusiveLock);
+ AlterObjectOwner_internal(catalog, objectId, newOwnerId);
+ heap_close(catalog, RowExclusiveLock);
+ }
+
+ /* which Oid to return here? */
+ return InvalidOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION ... RENAME TO ...
+ *
+ * There's no reason to be able to change the name of only a part of an
+ * extension's template (the control but not the template, or just the upgrade
+ * script).
+ */
+ Oid
+ AlterExtensionTemplateRename(const char *extname, const char *newname)
+ {
+ int controls = 0;
+ ListCell *lc;
+
+ /*
+ * Forbid renaming a template that's already in use: we wouldn't be able to
+ * pg_restore after that.
+ */
+ if (get_extension_oid(extname, true) != InvalidOid)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_IN_USE),
+ errmsg("template for extension \"%s\" is in use", extname),
+ errdetail("extension \"%s\" already exists", extname)));
+ }
+
+ /* Check that we don't already have an extension of this name available. */
+ if (!CheckExtensionAvailability(newname, NULL, false))
+ /* Messages have already been sent to the client */
+ return InvalidOid;
+
+ /* Rename all pg_extension_control entries for extname */
+ foreach(lc, list_pg_extension_control_oids_for(extname))
+ {
+ Relation catalog;
+ Oid objectId = lfirst_oid(lc);
+
+ controls++;
+ elog(DEBUG1, "rename pg_extension_control %u", objectId);
+
+ catalog = heap_open(ExtensionControlRelationId, RowExclusiveLock);
+ AlterObjectRename_internal(catalog, objectId, newname);
+ heap_close(catalog, RowExclusiveLock);
+ }
+
+ if (controls == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("template for extension \"%s\" does not exist", extname)));
+
+ /* Rename all pg_extension_template entries for extname */
+ foreach(lc, list_pg_extension_template_oids_for(extname))
+ {
+ Relation catalog;
+ Oid objectId = lfirst_oid(lc);
+
+ elog(DEBUG1, "rename pg_extension_template %u", objectId);
+
+ catalog = heap_open(ExtensionTemplateRelationId, RowExclusiveLock);
+ AlterObjectRename_internal(catalog, objectId, newname);
+ heap_close(catalog, RowExclusiveLock);
+ }
+
+ /* Rename all pg_extension_uptmpl entries for extname */
+ foreach(lc, list_pg_extension_uptmpl_oids_for(extname))
+ {
+ Relation catalog;
+ Oid objectId = lfirst_oid(lc);
+
+ elog(DEBUG1, "rename pg_extension_uptmpl %u", objectId);
+
+ catalog = heap_open(ExtensionUpTmplRelationId, RowExclusiveLock);
+ AlterObjectRename_internal(catalog, objectId, newname);
+ heap_close(catalog, RowExclusiveLock);
+ }
+
+ /* which Oid to return here? */
+ return InvalidOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION ... SET DEFAULT VERSION ...
+ *
+ * We refuse to run without a default, so we drop the current one when
+ * assigning a new one.
+ */
+ static Oid
+ AlterTemplateSetDefault(const char *extname, const char *version)
+ {
+ /* we need to know who's the default */
+ ExtensionControl *current =
+ find_default_pg_extension_control(extname, false);
+
+ if (!pg_extension_control_ownercheck(current->ctrlOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTCONTROL,
+ extname);
+
+ /* silently do nothing if the default is already set as wanted */
+ if (strcmp(current->default_version, version) == 0)
+ return current->ctrlOid;
+
+ /* set ctldefault to false on current default extension */
+ modify_pg_extension_control_default(current->name,
+ current->default_version,
+ false);
+
+ /* set ctldefault to true on new default extension */
+ return modify_pg_extension_control_default(extname, version, true);
+ }
+
+ /*
+ * Implement flipping the ctldefaultfull bit to given value.
+ */
+ static Oid
+ modify_pg_extension_control_default(const char *extname,
+ const char *version,
+ bool value)
+ {
+ Oid ctrlOid;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+ Datum values[Natts_pg_extension_control];
+ bool nulls[Natts_pg_extension_control];
+ bool repl[Natts_pg_extension_control];
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_control_ctlname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_control_ctlversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ NULL, 2, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ elog(ERROR,
+ "pg_extension_control for extension \"%s\" version \"%s\" does not exist",
+ extname, version);
+
+ ctrlOid = HeapTupleGetOid(tuple);
+
+ /* Modify ctldefault in the pg_extension_control tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+ memset(repl, 0, sizeof(repl));
+
+ values[Anum_pg_extension_control_ctldefault - 1] = BoolGetDatum(value);
+ repl[Anum_pg_extension_control_ctldefault - 1] = true;
+
+ tuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
+ values, nulls, repl);
+
+ simple_heap_update(rel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return ctrlOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION ... SET DEFAULT FULL VERSION ...
+ */
+ static Oid
+ AlterTemplateSetDefaultFull(const char *extname, const char *version)
+ {
+ /* we need to know who's the default */
+ ExtensionControl *current =
+ find_default_pg_extension_control(extname, false);
+
+ /* the target version must be an installation script */
+ Oid target = get_template_oid(extname, version, false);
+
+ (void) target; /* silence compiler */
+
+ /* only check the owner of one of those, as we maintain them all the same */
+ if (!pg_extension_control_ownercheck(current->ctrlOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTCONTROL,
+ extname);
+
+ /* silently do nothing if the default is already set as wanted */
+ if (strcmp(current->default_full_version, version) == 0)
+ return current->ctrlOid;
+
+ /* set ctldefault to false on current default extension */
+ modify_pg_extension_control_default_full(current->name,
+ current->default_full_version,
+ false);
+
+ /* set ctldefault to true on new default extension */
+ return modify_pg_extension_control_default_full(extname, version, true);
+ }
+
+ /*
+ * Implement flipping the ctldefaultfull bit to given value.
+ */
+ static Oid
+ modify_pg_extension_control_default_full(const char *extname,
+ const char *version,
+ bool value)
+ {
+ Oid ctrlOid;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+ Datum values[Natts_pg_extension_control];
+ bool nulls[Natts_pg_extension_control];
+ bool repl[Natts_pg_extension_control];
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_control_ctlname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_control_ctlversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ NULL, 2, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ elog(ERROR,
+ "pg_extension_control for extension \"%s\" version \"%s\" does not exist",
+ extname, version);
+
+ ctrlOid = HeapTupleGetOid(tuple);
+
+ /* Modify ctldefault in the pg_extension_control tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+ memset(repl, 0, sizeof(repl));
+
+ values[Anum_pg_extension_control_ctldefaultfull - 1] = BoolGetDatum(value);
+ repl[Anum_pg_extension_control_ctldefaultfull - 1] = true;
+
+ tuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
+ values, nulls, repl);
+
+ simple_heap_update(rel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return ctrlOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION ... AS $$ ... $$
+ */
+ static Oid
+ AlterTemplateSetScript(const char *extname,
+ const char *version,
+ const char *script)
+ {
+ Oid extTemplateOid;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+ Datum values[Natts_pg_extension_template];
+ bool nulls[Natts_pg_extension_template];
+ bool repl[Natts_pg_extension_template];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_template_tplname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_template_tplversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionTemplateNameVersionIndexId, true,
+ NULL, 2, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ elog(ERROR,
+ "pg_extension_template for extension \"%s\" version \"%s\" does not exist",
+ extname, version);
+
+ extTemplateOid = HeapTupleGetOid(tuple);
+
+ /* check privileges */
+ if (!pg_extension_template_ownercheck(extTemplateOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTTEMPLATE,
+ extname);
+
+ /* Modify ctldefault in the pg_extension_control tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+ memset(repl, 0, sizeof(repl));
+
+ repl[Anum_pg_extension_template_tplscript - 1] = true;
+ values[Anum_pg_extension_template_tplscript - 1] =
+ CStringGetTextDatum(script);
+
+ tuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
+ values, nulls, repl);
+
+ simple_heap_update(rel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return extTemplateOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION ... FROM ... TO ... AS $$ ... $$
+ */
+ static Oid
+ AlterUpTpmlSetScript(const char *extname,
+ const char *from,
+ const char *to,
+ const char *script)
+ {
+ Oid extUpTmplOid;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[3];
+ Datum values[Natts_pg_extension_uptmpl];
+ bool nulls[Natts_pg_extension_uptmpl];
+ bool repl[Natts_pg_extension_uptmpl];
+
+ rel = heap_open(ExtensionUpTmplRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_uptmpl_uptname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_uptmpl_uptfrom,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(from));
+
+ ScanKeyInit(&entry[2],
+ Anum_pg_extension_uptmpl_uptto,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(to));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionUpTpmlNameFromToIndexId, true,
+ NULL, 3, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ elog(ERROR,
+ "pg_extension_template for extension \"%s\" from version \"%s\" to version \"%s\" does not exist",
+ extname, from, to);
+
+ extUpTmplOid = HeapTupleGetOid(tuple);
+
+ /* check privileges */
+ if (!pg_extension_uptmpl_ownercheck(extUpTmplOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTUPTMPL,
+ extname);
+
+ /* Modify ctldefault in the pg_extension_control tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+ memset(repl, 0, sizeof(repl));
+
+ repl[Anum_pg_extension_uptmpl_uptscript - 1] = true;
+ values[Anum_pg_extension_uptmpl_uptscript - 1] =
+ CStringGetTextDatum(script);
+
+ tuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
+ values, nulls, repl);
+
+ simple_heap_update(rel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return extUpTmplOid;
+ }
+
+ /*
+ * AlterTemplateSetControl
+ */
+ static Oid
+ AlterTemplateSetControl(const char *extname,
+ const char *version,
+ List *options)
+ {
+ Oid ctrlOid;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+ Datum values[Natts_pg_extension_control];
+ bool nulls[Natts_pg_extension_control];
+ bool repl[Natts_pg_extension_control];
+
+ ExtensionControl *current_control;
+ ExtensionControl *new_control =
+ (ExtensionControl *) palloc0(sizeof(ExtensionControl));
+
+ /* parse the new control options given in the SQL command */
+ new_control->name = pstrdup(extname);
+ parse_statement_control_defelems(new_control, options);
+
+ /* now find the tuple we want to edit */
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_control_ctlname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_control_ctlversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ NULL, 2, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ elog(ERROR,
+ "pg_extension_control for extension \"%s\" version \"%s\" does not exist",
+ extname, version);
+
+ ctrlOid = HeapTupleGetOid(tuple);
+
+ /* check privileges */
+ if (!pg_extension_control_ownercheck(ctrlOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTCONTROL,
+ extname);
+
+ current_control = read_pg_extension_control(extname, rel, tuple);
+
+ /* Modify the pg_extension_control tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+ memset(repl, 0, sizeof(repl));
+
+ /*
+ * We don't compare with the current value, we directly set what's been
+ * given in the new command, if anything was given
+ */
+ if (new_control->schema)
+ {
+ values[Anum_pg_extension_control_ctlnamespace - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum((new_control->schema)));
+ repl[Anum_pg_extension_control_ctlnamespace - 1] = true;
+ }
+ if (new_control->requires)
+ {
+ values[Anum_pg_extension_control_ctlrequires - 1] =
+ construct_control_requires_datum(new_control->requires);
+ repl[Anum_pg_extension_control_ctlrequires - 1] = true;
+ }
+
+ /*
+ * We need to compare superuser and relocatable because those are bools,
+ * and we don't have a NULL pointer when they were omited in the command.
+ */
+ if (new_control->superuser != current_control->superuser)
+ {
+ values[Anum_pg_extension_control_ctlsuperuser - 1] =
+ BoolGetDatum(new_control->superuser);
+ repl[Anum_pg_extension_control_ctlsuperuser - 1] = true;
+ }
+ if (new_control->relocatable != current_control->relocatable)
+ {
+ values[Anum_pg_extension_control_ctlrelocatable - 1] =
+ BoolGetDatum(new_control->relocatable);
+ repl[Anum_pg_extension_control_ctlrelocatable - 1] = true;
+ }
+
+ tuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
+ values, nulls, repl);
+
+ simple_heap_update(rel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return ctrlOid;
+ }
+
+ /*
+ * get_template_oid - given an extension name and version, look up the template
+ * OID
+ *
+ * If missing_ok is false, throw an error if extension name not found. If
+ * true, just return InvalidOid.
+ */
+ Oid
+ get_template_oid(const char *extname, const char *version, bool missing_ok)
+ {
+ Oid result;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_template_tplname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_template_tplversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionTemplateNameVersionIndexId, true,
+ NULL, 2, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ result = HeapTupleGetOid(tuple);
+ else
+ result = InvalidOid;
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ if (!OidIsValid(result) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("template for extension \"%s\" version \"%s\" does not exist",
+ extname, version)));
+
+ return result;
+ }
+
+ /*
+ * Check that the given extension name has a create template.
+ */
+ bool
+ can_create_extension_from_template(const char *extname, bool missing_ok)
+ {
+ bool result;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_template_tplname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionTemplateNameVersionIndexId, true,
+ NULL, 1, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We only are interested into knowing if we found at least one tuple */
+ result = HeapTupleIsValid(tuple);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ if (!result && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("no template for extension \"%s\"", extname)));
+
+ return result;
+ }
+
+ /*
+ * get_uptmpl_oid - given an extension name, from version and to version, look
+ * up the uptmpl OID
+ *
+ * If missing_ok is false, throw an error if extension name not found. If
+ * true, just return InvalidOid.
+ */
+ Oid
+ get_uptmpl_oid(const char *extname, const char *from, const char *to,
+ bool missing_ok)
+ {
+ Oid result;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[3];
+
+ rel = heap_open(ExtensionUpTmplRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_uptmpl_uptname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_uptmpl_uptfrom,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(from));
+
+ ScanKeyInit(&entry[2],
+ Anum_pg_extension_uptmpl_uptto,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(to));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionUpTpmlNameFromToIndexId, true,
+ NULL, 3, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ result = HeapTupleGetOid(tuple);
+ else
+ result = InvalidOid;
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ if (!OidIsValid(result) && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("template for extension \"%s\" update from version \"%s\" to version \"%s\"does not exist",
+ extname, from, to)));
+
+ return result;
+ }
+
+ /*
+ * Remove Extension Control by OID
+ */
+ void
+ RemoveExtensionControlById(Oid extControlOid)
+ {
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionControlRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(extControlOid));
+ scandesc = systable_beginscan(rel, ExtensionControlOidIndexId, true,
+ NULL, 1, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ simple_heap_delete(rel, &tuple->t_self);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, RowExclusiveLock);
+ }
+
+ /*
+ * Remove Extension Control by OID
+ */
+ void
+ RemoveExtensionTemplateById(Oid extTemplateOid)
+ {
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionTemplateRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(extTemplateOid));
+ scandesc = systable_beginscan(rel, ExtensionTemplateOidIndexId, true,
+ NULL, 1, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ simple_heap_delete(rel, &tuple->t_self);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, RowExclusiveLock);
+ }
+
+ /*
+ * Remove Extension Control by OID
+ */
+ void
+ RemoveExtensionUpTmplById(Oid extUpTmplOid)
+ {
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionUpTmplRelationId, RowExclusiveLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(extUpTmplOid));
+ scandesc = systable_beginscan(rel, ExtensionUpTmplOidIndexId, true,
+ NULL, 1, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ simple_heap_delete(rel, &tuple->t_self);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, RowExclusiveLock);
+ }
+
+ /*
+ * Internal tool to get the version string of a pg_extension_control tuple.
+ */
+ static char *
+ extract_ctlversion(Relation rel, HeapTuple tuple)
+ {
+ bool isnull;
+ Datum dvers;
+
+ dvers = heap_getattr(tuple, Anum_pg_extension_control_ctlversion,
+ RelationGetDescr(rel), &isnull);
+ if (isnull)
+ elog(ERROR, "invalid null extension version");
+
+ return text_to_cstring(DatumGetTextPP(dvers));
+ }
+
+ /*
+ * read_pg_extension_control
+ *
+ * Read a pg_extension_control row and fill in an ExtensionControl
+ * structure with the right elements in there.
+ */
+ static ExtensionControl *
+ read_pg_extension_control(const char *extname, Relation rel, HeapTuple tuple)
+ {
+ Datum dreqs;
+ bool isnull;
+ Form_pg_extension_control ctrl =
+ (Form_pg_extension_control) GETSTRUCT(tuple);
+
+ ExtensionControl *control =
+ (ExtensionControl *) palloc0(sizeof(ExtensionControl));
+
+ /* Those fields are not null */
+ control->ctrlOid = HeapTupleGetOid(tuple);
+
+ if (extname)
+ control->name = pstrdup(extname);
+ else
+ control->name = pstrdup(NameStr(ctrl->ctlname));
+
+ control->is_template = true;
+ control->relocatable = ctrl->ctlrelocatable;
+ control->superuser = ctrl->ctlsuperuser;
+ control->schema = pstrdup(NameStr(ctrl->ctlnamespace));
+
+ /* set control->version */
+ control->version = extract_ctlversion(rel, tuple);
+
+ /* set control->default_version */
+ if (ctrl->ctldefault)
+ control->default_version = extract_ctlversion(rel, tuple);
+
+ /* set control->default_full_version */
+ if (ctrl->ctldefaultfull)
+ {
+ /* avoid extracting it again if we just did so */
+ if (control->default_version)
+ control->default_full_version = control->default_version;
+ else
+ control->default_full_version = extract_ctlversion(rel, tuple);
+ }
+
+ /* now see about the dependencies array */
+ dreqs = heap_getattr(tuple, Anum_pg_extension_control_ctlrequires,
+ RelationGetDescr(rel), &isnull);
+
+ if (!isnull)
+ {
+ ArrayType *arr = DatumGetArrayTypeP(dreqs);
+ Datum *elems;
+ int i;
+ int nelems;
+
+ if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID)
+ elog(ERROR, "expected 1-D text array");
+ deconstruct_array(arr, TEXTOID, -1, false, 'i', &elems, NULL, &nelems);
+
+ for (i = 0; i < nelems; ++i)
+ control->requires = lappend(control->requires,
+ TextDatumGetCString(elems[i]));
+
+ pfree(elems);
+ }
+ return control;
+ }
+
+ /*
+ * Find the pg_extension_control row for given extname and version, if any, and
+ * return a filled in ExtensionControl structure.
+ *
+ * In case we don't have any pg_extension_control row for given extname and
+ * version, return NULL.
+ */
+ ExtensionControl *
+ find_pg_extension_control(const char *extname,
+ const char *version,
+ bool missing_ok)
+ {
+ ExtensionControl *control = NULL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_control_ctlname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_control_ctlversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ NULL, 2, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ control = read_pg_extension_control(extname, rel, tuple);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ if (control == NULL && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension \"%s\" has no control template for version \"%s\"",
+ extname, version)));
+
+ /* Don't forget the comments! */
+ if (control != NULL)
+ control->comment = GetComment(control->ctrlOid,
+ ExtensionControlRelationId, 0);
+
+ return control;
+ }
+
+ /*
+ * Find the default extension's control properties, and its OID, for internal
+ * use (such as checking ACLs).
+ *
+ * In a single scan of the pg_extension_control catalog, also find out the
+ * default full version of that extension, needed in extension.c when doing
+ * multi steps CREATE EXTENSION.
+ */
+ ExtensionControl *
+ find_default_pg_extension_control(const char *extname, bool missing_ok)
+ {
+ ExtensionControl *control = NULL;
+ char *default_full_version = NULL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_control_ctlname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ NULL, 1, entry);
+
+ /* find all the control tuples for extname */
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ {
+ bool isnull;
+ Datum tmpdatum;
+ bool ctldefault;
+ bool ctldefaultfull;
+
+ tmpdatum = fastgetattr(tuple,
+ Anum_pg_extension_control_ctldefault,
+ RelationGetDescr(rel), &isnull);
+ if (isnull)
+ elog(ERROR, "invalid null ctldefault");
+ ctldefault = DatumGetBool(tmpdatum);
+
+ /* only one of these is the default */
+ if (ctldefault)
+ {
+ if (!control)
+ control = read_pg_extension_control(extname, rel, tuple);
+ else
+ /* should not happen */
+ elog(ERROR,
+ "extension \"%s\" has more than one default control template",
+ extname);
+ }
+
+ tmpdatum = fastgetattr(tuple,
+ Anum_pg_extension_control_ctldefaultfull,
+ RelationGetDescr(rel), &isnull);
+ if (isnull)
+ elog(ERROR, "invalid null ctldefaultfull");
+ ctldefaultfull = DatumGetBool(tmpdatum);
+
+ /* the default version and default full version might be different */
+ if (ctldefaultfull)
+ default_full_version = extract_ctlversion(rel, tuple);
+ }
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ /* we really need a single default version. */
+ if (!control && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension \"%s\" has no default control template",
+ extname)));
+
+ /* don't forget to add in the default full version */
+ if (control != NULL && default_full_version)
+ control->default_full_version = default_full_version;
+
+ /* Don't forget the comments! */
+ if (control != NULL)
+ control->comment = GetComment(control->ctrlOid,
+ ExtensionControlRelationId, 0);
+
+ return control;
+ }
+
+ /*
+ * read_pg_extension_template_script
+ *
+ * Return the script from the pg_extension_template catalogs.
+ */
+ char *
+ read_pg_extension_template_script(const char *extname, const char *version)
+ {
+ char *script;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_template_tplname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_template_tplversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionTemplateNameVersionIndexId, true,
+ NULL, 2, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ {
+ bool isnull;
+ Datum dscript;
+
+ dscript = heap_getattr(tuple, Anum_pg_extension_template_tplscript,
+ RelationGetDescr(rel), &isnull);
+
+ script = isnull? NULL : text_to_cstring(DatumGetTextPP(dscript));
+ }
+ else
+ /* can't happen */
+ elog(ERROR,
+ "Missing Extension Template entry for extension \"%s\" version \"%s\"",
+ extname, version);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return script;
+ }
+
+ /*
+ * read_pg_extension_uptmpl_script
+ *
+ * Return the script from the pg_extension_uptmpl catalogs.
+ */
+ char *
+ read_pg_extension_uptmpl_script(const char *extname,
+ const char *from_version, const char *version)
+ {
+ char *script;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[3];
+
+ rel = heap_open(ExtensionUpTmplRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_uptmpl_uptname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_uptmpl_uptfrom,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(from_version));
+
+ ScanKeyInit(&entry[2],
+ Anum_pg_extension_uptmpl_uptto,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionUpTpmlNameFromToIndexId, true,
+ NULL, 3, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ {
+ bool isnull;
+ Datum dscript;
+
+ dscript = heap_getattr(tuple, Anum_pg_extension_uptmpl_uptscript,
+ RelationGetDescr(rel), &isnull);
+
+ script = isnull? NULL : text_to_cstring(DatumGetTextPP(dscript));
+ }
+ else
+ /* can't happen */
+ elog(ERROR, "Extension Template Control entry for \"%s\" has no template for update from version \"%s\" to version \"%s\"",
+ extname, from_version, version);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return script;
+ }
+
+ /*
+ * read_extension_template_script
+ *
+ * Given an extension's name and a version, return the extension's script from
+ * the pg_extension_template or the pg_extension_uptmpl catalog. The former is
+ * used when from_version is NULL.
+ */
+ char *
+ read_extension_template_script(const char *extname,
+ const char *from_version, const char *version)
+ {
+ if (from_version)
+ return read_pg_extension_uptmpl_script(extname, from_version, version);
+ else
+ return read_pg_extension_template_script(extname, version);
+ }
+
+ /*
+ * Returns a list of cstring containing all known versions that you can install
+ * for a given extension.
+ */
+ List *
+ list_pg_extension_template_versions(const char *extname)
+ {
+ List *versions = NIL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_template_tplname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionTemplateNameVersionIndexId, true,
+ NULL, 1, entry);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ {
+ bool isnull;
+ Datum dvers =
+ heap_getattr(tuple, Anum_pg_extension_template_tplversion,
+ RelationGetDescr(rel), &isnull);
+
+ char *version = isnull? NULL : text_to_cstring(DatumGetTextPP(dvers));
+
+ versions = lappend(versions, version);
+ }
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return versions;
+ }
+
+ /*
+ * Returns a list of lists of source and target versions for which we have a
+ * direct upgrade path for.
+ */
+ List *
+ list_pg_extension_update_versions(const char *extname)
+ {
+ List *versions = NIL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionUpTmplRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_uptmpl_uptname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionUpTpmlNameFromToIndexId, true,
+ NULL, 1, entry);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ {
+ bool isnull;
+ Datum dfrom, dto;
+ char *from, *to;
+
+ /* neither from nor to are allowed to be null... */
+ dfrom = heap_getattr(tuple, Anum_pg_extension_uptmpl_uptfrom,
+ RelationGetDescr(rel), &isnull);
+
+ from = isnull ? NULL : text_to_cstring(DatumGetTextPP(dfrom));
+
+ dto = heap_getattr(tuple, Anum_pg_extension_uptmpl_uptto,
+ RelationGetDescr(rel), &isnull);
+
+ to = isnull ? NULL : text_to_cstring(DatumGetTextPP(dto));
+
+ versions = lappend(versions, list_make2(from, to));
+ }
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return versions;
+ }
+
+ /*
+ * pg_extension_controls
+ *
+ * List all extensions for which we have a default control entry. Returns a
+ * sorted list of name, version, comment of such extensions.
+ */
+ List *
+ pg_extension_default_controls(void)
+ {
+ List *extensions = NIL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ NULL, 0, NULL);
+
+ /* find all the control tuples for extname */
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ {
+ Form_pg_extension_control ctrl =
+ (Form_pg_extension_control) GETSTRUCT(tuple);
+
+ bool isnull;
+ bool ctldefault =
+ DatumGetBool(
+ fastgetattr(tuple, Anum_pg_extension_control_ctldefault,
+ RelationGetDescr(rel), &isnull));
+
+ /* only of those is the default */
+ if (ctldefault)
+ {
+ Datum dvers =
+ heap_getattr(tuple, Anum_pg_extension_control_ctlversion,
+ RelationGetDescr(rel), &isnull);
+
+ char *version = isnull? NULL:text_to_cstring(DatumGetTextPP(dvers));
+ char *comment = GetComment(HeapTupleGetOid(tuple),
+ ExtensionControlRelationId, 0);
+
+ extensions = lappend(extensions,
+ list_make3(pstrdup(NameStr(ctrl->ctlname)),
+ version,
+ comment));
+ }
+ }
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return extensions;
+ }
+
+ /*
+ * pg_extension_controls
+ *
+ * List all extensions for which we have a default control entry. Returns a
+ * sorted list of ExtensionControl *.
+ */
+ List *
+ pg_extension_controls(void)
+ {
+ List *extensions = NIL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ NULL, 0, NULL);
+
+ /* find all the control tuples for extname */
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ {
+ ExtensionControl *control = read_pg_extension_control(NULL, rel, tuple);
+
+ extensions = lappend(extensions, control);
+ }
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return extensions;
+ }
+
+ /*
+ * pg_extension_templates
+ *
+ * Return a list of list of (name, version) of extensions available to install
+ * from templates, in alphabetical order.
+ */
+ List *
+ pg_extension_templates(void)
+ {
+ List *templates = NIL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ scandesc = systable_beginscan(rel,
+ ExtensionTemplateNameVersionIndexId, true,
+ NULL, 0, entry);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ {
+ Form_pg_extension_template tmpl =
+ (Form_pg_extension_template) GETSTRUCT(tuple);
+
+ bool isnull;
+
+ Datum dvers =
+ heap_getattr(tuple, Anum_pg_extension_template_tplversion,
+ RelationGetDescr(rel), &isnull);
+
+ char *version = isnull? NULL : text_to_cstring(DatumGetTextPP(dvers));
+
+ templates = lappend(templates,
+ list_make2(pstrdup(NameStr(tmpl->tplname)),
+ version));
+ }
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return templates;
+ }
+
+ List *
+ list_pg_extension_control_oids_for(const char *extname)
+ {
+ List *oids = NIL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_control_ctlname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ NULL, 1, entry);
+
+ /* find all the control tuples for extname */
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ oids = lappend_oid(oids, HeapTupleGetOid(tuple));
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return oids;
+ }
+
+ List *
+ list_pg_extension_template_oids_for(const char *extname)
+ {
+ List *oids = NIL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_template_tplname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionTemplateNameVersionIndexId, true,
+ NULL, 1, entry);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ oids = lappend_oid(oids, HeapTupleGetOid(tuple));
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return oids;
+ }
+
+ List *
+ list_pg_extension_uptmpl_oids_for(const char *extname)
+ {
+ List *oids = NIL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionUpTmplRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_uptmpl_uptname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionUpTpmlNameFromToIndexId, true,
+ NULL, 1, entry);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ oids = lappend_oid(oids, HeapTupleGetOid(tuple));
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return oids;
+ }
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 219,225 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
! AlterRoleStmt AlterRoleSetStmt
AlterDefaultPrivilegesStmt DefACLAction
AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
--- 219,225 ----
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
! AlterRoleStmt AlterRoleSetStmt AlterExtTemplateStmt
AlterDefaultPrivilegesStmt DefACLAction
AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
***************
*** 228,238 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
! CreateUserStmt CreateUserMappingStmt CreateRoleStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
! DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
LockStmt NotifyStmt ExplainableStmt PreparableStmt
--- 228,238 ----
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
! CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreateExtTemplateStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
! DropTemplateStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
LockStmt NotifyStmt ExplainableStmt PreparableStmt
***************
*** 267,272 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 267,275 ----
transaction_mode_item
create_extension_opt_item alter_extension_opt_item
+ %type create_template_control create_template_control_plist
+ %type create_template_control_item
+
%type opt_lock lock_type cast_context
%type vacuum_option_list vacuum_option_elem
%type opt_force opt_or_replace
***************
*** 725,730 **** stmt :
--- 728,734 ----
| AlterCompositeTypeStmt
| AlterRoleSetStmt
| AlterRoleStmt
+ | AlterExtTemplateStmt
| AlterTSConfigurationStmt
| AlterTSDictionaryStmt
| AlterUserMappingStmt
***************
*** 763,768 **** stmt :
--- 767,773 ----
| CreateUserStmt
| CreateUserMappingStmt
| CreatedbStmt
+ | CreateExtTemplateStmt
| DeallocateStmt
| DeclareCursorStmt
| DefineStmt
***************
*** 786,791 **** stmt :
--- 791,797 ----
| DropUserStmt
| DropUserMappingStmt
| DropdbStmt
+ | DropTemplateStmt
| ExecuteStmt
| ExplainStmt
| FetchStmt
***************
*** 3616,3621 **** DropTableSpaceStmt: DROP TABLESPACE name
--- 3622,3884 ----
/*****************************************************************************
*
* QUERY:
+ * CREATE TEMPLATE FOR EXTENSION name
+ *
+ *****************************************************************************/
+
+ CreateExtTemplateStmt:
+ CREATE TEMPLATE FOR EXTENSION name VERSION_P NonReservedWord_or_Sconst
+ AS Sconst
+ {
+ CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->extname = $5;
+ n->version = $7;
+ n->control = NIL;
+ n->script = $9;
+ n->if_not_exists = false;
+ n->default_version = false;
+ $$ = (Node *) n;
+ }
+ | CREATE TEMPLATE FOR EXTENSION name VERSION_P NonReservedWord_or_Sconst
+ WITH create_template_control AS Sconst
+ {
+ CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->extname = $5;
+ n->version = $7;
+ n->control = $9;
+ n->script = $11;
+ n->if_not_exists = false;
+ n->default_version = false;
+ $$ = (Node *) n;
+ }
+ | CREATE TEMPLATE FOR EXTENSION name
+ DEFAULT VERSION_P NonReservedWord_or_Sconst AS Sconst
+ {
+ CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->extname = $5;
+ n->version = $8;
+ n->control = NIL;
+ n->script = $10;
+ n->if_not_exists = false;
+ n->default_version = true;
+ $$ = (Node *) n;
+ }
+ | CREATE TEMPLATE FOR EXTENSION name
+ DEFAULT VERSION_P NonReservedWord_or_Sconst
+ WITH create_template_control AS Sconst
+ {
+ CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->extname = $5;
+ n->version = $8;
+ n->control = $10;
+ n->script = $12;
+ n->if_not_exists = false;
+ n->default_version = true;
+ $$ = (Node *) n;
+ }
+ | CREATE TEMPLATE FOR EXTENSION name FOR UPDATE
+ FROM NonReservedWord_or_Sconst TO NonReservedWord_or_Sconst AS Sconst
+ {
+ CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+ n->tmpltype = TEMPLATE_UPDATE_EXTENSION;
+ n->extname = $5;
+ n->from = $9;
+ n->to = $11;
+ n->control = NIL;
+ n->script = $13;
+ n->if_not_exists = false;
+ $$ = (Node *) n;
+ }
+ | CREATE TEMPLATE FOR EXTENSION name FOR UPDATE
+ FROM NonReservedWord_or_Sconst TO NonReservedWord_or_Sconst
+ WITH create_template_control AS Sconst
+ {
+ CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+ n->tmpltype = TEMPLATE_UPDATE_EXTENSION;
+ n->extname = $5;
+ n->from = $9;
+ n->to = $11;
+ n->control = $13;
+ n->script = $15;
+ n->if_not_exists = false;
+ $$ = (Node *) n;
+ }
+ ;
+
+ /*****************************************************************************
+ *
+ * QUERY:
+ * ALTER TEMPLATE FOR EXTENSION name
+ *
+ * We only allow a single subcommand per command here.
+ *
+ *****************************************************************************/
+ AlterExtTemplateStmt:
+ ALTER TEMPLATE FOR EXTENSION name
+ SET DEFAULT VERSION_P NonReservedWord_or_Sconst
+ {
+ AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->cmdtype = AET_SET_DEFAULT;
+ n->extname = $5;
+ n->version = $9;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
+ | ALTER TEMPLATE FOR EXTENSION name
+ SET DEFAULT FULL VERSION_P NonReservedWord_or_Sconst
+ {
+ AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->cmdtype = AET_SET_DEFAULT_FULL;
+ n->extname = $5;
+ n->version = $10;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
+ | ALTER TEMPLATE FOR EXTENSION name VERSION_P NonReservedWord_or_Sconst
+ AS Sconst
+ {
+ AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->cmdtype = AET_SET_SCRIPT;
+ n->extname = $5;
+ n->version = $7;
+ n->script = $9;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
+ | ALTER TEMPLATE FOR EXTENSION name VERSION_P NonReservedWord_or_Sconst
+ WITH create_template_control
+ {
+ AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->cmdtype = AET_UPDATE_CONTROL;
+ n->extname = $5;
+ n->version = $7;
+ n->control = $9;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
+ | ALTER TEMPLATE FOR EXTENSION name FOR UPDATE
+ FROM NonReservedWord_or_Sconst TO NonReservedWord_or_Sconst
+ AS Sconst
+ {
+ AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+ n->tmpltype = TEMPLATE_UPDATE_EXTENSION;
+ n->cmdtype = AET_SET_SCRIPT;
+ n->extname = $5;
+ n->from = $9;
+ n->to = $11;
+ n->script = $13;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
+ ;
+
+ /*****************************************************************************
+ *
+ * QUERY:
+ * DROP TEMPLATE FOR EXTENSION name
+ *
+ *****************************************************************************/
+ DropTemplateStmt:
+ DROP TEMPLATE FOR EXTENSION name
+ VERSION_P NonReservedWord_or_Sconst opt_drop_behavior
+ {
+ DropStmt *n = makeNode(DropStmt);
+ n->removeType = OBJECT_EXTENSION_TEMPLATE;
+ n->objects = list_make1(list_make1(makeString($5)));
+ n->arguments = list_make1(list_make1(makeString($7)));
+ n->behavior = $8;
+ n->missing_ok = false;
+ n->concurrent = false;
+ $$ = (Node *)n;
+ }
+ | DROP TEMPLATE FOR EXTENSION name
+ FOR UPDATE FROM NonReservedWord_or_Sconst
+ TO NonReservedWord_or_Sconst opt_drop_behavior
+ {
+ DropStmt *n = makeNode(DropStmt);
+ n->removeType = OBJECT_EXTENSION_UPTMPL;
+ n->objects = list_make1(list_make1(makeString($5)));
+ n->arguments = list_make1(list_make2(makeString($9),
+ makeString($11)));
+ n->behavior = $12;
+ n->missing_ok = false;
+ n->concurrent = false;
+ $$ = (Node *)n;
+ }
+ ;
+
+ create_template_control:
+ '(' create_template_control_plist ')' { $$ = $2; }
+ ;
+
+
+ create_template_control_plist:
+ create_template_control_item
+ { $$ = list_make1($1); }
+ | create_template_control_plist ',' create_template_control_item
+ { $$ = lappend($1, $3); }
+ | /* EMPTY */
+ { $$ = NIL; }
+ ;
+
+ create_template_control_item:
+ SCHEMA name
+ {
+ $$ = makeDefElem("schema", (Node *)makeString($2));
+ }
+ | COMMENT Sconst
+ {
+ $$ = makeDefElem("comment", (Node *)makeString($2));
+ }
+ | IDENT
+ {
+ /*
+ * We handle identifiers that aren't parser keywords with
+ * the following special-case codes, to avoid bloating the
+ * size of the main parser.
+ */
+ if (strcmp($1, "superuser") == 0)
+ $$ = makeDefElem("superuser", (Node *)makeInteger(TRUE));
+ else if (strcmp($1, "nosuperuser") == 0)
+ $$ = makeDefElem("superuser", (Node *)makeInteger(FALSE));
+ else if (strcmp($1, "relocatable") == 0)
+ $$ = makeDefElem("relocatable", (Node *)makeInteger(TRUE));
+ else if (strcmp($1, "norelocatable") == 0)
+ $$ = makeDefElem("relocatable", (Node *)makeInteger(FALSE));
+ else if (strcmp($1, "requires") == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("template option \"%s\" takes an argument", $1),
+ parser_errposition(@1)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized template option \"%s\"", $1),
+ parser_errposition(@1)));
+ }
+ | IDENT Sconst
+ {
+ if (strcmp($1, "requires") == 0)
+ $$ = makeDefElem("requires", (Node *)makeString($2));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized template option \"%s\"", $1),
+ parser_errposition(@1)));
+ }
+ ;
+
+ /*****************************************************************************
+ *
+ * QUERY:
* CREATE EXTENSION extension
* [ WITH ] [ SCHEMA schema ] [ VERSION version ] [ FROM oldversion ]
*
***************
*** 7336,7341 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
--- 7599,7613 ----
n->missing_ok = false;
$$ = (Node *)n;
}
+ | ALTER TEMPLATE FOR EXTENSION name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_EXTENSION_TEMPLATE;
+ n->subname = $5;
+ n->newname = $8;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
;
opt_column: COLUMN { $$ = COLUMN; }
***************
*** 7739,7744 **** AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
--- 8011,8024 ----
n->newowner = $7;
$$ = (Node *)n;
}
+ | ALTER TEMPLATE FOR EXTENSION name OWNER TO RoleId
+ {
+ AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+ n->objectType = OBJECT_EXTENSION_TEMPLATE;
+ n->object = list_make1(makeString($5));
+ n->newowner = $8;
+ $$ = (Node *)n;
+ }
;
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 47,52 ****
--- 47,53 ----
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+ #include "commands/template.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "commands/user.h"
***************
*** 235,240 **** check_xact_readonly(Node *parsetree)
--- 236,243 ----
case T_CreateExtensionStmt:
case T_AlterExtensionStmt:
case T_AlterExtensionContentsStmt:
+ case T_CreateExtTemplateStmt:
+ case T_AlterExtTemplateStmt:
case T_CreateFdwStmt:
case T_AlterFdwStmt:
case T_CreateForeignServerStmt:
***************
*** 1181,1186 **** ProcessUtilitySlow(Node *parsetree,
--- 1184,1197 ----
ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
break;
+ case T_CreateExtTemplateStmt:
+ CreateTemplate((CreateExtTemplateStmt *) parsetree);
+ break;
+
+ case T_AlterExtTemplateStmt:
+ AlterTemplate((AlterExtTemplateStmt *) parsetree);
+ break;
+
case T_CreateFdwStmt:
CreateForeignDataWrapper((CreateFdwStmt *) parsetree);
break;
***************
*** 1676,1681 **** AlterObjectTypeCommandTag(ObjectType objtype)
--- 1687,1695 ----
case OBJECT_MATVIEW:
tag = "ALTER MATERIALIZED VIEW";
break;
+ case OBJECT_EXTENSION_TEMPLATE:
+ tag = "ALTER TEMPLATE FOR EXTENSION";
+ break;
default:
tag = "???";
break;
***************
*** 1829,1834 **** CreateCommandTag(Node *parsetree)
--- 1843,1856 ----
tag = "ALTER EXTENSION";
break;
+ case T_CreateExtTemplateStmt:
+ tag = "CREATE TEMPLATE FOR EXTENSION";
+ break;
+
+ case T_AlterExtTemplateStmt:
+ tag = "ALTER TEMPLATE FOR EXTENSION";
+ break;
+
case T_CreateFdwStmt:
tag = "CREATE FOREIGN DATA WRAPPER";
break;
***************
*** 1912,1917 **** CreateCommandTag(Node *parsetree)
--- 1934,1945 ----
case OBJECT_EXTENSION:
tag = "DROP EXTENSION";
break;
+ case OBJECT_EXTENSION_TEMPLATE:
+ tag = "DROP TEMPLATE FOR EXTENSION";
+ break;
+ case OBJECT_EXTENSION_UPTMPL:
+ tag = "DROP TEMPLATE FOR EXTENSION";
+ break;
case OBJECT_FUNCTION:
tag = "DROP FUNCTION";
break;
***************
*** 2511,2516 **** GetCommandLogLevel(Node *parsetree)
--- 2539,2549 ----
lev = LOGSTMT_DDL;
break;
+ case T_CreateExtTemplateStmt:
+ case T_AlterExtTemplateStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_CreateFdwStmt:
case T_AlterFdwStmt:
case T_CreateForeignServerStmt:
*** a/src/bin/pg_dump/common.c
--- b/src/bin/pg_dump/common.c
***************
*** 84,89 **** getSchemaData(Archive *fout, int *numTablesPtr)
--- 84,90 ----
InhInfo *inhinfo;
CollInfo *collinfo;
int numExtensions;
+ int numExtensionTemplates;
int numAggregates;
int numInherits;
int numRules;
***************
*** 121,126 **** getSchemaData(Archive *fout, int *numTablesPtr)
--- 122,131 ----
getOwnedSeqs(fout, tblinfo, numTables);
if (g_verbose)
+ write_msg(NULL, "reading extension templates\n");
+ getExtensionTemplates(fout, &numExtensionTemplates);
+
+ if (g_verbose)
write_msg(NULL, "reading extensions\n");
extinfo = getExtensions(fout, &numExtensions);
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
***************
*** 2903,2908 **** _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
--- 2903,2909 ----
if (strcmp(type, "COLLATION") == 0 ||
strcmp(type, "CONVERSION") == 0 ||
strcmp(type, "DOMAIN") == 0 ||
+ strcmp(type, "EXTENSION TEMPLATE") == 0 ||
strcmp(type, "TABLE") == 0 ||
strcmp(type, "TYPE") == 0 ||
strcmp(type, "FOREIGN TABLE") == 0 ||
***************
*** 2916,2921 **** _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH)
--- 2917,2926 ----
strcmp(type, "SERVER") == 0 ||
strcmp(type, "USER MAPPING") == 0)
{
+ /* use ALTER TEMPLATE FOR EXTENSION for extension templates */
+ if (strcmp(type, "EXTENSION TEMPLATE") == 0)
+ type = "TEMPLATE FOR EXTENSION";
+
/* We already know that search_path was set properly */
appendPQExpBuffer(buf, "%s %s", type, fmtId(te->tag));
return;
***************
*** 3100,3105 **** _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isDat
--- 3105,3111 ----
strcmp(te->desc, "CONVERSION") == 0 ||
strcmp(te->desc, "DATABASE") == 0 ||
strcmp(te->desc, "DOMAIN") == 0 ||
+ strcmp(te->desc, "EXTENSION TEMPLATE") == 0 ||
strcmp(te->desc, "FUNCTION") == 0 ||
strcmp(te->desc, "OPERATOR") == 0 ||
strcmp(te->desc, "OPERATOR CLASS") == 0 ||
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 171,176 **** static int collectSecLabels(Archive *fout, SecLabelItem **items);
--- 171,177 ----
static void dumpDumpableObject(Archive *fout, DumpableObject *dobj);
static void dumpNamespace(Archive *fout, NamespaceInfo *nspinfo);
static void dumpExtension(Archive *fout, ExtensionInfo *extinfo);
+ static void dumpExtensionTemplate(Archive *fout, ExtensionTemplateInfo *extinfo);
static void dumpType(Archive *fout, TypeInfo *tyinfo);
static void dumpBaseType(Archive *fout, TypeInfo *tyinfo);
static void dumpEnumType(Archive *fout, TypeInfo *tyinfo);
***************
*** 3015,3020 **** findNamespace(Archive *fout, Oid nsoid, Oid objoid)
--- 3016,3149 ----
}
/*
+ * getExtensionTemplates:
+ * read all extension template in the system catalogs and return them in the
+ * ExtensionTemplateInfo* structure
+ *
+ * numExtensionTemplates is set to the number of extensions templates read in
+ */
+ ExtensionTemplateInfo *
+ getExtensionTemplates(Archive *fout, int *numExtensionTemplates)
+ {
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ ExtensionTemplateInfo *exttmplinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_extname;
+ int i_rolname;
+ int i_namespace;
+ int i_isdefault;
+ int i_relocatable;
+ int i_superuser;
+ int i_requires;
+ int i_install;
+ int i_version;
+ int i_from;
+ int i_to;
+ int i_script;
+
+ /*
+ * Before 9.3, there are no extension templates.
+ */
+ if (fout->remoteVersion < 90400)
+ {
+ *numExtensionTemplates = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /*
+ * The main catalog object for an extension template is the
+ * pg_extension_control entry, from which thanks to pg_depend we fetch
+ * either the extension creation script or its upgrade script.
+ */
+ appendPQExpBuffer(query, "select c.tableoid, c.oid, "
+ "(%s ctlowner) AS rolname, "
+ "c.ctlname, c.ctldefault, c.ctlrelocatable, "
+ "c.ctlsuperuser, c.ctlnamespace, "
+ "array_to_string(c.ctlrequires, ',') as requires, "
+ "true as install, t.tplversion as version, "
+ "null as uptfrom, null as uptto, t.tplscript as script "
+ "from pg_extension_control c "
+ "join pg_depend d "
+ "on d.classid = 'pg_catalog.pg_extension_control'::regclass "
+ "and d.objid = c.oid "
+ "and d.refclassid = 'pg_catalog.pg_extension_template'::regclass "
+ "join pg_extension_template t on t.oid = d.refobjid "
+ "union all "
+ "select c.tableoid, c.oid, "
+ "(%s ctlowner) AS rolname, "
+ "c.ctlname, c.ctldefault, c.ctlrelocatable, "
+ "c.ctlsuperuser, c.ctlnamespace, "
+ "array_to_string(c.ctlrequires, ',') as requires, "
+ "false as install, null as version, "
+ "u.uptfrom, u.uptto, u.uptscript as script "
+ "from pg_extension_control c "
+ "join pg_depend d on d.classid = 'pg_catalog.pg_extension_control'::regclass "
+ "and d.objid = c.oid "
+ "and d.refclassid = 'pg_catalog.pg_extension_uptmpl'::regclass "
+ "join pg_extension_uptmpl u on u.oid = d.refobjid ",
+ username_subquery,
+ username_subquery);
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ exttmplinfo = (ExtensionTemplateInfo *)
+ pg_malloc(ntups * sizeof(ExtensionTemplateInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_extname = PQfnumber(res, "ctlname");
+ i_rolname = PQfnumber(res, "rolname");
+ i_isdefault = PQfnumber(res, "ctldefault");
+ i_relocatable = PQfnumber(res, "ctlrelocatable");
+ i_namespace = PQfnumber(res, "ctlnamespace");
+ i_requires = PQfnumber(res, "requires");
+ i_superuser = PQfnumber(res, "ctlsuperuser");
+ i_install = PQfnumber(res, "install");
+ i_version = PQfnumber(res, "version");
+ i_from = PQfnumber(res, "uptfrom");
+ i_to = PQfnumber(res, "uptto");
+ i_script = PQfnumber(res, "script");
+
+ for (i = 0; i < ntups; i++)
+ {
+ exttmplinfo[i].dobj.objType = DO_EXTENSION_TEMPLATE;
+ exttmplinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ exttmplinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&exttmplinfo[i].dobj);
+ exttmplinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_extname));
+ exttmplinfo[i].rolname = pg_strdup(PQgetvalue(res, i, i_rolname));
+ exttmplinfo[i].namespace = pg_strdup(PQgetvalue(res, i, i_namespace));
+ exttmplinfo[i].isdefault = *(PQgetvalue(res, i, i_isdefault)) == 't';
+ exttmplinfo[i].relocatable = *(PQgetvalue(res, i, i_relocatable)) == 't';
+ exttmplinfo[i].superuser = *(PQgetvalue(res, i, i_superuser)) == 't';
+ exttmplinfo[i].install = *(PQgetvalue(res, i, i_install)) == 't';
+ exttmplinfo[i].requires = pg_strdup(PQgetvalue(res, i, i_requires));
+ exttmplinfo[i].version = pg_strdup(PQgetvalue(res, i, i_version));
+ exttmplinfo[i].from = pg_strdup(PQgetvalue(res, i, i_from));
+ exttmplinfo[i].to = pg_strdup(PQgetvalue(res, i, i_to));
+ exttmplinfo[i].script = pg_strdup(PQgetvalue(res, i, i_script));
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+
+ *numExtensionTemplates = ntups;
+
+ return exttmplinfo;
+ }
+
+ /*
* getExtensions:
* read all extensions in the system catalogs and return them in the
* ExtensionInfo* structure
***************
*** 7731,7736 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj)
--- 7860,7868 ----
case DO_NAMESPACE:
dumpNamespace(fout, (NamespaceInfo *) dobj);
break;
+ case DO_EXTENSION_TEMPLATE:
+ dumpExtensionTemplate(fout, (ExtensionTemplateInfo *) dobj);
+ break;
case DO_EXTENSION:
dumpExtension(fout, (ExtensionInfo *) dobj);
break;
***************
*** 7906,7911 **** dumpNamespace(Archive *fout, NamespaceInfo *nspinfo)
--- 8038,8149 ----
}
/*
+ * dumpExtensionTemplate
+ * writes out to fout the queries to recreate an extension
+ */
+ static void
+ dumpExtensionTemplate(Archive *fout, ExtensionTemplateInfo *exttmplinfo)
+ {
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ char *qextname,
+ *qversion,
+ *qfrom,
+ *qto;
+ bool upgrade; /* install or upgrade script? */
+
+ /* Skip if not to be dumped */
+ if (!exttmplinfo->dobj.dump || dataOnly)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ qextname = pg_strdup(fmtId(exttmplinfo->dobj.name));
+ upgrade = !exttmplinfo->install;
+
+ if (upgrade)
+ {
+ qfrom = pg_strdup(fmtId(exttmplinfo->from));
+ qto = pg_strdup(fmtId(exttmplinfo->to));
+ qversion = NULL;
+
+ appendPQExpBuffer(delq, "DROP TEMPLATE FOR EXTENSION %s FOR UPDATE FROM %s TO %s;\n",
+ qextname, qfrom, qto);
+ }
+ else
+ {
+ qversion = pg_strdup(fmtId(exttmplinfo->version));
+
+ appendPQExpBuffer(delq, "DROP TEMPLATE FOR EXTENSION %s VERSION %s;\n",
+ qextname, qversion);
+ }
+
+ appendPQExpBuffer(q, "CREATE TEMPLATE FOR EXTENSION %s", qextname);
+
+ if (upgrade)
+ appendPQExpBuffer(q, " FOR UPDATE FROM %s TO %s", qfrom, qto);
+ else
+ {
+ if (exttmplinfo->isdefault)
+ appendPQExpBuffer(q, " DEFAULT");
+
+ appendPQExpBuffer(q, " VERSION %s", qversion);
+ }
+
+ /* control options */
+ appendPQExpBuffer(q, "\n WITH (schema %s, %ssuperuser, %srelocatable",
+ fmtId(exttmplinfo->namespace),
+ exttmplinfo->superuser ? "" : "no",
+ exttmplinfo->relocatable ? "" : "no");
+
+ if (exttmplinfo->requires && strlen(exttmplinfo->requires))
+ appendPQExpBuffer(q, ", requires '%s'", exttmplinfo->requires);
+ appendPQExpBuffer(q, ")\n");
+
+ /* extension script (either install or upgrade script) */
+ appendPQExpBuffer(q, " AS\n$%s$%s$%s$;\n",
+ qextname, exttmplinfo->script, qextname);
+
+ /*
+ * When the default version is not a create script, we need an extra ALTER
+ * statement here.
+ */
+ if (!upgrade && exttmplinfo->isdefault)
+ {
+ appendPQExpBuffer(q, "\nALTER TEMPLATE FOR EXTENSION %s ", qextname);
+ appendPQExpBuffer(q, "SET DEFAULT VERSION %s;\n", qversion);
+ }
+
+ appendPQExpBuffer(labelq, "TEMPLATE FOR EXTENSION %s", qextname);
+
+ ArchiveEntry(fout, exttmplinfo->dobj.catId, exttmplinfo->dobj.dumpId,
+ exttmplinfo->dobj.name,
+ NULL, NULL,
+ exttmplinfo->rolname,
+ false, "EXTENSION TEMPLATE", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Extension Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ NULL, "",
+ exttmplinfo->dobj.catId, 0, exttmplinfo->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ NULL, "",
+ exttmplinfo->dobj.catId, 0, exttmplinfo->dobj.dumpId);
+
+ free(qextname);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ }
+
+ /*
* dumpExtension
* writes out to fout the queries to recreate an extension
*/
***************
*** 15043,15048 **** addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
--- 15281,15287 ----
{
case DO_NAMESPACE:
case DO_EXTENSION:
+ case DO_EXTENSION_TEMPLATE:
case DO_TYPE:
case DO_SHELL_TYPE:
case DO_FUNC:
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
***************
*** 79,84 **** typedef enum
--- 79,85 ----
/* When modifying this enum, update priority tables in pg_dump_sort.c! */
DO_NAMESPACE,
DO_EXTENSION,
+ DO_EXTENSION_TEMPLATE,
DO_TYPE,
DO_SHELL_TYPE,
DO_FUNC,
***************
*** 145,150 **** typedef struct _extensionInfo
--- 146,167 ----
char *extcondition;
} ExtensionInfo;
+ typedef struct _extensionTemplateInfo
+ {
+ DumpableObject dobj;
+ char *rolname; /* name of owner, or empty string */
+ char *namespace; /* schema containing extension's objects */
+ bool isdefault; /* from pg_extension_control */
+ bool relocatable; /* from pg_extension_control */
+ bool superuser; /* from pg_extension_control */
+ char *requires; /* from pg_extension_control */
+ bool install; /* true if install template (not upgrade) */
+ char *version; /* from pg_extension_template */
+ char *from; /* from pg_extension_uptmpl */
+ char *to; /* from pg_extension_uptmpl */
+ char *script; /* both from template or uptmpl */
+ } ExtensionTemplateInfo;
+
typedef struct _typeInfo
{
DumpableObject dobj;
***************
*** 544,549 **** extern void sortDataAndIndexObjectsBySize(DumpableObject **objs, int numObjs);
--- 561,568 ----
*/
extern NamespaceInfo *getNamespaces(Archive *fout, int *numNamespaces);
extern ExtensionInfo *getExtensions(Archive *fout, int *numExtensions);
+ extern ExtensionTemplateInfo *getExtensionTemplates(Archive *fout,
+ int *numExtensionTemplates);
extern TypeInfo *getTypes(Archive *fout, int *numTypes);
extern FuncInfo *getFuncs(Archive *fout, int *numFuncs);
extern AggInfo *getAggregates(Archive *fout, int *numAggregates);
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
***************
*** 38,43 **** static const int oldObjectTypePriority[] =
--- 38,44 ----
{
1, /* DO_NAMESPACE */
1, /* DO_EXTENSION */
+ 1, /* DO_EXTENSION_TEMPLATE */
2, /* DO_TYPE */
2, /* DO_SHELL_TYPE */
2, /* DO_FUNC */
***************
*** 86,91 **** static const int newObjectTypePriority[] =
--- 87,93 ----
{
1, /* DO_NAMESPACE */
4, /* DO_EXTENSION */
+ 4, /* DO_EXTENSION_TEMPLATE */
5, /* DO_TYPE */
5, /* DO_SHELL_TYPE */
6, /* DO_FUNC */
***************
*** 1178,1183 **** describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
--- 1180,1190 ----
"SCHEMA %s (ID %d OID %u)",
obj->name, obj->dumpId, obj->catId.oid);
return;
+ case DO_EXTENSION_TEMPLATE:
+ snprintf(buf, bufsize,
+ "EXTENSION TEMPLATE %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
case DO_EXTENSION:
snprintf(buf, bufsize,
"EXTENSION %s (ID %d OID %u)",
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
***************
*** 146,151 **** typedef enum ObjectClass
--- 146,154 ----
OCLASS_USER_MAPPING, /* pg_user_mapping */
OCLASS_DEFACL, /* pg_default_acl */
OCLASS_EXTENSION, /* pg_extension */
+ OCLASS_EXTENSION_CONTROL, /* pg_extension_control */
+ OCLASS_EXTENSION_TEMPLATE, /* pg_extension_template */
+ OCLASS_EXTENSION_UPTMPL, /* pg_extension_uptmpl */
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
MAX_OCLASS /* MUST BE LAST */
} ObjectClass;
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
***************
*** 309,314 **** DECLARE_UNIQUE_INDEX(pg_extension_oid_index, 3080, on pg_extension using btree(o
--- 309,331 ----
DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(extname name_ops));
#define ExtensionNameIndexId 3081
+ DECLARE_UNIQUE_INDEX(pg_extension_template_oid_index, 3180, on pg_extension_template using btree(oid oid_ops));
+ #define ExtensionTemplateOidIndexId 3180
+
+ DECLARE_UNIQUE_INDEX(pg_extension_template_name_version_index, 3481, on pg_extension_template using btree(tplname name_ops, tplversion text_ops));
+ #define ExtensionTemplateNameVersionIndexId 3481
+
+ DECLARE_UNIQUE_INDEX(pg_extension_uptmpl_oid_index, 3280, on pg_extension_uptmpl using btree(oid oid_ops));
+ #define ExtensionUpTmplOidIndexId 3280
+
+ DECLARE_UNIQUE_INDEX(pg_extension_uptmpl_name_from_to_index, 3281, on pg_extension_uptmpl using btree(uptname name_ops, uptfrom text_ops, uptto text_ops));
+ #define ExtensionUpTpmlNameFromToIndexId 3281
+
+ DECLARE_UNIQUE_INDEX(pg_extension_control_oid_index, 3380, on pg_extension_control using btree(oid oid_ops));
+ #define ExtensionControlOidIndexId 3380
+
+ DECLARE_UNIQUE_INDEX(pg_extension_control_name_version_index, 3381, on pg_extension_control using btree(ctlname name_ops, ctlversion text_ops));
+ #define ExtensionControlNameVersionIndexId 3381
DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops));
#define RangeTypidIndexId 3542
*** /dev/null
--- b/src/include/catalog/pg_extension_control.h
***************
*** 0 ****
--- 1,75 ----
+ /*-------------------------------------------------------------------------
+ *
+ * pg_extension_control.h
+ * definition of the system "extension_control" relation
+ * (pg_extension_control) along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_extension_control.h
+ *
+ * NOTES
+ * the genbki.pl script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PG_EXTENSION_CONTROL_H
+ #define PG_EXTENSION_CONTROL_H
+
+ #include "catalog/genbki.h"
+
+ /* ----------------
+ * pg_extension_install_control definition. cpp turns this into
+ * typedef struct FormData_pg_extension_control
+ * ----------------
+ */
+ #define ExtensionControlRelationId 3379
+
+ CATALOG(pg_extension_control,3379)
+ {
+ NameData ctlname; /* extension name */
+ Oid ctlowner; /* control owner */
+ bool ctldefault; /* is this version the default? */
+ bool ctldefaultfull; /* is this version the default full version? */
+ bool ctlrelocatable; /* extension is relocatable? */
+ bool ctlsuperuser; /* extension is superuser only? */
+ NameData ctlnamespace; /* namespace of contained objects */
+
+ #ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text ctlversion; /* version to install with this control */
+ text ctlrequires[1]; /* extension dependency list */
+ #endif
+ } FormData_pg_extension_control;
+
+ /* ----------------
+ * Form_pg_extension_control corresponds to a pointer to a tuple with the
+ * format of pg_extension_control relation.
+ * ----------------
+ */
+ typedef FormData_pg_extension_control *Form_pg_extension_control;
+
+ /* ----------------
+ * compiler constants for pg_extension_control
+ * ----------------
+ */
+
+ #define Natts_pg_extension_control 9
+ #define Anum_pg_extension_control_ctlname 1
+ #define Anum_pg_extension_control_ctlowner 2
+ #define Anum_pg_extension_control_ctldefault 3
+ #define Anum_pg_extension_control_ctldefaultfull 4
+ #define Anum_pg_extension_control_ctlrelocatable 5
+ #define Anum_pg_extension_control_ctlsuperuser 6
+ #define Anum_pg_extension_control_ctlnamespace 7
+ #define Anum_pg_extension_control_ctlversion 8
+ #define Anum_pg_extension_control_ctlrequires 9
+
+ /* ----------------
+ * pg_extension_control has no initial contents
+ * ----------------
+ */
+
+ #endif /* PG_EXTENSION_CONTROL_H */
*** /dev/null
--- b/src/include/catalog/pg_extension_template.h
***************
*** 0 ****
--- 1,65 ----
+ /*-------------------------------------------------------------------------
+ *
+ * pg_extension_template.h
+ * definition of the system "extension_template" relation
+ * (pg_extension_template) along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_extension_template.h
+ *
+ * NOTES
+ * the genbki.pl script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PG_EXTENSION_TEMPLATE_H
+ #define PG_EXTENSION_TEMPLATE_H
+
+ #include "catalog/genbki.h"
+
+ /* ----------------
+ * pg_extension_install_template definition. cpp turns this into
+ * typedef struct FormData_pg_extension_template
+ * ----------------
+ */
+ #define ExtensionTemplateRelationId 3179
+
+ CATALOG(pg_extension_template,3179)
+ {
+ NameData tplname; /* extension name */
+ Oid tplowner; /* template owner */
+
+ #ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text tplversion; /* version to install with this template */
+ text tplscript; /* extension's install script */
+ #endif
+ } FormData_pg_extension_template;
+
+ /* ----------------
+ * Form_pg_extension_template corresponds to a pointer to a tuple with the
+ * format of pg_extension_template relation.
+ * ----------------
+ */
+ typedef FormData_pg_extension_template *Form_pg_extension_template;
+
+ /* ----------------
+ * compiler constants for pg_extension_template
+ * ----------------
+ */
+
+ #define Natts_pg_extension_template 4
+ #define Anum_pg_extension_template_tplname 1
+ #define Anum_pg_extension_template_tplowner 2
+ #define Anum_pg_extension_template_tplversion 3
+ #define Anum_pg_extension_template_tplscript 4
+
+ /* ----------------
+ * pg_extension_template has no initial contents
+ * ----------------
+ */
+
+ #endif /* PG_EXTENSION_TEMPLATE_H */
*** /dev/null
--- b/src/include/catalog/pg_extension_uptmpl.h
***************
*** 0 ****
--- 1,67 ----
+ /*-------------------------------------------------------------------------
+ *
+ * pg_extension_uptmpl.h
+ * definition of the system "extension_uptmpl" relation
+ * (pg_extension_uptmpl) along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_extension_uptmpl.h
+ *
+ * NOTES
+ * the genbki.pl script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PG_EXTENSION_UPTMPL_H
+ #define PG_EXTENSION_UPTMPL_H
+
+ #include "catalog/genbki.h"
+
+ /* ----------------
+ * pg_extension_install_uptmpl definition. cpp turns this into
+ * typedef struct FormData_pg_extension_uptmpl
+ * ----------------
+ */
+ #define ExtensionUpTmplRelationId 3279
+
+ CATALOG(pg_extension_uptmpl,3279)
+ {
+ NameData uptname; /* extension name */
+ Oid uptowner; /* template owner */
+
+ #ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text uptfrom; /* version this template updates from */
+ text uptto; /* version this template updated to */
+ text uptscript; /* extension's update script */
+ #endif
+ } FormData_pg_extension_uptmpl;
+
+ /* ----------------
+ * Form_pg_extension_uptmpl corresponds to a pointer to a tuple with the
+ * format of pg_extension_uptmpl relation.
+ * ----------------
+ */
+ typedef FormData_pg_extension_uptmpl *Form_pg_extension_uptmpl;
+
+ /* ----------------
+ * compiler constants for pg_extension_uptmpl
+ * ----------------
+ */
+
+ #define Natts_pg_extension_uptmpl 5
+ #define Anum_pg_extension_uptmpl_uptname 1
+ #define Anum_pg_extension_uptmpl_uptowner 2
+ #define Anum_pg_extension_uptmpl_uptfrom 3
+ #define Anum_pg_extension_uptmpl_uptto 4
+ #define Anum_pg_extension_uptmpl_uptscript 5
+
+ /* ----------------
+ * pg_extension_uptmpl has no initial contents
+ * ----------------
+ */
+
+ #endif /* PG_EXTENSION_UPTMPL_H */
*** a/src/include/catalog/toasting.h
--- b/src/include/catalog/toasting.h
***************
*** 48,53 **** DECLARE_TOAST(pg_rewrite, 2838, 2839);
--- 48,55 ----
DECLARE_TOAST(pg_seclabel, 3598, 3599);
DECLARE_TOAST(pg_statistic, 2840, 2841);
DECLARE_TOAST(pg_trigger, 2336, 2337);
+ DECLARE_TOAST(pg_extension_template, 3482, 3483);
+ DECLARE_TOAST(pg_extension_uptmpl, 3484, 3485);
/* shared catalogs */
DECLARE_TOAST(pg_shdescription, 2846, 2847);
*** a/src/include/commands/alter.h
--- b/src/include/commands/alter.h
***************
*** 18,24 ****
#include "nodes/parsenodes.h"
#include "utils/relcache.h"
! extern Oid ExecRenameStmt(RenameStmt *stmt);
extern Oid ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
--- 18,26 ----
#include "nodes/parsenodes.h"
#include "utils/relcache.h"
! extern Oid ExecRenameStmt(RenameStmt *stmt);
! extern void AlterObjectRename_internal(Relation rel,
! Oid objectId, const char *new_name);
extern Oid ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
*** a/src/include/commands/extension.h
--- b/src/include/commands/extension.h
***************
*** 26,40 ****
extern bool creating_extension;
extern Oid CurrentExtensionObject;
extern Oid CreateExtension(CreateExtensionStmt *stmt);
extern void RemoveExtensionById(Oid extId);
extern Oid InsertExtensionTuple(const char *extName, Oid extOwner,
! Oid schemaOid, bool relocatable, const char *extVersion,
! Datum extConfig, Datum extCondition,
! List *requiredExtensions);
extern Oid ExecAlterExtensionStmt(AlterExtensionStmt *stmt);
--- 26,67 ----
extern bool creating_extension;
extern Oid CurrentExtensionObject;
+ /*
+ * Internal data structure to hold the extension control information, that we
+ * get either from parsing a control file or from the pg_extension_control
+ * catalog when working from Extension Templates.
+ */
+ typedef struct ExtensionControl
+ {
+ Oid ctrlOid; /* pg_control_extension oid, or invalidoid */
+ char *name; /* name of the extension */
+ char *version; /* version name of this extension's control */
+ char *directory; /* directory for script files */
+ char *default_version; /* default install target version, if any */
+ char *default_full_version; /* default install source version, if any */
+ char *module_pathname; /* string to substitute for module_pathname */
+ char *comment; /* comment, if any */
+ char *schema; /* target schema (allowed if !relocatable) */
+ bool relocatable; /* is alter extension set schema supported? */
+ bool superuser; /* must be superuser to install? */
+ int encoding; /* encoding of the script file, or -1 */
+ List *requires; /* names of prerequisite extensions */
+ bool is_template; /* true if we're using catalog templates */
+ } ExtensionControl;
+
+ extern char *get_extension_control_filename(const char *extname);
+ extern ExtensionControl *read_extension_control_file(const char *extname);
+ extern void check_valid_extension_name(const char *extensionname);
extern Oid CreateExtension(CreateExtensionStmt *stmt);
extern void RemoveExtensionById(Oid extId);
extern Oid InsertExtensionTuple(const char *extName, Oid extOwner,
! Oid schemaOid, bool relocatable,
! const char *extVersion,
! Datum extConfig, Datum extCondition,
! List *requiredExtensions, Oid ctrlOid);
extern Oid ExecAlterExtensionStmt(AlterExtensionStmt *stmt);
*** /dev/null
--- b/src/include/commands/template.h
***************
*** 0 ****
--- 1,74 ----
+ /*-------------------------------------------------------------------------
+ *
+ * template.h
+ * Template management commands (create/drop template).
+ *
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/commands/template.h
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef TEMPLATE_H
+ #define TEMPLATE_H
+
+ #include "nodes/parsenodes.h"
+ #include "extension.h"
+
+ extern Oid CreateTemplate(CreateExtTemplateStmt *stmt);
+ extern Oid CreateExtensionTemplate(CreateExtTemplateStmt *stmt);
+ extern Oid CreateExtensionUpdateTemplate(CreateExtTemplateStmt *stmt);
+
+ extern char *get_extension_control_name(Oid ctrlOid);
+ extern char *get_extension_template_name(Oid tmplOid);
+ extern char *get_extension_uptmpl_name(Oid tmplOid);
+
+ extern Oid AlterExtensionTemplateOwner(const char *extname, Oid newowner);
+ extern Oid AlterExtensionTemplateRename(const char *extname,
+ const char *newname);
+
+ extern Oid AlterTemplate(AlterExtTemplateStmt *stmt);
+ extern Oid AlterExtensionTemplate(AlterExtTemplateStmt *stmt);
+ extern Oid AlterExtensionUpdateTemplate(AlterExtTemplateStmt *stmt);
+
+ extern Oid get_template_oid(const char *extname, const char *version,
+ bool missing_ok);
+ extern bool can_create_extension_from_template(const char *extname,
+ bool missing_ok);
+ extern Oid get_uptmpl_oid(const char *extname,
+ const char *from, const char *to,
+ bool missing_ok);
+
+ extern void RemoveExtensionControlById(Oid extControlOid);
+ extern void RemoveExtensionTemplateById(Oid extTemplateOid);
+ extern void RemoveExtensionUpTmplById(Oid extUpTmplOid);
+
+ extern ExtensionControl *find_pg_extension_control(const char *extname,
+ const char *version,
+ bool missing_ok);
+
+ extern ExtensionControl *find_default_pg_extension_control(const char *extname,
+ bool missing_ok);
+
+ extern char *read_pg_extension_template_script(const char *extname,
+ const char *version);
+ extern char *read_pg_extension_uptmpl_script(const char *extname,
+ const char *from_version,
+ const char *version);
+ extern char *read_extension_template_script(const char *extname,
+ const char *from_version,
+ const char *version);
+
+ extern List *list_pg_extension_template_versions(const char *extname);
+ extern List *list_pg_extension_update_versions(const char *extname);
+ extern List *pg_extension_default_controls(void);
+ extern List *pg_extension_controls(void);
+ extern List *pg_extension_templates(void);
+
+ extern List *list_pg_extension_control_oids_for(const char *extname);
+ extern List *list_pg_extension_template_oids_for(const char *extname);
+ extern List *list_pg_extension_uptmpl_oids_for(const char *extname);
+
+ #endif /* TEMPLATE_H */
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 363,368 **** typedef enum NodeTag
--- 363,370 ----
T_AlterEventTrigStmt,
T_RefreshMatViewStmt,
T_ReplicaIdentityStmt,
+ T_CreateExtTemplateStmt,
+ T_AlterExtTemplateStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1167,1172 **** typedef enum ObjectType
--- 1167,1174 ----
OBJECT_DOMAIN,
OBJECT_EVENT_TRIGGER,
OBJECT_EXTENSION,
+ OBJECT_EXTENSION_TEMPLATE,
+ OBJECT_EXTENSION_UPTMPL,
OBJECT_FDW,
OBJECT_FOREIGN_SERVER,
OBJECT_FOREIGN_TABLE,
***************
*** 1686,1691 **** typedef struct AlterExtensionContentsStmt
--- 1688,1736 ----
List *objargs; /* Arguments if needed (eg, for functions) */
} AlterExtensionContentsStmt;
+ typedef enum ExtTemplateType
+ {
+ TEMPLATE_CREATE_EXTENSION,
+ TEMPLATE_UPDATE_EXTENSION
+ } ExtTemplateType;
+
+ typedef struct CreateExtTemplateStmt
+ {
+ NodeTag type;
+ ExtTemplateType tmpltype;
+ char *extname; /* Extension's name */
+ char *version; /* Version to create from the template */
+ char *from; /* In case of an update template, we update */
+ char *to; /* from version to version */
+ List *control; /* List of DefElem nodes */
+ char *script; /* Extension's install script */
+ bool if_not_exists; /* just do nothing if it already exists? */
+ bool default_version; /* default version of this extension */
+ } CreateExtTemplateStmt;
+
+ typedef enum AlterExtTemplateType
+ {
+ AET_SET_DEFAULT,
+ AET_SET_DEFAULT_FULL,
+ AET_SET_SCRIPT,
+ AET_UPDATE_CONTROL,
+ } AlterExtTemplateType;
+
+ typedef struct AlterExtTemplateStmt
+ {
+ NodeTag type;
+ ExtTemplateType tmpltype;
+ AlterExtTemplateType cmdtype; /* Type of command */
+ char *extname; /* Extension's name */
+ char *version; /* Extension's version */
+ char *from; /* In case of an update template, we update */
+ char *to; /* from version to version */
+ List *control; /* List of DefElem nodes */
+ char *script; /* Extension's install script */
+ bool missing_ok; /* skip error if missing? */
+
+ } AlterExtTemplateStmt;
+
/* ----------------------
* Create/Alter FOREIGN DATA WRAPPER Statements
* ----------------------
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
***************
*** 197,202 **** typedef enum AclObjectKind
--- 197,205 ----
ACL_KIND_FOREIGN_SERVER, /* pg_foreign_server */
ACL_KIND_EVENT_TRIGGER, /* pg_event_trigger */
ACL_KIND_EXTENSION, /* pg_extension */
+ ACL_KIND_EXTCONTROL, /* pg_extension_control */
+ ACL_KIND_EXTTEMPLATE, /* pg_extension_template */
+ ACL_KIND_EXTUPTMPL, /* pg_extension_uptmpl */
MAX_ACL_KIND /* MUST BE LAST */
} AclObjectKind;
***************
*** 325,330 **** extern bool pg_foreign_data_wrapper_ownercheck(Oid srv_oid, Oid roleid);
--- 328,336 ----
extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
+ extern bool pg_extension_control_ownercheck(Oid ext_oid, Oid roleid);
+ extern bool pg_extension_template_ownercheck(Oid ext_template_oid, Oid roleid);
+ extern bool pg_extension_uptmpl_ownercheck(Oid ext_uptmpl_oid, Oid roleid);
extern bool has_createrole_privilege(Oid roleid);
#endif /* ACL_H */
*** /dev/null
--- b/src/test/regress/expected/extension.out
***************
*** 0 ****
--- 1,320 ----
+ -- test case without an explicit schema
+ create template for extension myextension version '1.0'
+ as $$ create table foobar(i int4) $$;
+ create extension myextension;
+ -- check that it went to 'public'
+ select nspname
+ from pg_class c join pg_namespace n on n.oid = c.relnamespace
+ where relname ~ 'foobar';
+ nspname
+ ---------
+ public
+ (1 row)
+
+ -- cleanup
+ drop extension myextension;
+ drop template for extension myextension version '1.0';
+ -- test case without an explicit schema in an upgrade path
+ create template for extension test version 'abc' with (nosuperuser) as $$
+ create function f1(i int) returns int language sql as $_$ select 1; $_$;
+ $$;
+ create template for extension test for update from 'abc' to 'xyz' with (nosuperuser) as $$
+ create function f2(i int) returns int language sql as $_$ select 1; $_$;
+ $$;
+ create template for extension test for update from 'xyz' to '123' with (nosuperuser) as $$
+ create function f3(i int) returns int language sql as $_$ select 1; $_$;
+ $$;
+ create extension test version '123';
+ \dx+ test
+ Objects in extension "test"
+ Object Description
+ ----------------------
+ function f1(integer)
+ function f2(integer)
+ function f3(integer)
+ (3 rows)
+
+ -- cleanup
+ drop extension test;
+ drop template for extension test for update from 'xyz' to '123';
+ drop template for extension test for update from 'abc' to 'xyz';
+ drop template for extension test version 'abc';
+ -- testing dependency in between template and instanciated extensions
+ create template for extension deps version 'a' as '';
+ create template for extension deps for update from 'a' to 'b' as '';
+ alter template for extension deps set default version 'b';
+ create extension deps;
+ \dx
+ List of installed extensions
+ Name | Version | Schema | Description
+ ---------+---------+------------+------------------------------
+ deps | b | public |
+ plpgsql | 1.0 | pg_catalog | PL/pgSQL procedural language
+ (2 rows)
+
+ -- that should be an error
+ drop template for extension deps version 'a';
+ ERROR: cannot drop create template for extension deps because other objects depend on it
+ DETAIL: extension deps depends on control template for extension deps
+ HINT: Use DROP ... CASCADE to drop the dependent objects too.
+ -- that too should be an error
+ drop template for extension deps for update from 'a' to 'b';
+ ERROR: cannot drop update template for extension deps because other objects depend on it
+ DETAIL: extension deps depends on control template for extension deps
+ HINT: Use DROP ... CASCADE to drop the dependent objects too.
+ -- check that we can add a new template for directly installing version 'b'
+ create template for extension deps version 'b' as '';
+ -- and test some control parameters conflicts now
+ create template for extension deps for update from 'b' to 'c' as '';
+ -- those should all fail
+ create template for extension deps version 'c' with (schema foo) as '';
+ ERROR: invalid setting for "schema"
+ DETAIL: Template for extension "deps" version "c" is set already with "schema" = "public".
+ create template for extension deps version 'c' with (superuser) as '';
+ ERROR: invalid setting for "superuser"
+ DETAIL: Template for extension "deps" version "c" is already set with "superuser" = "false".
+ create template for extension deps version 'c' with (relocatable) as '';
+ ERROR: invalid setting for "relocatable"
+ DETAIL: Template for extension "deps" version "c" is already set with "relocatable" = "false".
+ create template for extension deps version 'c' with (requires 'x, y') as '';
+ ERROR: invalid setting for "requires"
+ DETAIL: Template for extension "deps" version "c" is already set with a different "requires" list.
+ -- that one should succeed: no conflict
+ create template for extension deps version 'c'
+ with (schema public, nosuperuser, norelocatable) as '';
+ -- cleanup
+ drop extension deps;
+ drop template for extension deps version 'a' cascade;
+ drop template for extension deps version 'b' cascade;
+ NOTICE: drop cascades to update template for extension deps
+ drop template for extension deps version 'c' cascade;
+ NOTICE: drop cascades to update template for extension deps
+ -- check that we no longer have control entries
+ select * from pg_extension_control;
+ ctlname | ctlowner | ctldefault | ctldefaultfull | ctlrelocatable | ctlsuperuser | ctlnamespace | ctlversion | ctlrequires
+ ---------+----------+------------+----------------+----------------+--------------+--------------+------------+-------------
+ (0 rows)
+
+ -- test that we can not rename a template in use
+ create template for extension foo version 'v' AS '';
+ create extension foo;
+ alter template for extension foo rename to bar;
+ ERROR: template for extension "foo" is in use
+ DETAIL: extension "foo" already exists
+ drop extension foo;
+ drop template for extension foo version 'v';
+ -- now create some templates and an upgrade path
+ CREATE TEMPLATE
+ FOR EXTENSION pair DEFAULT VERSION '1.0'
+ WITH (superuser, norelocatable, schema public)
+ AS $$
+ CREATE TYPE pair AS ( k text, v text );
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;';
+ $$;
+ -- we want to test alter extension update
+ CREATE TEMPLATE FOR EXTENSION pair
+ FOR UPDATE FROM '1.0' TO '1.1'
+ WITH (superuser, norelocatable, schema public)
+ AS $$
+ CREATE OPERATOR ~> (LEFTARG = text,
+ RIGHTARG = anyelement,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = anyelement,
+ RIGHTARG = text,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = anyelement,
+ RIGHTARG = anyelement,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = text,
+ RIGHTARG = text,
+ PROCEDURE = pair);
+ $$;
+ -- and we want to test update with more than 1 step
+ CREATE TEMPLATE FOR EXTENSION pair
+ FOR UPDATE FROM '1.1' TO '1.2'
+ AS
+ $$
+ COMMENT ON EXTENSION pair IS 'Simple Key Value Text Type';
+ $$;
+ -- test some ALTER commands
+ -- ok
+ ALTER TEMPLATE FOR EXTENSION pair VERSION '1.0' WITH (relocatable);
+ -- we don't have a version 1.3 known yet
+ ALTER TEMPLATE FOR EXTENSION pair VERSION '1.3' WITH (relocatable);
+ ERROR: pg_extension_control for extension "pair" version "1.3" does not exist
+ -- you can't set the default on an upgrade script, only an extension's version
+ ALTER TEMPLATE FOR EXTENSION pair FOR UPDATE FROM '1.0' TO '1.1' SET DEFAULT;
+ ERROR: syntax error at or near "SET"
+ LINE 1: ...FOR EXTENSION pair FOR UPDATE FROM '1.0' TO '1.1' SET DEFAUL...
+ ^
+ -- you can't set control properties on an upgrade script, only an
+ -- extension's version
+ ALTER TEMPLATE FOR EXTENSION pair
+ FOR UPDATE FROM '1.0' TO '1.1' WITH (relocatable);
+ ERROR: syntax error at or near "WITH ("
+ LINE 2: FOR UPDATE FROM '1.0' TO '1.1' WITH (relocatabl...
+ ^
+ -- try to set the default full version to an unknown extension version
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT FULL VERSION '1.1';
+ ERROR: template for extension "pair" version "1.1" does not exist
+ -- now set it to the current one already, should silently do nothing
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT FULL VERSION '1.0';
+ -- you can actually change the script used to update, though
+ ALTER TEMPLATE FOR EXTENSION pair FOR UPDATE FROM '1.1' TO '1.2'
+ AS $$
+ COMMENT ON EXTENSION pair IS 'A Toy Key Value Text Type';
+ $$;
+ CREATE EXTENSION pair;
+ \dx pair
+ List of installed extensions
+ Name | Version | Schema | Description
+ ------+---------+--------+-------------
+ pair | 1.0 | public |
+ (1 row)
+
+ \dx+ pair
+ Objects in extension "pair"
+ Object Description
+ --------------------------------------
+ function pair(anyelement,anyelement)
+ function pair(anyelement,text)
+ function pair(text,anyelement)
+ function pair(text,text)
+ type pair
+ (5 rows)
+
+ ALTER EXTENSION pair UPDATE TO '1.2';
+ \dx+ pair
+ Objects in extension "pair"
+ Object Description
+ --------------------------------------
+ function pair(anyelement,anyelement)
+ function pair(anyelement,text)
+ function pair(text,anyelement)
+ function pair(text,text)
+ operator ~>(anyelement,anyelement)
+ operator ~>(anyelement,text)
+ operator ~>(text,anyelement)
+ operator ~>(text,text)
+ type pair
+ (9 rows)
+
+ DROP EXTENSION pair;
+ -- test with another full version that's not the default
+ CREATE TEMPLATE
+ FOR EXTENSION pair VERSION '1.3'
+ WITH (superuser, norelocatable, schema public)
+ AS $$
+ CREATE TYPE pair AS ( k text, v text );
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;';
+
+ CREATE OPERATOR ~> (LEFTARG = text,
+ RIGHTARG = anyelement,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = anyelement,
+ RIGHTARG = text,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = anyelement,
+ RIGHTARG = anyelement,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = text,
+ RIGHTARG = text,
+ PROCEDURE = pair);
+
+ COMMENT ON EXTENSION pair IS 'Simple Key Value Text Type';
+ $$;
+ -- that's ok
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.1';
+ -- that will install 1.0 then run the 1.0 -- 1.1 update script
+ CREATE EXTENSION pair;
+ \dx pair
+ List of installed extensions
+ Name | Version | Schema | Description
+ ------+---------+--------+-------------
+ pair | 1.1 | public |
+ (1 row)
+
+ DROP EXTENSION pair;
+ -- now that should install from the extension from the 1.3 template, even if
+ -- we have a default_major_version pointing to 1.0, because we actually have
+ -- a 1.3 create script.
+ CREATE EXTENSION pair VERSION '1.3';
+ \dx pair
+ List of installed extensions
+ Name | Version | Schema | Description
+ ------+---------+--------+----------------------------
+ pair | 1.3 | public | Simple Key Value Text Type
+ (1 row)
+
+ DROP EXTENSION pair;
+ -- and now let's ask for 1.3 by default while still leaving the
+ -- default_major_version at 1.0, so that it's possible to directly install
+ -- 1.2 if needed.
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.3';
+ CREATE EXTENSION pair;
+ \dx pair
+ List of installed extensions
+ Name | Version | Schema | Description
+ ------+---------+--------+----------------------------
+ pair | 1.3 | public | Simple Key Value Text Type
+ (1 row)
+
+ DROP EXTENSION pair;
+ CREATE EXTENSION pair VERSION '1.2';
+ \dx pair
+ List of installed extensions
+ Name | Version | Schema | Description
+ ------+---------+--------+---------------------------
+ pair | 1.2 | public | A Toy Key Value Text Type
+ (1 row)
+
+ DROP EXTENSION pair;
+ -- test owner change
+ CREATE ROLE regression_bob;
+ ALTER TEMPLATE FOR EXTENSION pair OWNER TO regression_bob;
+ select ctlname, rolname
+ from pg_extension_control c join pg_roles r on r.oid = c.ctlowner;
+ ctlname | rolname
+ ---------+----------------
+ pair | regression_bob
+ pair | regression_bob
+ pair | regression_bob
+ pair | regression_bob
+ (4 rows)
+
+ -- test renaming
+ ALTER TEMPLATE FOR EXTENSION pair RENAME TO keyval;
+ -- cleanup
+ DROP TEMPLATE FOR EXTENSION keyval FOR UPDATE FROM '1.1' TO '1.2';
+ DROP TEMPLATE FOR EXTENSION keyval FOR UPDATE FROM '1.0' TO '1.1';
+ DROP TEMPLATE FOR EXTENSION keyval VERSION '1.0';
+ DROP TEMPLATE FOR EXTENSION keyval VERSION '1.3';
+ DROP ROLE regression_bob;
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
***************
*** 105,110 **** pg_description|t
--- 105,113 ----
pg_enum|t
pg_event_trigger|t
pg_extension|t
+ pg_extension_control|t
+ pg_extension_template|t
+ pg_extension_uptmpl|t
pg_foreign_data_wrapper|t
pg_foreign_server|t
pg_foreign_table|t
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
***************
*** 94,99 **** test: alter_generic misc psql async
--- 94,100 ----
test: rules
# event triggers cannot run concurrently with any test that runs DDL
test: event_trigger
+ test: extension
# ----------
# Another group of parallel tests
*** a/src/test/regress/serial_schedule
--- b/src/test/regress/serial_schedule
***************
*** 142,144 **** test: largeobject
--- 142,145 ----
test: with
test: xml
test: stats
+ test: extension
*** /dev/null
--- b/src/test/regress/sql/extension.sql
***************
*** 0 ****
--- 1,254 ----
+ -- test case without an explicit schema
+ create template for extension myextension version '1.0'
+ as $$ create table foobar(i int4) $$;
+
+ create extension myextension;
+
+ -- check that it went to 'public'
+ select nspname
+ from pg_class c join pg_namespace n on n.oid = c.relnamespace
+ where relname ~ 'foobar';
+
+ -- cleanup
+ drop extension myextension;
+ drop template for extension myextension version '1.0';
+
+ -- test case without an explicit schema in an upgrade path
+ create template for extension test version 'abc' with (nosuperuser) as $$
+ create function f1(i int) returns int language sql as $_$ select 1; $_$;
+ $$;
+
+ create template for extension test for update from 'abc' to 'xyz' with (nosuperuser) as $$
+ create function f2(i int) returns int language sql as $_$ select 1; $_$;
+ $$;
+
+ create template for extension test for update from 'xyz' to '123' with (nosuperuser) as $$
+ create function f3(i int) returns int language sql as $_$ select 1; $_$;
+ $$;
+
+ create extension test version '123';
+
+ \dx+ test
+
+ -- cleanup
+ drop extension test;
+ drop template for extension test for update from 'xyz' to '123';
+ drop template for extension test for update from 'abc' to 'xyz';
+ drop template for extension test version 'abc';
+
+ -- testing dependency in between template and instanciated extensions
+ create template for extension deps version 'a' as '';
+ create template for extension deps for update from 'a' to 'b' as '';
+ alter template for extension deps set default version 'b';
+ create extension deps;
+ \dx
+ -- that should be an error
+ drop template for extension deps version 'a';
+ -- that too should be an error
+ drop template for extension deps for update from 'a' to 'b';
+
+ -- check that we can add a new template for directly installing version 'b'
+ create template for extension deps version 'b' as '';
+
+ -- and test some control parameters conflicts now
+ create template for extension deps for update from 'b' to 'c' as '';
+
+ -- those should all fail
+ create template for extension deps version 'c' with (schema foo) as '';
+ create template for extension deps version 'c' with (superuser) as '';
+ create template for extension deps version 'c' with (relocatable) as '';
+ create template for extension deps version 'c' with (requires 'x, y') as '';
+
+ -- that one should succeed: no conflict
+ create template for extension deps version 'c'
+ with (schema public, nosuperuser, norelocatable) as '';
+
+ -- cleanup
+ drop extension deps;
+ drop template for extension deps version 'a' cascade;
+ drop template for extension deps version 'b' cascade;
+ drop template for extension deps version 'c' cascade;
+
+ -- check that we no longer have control entries
+ select * from pg_extension_control;
+
+ -- test that we can not rename a template in use
+ create template for extension foo version 'v' AS '';
+ create extension foo;
+ alter template for extension foo rename to bar;
+
+ drop extension foo;
+ drop template for extension foo version 'v';
+
+ -- now create some templates and an upgrade path
+ CREATE TEMPLATE
+ FOR EXTENSION pair DEFAULT VERSION '1.0'
+ WITH (superuser, norelocatable, schema public)
+ AS $$
+ CREATE TYPE pair AS ( k text, v text );
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;';
+ $$;
+
+ -- we want to test alter extension update
+ CREATE TEMPLATE FOR EXTENSION pair
+ FOR UPDATE FROM '1.0' TO '1.1'
+ WITH (superuser, norelocatable, schema public)
+ AS $$
+ CREATE OPERATOR ~> (LEFTARG = text,
+ RIGHTARG = anyelement,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = anyelement,
+ RIGHTARG = text,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = anyelement,
+ RIGHTARG = anyelement,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = text,
+ RIGHTARG = text,
+ PROCEDURE = pair);
+ $$;
+
+ -- and we want to test update with more than 1 step
+ CREATE TEMPLATE FOR EXTENSION pair
+ FOR UPDATE FROM '1.1' TO '1.2'
+ AS
+ $$
+ COMMENT ON EXTENSION pair IS 'Simple Key Value Text Type';
+ $$;
+
+ -- test some ALTER commands
+
+ -- ok
+ ALTER TEMPLATE FOR EXTENSION pair VERSION '1.0' WITH (relocatable);
+
+ -- we don't have a version 1.3 known yet
+ ALTER TEMPLATE FOR EXTENSION pair VERSION '1.3' WITH (relocatable);
+
+ -- you can't set the default on an upgrade script, only an extension's version
+ ALTER TEMPLATE FOR EXTENSION pair FOR UPDATE FROM '1.0' TO '1.1' SET DEFAULT;
+
+ -- you can't set control properties on an upgrade script, only an
+ -- extension's version
+ ALTER TEMPLATE FOR EXTENSION pair
+ FOR UPDATE FROM '1.0' TO '1.1' WITH (relocatable);
+
+ -- try to set the default full version to an unknown extension version
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT FULL VERSION '1.1';
+
+ -- now set it to the current one already, should silently do nothing
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT FULL VERSION '1.0';
+
+ -- you can actually change the script used to update, though
+ ALTER TEMPLATE FOR EXTENSION pair FOR UPDATE FROM '1.1' TO '1.2'
+ AS $$
+ COMMENT ON EXTENSION pair IS 'A Toy Key Value Text Type';
+ $$;
+
+ CREATE EXTENSION pair;
+
+ \dx pair
+ \dx+ pair
+
+ ALTER EXTENSION pair UPDATE TO '1.2';
+
+ \dx+ pair
+
+ DROP EXTENSION pair;
+
+ -- test with another full version that's not the default
+ CREATE TEMPLATE
+ FOR EXTENSION pair VERSION '1.3'
+ WITH (superuser, norelocatable, schema public)
+ AS $$
+ CREATE TYPE pair AS ( k text, v text );
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;';
+
+ CREATE OPERATOR ~> (LEFTARG = text,
+ RIGHTARG = anyelement,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = anyelement,
+ RIGHTARG = text,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = anyelement,
+ RIGHTARG = anyelement,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = text,
+ RIGHTARG = text,
+ PROCEDURE = pair);
+
+ COMMENT ON EXTENSION pair IS 'Simple Key Value Text Type';
+ $$;
+
+ -- that's ok
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.1';
+
+ -- that will install 1.0 then run the 1.0 -- 1.1 update script
+ CREATE EXTENSION pair;
+ \dx pair
+ DROP EXTENSION pair;
+
+ -- now that should install from the extension from the 1.3 template, even if
+ -- we have a default_major_version pointing to 1.0, because we actually have
+ -- a 1.3 create script.
+ CREATE EXTENSION pair VERSION '1.3';
+ \dx pair
+ DROP EXTENSION pair;
+
+ -- and now let's ask for 1.3 by default while still leaving the
+ -- default_major_version at 1.0, so that it's possible to directly install
+ -- 1.2 if needed.
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.3';
+
+ CREATE EXTENSION pair;
+ \dx pair
+ DROP EXTENSION pair;
+
+ CREATE EXTENSION pair VERSION '1.2';
+ \dx pair
+ DROP EXTENSION pair;
+
+ -- test owner change
+ CREATE ROLE regression_bob;
+
+ ALTER TEMPLATE FOR EXTENSION pair OWNER TO regression_bob;
+
+ select ctlname, rolname
+ from pg_extension_control c join pg_roles r on r.oid = c.ctlowner;
+
+ -- test renaming
+ ALTER TEMPLATE FOR EXTENSION pair RENAME TO keyval;
+
+ -- cleanup
+ DROP TEMPLATE FOR EXTENSION keyval FOR UPDATE FROM '1.1' TO '1.2';
+ DROP TEMPLATE FOR EXTENSION keyval FOR UPDATE FROM '1.0' TO '1.1';
+ DROP TEMPLATE FOR EXTENSION keyval VERSION '1.0';
+ DROP TEMPLATE FOR EXTENSION keyval VERSION '1.3';
+ DROP ROLE regression_bob;