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