*** 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,
*** a/doc/src/sgml/ref/allfiles.sgml
--- b/doc/src/sgml/ref/allfiles.sgml
***************
*** 30,35 **** Complete list of usable sgml source files in this directory.
--- 30,36 ----
+
***************
*** 73,78 **** Complete list of usable sgml source files in this directory.
--- 74,80 ----
+
***************
*** 112,117 **** Complete list of usable sgml source files in this directory.
--- 114,120 ----
+
*** /dev/null
--- b/doc/src/sgml/ref/alter_extension_template.sgml
***************
*** 0 ****
--- 1,81 ----
+
+
+
+
+ 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
+
+
+
+
+ Description
+
+
+ ALTER TEMPLATE FOR EXTENSION changes the definition of
+ an extension template. Currently, the only supported functionality is to
+ change the template's default version.
+
+
+
+
+ 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.
+
+
+
+
+
+
+
+
+ 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,104 ----
+
+
+
+
+ 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
+ FROM old_version TO new_version
+ [ WITH [ (
+ [ control_parameter ] [, ... ]
+ ) ] ]
+ AS script
+
+
+ where control_parameter is one of:
+
+ SCHEMA schema_name
+ 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 .
+
+
+
+ The arguments can appear in any order, not only the one shown above.
+
+
+
+
+ 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 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
***************
*** 58,63 ****
--- 58,64 ----
&alterServer;
&alterTable;
&alterTableSpace;
+ &alterTemplateForExtension;
&alterTSConfig;
&alterTSDictionary;
&alterTSParser;
***************
*** 101,106 ****
--- 102,108 ----
&createTable;
&createTableAs;
&createTableSpace;
+ &createTemplateForExtension;
&createTSConfig;
&createTSDictionary;
&createTSParser;
***************
*** 140,145 ****
--- 142,148 ----
&dropServer;
&dropTable;
&dropTableSpace;
+ &dropTemplateForExtension;
&dropTSConfig;
&dropTSDictionary;
&dropTSParser;
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
***************
*** 37,42 **** POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
--- 37,43 ----
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/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 37,42 ****
--- 37,45 ----
#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"
***************
*** 64,69 ****
--- 67,73 ----
#include "commands/schemacmds.h"
#include "commands/seclabel.h"
#include "commands/tablespace.h"
+ #include "commands/template.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "foreign/foreign.h"
***************
*** 1234,1239 **** doDeletion(const ObjectAddress *object, int flags)
--- 1238,1255 ----
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;
***************
*** 2295,2300 **** getObjectClass(const ObjectAddress *object)
--- 2311,2325 ----
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
***************
*** 28,33 ****
--- 28,36 ----
#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"
***************
*** 52,57 ****
--- 55,61 ----
#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 "libpq/be-fsstubs.h"
***************
*** 157,162 **** static ObjectPropertyType ObjectProperty[] =
--- 161,199 ----
ACL_KIND_EXTENSION
},
{
+ ExtensionControlRelationId,
+ ExtensionControlOidIndexId,
+ -1,
+ -1,
+ Anum_pg_extension_control_ctlname,
+ InvalidAttrNumber, /* extension doesn't belong to extnamespace */
+ Anum_pg_extension_control_ctlowner,
+ InvalidAttrNumber,
+ ACL_KIND_TEMPLATE
+ },
+ {
+ ExtensionTemplateRelationId,
+ ExtensionTemplateOidIndexId,
+ -1,
+ -1,
+ Anum_pg_extension_template_tplname,
+ InvalidAttrNumber, /* extension doesn't belong to extnamespace */
+ Anum_pg_extension_template_tplowner,
+ InvalidAttrNumber,
+ ACL_KIND_TEMPLATE
+ },
+ {
+ ExtensionUpTmplRelationId,
+ ExtensionUpTmplOidIndexId,
+ -1,
+ -1,
+ Anum_pg_extension_uptmpl_uptname,
+ InvalidAttrNumber, /* extension doesn't belong to extnamespace */
+ Anum_pg_extension_uptmpl_uptowner,
+ InvalidAttrNumber,
+ ACL_KIND_TEMPLATE
+ },
+ {
ForeignDataWrapperRelationId,
ForeignDataWrapperOidIndexId,
FOREIGNDATAWRAPPEROID,
***************
*** 392,397 **** static ObjectAddress get_object_address_type(ObjectType objtype,
--- 429,436 ----
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);
/*
***************
*** 472,477 **** get_object_address(ObjectType objtype, List *objname, List *objargs,
--- 511,521 ----
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);
***************
*** 718,728 **** get_object_address_unqualified(ObjectType objtype,
address.objectId = get_extension_oid(name, missing_ok);
address.objectSubId = 0;
break;
- case OBJECT_TABLESPACE:
- address.classId = TableSpaceRelationId;
- address.objectId = get_tablespace_oid(name, missing_ok);
- address.objectSubId = 0;
- break;
case OBJECT_ROLE:
address.classId = AuthIdRelationId;
address.objectId = get_role_oid(name, missing_ok);
--- 762,767 ----
***************
*** 1059,1064 **** get_object_address_opcf(ObjectType objtype,
--- 1098,1179 ----
}
/*
+ * 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
***************
*** 1122,1127 **** check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
--- 1237,1248 ----
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTENSION,
NameListToString(objname));
break;
+ case OBJECT_EXTENSION_TEMPLATE:
+ case OBJECT_EXTENSION_UPTMPL:
+ if (!pg_extension_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TEMPLATE,
+ NameListToString(objname));
+ break;
case OBJECT_FDW:
if (!pg_foreign_data_wrapper_ownercheck(address.objectId, roleid))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FDW,
*** 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 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 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/event_trigger.c
--- b/src/backend/commands/event_trigger.c
***************
*** 84,89 **** static event_trigger_support_data event_trigger_support[] = {
--- 84,90 ----
{ "TEXT SEARCH TEMPLATE", true },
{ "TYPE", true },
{ "USER MAPPING", true },
+ { "TEMPLATE FOR EXTENSION", true },
{ "VIEW", true },
{ NULL, false }
};
*** a/src/backend/commands/extension.c
--- b/src/backend/commands/extension.c
***************
*** 43,48 ****
--- 43,49 ----
#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
--- 62,67 ----
***************
*** 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);
--- 80,90 ----
ExtensionVersionInfo *evi_start,
ExtensionVersionInfo *evi_target,
bool reinitialize);
! static void get_available_versions_for_extension(ExtensionControl *pcontrol,
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);
--- 216,222 ----
/*
* Utility functions to check validity of extension and version names
*/
! void
check_valid_extension_name(const char *extensionname)
{
int namelen = strlen(extensionname);
***************
*** 370,376 **** get_extension_control_filename(const char *extname)
}
static char *
! get_extension_script_directory(ExtensionControlFile *control)
{
char sharepath[MAXPGPATH];
char *result;
--- 354,360 ----
}
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;
--- 377,383 ----
}
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;
--- 395,401 ----
}
static char *
! get_extension_script_filename(ExtensionControl *control,
const char *from_version, const char *version)
{
char *result;
***************
*** 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;
--- 427,433 ----
* 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)
{
--- 467,473 ----
FreeFile(file);
/*
! * Convert the ConfigVariable list into ExtensionControl entries.
*/
for (item = head; item != NULL; item = item->next)
{
***************
*** 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;
--- 563,579 ----
/*
* 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->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
--- 589,608 ----
/*
* 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;
--- 613,687 ----
}
/*
+ * Read the control properties for given extension, either from a file on the
+ * dile 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, which maybe
+ * should get renamed now.
+ */
+ 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 */
+ return find_default_pg_extension_control(extname, false);
+ }
+ 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
--- 724,729 ----
***************
*** 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;
--- 733,739 ----
* 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;
--- 818,829 ----
* 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
--- 849,854 ----
***************
*** 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);
--- 903,925 ----
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();
{
--- 968,974 ----
/* 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);
--- 1057,1063 ----
* 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)
--- 1113,1170 ----
}
/*
+ * 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_catalogs(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);
+ }
+
+ /* 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_catalogs(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;
--- 1172,1178 ----
* version is *not* included).
*/
static List *
! identify_update_path(ExtensionControl *control,
const char *oldVersion, const char *newVersion)
{
List *result;
***************
*** 1185,1192 **** CreateExtension(CreateExtensionStmt *stmt)
char *versionName;
char *oldVersionName;
Oid extowner = GetUserId();
! ExtensionControlFile *pcontrol;
! ExtensionControlFile *control;
List *updateVersions;
List *requiredExtensions;
List *requiredSchemas;
--- 1297,1304 ----
char *versionName;
char *oldVersionName;
Oid extowner = GetUserId();
! ExtensionControl *pcontrol;
! ExtensionControl *control;
List *updateVersions;
List *requiredExtensions;
List *requiredSchemas;
***************
*** 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
--- 1345,1351 ----
* 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)
--- 1384,1394 ----
/*
* 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);
***************
*** 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
--- 1451,1457 ----
/*
* Fetch control parameters for installation target version
*/
! control = read_extension_aux_control(pcontrol, versionName);
/*
* Determine the target schema to install the extension into
***************
*** 1632,1644 **** 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))
--- 1749,1763 ----
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))
***************
*** 1681,1687 **** pg_available_extensions(PG_FUNCTION_ARGS)
{
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControlFile *control;
char *extname;
Datum values[3];
bool nulls[3];
--- 1800,1806 ----
{
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControl *control;
char *extname;
Datum values[3];
bool nulls[3];
***************
*** 1722,1727 **** pg_available_extensions(PG_FUNCTION_ARGS)
--- 1841,1869 ----
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));
+ 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 */
+ nulls[2] = true;
+
+ tuplestore_putvalues(tupstore, tupdesc, values, nulls);
+ }
+
/* clean up and return the tuplestore */
tuplestore_donestoring(tupstore);
***************
*** 1790,1796 **** 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))
--- 1932,1938 ----
{
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControl *control;
char *extname;
if (!is_extension_control_filename(de->d_name))
***************
*** 1825,1831 **** pg_available_extension_versions(PG_FUNCTION_ARGS)
* read versions of one extension, add rows to tupstore
*/
static void
! get_available_versions_for_extension(ExtensionControlFile *pcontrol,
Tuplestorestate *tupstore,
TupleDesc tupdesc)
{
--- 1967,1973 ----
* read versions of one extension, add rows to tupstore
*/
static void
! get_available_versions_for_extension(ExtensionControl *pcontrol,
Tuplestorestate *tupstore,
TupleDesc tupdesc)
{
***************
*** 1839,1845 **** 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];
--- 1981,1987 ----
/* Note this will fail if script directory doesn't exist */
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControl *control;
char *vername;
Datum values[7];
bool nulls[7];
***************
*** 1936,1942 **** 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 */
--- 2078,2084 ----
MemoryContext per_query_ctx;
MemoryContext oldcontext;
List *evi_list;
! ExtensionControl *control;
ListCell *lc1;
/* Check extension name validity before any filesystem access */
***************
*** 1969,1975 **** 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);
--- 2111,2117 ----
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);
***************
*** 2586,2592 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
DefElem *d_new_version = NULL;
char *versionName;
char *oldVersionName;
! ExtensionControlFile *control;
Oid extensionOid;
Relation extRel;
ScanKeyData key[1];
--- 2728,2734 ----
DefElem *d_new_version = NULL;
char *versionName;
char *oldVersionName;
! ExtensionControl *control;
Oid extensionOid;
Relation extRel;
ScanKeyData key[1];
***************
*** 2652,2658 **** 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
--- 2794,2800 ----
* 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
***************
*** 2727,2733 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
*/
static void
ApplyExtensionUpdates(Oid extensionOid,
! ExtensionControlFile *pcontrol,
const char *initialVersion,
List *updateVersions)
{
--- 2869,2875 ----
*/
static void
ApplyExtensionUpdates(Oid extensionOid,
! ExtensionControl *pcontrol,
const char *initialVersion,
List *updateVersions)
{
***************
*** 2737,2743 **** ApplyExtensionUpdates(Oid extensionOid,
foreach(lcv, updateVersions)
{
char *versionName = (char *) lfirst(lcv);
! ExtensionControlFile *control;
char *schemaName;
Oid schemaOid;
List *requiredExtensions;
--- 2879,2885 ----
foreach(lcv, updateVersions)
{
char *versionName = (char *) lfirst(lcv);
! ExtensionControl *control;
char *schemaName;
Oid schemaOid;
List *requiredExtensions;
***************
*** 2756,2762 **** 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);
--- 2898,2904 ----
/*
* 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);
*** /dev/null
--- b/src/backend/commands/template.c
***************
*** 0 ****
--- 1,1536 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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 "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/extension.h"
+ #include "commands/template.h"
+ #include "funcapi.h"
+ #include "miscadmin.h"
+ #include "tcop/utility.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 modify_pg_extension_control_default(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_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, "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);
+ }
+ }
+
+ /*
+ * 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(CreateTemplateStmt *stmt)
+ {
+ switch (stmt->template)
+ {
+ 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(CreateTemplateStmt *stmt)
+ {
+ Oid extTemplateOid;
+ Oid owner = GetUserId();
+ ExtensionControl *control;
+
+ /* Check extension name validity before any filesystem access */
+ check_valid_extension_name(stmt->extname);
+
+ /*
+ * Check for duplicate extension name in the pg_extension catalogs. Any
+ * extension that already is known in the catalogs needs no template for
+ * creating it in the first place.
+ */
+ if (get_extension_oid(stmt->extname, true) != InvalidOid)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("extension \"%s\" already exists",
+ stmt->extname)));
+ }
+
+ /*
+ * 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, and besides we need a check to support IF
+ * NOT EXISTS.
+ */
+ if (get_template_oid(stmt->extname, stmt->version, true) != InvalidOid)
+ {
+ if (stmt->if_not_exists)
+ {
+ ereport(NOTICE,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("template for extension \"%s\" version \"%s\" already exists, skipping",
+ stmt->extname, stmt->version)));
+ return InvalidOid;
+ }
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("template for extension \"%s\" version \"%s\" already exists",
+ stmt->extname, stmt->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.
+ */
+ PG_TRY();
+ {
+ control = read_extension_control_file(stmt->extname);
+ }
+ PG_CATCH();
+ {
+ /* no control file found is good news for us */
+ control = NULL;
+ }
+ PG_END_TRY();
+
+ if (control)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("extension \"%s\" is already available",
+ stmt->extname)));
+ }
+
+ /* Now read the control properties from the statement */
+ control = (ExtensionControl *) palloc0(sizeof(ExtensionControl));
+ control->name = pstrdup(stmt->extname);
+ parse_statement_control_defelems(control, stmt->control);
+
+ /*
+ * 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.
+ */
+ if (stmt->default_version)
+ {
+ ExtensionControl *default_version =
+ find_default_pg_extension_control(control->name, true);
+
+ 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);
+ }
+
+ 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(CreateTemplateStmt *stmt)
+ {
+ Oid owner = GetUserId();
+ ExtensionControl *control;
+
+ /* 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.
+ */
+ PG_TRY();
+ {
+ control = read_extension_control_file(stmt->extname);
+ }
+ PG_CATCH();
+ {
+ /* no control file found is good news for us */
+ control = NULL;
+ }
+ PG_END_TRY();
+
+ if (control)
+ {
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("extension \"%s\" is already available",
+ stmt->extname)));
+ }
+
+ /* Now read the (optional) control properties from the statement */
+ if (stmt->control)
+ {
+ control = (ExtensionControl *) palloc0(sizeof(ExtensionControl));
+ control->name = pstrdup(stmt->extname);
+
+ parse_statement_control_defelems(control, stmt->control);
+ }
+
+ return InsertExtensionUpTmplTuple(owner, stmt->extname, control,
+ stmt->from, stmt->to, stmt->script);
+ }
+
+ /*
+ * 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->requires == NULL)
+ nulls[Anum_pg_extension_control_ctlrequires - 1] = 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[Anum_pg_extension_control_ctlrequires - 1] =
+ PointerGetDatum(a);
+ }
+
+ 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 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(ExtensionControlRelationId, extControlOid, owner);
+
+ myself.classId = ExtensionControlRelationId;
+ myself.objectId = extControlOid;
+ myself.objectSubId = 0;
+
+ /* Post creation hook for new extension control */
+ InvokeObjectAccessHook(OAT_POST_CREATE,
+ ExtensionControlRelationId, extControlOid, 0, NULL);
+
+ 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;
+
+ /* First 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);
+
+ /* Post creation hook for new extension control */
+ InvokeObjectAccessHook(OAT_POST_CREATE,
+ ExtensionTemplateRelationId, extTemplateOid, 0, NULL);
+
+ 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;
+
+ /*
+ * First create the companion extension control entry, if any. In the case
+ * of an Update Template the comanion control entry is somilar in scope to
+ * a secondary control file, and is attached to the target version.
+ */
+ if (control)
+ extControlOid = InsertExtensionControlTuple(owner, control, to);
+ else
+ extControlOid = InvalidOid;
+
+ /*
+ * 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 */
+ if (control)
+ {
+ ObjectAddress ctrl;
+
+ ctrl.classId = ExtensionControlRelationId;
+ ctrl.objectId = extControlOid;
+ ctrl.objectSubId = 0;
+
+ recordDependencyOn(&ctrl, &myself, DEPENDENCY_INTERNAL);
+ }
+
+ /* Post creation hook for new extension control */
+ InvokeObjectAccessHook(OAT_POST_CREATE,
+ ExtensionUpTmplRelationId, extUpTmplOid, 0, NULL);
+
+ return extUpTmplOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION name VERSION version
+ *
+ * This implements high level routing for sub commands.
+ */
+ Oid
+ AlterTemplate(AlterTemplateStmt *stmt)
+ {
+ switch (stmt->template)
+ {
+ case TEMPLATE_CREATE_EXTENSION:
+ return AlterExtensionTemplate(stmt);
+
+ case TEMPLATE_UPDATE_EXTENSION:
+ return AlterExtensionUpdateTemplate(stmt);
+ }
+ /* keep compiler happy */
+ return InvalidOid;
+ }
+
+ Oid
+ AlterExtensionTemplate(AlterTemplateStmt *stmt)
+ {
+ switch (stmt->cmdtype)
+ {
+ case AET_SET_DEFAULT:
+ return AlterTemplateSetDefault(stmt->extname, stmt->version);
+
+ case AET_SET_SCRIPT:
+ elog(WARNING, "Not Yet Implemented");
+ break;
+
+ case AET_UPDATE_CONTROL:
+ elog(WARNING, "Not Yet Implemented");
+ break;
+ }
+
+ 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, true);
+
+ if (current)
+ {
+ if (strcmp(current->default_version, version) == 0)
+ /* silently do nothing */
+ return InvalidOid;
+
+ /* 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 ctldefault bit from value to repl.
+ */
+ 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,
+ SnapshotNow, 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 name FROM old TO new
+ *
+ * This implements high level routing for sub commands.
+ */
+ Oid
+ AlterExtensionUpdateTemplate(AlterTemplateStmt *stmt)
+ {
+ elog(WARNING, "Not Yet Implemented");
+ return InvalidOid;
+ }
+
+ /*
+ * 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,
+ SnapshotNow, 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,
+ SnapshotNow, 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,
+ SnapshotNow, 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,
+ SnapshotNow, 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,
+ SnapshotNow, 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,
+ SnapshotNow, 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);
+ }
+
+ /*
+ * 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->name = pstrdup(extname);
+ control->is_template = true;
+ control->relocatable = ctrl->ctlrelocatable;
+ control->superuser = ctrl->ctlsuperuser;
+ control->schema = pstrdup(NameStr(ctrl->ctlnamespace));
+
+ if (ctrl->ctldefault)
+ {
+ Datum dvers =
+ heap_getattr(tuple, Anum_pg_extension_control_ctlversion,
+ RelationGetDescr(rel), &isnull);
+
+ char *version = isnull? NULL : text_to_cstring(DatumGetTextPP(dvers));
+
+ if (isnull)
+ {
+ /* shouldn't happen */
+ elog(ERROR,
+ "pg_extension_control row without version for \"%s\"",
+ extname);
+ }
+
+ /* get the version and requires fields from here */
+ control->default_version = pstrdup(version);
+ }
+
+ /* 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,
+ SnapshotNow, 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)));
+
+ return control;
+ }
+
+ /*
+ * Find the default extension's control properties.
+ */
+ ExtensionControl *
+ find_default_pg_extension_control(const char *extname, bool missing_ok)
+ {
+ ExtensionControl *control = 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,
+ SnapshotNow, 1, entry);
+
+ /* find all the control tuples for extname */
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ {
+ bool isnull;
+ bool ctldefault =
+ DatumGetBool(
+ fastgetattr(tuple, Anum_pg_extension_control_ctldefault,
+ RelationGetDescr(rel), &isnull));
+
+ /* only of those is the default */
+ if (ctldefault)
+ {
+ if (control == NULL)
+ control = read_pg_extension_control(extname, rel, tuple);
+ else
+ /* should not happen */
+ elog(ERROR,
+ "Extension \"%s\" has more than one default control template",
+ extname);
+ }
+ }
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ /* we really need a single default version. */
+ if (control == NULL && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension \"%s\" has no default control template",
+ extname)));
+
+ return control;
+ }
+
+ /*
+ * read_pg_extension_uptmpl_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,
+ SnapshotNow, 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,
+ SnapshotNow, 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,
+ SnapshotNow, 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,
+ SnapshotNow, 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 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,
+ SnapshotNow, 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));
+
+ extensions = lappend(extensions,
+ list_make2(pstrdup(NameStr(ctrl->ctlname)),
+ version));
+ }
+ }
+
+ 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,
+ SnapshotNow, 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;
+ }
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 220,226 **** static void processCASbits(int cas_bits, int location, const char *constrType,
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
! AlterRoleStmt AlterRoleSetStmt
AlterDefaultPrivilegesStmt DefACLAction
AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
--- 220,226 ----
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
! AlterRoleStmt AlterRoleSetStmt AlterTemplateStmt
AlterDefaultPrivilegesStmt DefACLAction
AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
***************
*** 229,239 **** static void processCASbits(int cas_bits, int location, const char *constrType,
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
--- 229,239 ----
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
! CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreateTemplateStmt
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
***************
*** 266,271 **** static void processCASbits(int cas_bits, int location, const char *constrType,
--- 266,274 ----
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
***************
*** 720,725 **** stmt :
--- 723,729 ----
| AlterCompositeTypeStmt
| AlterRoleSetStmt
| AlterRoleStmt
+ | AlterTemplateStmt
| AlterTSConfigurationStmt
| AlterTSDictionaryStmt
| AlterUserMappingStmt
***************
*** 757,762 **** stmt :
--- 761,767 ----
| CreateUserStmt
| CreateUserMappingStmt
| CreatedbStmt
+ | CreateTemplateStmt
| DeallocateStmt
| DeclareCursorStmt
| DefineStmt
***************
*** 780,785 **** stmt :
--- 785,791 ----
| DropUserStmt
| DropUserMappingStmt
| DropdbStmt
+ | DropTemplateStmt
| ExecuteStmt
| ExplainStmt
| FetchStmt
***************
*** 3440,3445 **** DropTableSpaceStmt: DROP TABLESPACE name
--- 3446,3675 ----
/*****************************************************************************
*
* QUERY:
+ * CREATE TEMPLATE FOR EXTENSION name
+ *
+ *****************************************************************************/
+
+ CreateTemplateStmt:
+ CREATE TEMPLATE FOR EXTENSION name VERSION_P ColId_or_Sconst
+ WITH create_template_control AS Sconst
+ {
+ CreateTemplateStmt *n = makeNode(CreateTemplateStmt);
+ n->template = 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 ColId_or_Sconst
+ WITH create_template_control AS Sconst
+ {
+ CreateTemplateStmt *n = makeNode(CreateTemplateStmt);
+ n->template = 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
+ FROM ColId_or_Sconst TO ColId_or_Sconst AS Sconst
+ {
+ CreateTemplateStmt *n = makeNode(CreateTemplateStmt);
+ n->template = TEMPLATE_UPDATE_EXTENSION;
+ n->extname = $5;
+ n->from = $7;
+ n->to = $9;
+ n->control = NIL;
+ n->script = $11;
+ n->if_not_exists = false;
+ $$ = (Node *) n;
+ }
+ | CREATE TEMPLATE FOR EXTENSION name
+ FROM ColId_or_Sconst TO ColId_or_Sconst
+ WITH create_template_control AS Sconst
+ {
+ CreateTemplateStmt *n = makeNode(CreateTemplateStmt);
+ n->template = TEMPLATE_UPDATE_EXTENSION;
+ n->extname = $5;
+ n->from = $7;
+ n->to = $9;
+ n->control = $11;
+ n->script = $13;
+ n->if_not_exists = false;
+ $$ = (Node *) n;
+ }
+ ;
+
+ /*****************************************************************************
+ *
+ * QUERY:
+ * ALTER TEMPLATE FOR EXTENSION name
+ *
+ * We only allow a single subcommand per command here.
+ *
+ *****************************************************************************/
+ AlterTemplateStmt:
+ ALTER TEMPLATE FOR EXTENSION name
+ SET DEFAULT VERSION_P ColId_or_Sconst
+ {
+ AlterTemplateStmt *n = makeNode(AlterTemplateStmt);
+ n->template = 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 VERSION_P ColId_or_Sconst
+ AS Sconst
+ {
+ AlterTemplateStmt *n = makeNode(AlterTemplateStmt);
+ n->template = 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 ColId_or_Sconst
+ WITH create_template_control
+ {
+ AlterTemplateStmt *n = makeNode(AlterTemplateStmt);
+ n->template = 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
+ FROM ColId_or_Sconst TO ColId_or_Sconst
+ AS Sconst
+ {
+ AlterTemplateStmt *n = makeNode(AlterTemplateStmt);
+ n->template = TEMPLATE_UPDATE_EXTENSION;
+ n->cmdtype = AET_SET_SCRIPT;
+ n->extname = $5;
+ n->from = $7;
+ n->to = $9;
+ n->script = $11;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
+ | ALTER TEMPLATE FOR EXTENSION name
+ FROM ColId_or_Sconst TO ColId_or_Sconst
+ WITH create_template_control
+ {
+ AlterTemplateStmt *n = makeNode(AlterTemplateStmt);
+ n->template = TEMPLATE_UPDATE_EXTENSION;
+ n->cmdtype = AET_UPDATE_CONTROL;
+ n->extname = $5;
+ n->from = $7;
+ n->to = $9;
+ n->control = $11;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
+ ;
+
+ /*****************************************************************************
+ *
+ * QUERY:
+ * DROP TEMPLATE FOR EXTENSION name
+ *
+ *****************************************************************************/
+ DropTemplateStmt:
+ DROP TEMPLATE FOR EXTENSION name
+ VERSION_P ColId_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
+ FROM ColId_or_Sconst TO ColId_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($7),
+ makeString($9)));
+ n->behavior = $10;
+ 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));
+ }
+ | 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
+ 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 ]
*
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 46,51 ****
--- 46,52 ----
#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"
***************
*** 224,229 **** check_xact_readonly(Node *parsetree)
--- 225,232 ----
case T_CreateExtensionStmt:
case T_AlterExtensionStmt:
case T_AlterExtensionContentsStmt:
+ case T_CreateTemplateStmt:
+ case T_AlterTemplateStmt:
case T_CreateFdwStmt:
case T_AlterFdwStmt:
case T_CreateForeignServerStmt:
***************
*** 621,626 **** standard_ProcessUtility(Node *parsetree,
--- 624,641 ----
ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree);
break;
+ case T_CreateTemplateStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
+ CreateTemplate((CreateTemplateStmt *) parsetree);
+ break;
+
+ case T_AlterTemplateStmt:
+ if (isCompleteQuery)
+ EventTriggerDDLCommandStart(parsetree);
+ AlterTemplate((AlterTemplateStmt *)parsetree);
+ break;
+
case T_CreateFdwStmt:
if (isCompleteQuery)
EventTriggerDDLCommandStart(parsetree);
***************
*** 1781,1786 **** CreateCommandTag(Node *parsetree)
--- 1796,1809 ----
tag = "ALTER EXTENSION";
break;
+ case T_CreateTemplateStmt:
+ tag = "CREATE TEMPLATE FOR EXTENSION";
+ break;
+
+ case T_AlterTemplateStmt:
+ tag = "ALTER TEMPLATE FOR EXTENSION";
+ break;
+
case T_CreateFdwStmt:
tag = "CREATE FOREIGN DATA WRAPPER";
break;
***************
*** 1861,1866 **** CreateCommandTag(Node *parsetree)
--- 1884,1895 ----
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;
***************
*** 2411,2416 **** GetCommandLogLevel(Node *parsetree)
--- 2440,2450 ----
lev = LOGSTMT_DDL;
break;
+ case T_CreateTemplateStmt:
+ case T_AlterTemplateStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_CreateFdwStmt:
case T_AlterFdwStmt:
case T_CreateForeignServerStmt:
*** 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
***************
*** 307,312 **** DECLARE_UNIQUE_INDEX(pg_extension_oid_index, 3080, on pg_extension using btree(o
--- 307,329 ----
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, 3181, on pg_extension_template using btree(tplname name_ops, tplversion text_ops));
+ #define ExtensionTemplateNameVersionIndexId 3181
+
+ 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,73 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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; /* this version is the extension's default? */
+ 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; /* 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 8
+ #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_ctlrelocatable 4
+ #define Anum_pg_extension_control_ctlsuperuser 5
+ #define Anum_pg_extension_control_ctlnamespace 6
+ #define Anum_pg_extension_control_ctlversion 7
+ #define Anum_pg_extension_control_ctlrequires 8
+
+ /* ----------------
+ * 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/commands/extension.h
--- b/src/include/commands/extension.h
***************
*** 26,31 ****
--- 26,51 ----
extern bool creating_extension;
extern Oid CurrentExtensionObject;
+ /*
+ * Internal data structure to hold the results of parsing a control file
+ */
+ typedef struct ExtensionControl
+ {
+ 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 */
+ bool is_template; /* true if we're using catalog templates */
+ } ExtensionControl;
+
+ extern ExtensionControl *read_extension_control_file(const char *extname);
+ extern void check_valid_extension_name(const char *extensionname);
extern Oid CreateExtension(CreateExtensionStmt *stmt);
*** /dev/null
--- b/src/include/commands/template.h
***************
*** 0 ****
--- 1,60 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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"
+
+ extern Oid CreateTemplate(CreateTemplateStmt *stmt);
+ extern Oid CreateExtensionTemplate(CreateTemplateStmt *stmt);
+ extern Oid CreateExtensionUpdateTemplate(CreateTemplateStmt *stmt);
+
+ extern Oid AlterTemplate(AlterTemplateStmt *stmt);
+ extern Oid AlterExtensionTemplate(AlterTemplateStmt *stmt);
+ extern Oid AlterExtensionUpdateTemplate(AlterTemplateStmt *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_templates(void);
+
+ #endif /* TEMPLATE_H */
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 361,366 **** typedef enum NodeTag
--- 361,368 ----
T_AlterExtensionContentsStmt,
T_CreateEventTrigStmt,
T_AlterEventTrigStmt,
+ T_CreateTemplateStmt,
+ T_AlterTemplateStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1118,1123 **** typedef enum ObjectType
--- 1118,1125 ----
OBJECT_DOMAIN,
OBJECT_EVENT_TRIGGER,
OBJECT_EXTENSION,
+ OBJECT_EXTENSION_TEMPLATE,
+ OBJECT_EXTENSION_UPTMPL,
OBJECT_FDW,
OBJECT_FOREIGN_SERVER,
OBJECT_FOREIGN_TABLE,
***************
*** 1625,1630 **** typedef struct AlterExtensionContentsStmt
--- 1627,1674 ----
List *objargs; /* Arguments if needed (eg, for functions) */
} AlterExtensionContentsStmt;
+ typedef enum TemplateType
+ {
+ TEMPLATE_CREATE_EXTENSION,
+ TEMPLATE_UPDATE_EXTENSION
+ } TemplateType;
+
+ typedef struct CreateTemplateStmt
+ {
+ NodeTag type;
+ TemplateType template;
+ 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 */
+ } CreateTemplateStmt;
+
+ typedef enum AlterTemplateType
+ {
+ AET_SET_DEFAULT,
+ AET_SET_SCRIPT,
+ AET_UPDATE_CONTROL,
+ } AlterTemplateType;
+
+ typedef struct AlterTemplateStmt
+ {
+ NodeTag type;
+ TemplateType template;
+ AlterTemplateType 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? */
+
+ } AlterTemplateStmt;
+
/* ----------------------
* Create/Alter FOREIGN DATA WRAPPER Statements
* ----------------------
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
***************
*** 197,202 **** typedef enum AclObjectKind
--- 197,203 ----
ACL_KIND_FOREIGN_SERVER, /* pg_foreign_server */
ACL_KIND_EVENT_TRIGGER, /* pg_event_trigger */
ACL_KIND_EXTENSION, /* pg_extension */
+ ACL_KIND_TEMPLATE, /* pg_template */
MAX_ACL_KIND /* MUST BE LAST */
} AclObjectKind;
*** /dev/null
--- b/src/test/regress/expected/extension.out
***************
*** 0 ****
--- 1,97 ----
+ -- first create some templates
+ 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 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 a cycle
+ CREATE TEMPLATE
+ FOR EXTENSION PAIR FROM '1.1' TO '1.2'
+ AS
+ $$
+ COMMENT ON EXTENSION pair IS 'Simple 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;
+ -- error, we don't have control settings for 1.2
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.2';
+ ERROR: pg_extension_control for extension "pair" version "1.2" does not exist
+ -- that's accepted
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.1';
+ -- but we don't know how to apply 1.0 -- 1.1 at install yet
+ CREATE EXTENSION pair;
+ ERROR: Missing Extension Template entry for extension "pair" version "1.1"
+ \dx pair
+ List of installed extensions
+ Name | Version | Schema | Description
+ ------+---------+--------+-------------
+ (0 rows)
+
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
***************
*** 104,109 **** SELECT relname, relhasindex
--- 104,112 ----
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
***************
*** 166,172 **** SELECT relname, relhasindex
timetz_tbl | f
tinterval_tbl | f
varchar_tbl | f
! (155 rows)
--
-- another sanity check: every system catalog that has OIDs should have
--- 169,175 ----
timetz_tbl | f
tinterval_tbl | f
varchar_tbl | f
! (158 rows)
--
-- another sanity check: every system catalog that has OIDs should have
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
***************
*** 83,89 **** test: select_into select_distinct select_distinct_on select_implicit select_havi
# ----------
# Another group of parallel tests
# ----------
! test: privileges security_label collate
# ----------
# Another group of parallel tests
--- 83,89 ----
# ----------
# Another group of parallel tests
# ----------
! test: privileges security_label collate extension
# ----------
# Another group of parallel tests
*** /dev/null
--- b/src/test/regress/sql/extension.sql
***************
*** 0 ****
--- 1,73 ----
+ -- first create some templates
+ 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 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 a cycle
+ CREATE TEMPLATE
+ FOR EXTENSION PAIR FROM '1.1' TO '1.2'
+ AS
+ $$
+ COMMENT ON EXTENSION pair IS 'Simple Key Value Text Type';
+ $$;
+
+ CREATE EXTENSION pair;
+
+ \dx pair
+ \dx+ pair
+
+ ALTER EXTENSION pair UPDATE TO '1.2';
+
+ \dx+ pair
+
+ DROP EXTENSION pair;
+
+ -- error, we don't have control settings for 1.2
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.2';
+
+ -- that's accepted
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.1';
+
+ -- but we don't know how to apply 1.0 -- 1.1 at install yet
+ CREATE EXTENSION pair;
+
+ \dx pair
+
+