*** a/doc/src/sgml/extend.sgml --- b/doc/src/sgml/extend.sgml *************** *** 350,355 **** --- 350,363 ---- + When an extension only uses SQL definitions (and does not need to ship + compiled binary code, usually from C source), then it can use + the template facility in order to upload the necessary + script to the PostgreSQL server, all using the usual + clients and protocol. + + + The kinds of SQL objects that can be members of an extension are shown in the description of . Notably, objects that are database-cluster-wide, such as databases, roles, and tablespaces, *** a/doc/src/sgml/ref/allfiles.sgml --- b/doc/src/sgml/ref/allfiles.sgml *************** *** 30,35 **** Complete list of usable sgml source files in this directory. --- 30,36 ---- + *************** *** 73,78 **** Complete list of usable sgml source files in this directory. --- 74,80 ---- + *************** *** 112,117 **** Complete list of usable sgml source files in this directory. --- 114,120 ---- + *** /dev/null --- b/doc/src/sgml/ref/alter_extension_template.sgml *************** *** 0 **** --- 1,81 ---- + + + + + ALTER TEMPLATE FOR EXTENSION + 7 + SQL - Language Statements + + + + ALTER TEMPLATE FOR EXTENSION + change the definition of a template for an extension + + + + ALTER TEMPLATE FOR EXTENSION + + + + + ALTER TEMPLATE FOR EXTENSION name SET DEFAULT VERSION version + + + + + Description + + + ALTER TEMPLATE FOR EXTENSION changes the definition of + an extension template. Currently, the only supported functionality is to + change the template's default version. + + + + + Parameters + + + + name + + + The name of an extension that already has templates. + + + + + + version + + + The version of the extension we want to install by default when using + its template. + + + + + + + + + Compatibility + + + There is no ALTER TEMPLATE FOR EXTENSION statement in + the SQL standard. + + + + + See Also + + + + + + + *** /dev/null --- b/doc/src/sgml/ref/create_extension_template.sgml *************** *** 0 **** --- 1,104 ---- + + + + + CREATE TEMPLATE FOR EXTENSION + 7 + SQL - Language Statements + + + + CREATE TEMPLATE FOR EXTENSION + define a new template for extension + + + + CREATE TEMPLATE FOR EXTENSION + + + + + CREATE TEMPLATE FOR EXTENSION name + [ DEFAULT ] VERSION version + [ WITH [ ( + [ control_parameter ] [, ... ] + ) ] ] + AS script + + CREATE TEMPLATE FOR EXTENSION name + FROM old_version TO new_version + [ WITH [ ( + [ control_parameter ] [, ... ] + ) ] ] + AS script + + + where control_parameter is one of: + + SCHEMA schema_name + SUPERUSER + NOSUPERUSER + RELOCATABLE + NORELOCATABLE + REQUIRES requirements + + + + + + Description + + + CREATE TEMPLATE FOR EXTENSION creates a new template + for creating the extension of the same name. It allows tools and users to + upload an extension script and control file without needing to access the + file system of the server which is running + the PostgreSQL service. + + + + Using the CREATE TEMPLATE FOR EXTENSION command you + can upload script to be run at CREATE EXTENSION time + and at ALTER EXTENSION ... UPDATE time. + + + + Refer to for further information. + + + + + Control Parameters + + + For details about the control parameters meaning, please refer + to . + + + + The arguments can appear in any order, not only the one shown above. + + + + + Compatibility + + + There is no + CREATE TEMPLATE FOR EXTENSION statement in the SQL + standard. + + + + + See Also + + + + + + + *** /dev/null --- b/doc/src/sgml/ref/drop_extension_template.sgml *************** *** 0 **** --- 1,81 ---- + + + + + DROP TEMPLATE FOR EXTENSION + 7 + SQL - Language Statements + + + + DROP TEMPLATE FOR EXTENSION + remove a template for an extension + + + + DROP TEMPLATE FOR EXTENSION + + + + + DROP TEMPLATE FOR EXTENSION name VERSION version [ CASCADE | RESTRICT ] + DROP TEMPLATE FOR EXTENSION name FROM old_version TO new_version [ CASCADE | RESTRICT ] + + + + + Description + + + DROP TEMPLATE FOR EXTENSION drops an existing template + for named extension + + + + + Parameters + + + + name + + + The name of an existing text search template. + + + + + + CASCADE + + + Automatically drop objects that depend on the template. + + + + + + RESTRICT + + + Refuse to drop the template if any objects depend on it. This is the + default. + + + + + + + + See Also + + + + + + + + *** a/doc/src/sgml/reference.sgml --- b/doc/src/sgml/reference.sgml *************** *** 58,63 **** --- 58,64 ---- &alterServer; &alterTable; &alterTableSpace; + &alterTemplateForExtension; &alterTSConfig; &alterTSDictionary; &alterTSParser; *************** *** 101,106 **** --- 102,108 ---- &createTable; &createTableAs; &createTableSpace; + &createTemplateForExtension; &createTSConfig; &createTSDictionary; &createTSParser; *************** *** 140,145 **** --- 142,148 ---- &dropServer; &dropTable; &dropTableSpace; + &dropTemplateForExtension; &dropTSConfig; &dropTSDictionary; &dropTSParser; *** a/src/backend/catalog/Makefile --- b/src/backend/catalog/Makefile *************** *** 37,42 **** POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ --- 37,43 ---- pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \ pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \ pg_ts_parser.h pg_ts_template.h pg_extension.h \ + pg_extension_control.h pg_extension_template.h pg_extension_uptmpl.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_foreign_table.h \ pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \ *** a/src/backend/catalog/dependency.c --- b/src/backend/catalog/dependency.c *************** *** 37,42 **** --- 37,45 ---- #include "catalog/pg_depend.h" #include "catalog/pg_event_trigger.h" #include "catalog/pg_extension.h" + #include "catalog/pg_extension_control.h" + #include "catalog/pg_extension_template.h" + #include "catalog/pg_extension_uptmpl.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_language.h" *************** *** 64,69 **** --- 67,73 ---- #include "commands/schemacmds.h" #include "commands/seclabel.h" #include "commands/tablespace.h" + #include "commands/template.h" #include "commands/trigger.h" #include "commands/typecmds.h" #include "foreign/foreign.h" *************** *** 1234,1239 **** doDeletion(const ObjectAddress *object, int flags) --- 1238,1255 ---- RemoveExtensionById(object->objectId); break; + case OCLASS_EXTENSION_CONTROL: + RemoveExtensionControlById(object->objectId); + break; + + case OCLASS_EXTENSION_TEMPLATE: + RemoveExtensionTemplateById(object->objectId); + break; + + case OCLASS_EXTENSION_UPTMPL: + RemoveExtensionUpTmplById(object->objectId); + break; + case OCLASS_EVENT_TRIGGER: RemoveEventTriggerById(object->objectId); break; *************** *** 2295,2300 **** getObjectClass(const ObjectAddress *object) --- 2311,2325 ---- case ExtensionRelationId: return OCLASS_EXTENSION; + case ExtensionControlRelationId: + return OCLASS_EXTENSION_CONTROL; + + case ExtensionTemplateRelationId: + return OCLASS_EXTENSION_TEMPLATE; + + case ExtensionUpTmplRelationId: + return OCLASS_EXTENSION_UPTMPL; + case EventTriggerRelationId: return OCLASS_EVENT_TRIGGER; } *** a/src/backend/catalog/objectaddress.c --- b/src/backend/catalog/objectaddress.c *************** *** 28,33 **** --- 28,36 ---- #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" #include "catalog/pg_extension.h" + #include "catalog/pg_extension_control.h" + #include "catalog/pg_extension_template.h" + #include "catalog/pg_extension_uptmpl.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_language.h" *************** *** 52,57 **** --- 55,61 ---- #include "commands/extension.h" #include "commands/proclang.h" #include "commands/tablespace.h" + #include "commands/template.h" #include "commands/trigger.h" #include "foreign/foreign.h" #include "libpq/be-fsstubs.h" *************** *** 157,162 **** static ObjectPropertyType ObjectProperty[] = --- 161,199 ---- ACL_KIND_EXTENSION }, { + ExtensionControlRelationId, + ExtensionControlOidIndexId, + -1, + -1, + Anum_pg_extension_control_ctlname, + InvalidAttrNumber, /* extension doesn't belong to extnamespace */ + Anum_pg_extension_control_ctlowner, + InvalidAttrNumber, + ACL_KIND_TEMPLATE + }, + { + ExtensionTemplateRelationId, + ExtensionTemplateOidIndexId, + -1, + -1, + Anum_pg_extension_template_tplname, + InvalidAttrNumber, /* extension doesn't belong to extnamespace */ + Anum_pg_extension_template_tplowner, + InvalidAttrNumber, + ACL_KIND_TEMPLATE + }, + { + ExtensionUpTmplRelationId, + ExtensionUpTmplOidIndexId, + -1, + -1, + Anum_pg_extension_uptmpl_uptname, + InvalidAttrNumber, /* extension doesn't belong to extnamespace */ + Anum_pg_extension_uptmpl_uptowner, + InvalidAttrNumber, + ACL_KIND_TEMPLATE + }, + { ForeignDataWrapperRelationId, ForeignDataWrapperOidIndexId, FOREIGNDATAWRAPPEROID, *************** *** 392,397 **** static ObjectAddress get_object_address_type(ObjectType objtype, --- 429,436 ---- List *objname, bool missing_ok); static ObjectAddress get_object_address_opcf(ObjectType objtype, List *objname, List *objargs, bool missing_ok); + static ObjectAddress get_object_address_tmpl(ObjectType objtype, + List *objname, List *objargs, bool missing_ok); static ObjectPropertyType *get_object_property_data(Oid class_id); /* *************** *** 472,477 **** get_object_address(ObjectType objtype, List *objname, List *objargs, --- 511,521 ---- address = get_object_address_unqualified(objtype, objname, missing_ok); break; + case OBJECT_EXTENSION_TEMPLATE: + case OBJECT_EXTENSION_UPTMPL: + address = get_object_address_tmpl(objtype, + objname, objargs, missing_ok); + break; case OBJECT_TYPE: case OBJECT_DOMAIN: address = get_object_address_type(objtype, objname, missing_ok); *************** *** 718,728 **** get_object_address_unqualified(ObjectType objtype, address.objectId = get_extension_oid(name, missing_ok); address.objectSubId = 0; break; - case OBJECT_TABLESPACE: - address.classId = TableSpaceRelationId; - address.objectId = get_tablespace_oid(name, missing_ok); - address.objectSubId = 0; - break; case OBJECT_ROLE: address.classId = AuthIdRelationId; address.objectId = get_role_oid(name, missing_ok); --- 762,767 ---- *************** *** 1059,1064 **** get_object_address_opcf(ObjectType objtype, --- 1098,1179 ---- } /* + * Find the ObjectAddress for an extension template, control or update + * template. + */ + static ObjectAddress + get_object_address_tmpl(ObjectType objtype, + List *objname, List *objargs, bool missing_ok) + { + const char *name; + ObjectAddress address; + + /* + * The types of names handled by this function are not permitted to be + * schema-qualified or catalog-qualified. + */ + if (list_length(objname) != 1) + { + const char *msg; + + switch (objtype) + { + case OBJECT_EXTENSION_TEMPLATE: + msg = gettext_noop("extension template name cannot be qualified"); + break; + case OBJECT_EXTENSION_UPTMPL: + msg = gettext_noop("extension update template name cannot be qualified"); + break; + default: + elog(ERROR, "unrecognized objtype: %d", (int) objtype); + msg = NULL; /* placate compiler */ + } + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("%s", _(msg)))); + } + + name = strVal(linitial(objname)); + + switch (objtype) + { + case OBJECT_EXTENSION_TEMPLATE: + { + const char *version; + + Assert(list_length(objargs) == 1); + version = strVal(linitial(objargs)); + + address.classId = ExtensionTemplateRelationId; + address.objectId = get_template_oid(name, version, missing_ok); + address.objectSubId = 0; + break; + } + case OBJECT_EXTENSION_UPTMPL: + { + const char *from, *to; + + Assert(list_length(objargs) == 2); + + from = strVal(linitial(objargs)); + to = strVal(lsecond(objargs)); + + address.classId = ExtensionUpTmplRelationId; + address.objectId = get_uptmpl_oid(name, from, to, missing_ok); + address.objectSubId = 0; + break; + } + default: + elog(ERROR, "unrecognized objtype: %d", (int) objtype); + /* placate compiler, which doesn't know elog won't return */ + address.classId = InvalidOid; + address.objectId = InvalidOid; + address.objectSubId = 0; + } + return address; + } + + /* * Check ownership of an object previously identified by get_object_address. */ void *************** *** 1122,1127 **** check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address, --- 1237,1248 ---- aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTENSION, NameListToString(objname)); break; + case OBJECT_EXTENSION_TEMPLATE: + case OBJECT_EXTENSION_UPTMPL: + if (!pg_extension_ownercheck(address.objectId, roleid)) + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_TEMPLATE, + NameListToString(objname)); + break; case OBJECT_FDW: if (!pg_foreign_data_wrapper_ownercheck(address.objectId, roleid)) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FDW, *** a/src/backend/commands/Makefile --- b/src/backend/commands/Makefile *************** *** 19,25 **** OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \ portalcmds.o prepare.o proclang.o \ schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \ ! tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \ variable.o view.o include $(top_srcdir)/src/backend/common.mk --- 19,25 ---- indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \ portalcmds.o prepare.o proclang.o \ schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \ ! template.o tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \ variable.o view.o include $(top_srcdir)/src/backend/common.mk *** a/src/backend/commands/event_trigger.c --- b/src/backend/commands/event_trigger.c *************** *** 84,89 **** static event_trigger_support_data event_trigger_support[] = { --- 84,90 ---- { "TEXT SEARCH TEMPLATE", true }, { "TYPE", true }, { "USER MAPPING", true }, + { "TEMPLATE FOR EXTENSION", true }, { "VIEW", true }, { NULL, false } }; *** a/src/backend/commands/extension.c --- b/src/backend/commands/extension.c *************** *** 43,48 **** --- 43,49 ---- #include "commands/comment.h" #include "commands/extension.h" #include "commands/schemacmds.h" + #include "commands/template.h" #include "funcapi.h" #include "mb/pg_wchar.h" #include "miscadmin.h" *************** *** 61,83 **** bool creating_extension = false; Oid CurrentExtensionObject = InvalidOid; /* - * Internal data structure to hold the results of parsing a control file - */ - typedef struct ExtensionControlFile - { - char *name; /* name of the extension */ - char *directory; /* directory for script files */ - char *default_version; /* default install target version, if any */ - char *module_pathname; /* string to substitute for MODULE_PATHNAME */ - char *comment; /* comment, if any */ - char *schema; /* target schema (allowed if !relocatable) */ - bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */ - bool superuser; /* must be superuser to install? */ - int encoding; /* encoding of the script file, or -1 */ - List *requires; /* names of prerequisite extensions */ - } ExtensionControlFile; - - /* * Internal data structure for update path information */ typedef struct ExtensionVersionInfo --- 62,67 ---- *************** *** 96,106 **** static List *find_update_path(List *evi_list, ExtensionVersionInfo *evi_start, ExtensionVersionInfo *evi_target, bool reinitialize); ! static void get_available_versions_for_extension(ExtensionControlFile *pcontrol, Tuplestorestate *tupstore, TupleDesc tupdesc); static void ApplyExtensionUpdates(Oid extensionOid, ! ExtensionControlFile *pcontrol, const char *initialVersion, List *updateVersions); --- 80,90 ---- ExtensionVersionInfo *evi_start, ExtensionVersionInfo *evi_target, bool reinitialize); ! static void get_available_versions_for_extension(ExtensionControl *pcontrol, Tuplestorestate *tupstore, TupleDesc tupdesc); static void ApplyExtensionUpdates(Oid extensionOid, ! ExtensionControl *pcontrol, const char *initialVersion, List *updateVersions); *************** *** 232,238 **** get_extension_schema(Oid ext_oid) /* * Utility functions to check validity of extension and version names */ ! static void check_valid_extension_name(const char *extensionname) { int namelen = strlen(extensionname); --- 216,222 ---- /* * Utility functions to check validity of extension and version names */ ! void check_valid_extension_name(const char *extensionname) { int namelen = strlen(extensionname); *************** *** 370,376 **** get_extension_control_filename(const char *extname) } static char * ! get_extension_script_directory(ExtensionControlFile *control) { char sharepath[MAXPGPATH]; char *result; --- 354,360 ---- } static char * ! get_extension_script_directory(ExtensionControl *control) { char sharepath[MAXPGPATH]; char *result; *************** *** 393,399 **** get_extension_script_directory(ExtensionControlFile *control) } static char * ! get_extension_aux_control_filename(ExtensionControlFile *control, const char *version) { char *result; --- 377,383 ---- } static char * ! get_extension_aux_control_filename(ExtensionControl *control, const char *version) { char *result; *************** *** 411,417 **** get_extension_aux_control_filename(ExtensionControlFile *control, } static char * ! get_extension_script_filename(ExtensionControlFile *control, const char *from_version, const char *version) { char *result; --- 395,401 ---- } static char * ! get_extension_script_filename(ExtensionControl *control, const char *from_version, const char *version) { char *result; *************** *** 443,449 **** get_extension_script_filename(ExtensionControlFile *control, * worry about what encoding it's in; all values are expected to be ASCII. */ static void ! parse_extension_control_file(ExtensionControlFile *control, const char *version) { char *filename; --- 427,433 ---- * worry about what encoding it's in; all values are expected to be ASCII. */ static void ! parse_extension_control_file(ExtensionControl *control, const char *version) { char *filename; *************** *** 483,489 **** parse_extension_control_file(ExtensionControlFile *control, FreeFile(file); /* ! * Convert the ConfigVariable list into ExtensionControlFile entries. */ for (item = head; item != NULL; item = item->next) { --- 467,473 ---- FreeFile(file); /* ! * Convert the ConfigVariable list into ExtensionControl entries. */ for (item = head; item != NULL; item = item->next) { *************** *** 579,594 **** parse_extension_control_file(ExtensionControlFile *control, /* * Read the primary control file for the specified extension. */ ! static ExtensionControlFile * read_extension_control_file(const char *extname) { ! ExtensionControlFile *control; /* * Set up default values. Pointer fields are initially null. */ ! control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile)); control->name = pstrdup(extname); control->relocatable = false; control->superuser = true; control->encoding = -1; --- 563,579 ---- /* * Read the primary control file for the specified extension. */ ! ExtensionControl * read_extension_control_file(const char *extname) { ! ExtensionControl *control; /* * Set up default values. Pointer fields are initially null. */ ! control = (ExtensionControl *) palloc0(sizeof(ExtensionControl)); control->name = pstrdup(extname); + control->is_template = false; control->relocatable = false; control->superuser = true; control->encoding = -1; *************** *** 604,623 **** read_extension_control_file(const char *extname) /* * Read the auxiliary control file for the specified extension and version. * ! * Returns a new modified ExtensionControlFile struct; the original struct * (reflecting just the primary control file) is not modified. */ ! static ExtensionControlFile * ! read_extension_aux_control_file(const ExtensionControlFile *pcontrol, const char *version) { ! ExtensionControlFile *acontrol; /* * Flat-copy the struct. Pointer fields share values with original. */ ! acontrol = (ExtensionControlFile *) palloc(sizeof(ExtensionControlFile)); ! memcpy(acontrol, pcontrol, sizeof(ExtensionControlFile)); /* * Parse the auxiliary control file, overwriting struct fields --- 589,608 ---- /* * Read the auxiliary control file for the specified extension and version. * ! * Returns a new modified ExtensionControl struct; the original struct * (reflecting just the primary control file) is not modified. */ ! static ExtensionControl * ! read_extension_aux_control_file(const ExtensionControl *pcontrol, const char *version) { ! ExtensionControl *acontrol; /* * Flat-copy the struct. Pointer fields share values with original. */ ! acontrol = (ExtensionControl *) palloc(sizeof(ExtensionControl)); ! memcpy(acontrol, pcontrol, sizeof(ExtensionControl)); /* * Parse the auxiliary control file, overwriting struct fields *************** *** 628,637 **** read_extension_aux_control_file(const ExtensionControlFile *pcontrol, } /* * Read an SQL script file into a string, and convert to database encoding */ static char * ! read_extension_script_file(const ExtensionControlFile *control, const char *filename) { int src_encoding; --- 613,687 ---- } /* + * Read the control properties for given extension, either from a file on the + * dile system or if it does not exists there, from a template catalog in + * pg_extension_control, if it exists. + * + * In the file system case, we get the default properties for the extension and + * one of them is the default_version property that allows us to know which + * version to install. Knowing that we can then read the right auxilliary + * control file to override some defaults if needs be. + * + * When reading from the catalogs, we have in pg_extension_control at most a + * row per version, with the whole set of properties we need to apply. So once + * we found the current default version to install, we don't need to read and + * another set of properties and override them. + * + * In both cases we return the structure ExtensionControl, which maybe + * should get renamed now. + */ + static ExtensionControl * + read_extension_control(const char *extname) + { + char *filename; + + filename = get_extension_control_filename(extname); + + if (access(filename, F_OK) == -1 && errno == ENOENT) + { + /* ENOENT: let's look at the control templates */ + return find_default_pg_extension_control(extname, false); + } + else + /* we let the file specific routines deal with any other error */ + return read_extension_control_file(extname); + } + + static ExtensionControl * + read_extension_aux_control(const ExtensionControl *pcontrol, + const char *version) + { + if (pcontrol->is_template) + { + /* we might already have read the right version */ + if (strcmp(pcontrol->default_version, version) != 0) + { + ExtensionControl *control; + /* + * While read_extension_aux_control() override pcontrol with the + * auxilliary control file properties, in the case when we read + * from the catalogs, the overriding has been done already at + * CREATE TEMPLATE time, so we only need to load a single row from + * pg_extension_control at any time. + */ + control = find_pg_extension_control(pcontrol->name, version, true); + + return control ? control : (ExtensionControl *)pcontrol; + } + else + /* pcontrol is the control file for the right version. */ + return (ExtensionControl *)pcontrol; + } + else + /* read ExtensionControl from files */ + return read_extension_aux_control_file(pcontrol, version); + } + + /* * Read an SQL script file into a string, and convert to database encoding */ static char * ! read_extension_script_file(const ExtensionControl *control, const char *filename) { int src_encoding; *************** *** 674,681 **** read_extension_script_file(const ExtensionControlFile *control, /* * Execute given SQL string. * - * filename is used only to report errors. - * * Note: it's tempting to just use SPI to execute the string, but that does * not work very well. The really serious problem is that SPI will parse, * analyze, and plan the whole string before executing any of it; of course --- 724,729 ---- *************** *** 685,691 **** read_extension_script_file(const ExtensionControlFile *control, * could be very long. */ static void ! execute_sql_string(const char *sql, const char *filename) { List *raw_parsetree_list; DestReceiver *dest; --- 733,739 ---- * could be very long. */ static void ! execute_sql_string(const char *sql) { List *raw_parsetree_list; DestReceiver *dest; *************** *** 770,782 **** execute_sql_string(const char *sql, const char *filename) * If from_version isn't NULL, it's an update */ static void ! execute_extension_script(Oid extensionOid, ExtensionControlFile *control, const char *from_version, const char *version, List *requiredSchemas, const char *schemaName, Oid schemaOid) { - char *filename; int save_nestlevel; StringInfoData pathbuf; ListCell *lc; --- 818,829 ---- * If from_version isn't NULL, it's an update */ static void ! execute_extension_script(Oid extensionOid, ExtensionControl *control, const char *from_version, const char *version, List *requiredSchemas, const char *schemaName, Oid schemaOid) { int save_nestlevel; StringInfoData pathbuf; ListCell *lc; *************** *** 802,809 **** execute_extension_script(Oid extensionOid, ExtensionControlFile *control, errhint("Must be superuser to update this extension."))); } - filename = get_extension_script_filename(control, from_version, version); - /* * Force client_min_messages and log_min_messages to be at least WARNING, * so that we won't spam the user with useless NOTICE messages from common --- 849,854 ---- *************** *** 858,865 **** execute_extension_script(Oid extensionOid, ExtensionControlFile *control, CurrentExtensionObject = extensionOid; PG_TRY(); { ! char *c_sql = read_extension_script_file(control, filename); ! Datum t_sql; /* We use various functions that want to operate on text datums */ t_sql = CStringGetTextDatum(c_sql); --- 903,925 ---- CurrentExtensionObject = extensionOid; PG_TRY(); { ! char *c_sql; ! Datum t_sql; ! ! if (control->is_template) ! { ! c_sql = read_extension_template_script(control->name, ! from_version, ! version); ! } ! else ! { ! char *filename = get_extension_script_filename(control, ! from_version, ! version); ! ! c_sql = read_extension_script_file(control, filename); ! } /* We use various functions that want to operate on text datums */ t_sql = CStringGetTextDatum(c_sql); *************** *** 908,914 **** execute_extension_script(Oid extensionOid, ExtensionControlFile *control, /* And now back to C string */ c_sql = text_to_cstring(DatumGetTextPP(t_sql)); ! execute_sql_string(c_sql, filename); } PG_CATCH(); { --- 968,974 ---- /* And now back to C string */ c_sql = text_to_cstring(DatumGetTextPP(t_sql)); ! execute_sql_string(c_sql); } PG_CATCH(); { *************** *** 997,1003 **** get_nearest_unprocessed_vertex(List *evi_list) * the versions that can be reached in one step from that version. */ static List * ! get_ext_ver_list(ExtensionControlFile *control) { List *evi_list = NIL; int extnamelen = strlen(control->name); --- 1057,1063 ---- * the versions that can be reached in one step from that version. */ static List * ! get_ext_ver_list_from_files(ExtensionControl *control) { List *evi_list = NIL; int extnamelen = strlen(control->name); *************** *** 1053,1058 **** get_ext_ver_list(ExtensionControlFile *control) --- 1113,1170 ---- } /* + * We scan pg_extension_template for all install scripts of given extension, + * then pg_extension_uptmpl for all update scripts of same extension. + */ + static List * + get_ext_ver_list_from_catalogs(ExtensionControl *control) + { + List *evi_list = NIL; + List *installable, *direct_update_paths; + ListCell *lc; + + /* pg_extension_template contains install scripts */ + installable = list_pg_extension_template_versions(control->name); + + foreach(lc, installable) + { + ExtensionVersionInfo *evi; + char *vername = (char *) lfirst(lc); + + evi = get_ext_ver_info(vername, &evi_list); + } + + /* pg_extension_uptmpl contains upgrade scripts */ + direct_update_paths = list_pg_extension_update_versions(control->name); + + foreach(lc, direct_update_paths) + { + ExtensionVersionInfo *evi, *evi2; + char *vername = (char *) linitial(lfirst(lc)); + char *vername2 = (char *) lsecond(lfirst(lc)); + + evi = get_ext_ver_info(vername, &evi_list); + evi2 = get_ext_ver_info(vername2, &evi_list); + evi->reachable = lappend(evi->reachable, evi2); + } + return evi_list; + } + + /* + * We have to implement that function twice. The first implementation deals + * with control files and sql scripts on the file system while the second one + * deals with the catalogs pg_extension_template and pg_extension_uptmpl. + */ + static List * + get_ext_ver_list(ExtensionControl *control) + { + if (control->is_template) + return get_ext_ver_list_from_catalogs(control); + else + return get_ext_ver_list_from_files(control); + } + + /* * Given an initial and final version name, identify the sequence of update * scripts that have to be applied to perform that update. * *************** *** 1060,1066 **** get_ext_ver_list(ExtensionControlFile *control) * version is *not* included). */ static List * ! identify_update_path(ExtensionControlFile *control, const char *oldVersion, const char *newVersion) { List *result; --- 1172,1178 ---- * version is *not* included). */ static List * ! identify_update_path(ExtensionControl *control, const char *oldVersion, const char *newVersion) { List *result; *************** *** 1185,1192 **** CreateExtension(CreateExtensionStmt *stmt) char *versionName; char *oldVersionName; Oid extowner = GetUserId(); ! ExtensionControlFile *pcontrol; ! ExtensionControlFile *control; List *updateVersions; List *requiredExtensions; List *requiredSchemas; --- 1297,1304 ---- char *versionName; char *oldVersionName; Oid extowner = GetUserId(); ! ExtensionControl *pcontrol; ! ExtensionControl *control; List *updateVersions; List *requiredExtensions; List *requiredSchemas; *************** *** 1233,1239 **** CreateExtension(CreateExtensionStmt *stmt) * any non-ASCII data, so there is no need to worry about encoding at this * point. */ ! pcontrol = read_extension_control_file(stmt->extname); /* * Read the statement option list --- 1345,1351 ---- * any non-ASCII data, so there is no need to worry about encoding at this * point. */ ! pcontrol = read_extension_control(stmt->extname); /* * Read the statement option list *************** *** 1272,1277 **** CreateExtension(CreateExtensionStmt *stmt) --- 1384,1394 ---- /* * Determine the version to install + * + * Note that in the case when we install an extension from a template, and + * when the target version to install is given in the SQL command, we could + * arrange the code to only scan pg_extension_control once: there's no need + * to read any primary control row in that case. There's no harm doing so. */ if (d_new_version && d_new_version->arg) versionName = strVal(d_new_version->arg); *************** *** 1334,1340 **** CreateExtension(CreateExtensionStmt *stmt) /* * Fetch control parameters for installation target version */ ! control = read_extension_aux_control_file(pcontrol, versionName); /* * Determine the target schema to install the extension into --- 1451,1457 ---- /* * Fetch control parameters for installation target version */ ! control = read_extension_aux_control(pcontrol, versionName); /* * Determine the target schema to install the extension into *************** *** 1632,1644 **** Datum pg_available_extensions(PG_FUNCTION_ARGS) { ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; ! TupleDesc tupdesc; ! Tuplestorestate *tupstore; ! MemoryContext per_query_ctx; ! MemoryContext oldcontext; ! char *location; ! DIR *dir; ! struct dirent *de; /* check to see if caller supports us returning a tuplestore */ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) --- 1749,1763 ---- pg_available_extensions(PG_FUNCTION_ARGS) { ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; ! TupleDesc tupdesc; ! Tuplestorestate *tupstore; ! MemoryContext per_query_ctx; ! MemoryContext oldcontext; ! char *location; ! DIR *dir; ! struct dirent *de; ! List *templates; ! ListCell *lc; /* check to see if caller supports us returning a tuplestore */ if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo)) *************** *** 1681,1687 **** pg_available_extensions(PG_FUNCTION_ARGS) { while ((de = ReadDir(dir, location)) != NULL) { ! ExtensionControlFile *control; char *extname; Datum values[3]; bool nulls[3]; --- 1800,1806 ---- { while ((de = ReadDir(dir, location)) != NULL) { ! ExtensionControl *control; char *extname; Datum values[3]; bool nulls[3]; *************** *** 1722,1727 **** pg_available_extensions(PG_FUNCTION_ARGS) --- 1841,1869 ---- FreeDir(dir); } + /* add in the extension we can install from a template */ + templates = pg_extension_default_controls(); + + foreach(lc, templates) + { + char *name = (char *)linitial(lfirst(lc)); + char *vers = (char *)lsecond(lfirst(lc)); + Datum values[3]; + bool nulls[3]; + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + /* name */ + values[0] = DirectFunctionCall1(namein, CStringGetDatum(name)); + /* default_version */ + values[1] = CStringGetTextDatum(vers); + /* comment */ + nulls[2] = true; + + tuplestore_putvalues(tupstore, tupdesc, values, nulls); + } + /* clean up and return the tuplestore */ tuplestore_donestoring(tupstore); *************** *** 1790,1796 **** pg_available_extension_versions(PG_FUNCTION_ARGS) { while ((de = ReadDir(dir, location)) != NULL) { ! ExtensionControlFile *control; char *extname; if (!is_extension_control_filename(de->d_name)) --- 1932,1938 ---- { while ((de = ReadDir(dir, location)) != NULL) { ! ExtensionControl *control; char *extname; if (!is_extension_control_filename(de->d_name)) *************** *** 1825,1831 **** pg_available_extension_versions(PG_FUNCTION_ARGS) * read versions of one extension, add rows to tupstore */ static void ! get_available_versions_for_extension(ExtensionControlFile *pcontrol, Tuplestorestate *tupstore, TupleDesc tupdesc) { --- 1967,1973 ---- * read versions of one extension, add rows to tupstore */ static void ! get_available_versions_for_extension(ExtensionControl *pcontrol, Tuplestorestate *tupstore, TupleDesc tupdesc) { *************** *** 1839,1845 **** get_available_versions_for_extension(ExtensionControlFile *pcontrol, /* Note this will fail if script directory doesn't exist */ while ((de = ReadDir(dir, location)) != NULL) { ! ExtensionControlFile *control; char *vername; Datum values[7]; bool nulls[7]; --- 1981,1987 ---- /* Note this will fail if script directory doesn't exist */ while ((de = ReadDir(dir, location)) != NULL) { ! ExtensionControl *control; char *vername; Datum values[7]; bool nulls[7]; *************** *** 1936,1942 **** pg_extension_update_paths(PG_FUNCTION_ARGS) MemoryContext per_query_ctx; MemoryContext oldcontext; List *evi_list; ! ExtensionControlFile *control; ListCell *lc1; /* Check extension name validity before any filesystem access */ --- 2078,2084 ---- MemoryContext per_query_ctx; MemoryContext oldcontext; List *evi_list; ! ExtensionControl *control; ListCell *lc1; /* Check extension name validity before any filesystem access */ *************** *** 1969,1975 **** pg_extension_update_paths(PG_FUNCTION_ARGS) MemoryContextSwitchTo(oldcontext); /* Read the extension's control file */ ! control = read_extension_control_file(NameStr(*extname)); /* Extract the version update graph from the script directory */ evi_list = get_ext_ver_list(control); --- 2111,2117 ---- MemoryContextSwitchTo(oldcontext); /* Read the extension's control file */ ! control = read_extension_control(NameStr(*extname)); /* Extract the version update graph from the script directory */ evi_list = get_ext_ver_list(control); *************** *** 2586,2592 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt) DefElem *d_new_version = NULL; char *versionName; char *oldVersionName; ! ExtensionControlFile *control; Oid extensionOid; Relation extRel; ScanKeyData key[1]; --- 2728,2734 ---- DefElem *d_new_version = NULL; char *versionName; char *oldVersionName; ! ExtensionControl *control; Oid extensionOid; Relation extRel; ScanKeyData key[1]; *************** *** 2652,2658 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt) * any non-ASCII data, so there is no need to worry about encoding at this * point. */ ! control = read_extension_control_file(stmt->extname); /* * Read the statement option list --- 2794,2800 ---- * any non-ASCII data, so there is no need to worry about encoding at this * point. */ ! control = read_extension_control(stmt->extname); /* * Read the statement option list *************** *** 2727,2733 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt) */ static void ApplyExtensionUpdates(Oid extensionOid, ! ExtensionControlFile *pcontrol, const char *initialVersion, List *updateVersions) { --- 2869,2875 ---- */ static void ApplyExtensionUpdates(Oid extensionOid, ! ExtensionControl *pcontrol, const char *initialVersion, List *updateVersions) { *************** *** 2737,2743 **** ApplyExtensionUpdates(Oid extensionOid, foreach(lcv, updateVersions) { char *versionName = (char *) lfirst(lcv); ! ExtensionControlFile *control; char *schemaName; Oid schemaOid; List *requiredExtensions; --- 2879,2885 ---- foreach(lcv, updateVersions) { char *versionName = (char *) lfirst(lcv); ! ExtensionControl *control; char *schemaName; Oid schemaOid; List *requiredExtensions; *************** *** 2756,2762 **** ApplyExtensionUpdates(Oid extensionOid, /* * Fetch parameters for specific version (pcontrol is not changed) */ ! control = read_extension_aux_control_file(pcontrol, versionName); /* Find the pg_extension tuple */ extRel = heap_open(ExtensionRelationId, RowExclusiveLock); --- 2898,2904 ---- /* * Fetch parameters for specific version (pcontrol is not changed) */ ! control = read_extension_aux_control(pcontrol, versionName); /* Find the pg_extension tuple */ extRel = heap_open(ExtensionRelationId, RowExclusiveLock); *** /dev/null --- b/src/backend/commands/template.c *************** *** 0 **** --- 1,1536 ---- + /*------------------------------------------------------------------------- + * + * template.c + * Commands to manipulate templates + * + * Extension Templates in PostgreSQL allow creation of Extension from the + * protocol only. + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/template.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include "access/heapam.h" + #include "access/htup_details.h" + #include "access/sysattr.h" + #include "access/xact.h" + #include "catalog/dependency.h" + #include "catalog/indexing.h" + #include "catalog/namespace.h" + #include "catalog/objectaccess.h" + #include "catalog/pg_depend.h" + #include "catalog/pg_extension.h" + #include "catalog/pg_extension_control.h" + #include "catalog/pg_extension_template.h" + #include "catalog/pg_extension_uptmpl.h" + #include "catalog/pg_namespace.h" + #include "catalog/pg_type.h" + #include "commands/extension.h" + #include "commands/template.h" + #include "funcapi.h" + #include "miscadmin.h" + #include "tcop/utility.h" + #include "utils/builtins.h" + #include "utils/fmgroids.h" + #include "utils/lsyscache.h" + #include "utils/rel.h" + #include "utils/snapmgr.h" + #include "utils/tqual.h" + + static Oid InsertExtensionControlTuple(Oid owner, + ExtensionControl *control, + const char *version); + + static Oid InsertExtensionTemplateTuple(Oid owner, + ExtensionControl *control, + const char *version, + const char *script); + + static Oid InsertExtensionUpTmplTuple(Oid owner, + const char *extname, + ExtensionControl *control, + const char *from, + const char *to, + const char *script); + + static Oid AlterTemplateSetDefault(const char *extname, const char *version); + static Oid modify_pg_extension_control_default(const char *extname, + const char *version, + bool value); + + static ExtensionControl *read_pg_extension_control(const char *extname, + Relation rel, + HeapTuple tuple); + + + /* + * The grammar accumulates control properties into a DefElem list that we have + * to process in multiple places. + */ + static void + parse_statement_control_defelems(ExtensionControl *control, List *defelems) + { + ListCell *lc; + DefElem *d_schema = NULL; + DefElem *d_superuser = NULL; + DefElem *d_relocatable = NULL; + DefElem *d_requires = NULL; + + /* + * Read the statement option list + */ + foreach(lc, defelems) + { + DefElem *defel = (DefElem *) lfirst(lc); + + if (strcmp(defel->defname, "schema") == 0) + { + if (d_schema) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + d_schema = defel; + + control->schema = strVal(d_schema->arg); + } + else if (strcmp(defel->defname, "superuser") == 0) + { + if (d_superuser) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + d_superuser = defel; + + control->superuser = intVal(d_superuser->arg) != 0; + } + else if (strcmp(defel->defname, "relocatable") == 0) + { + if (d_relocatable) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + d_relocatable = defel; + + control->relocatable = intVal(d_relocatable->arg) != 0; + } + else if (strcmp(defel->defname, "requires") == 0) + { + if (d_requires) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("conflicting or redundant options"))); + d_requires = defel; + + if (!SplitIdentifierString(pstrdup(strVal(d_requires->arg)), + ',', + &control->requires)) + { + /* syntax error in name list */ + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("parameter \"requires\" must be a list of extension names"))); + } + } + else + elog(ERROR, "unrecognized option: %s", defel->defname); + } + } + + /* + * CREATE TEMPLATE FOR EXTENSION + * + * Routing function, the statement can be either about a template for creating + * an extension or a template for updating and extension. + */ + Oid + CreateTemplate(CreateTemplateStmt *stmt) + { + switch (stmt->template) + { + case TEMPLATE_CREATE_EXTENSION: + return CreateExtensionTemplate(stmt); + + case TEMPLATE_UPDATE_EXTENSION: + return CreateExtensionUpdateTemplate(stmt); + } + /* keep compiler happy */ + return InvalidOid; + } + + /* + * CREATE TEMPLATE FOR EXTENSION + * + * Create a template for an extension's given version. + */ + Oid + CreateExtensionTemplate(CreateTemplateStmt *stmt) + { + Oid extTemplateOid; + Oid owner = GetUserId(); + ExtensionControl *control; + + /* Check extension name validity before any filesystem access */ + check_valid_extension_name(stmt->extname); + + /* + * Check for duplicate extension name in the pg_extension catalogs. Any + * extension that already is known in the catalogs needs no template for + * creating it in the first place. + */ + if (get_extension_oid(stmt->extname, true) != InvalidOid) + { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("extension \"%s\" already exists", + stmt->extname))); + } + + /* + * Check for duplicate template for given extension and version. The unique + * index on pg_extension_template(extname, version) would catch this + * anyway, and serves as a backstop in case of race conditions; but this is + * a friendlier error message, and besides we need a check to support IF + * NOT EXISTS. + */ + if (get_template_oid(stmt->extname, stmt->version, true) != InvalidOid) + { + if (stmt->if_not_exists) + { + ereport(NOTICE, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("template for extension \"%s\" version \"%s\" already exists, skipping", + stmt->extname, stmt->version))); + return InvalidOid; + } + else + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("template for extension \"%s\" version \"%s\" already exists", + stmt->extname, stmt->version))); + } + + /* + * Check that no control file of the same extension's name is already + * available on disk, as a friendliness service to our users. Between + * CREATE TEMPLATE FOR EXTENSION and CREATE EXTENSION time, some new file + * might have been added to the file-system and would then be prefered, but + * at least we tried to be as nice as we possibly can. + */ + PG_TRY(); + { + control = read_extension_control_file(stmt->extname); + } + PG_CATCH(); + { + /* no control file found is good news for us */ + control = NULL; + } + PG_END_TRY(); + + if (control) + { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("extension \"%s\" is already available", + stmt->extname))); + } + + /* Now read the control properties from the statement */ + control = (ExtensionControl *) palloc0(sizeof(ExtensionControl)); + control->name = pstrdup(stmt->extname); + parse_statement_control_defelems(control, stmt->control); + + /* + * Check that there's no other pg_extension_control row already claiming to + * be the default for this extension, when the statement claims to be the + * default. + */ + if (stmt->default_version) + { + ExtensionControl *default_version = + find_default_pg_extension_control(control->name, true); + + if (default_version) + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("extension \"%s\" already has a default control template", + control->name), + errdetail("default version is \"%s\"", + default_version->default_version))); + + /* no pre-existing */ + control->default_version = pstrdup(stmt->version); + } + + extTemplateOid = InsertExtensionTemplateTuple(owner, + control, + stmt->version, + stmt->script); + + /* Check that we have a default version target now */ + CommandCounterIncrement(); + find_default_pg_extension_control(stmt->extname, false); + + return extTemplateOid; + } + + /* + * CREATE TEMPLATE FOR UPDATE OF EXTENSION + */ + Oid + CreateExtensionUpdateTemplate(CreateTemplateStmt *stmt) + { + Oid owner = GetUserId(); + ExtensionControl *control; + + /* Check extension name validity before any filesystem access */ + check_valid_extension_name(stmt->extname); + + /* + * Check that a template for installing extension already exists in the + * catalogs. Do not enforce that we have a complete path upgrade path at + * template creation time, that will get checked at CREATE EXTENSION time. + */ + (void) can_create_extension_from_template(stmt->extname, false); + + /* + * Check for duplicate template for given extension and versions. The + * unique index on pg_extension_uptmpl(uptname, uptfrom, uptto) would catch + * this anyway, and serves as a backstop in case of race conditions; but + * this is a friendlier error message, and besides we need a check to + * support IF NOT EXISTS. + */ + if (get_uptmpl_oid(stmt->extname, stmt->from, stmt->to, true) != InvalidOid) + { + if (stmt->if_not_exists) + { + ereport(NOTICE, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("template for extension \"%s\" update from version \"%s\" to version \"%s\" already exists, skipping", + stmt->extname, stmt->from, stmt->to))); + return InvalidOid; + } + else + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("template for extension \"%s\" update from version \"%s\" to version \"%s\" already exists", + stmt->extname, stmt->from, stmt->to))); + } + + /* + * Check that no control file of the same extension's name is already + * available on disk, as a friendliness service to our users. Between + * CREATE TEMPLATE FOR EXTENSION and CREATE EXTENSION time, some new file + * might have been added to the file-system and would then be prefered, but + * at least we tried to be as nice as we possibly can. + */ + PG_TRY(); + { + control = read_extension_control_file(stmt->extname); + } + PG_CATCH(); + { + /* no control file found is good news for us */ + control = NULL; + } + PG_END_TRY(); + + if (control) + { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("extension \"%s\" is already available", + stmt->extname))); + } + + /* Now read the (optional) control properties from the statement */ + if (stmt->control) + { + control = (ExtensionControl *) palloc0(sizeof(ExtensionControl)); + control->name = pstrdup(stmt->extname); + + parse_statement_control_defelems(control, stmt->control); + } + + return InsertExtensionUpTmplTuple(owner, stmt->extname, control, + stmt->from, stmt->to, stmt->script); + } + + /* + * InsertExtensionControlTuple + * + * Insert the new pg_extension_control row and register its dependency to its + * owner. Return the OID assigned to the new row. + */ + static Oid + InsertExtensionControlTuple(Oid owner, + ExtensionControl *control, + const char *version) + { + Oid extControlOid; + Relation rel; + Datum values[Natts_pg_extension_control]; + bool nulls[Natts_pg_extension_control]; + HeapTuple tuple; + ObjectAddress myself; + + /* + * Build and insert the pg_extension_control tuple + */ + rel = heap_open(ExtensionControlRelationId, RowExclusiveLock); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + values[Anum_pg_extension_control_ctlname - 1] = + DirectFunctionCall1(namein, CStringGetDatum(control->name)); + + values[Anum_pg_extension_control_ctlowner - 1] = + ObjectIdGetDatum(owner); + + values[Anum_pg_extension_control_ctlrelocatable - 1] = + BoolGetDatum(control->relocatable); + + values[Anum_pg_extension_control_ctlsuperuser - 1] = + BoolGetDatum(control->superuser); + + if (control->schema == NULL) + nulls[Anum_pg_extension_control_ctlnamespace - 1] = true; + else + values[Anum_pg_extension_control_ctlnamespace - 1] = + DirectFunctionCall1(namein, CStringGetDatum((control->schema))); + + values[Anum_pg_extension_control_ctlversion - 1] = + CStringGetTextDatum(version); + + /* + * We only register that this pg_extension_control row is the default for + * the given extension. Necessary controls must have been made before. + */ + if (control->default_version == NULL) + values[Anum_pg_extension_control_ctldefault - 1] = false; + else + values[Anum_pg_extension_control_ctldefault - 1] = true; + + if (control->requires == NULL) + nulls[Anum_pg_extension_control_ctlrequires - 1] = true; + else + { + Datum *datums; + int ndatums; + ArrayType *a; + ListCell *lc; + + ndatums = list_length(control->requires); + datums = (Datum *) palloc(ndatums * sizeof(Datum)); + ndatums = 0; + foreach(lc, control->requires) + { + char *curreq = (char *) lfirst(lc); + + datums[ndatums++] = + DirectFunctionCall1(namein, CStringGetDatum(curreq)); + } + a = construct_array(datums, ndatums, + NAMEOID, + NAMEDATALEN, false, 'c'); + + values[Anum_pg_extension_control_ctlrequires - 1] = + PointerGetDatum(a); + } + + tuple = heap_form_tuple(rel->rd_att, values, nulls); + + extControlOid = simple_heap_insert(rel, tuple); + CatalogUpdateIndexes(rel, tuple); + + heap_freetuple(tuple); + heap_close(rel, RowExclusiveLock); + + /* + * Record dependencies on owner only. + * + * When we create the extension template and control file, the target + * extension, its schema and requirements usually do not exist in the + * database. Don't even think about registering a dependency from the + * template. + */ + recordDependencyOnOwner(ExtensionControlRelationId, extControlOid, owner); + + myself.classId = ExtensionControlRelationId; + myself.objectId = extControlOid; + myself.objectSubId = 0; + + /* Post creation hook for new extension control */ + InvokeObjectAccessHook(OAT_POST_CREATE, + ExtensionControlRelationId, extControlOid, 0, NULL); + + return extControlOid; + } + + /* + * InsertExtensionTemplateTuple + * + * Insert the new pg_extension_template row and register its dependencies. + * Return the OID assigned to the new row. + */ + static Oid + InsertExtensionTemplateTuple(Oid owner, ExtensionControl *control, + const char *version, const char *script) + { + Oid extControlOid, extTemplateOid; + Relation rel; + Datum values[Natts_pg_extension_template]; + bool nulls[Natts_pg_extension_template]; + HeapTuple tuple; + ObjectAddress myself, ctrl; + + /* First create the companion extension control entry */ + extControlOid = InsertExtensionControlTuple(owner, control, version); + + /* + * Build and insert the pg_extension_template tuple + */ + rel = heap_open(ExtensionTemplateRelationId, RowExclusiveLock); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + values[Anum_pg_extension_template_tplname - 1] = + DirectFunctionCall1(namein, CStringGetDatum(control->name)); + + values[Anum_pg_extension_template_tplowner - 1] = + ObjectIdGetDatum(owner); + + values[Anum_pg_extension_template_tplversion - 1] = + CStringGetTextDatum(version); + + values[Anum_pg_extension_template_tplscript - 1] = + CStringGetTextDatum(script); + + tuple = heap_form_tuple(rel->rd_att, values, nulls); + + extTemplateOid = simple_heap_insert(rel, tuple); + CatalogUpdateIndexes(rel, tuple); + + heap_freetuple(tuple); + heap_close(rel, RowExclusiveLock); + + /* + * Record dependencies on owner only. + * + * When we create the extension template and control file, the target + * extension, its schema and requirements usually do not exist in the + * database. Don't even think about registering a dependency from the + * template. + */ + recordDependencyOnOwner(ExtensionTemplateRelationId, extTemplateOid, owner); + + myself.classId = ExtensionTemplateRelationId; + myself.objectId = extTemplateOid; + myself.objectSubId = 0; + + /* record he dependency between the control row and the template row */ + ctrl.classId = ExtensionControlRelationId; + ctrl.objectId = extControlOid; + ctrl.objectSubId = 0; + + recordDependencyOn(&ctrl, &myself, DEPENDENCY_INTERNAL); + + /* Post creation hook for new extension control */ + InvokeObjectAccessHook(OAT_POST_CREATE, + ExtensionTemplateRelationId, extTemplateOid, 0, NULL); + + return extTemplateOid; + } + + /* + * InsertExtensionUpTmplTuple + * + * Insert the new pg_extension_uptmpl row and register its dependencies. + * Return the OID assigned to the new row. + */ + static Oid + InsertExtensionUpTmplTuple(Oid owner, + const char *extname, + ExtensionControl *control, + const char *from, + const char *to, + const char *script) + { + Oid extControlOid, extUpTmplOid; + Relation rel; + Datum values[Natts_pg_extension_uptmpl]; + bool nulls[Natts_pg_extension_uptmpl]; + HeapTuple tuple; + ObjectAddress myself; + + /* + * First create the companion extension control entry, if any. In the case + * of an Update Template the comanion control entry is somilar in scope to + * a secondary control file, and is attached to the target version. + */ + if (control) + extControlOid = InsertExtensionControlTuple(owner, control, to); + else + extControlOid = InvalidOid; + + /* + * Build and insert the pg_extension_uptmpl tuple + */ + rel = heap_open(ExtensionUpTmplRelationId, RowExclusiveLock); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + values[Anum_pg_extension_uptmpl_uptname - 1] = + DirectFunctionCall1(namein, CStringGetDatum(extname)); + values[Anum_pg_extension_uptmpl_uptowner - 1] = ObjectIdGetDatum(owner); + values[Anum_pg_extension_uptmpl_uptfrom - 1] = CStringGetTextDatum(from); + values[Anum_pg_extension_uptmpl_uptto - 1] = CStringGetTextDatum(to); + values[Anum_pg_extension_uptmpl_uptscript - 1] = CStringGetTextDatum(script); + + tuple = heap_form_tuple(rel->rd_att, values, nulls); + + extUpTmplOid = simple_heap_insert(rel, tuple); + CatalogUpdateIndexes(rel, tuple); + + heap_freetuple(tuple); + heap_close(rel, RowExclusiveLock); + + /* + * Record dependencies on owner only. + * + * When we create the extension template and control file, the target + * extension, its schema and requirements usually do not exist in the + * database. Don't even think about registering a dependency from the + * template. + */ + recordDependencyOnOwner(ExtensionUpTmplRelationId, extUpTmplOid, owner); + + myself.classId = ExtensionUpTmplRelationId; + myself.objectId = extUpTmplOid; + myself.objectSubId = 0; + + /* record he dependency between the control row and the template row */ + if (control) + { + ObjectAddress ctrl; + + ctrl.classId = ExtensionControlRelationId; + ctrl.objectId = extControlOid; + ctrl.objectSubId = 0; + + recordDependencyOn(&ctrl, &myself, DEPENDENCY_INTERNAL); + } + + /* Post creation hook for new extension control */ + InvokeObjectAccessHook(OAT_POST_CREATE, + ExtensionUpTmplRelationId, extUpTmplOid, 0, NULL); + + return extUpTmplOid; + } + + /* + * ALTER TEMPLATE FOR EXTENSION name VERSION version + * + * This implements high level routing for sub commands. + */ + Oid + AlterTemplate(AlterTemplateStmt *stmt) + { + switch (stmt->template) + { + case TEMPLATE_CREATE_EXTENSION: + return AlterExtensionTemplate(stmt); + + case TEMPLATE_UPDATE_EXTENSION: + return AlterExtensionUpdateTemplate(stmt); + } + /* keep compiler happy */ + return InvalidOid; + } + + Oid + AlterExtensionTemplate(AlterTemplateStmt *stmt) + { + switch (stmt->cmdtype) + { + case AET_SET_DEFAULT: + return AlterTemplateSetDefault(stmt->extname, stmt->version); + + case AET_SET_SCRIPT: + elog(WARNING, "Not Yet Implemented"); + break; + + case AET_UPDATE_CONTROL: + elog(WARNING, "Not Yet Implemented"); + break; + } + + return InvalidOid; + } + + /* + * ALTER TEMPLATE FOR EXTENSION ... SET DEFAULT VERSION ... + * + * We refuse to run without a default, so we drop the current one when + * assigning a new one. + */ + static Oid + AlterTemplateSetDefault(const char *extname, const char *version) + { + /* we need to know who's the default */ + ExtensionControl *current = + find_default_pg_extension_control(extname, true); + + if (current) + { + if (strcmp(current->default_version, version) == 0) + /* silently do nothing */ + return InvalidOid; + + /* set ctldefault to false on current default extension */ + modify_pg_extension_control_default(current->name, + current->default_version, + false); + } + /* set ctldefault to true on new default extension */ + return modify_pg_extension_control_default(extname, version, true); + } + + /* + * Implement flipping the ctldefault bit from value to repl. + */ + static Oid + modify_pg_extension_control_default(const char *extname, + const char *version, + bool value) + { + Oid ctrlOid; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[2]; + Datum values[Natts_pg_extension_control]; + bool nulls[Natts_pg_extension_control]; + bool repl[Natts_pg_extension_control]; + + rel = heap_open(ExtensionControlRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_control_ctlname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + + ScanKeyInit(&entry[1], + Anum_pg_extension_control_ctlversion, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(version)); + + scandesc = systable_beginscan(rel, + ExtensionControlNameVersionIndexId, true, + SnapshotNow, 2, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + + if (!HeapTupleIsValid(tuple)) /* should not happen */ + elog(ERROR, + "pg_extension_control for extension \"%s\" version \"%s\" does not exist", + extname, version); + + ctrlOid = HeapTupleGetOid(tuple); + + /* Modify ctldefault in the pg_extension_control tuple */ + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + memset(repl, 0, sizeof(repl)); + + values[Anum_pg_extension_control_ctldefault - 1] = BoolGetDatum(value); + repl[Anum_pg_extension_control_ctldefault - 1] = true; + + tuple = heap_modify_tuple(tuple, RelationGetDescr(rel), + values, nulls, repl); + + simple_heap_update(rel, &tuple->t_self, tuple); + CatalogUpdateIndexes(rel, tuple); + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + return ctrlOid; + } + + /* + * ALTER TEMPLATE FOR EXTENSION name FROM old TO new + * + * This implements high level routing for sub commands. + */ + Oid + AlterExtensionUpdateTemplate(AlterTemplateStmt *stmt) + { + elog(WARNING, "Not Yet Implemented"); + return InvalidOid; + } + + /* + * get_template_oid - given an extension name and version, look up the template + * OID + * + * If missing_ok is false, throw an error if extension name not found. If + * true, just return InvalidOid. + */ + Oid + get_template_oid(const char *extname, const char *version, bool missing_ok) + { + Oid result; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[2]; + + rel = heap_open(ExtensionTemplateRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_template_tplname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + + ScanKeyInit(&entry[1], + Anum_pg_extension_template_tplversion, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(version)); + + scandesc = systable_beginscan(rel, + ExtensionTemplateNameVersionIndexId, true, + SnapshotNow, 2, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = HeapTupleGetOid(tuple); + else + result = InvalidOid; + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + if (!OidIsValid(result) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("template for extension \"%s\" version \"%s\" does not exist", + extname, version))); + + return result; + } + + /* + * Check that the given extension name has a create template. + */ + bool + can_create_extension_from_template(const char *extname, bool missing_ok) + { + bool result; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionTemplateRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_template_tplname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + + scandesc = systable_beginscan(rel, + ExtensionTemplateNameVersionIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We only are interested into knowing if we found at least one tuple */ + result = HeapTupleIsValid(tuple); + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + if (!result && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("no template for extension \"%s\"", extname))); + + return result; + } + + /* + * get_uptmpl_oid - given an extension name, from version and to version, look + * up the uptmpl OID + * + * If missing_ok is false, throw an error if extension name not found. If + * true, just return InvalidOid. + */ + Oid + get_uptmpl_oid(const char *extname, const char *from, const char *to, + bool missing_ok) + { + Oid result; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[3]; + + rel = heap_open(ExtensionUpTmplRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_uptmpl_uptname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + + ScanKeyInit(&entry[1], + Anum_pg_extension_uptmpl_uptfrom, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(from)); + + ScanKeyInit(&entry[2], + Anum_pg_extension_uptmpl_uptto, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(to)); + + scandesc = systable_beginscan(rel, + ExtensionUpTpmlNameFromToIndexId, true, + SnapshotNow, 3, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = HeapTupleGetOid(tuple); + else + result = InvalidOid; + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + if (!OidIsValid(result) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("template for extension \"%s\" update from version \"%s\" to version \"%s\"does not exist", + extname, from, to))); + + return result; + } + + /* + * Remove Extension Control by OID + */ + void + RemoveExtensionControlById(Oid extControlOid) + { + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionControlRelationId, RowExclusiveLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extControlOid)); + scandesc = systable_beginscan(rel, ExtensionControlOidIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + simple_heap_delete(rel, &tuple->t_self); + + systable_endscan(scandesc); + + heap_close(rel, RowExclusiveLock); + } + + /* + * Remove Extension Control by OID + */ + void + RemoveExtensionTemplateById(Oid extTemplateOid) + { + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionTemplateRelationId, RowExclusiveLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extTemplateOid)); + scandesc = systable_beginscan(rel, ExtensionTemplateOidIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + simple_heap_delete(rel, &tuple->t_self); + + systable_endscan(scandesc); + + heap_close(rel, RowExclusiveLock); + } + + /* + * Remove Extension Control by OID + */ + void + RemoveExtensionUpTmplById(Oid extUpTmplOid) + { + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionUpTmplRelationId, RowExclusiveLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extUpTmplOid)); + scandesc = systable_beginscan(rel, ExtensionUpTmplOidIndexId, true, + SnapshotNow, 1, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + simple_heap_delete(rel, &tuple->t_self); + + systable_endscan(scandesc); + + heap_close(rel, RowExclusiveLock); + } + + /* + * read_pg_extension_control + * + * Read a pg_extension_control row and fill in an ExtensionControl + * structure with the right elements in there. + */ + static ExtensionControl * + read_pg_extension_control(const char *extname, Relation rel, HeapTuple tuple) + { + Datum dreqs; + bool isnull; + Form_pg_extension_control ctrl = + (Form_pg_extension_control) GETSTRUCT(tuple); + + ExtensionControl *control = + (ExtensionControl *) palloc0(sizeof(ExtensionControl)); + + /* Those fields are not null */ + control->name = pstrdup(extname); + control->is_template = true; + control->relocatable = ctrl->ctlrelocatable; + control->superuser = ctrl->ctlsuperuser; + control->schema = pstrdup(NameStr(ctrl->ctlnamespace)); + + if (ctrl->ctldefault) + { + Datum dvers = + heap_getattr(tuple, Anum_pg_extension_control_ctlversion, + RelationGetDescr(rel), &isnull); + + char *version = isnull? NULL : text_to_cstring(DatumGetTextPP(dvers)); + + if (isnull) + { + /* shouldn't happen */ + elog(ERROR, + "pg_extension_control row without version for \"%s\"", + extname); + } + + /* get the version and requires fields from here */ + control->default_version = pstrdup(version); + } + + /* now see about the dependencies array */ + dreqs = heap_getattr(tuple, Anum_pg_extension_control_ctlrequires, + RelationGetDescr(rel), &isnull); + + if (!isnull) + { + ArrayType *arr = DatumGetArrayTypeP(dreqs); + Datum *elems; + int i; + int nelems; + + if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID) + elog(ERROR, "expected 1-D text array"); + deconstruct_array(arr, TEXTOID, -1, false, 'i', &elems, NULL, &nelems); + + for (i = 0; i < nelems; ++i) + control->requires = lappend(control->requires, + TextDatumGetCString(elems[i])); + + pfree(elems); + } + return control; + } + + /* + * Find the pg_extension_control row for given extname and version, if any, and + * return a filled in ExtensionControl structure. + * + * In case we don't have any pg_extension_control row for given extname and + * version, return NULL. + */ + ExtensionControl * + find_pg_extension_control(const char *extname, + const char *version, + bool missing_ok) + { + ExtensionControl *control = NULL; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[2]; + + rel = heap_open(ExtensionControlRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_control_ctlname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + + ScanKeyInit(&entry[1], + Anum_pg_extension_control_ctlversion, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(version)); + + scandesc = systable_beginscan(rel, + ExtensionControlNameVersionIndexId, true, + SnapshotNow, 2, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + control = read_pg_extension_control(extname, rel, tuple); + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + if (control == NULL && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("extension \"%s\" has no control template for version \"%s\"", + extname, version))); + + return control; + } + + /* + * Find the default extension's control properties. + */ + ExtensionControl * + find_default_pg_extension_control(const char *extname, bool missing_ok) + { + ExtensionControl *control = NULL; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionControlRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_control_ctlname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + + scandesc = systable_beginscan(rel, + ExtensionControlNameVersionIndexId, true, + SnapshotNow, 1, entry); + + /* find all the control tuples for extname */ + while (HeapTupleIsValid(tuple = systable_getnext(scandesc))) + { + bool isnull; + bool ctldefault = + DatumGetBool( + fastgetattr(tuple, Anum_pg_extension_control_ctldefault, + RelationGetDescr(rel), &isnull)); + + /* only of those is the default */ + if (ctldefault) + { + if (control == NULL) + control = read_pg_extension_control(extname, rel, tuple); + else + /* should not happen */ + elog(ERROR, + "Extension \"%s\" has more than one default control template", + extname); + } + } + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + /* we really need a single default version. */ + if (control == NULL && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("extension \"%s\" has no default control template", + extname))); + + return control; + } + + /* + * read_pg_extension_uptmpl_script + * + * Return the script from the pg_extension_template catalogs. + */ + char * + read_pg_extension_template_script(const char *extname, const char *version) + { + char *script; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[2]; + + rel = heap_open(ExtensionTemplateRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_template_tplname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + + ScanKeyInit(&entry[1], + Anum_pg_extension_template_tplversion, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(version)); + + scandesc = systable_beginscan(rel, + ExtensionTemplateNameVersionIndexId, true, + SnapshotNow, 2, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + { + bool isnull; + Datum dscript; + + dscript = heap_getattr(tuple, Anum_pg_extension_template_tplscript, + RelationGetDescr(rel), &isnull); + + script = isnull? NULL : text_to_cstring(DatumGetTextPP(dscript)); + } + else + /* can't happen */ + elog(ERROR, + "Missing Extension Template entry for extension \"%s\" version \"%s\"", + extname, version); + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + return script; + } + + /* + * read_pg_extension_uptmpl_script + * + * Return the script from the pg_extension_uptmpl catalogs. + */ + char * + read_pg_extension_uptmpl_script(const char *extname, + const char *from_version, const char *version) + { + char *script; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[3]; + + rel = heap_open(ExtensionUpTmplRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_uptmpl_uptname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + + ScanKeyInit(&entry[1], + Anum_pg_extension_uptmpl_uptfrom, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(from_version)); + + ScanKeyInit(&entry[2], + Anum_pg_extension_uptmpl_uptto, + BTEqualStrategyNumber, F_TEXTEQ, + CStringGetTextDatum(version)); + + scandesc = systable_beginscan(rel, + ExtensionUpTpmlNameFromToIndexId, true, + SnapshotNow, 3, entry); + + tuple = systable_getnext(scandesc); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + { + bool isnull; + Datum dscript; + + dscript = heap_getattr(tuple, Anum_pg_extension_uptmpl_uptscript, + RelationGetDescr(rel), &isnull); + + script = isnull? NULL : text_to_cstring(DatumGetTextPP(dscript)); + } + else + /* can't happen */ + elog(ERROR, "Extension Template Control entry for \"%s\" has no template for update from version \"%s\" to version \"%s\"", + extname, from_version, version); + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + return script; + } + + /* + * read_extension_template_script + * + * Given an extension's name and a version, return the extension's script from + * the pg_extension_template or the pg_extension_uptmpl catalog. The former is + * used when from_version is NULL. + */ + char * + read_extension_template_script(const char *extname, + const char *from_version, const char *version) + { + if (from_version) + return read_pg_extension_uptmpl_script(extname, from_version, version); + else + return read_pg_extension_template_script(extname, version); + } + + /* + * Returns a list of cstring containing all known versions that you can install + * for a given extension. + */ + List * + list_pg_extension_template_versions(const char *extname) + { + List *versions = NIL; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionTemplateRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_template_tplname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + + scandesc = systable_beginscan(rel, + ExtensionTemplateNameVersionIndexId, true, + SnapshotNow, 1, entry); + + while (HeapTupleIsValid(tuple = systable_getnext(scandesc))) + { + bool isnull; + Datum dvers = + heap_getattr(tuple, Anum_pg_extension_template_tplversion, + RelationGetDescr(rel), &isnull); + + char *version = isnull? NULL : text_to_cstring(DatumGetTextPP(dvers)); + + versions = lappend(versions, version); + } + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + return versions; + } + + /* + * Returns a list of lists of source and target versions for which we have a + * direct upgrade path for. + */ + List * + list_pg_extension_update_versions(const char *extname) + { + List *versions = NIL; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionUpTmplRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_uptmpl_uptname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + + scandesc = systable_beginscan(rel, + ExtensionUpTpmlNameFromToIndexId, true, + SnapshotNow, 1, entry); + + while (HeapTupleIsValid(tuple = systable_getnext(scandesc))) + { + bool isnull; + Datum dfrom, dto; + char *from, *to; + + /* neither from nor to are allowed to be null... */ + dfrom = heap_getattr(tuple, Anum_pg_extension_uptmpl_uptfrom, + RelationGetDescr(rel), &isnull); + + from = isnull ? NULL : text_to_cstring(DatumGetTextPP(dfrom)); + + dto = heap_getattr(tuple, Anum_pg_extension_uptmpl_uptto, + RelationGetDescr(rel), &isnull); + + to = isnull ? NULL : text_to_cstring(DatumGetTextPP(dto)); + + versions = lappend(versions, list_make2(from, to)); + } + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + return versions; + } + + /* + * pg_extension_controls + * + * List all extensions for which we have a default control entry. Returns a + * sorted list of name, version of such extensions. + */ + List * + pg_extension_default_controls(void) + { + List *extensions = NIL; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + + rel = heap_open(ExtensionControlRelationId, AccessShareLock); + + scandesc = systable_beginscan(rel, + ExtensionControlNameVersionIndexId, true, + SnapshotNow, 0, NULL); + + /* find all the control tuples for extname */ + while (HeapTupleIsValid(tuple = systable_getnext(scandesc))) + { + Form_pg_extension_control ctrl = + (Form_pg_extension_control) GETSTRUCT(tuple); + + bool isnull; + bool ctldefault = + DatumGetBool( + fastgetattr(tuple, Anum_pg_extension_control_ctldefault, + RelationGetDescr(rel), &isnull)); + + /* only of those is the default */ + if (ctldefault) + { + Datum dvers = + heap_getattr(tuple, Anum_pg_extension_control_ctlversion, + RelationGetDescr(rel), &isnull); + + char *version = isnull? NULL:text_to_cstring(DatumGetTextPP(dvers)); + + extensions = lappend(extensions, + list_make2(pstrdup(NameStr(ctrl->ctlname)), + version)); + } + } + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + return extensions; + } + + /* + * pg_extension_templates + * + * Return a list of list of name, version of extensions available to install + * from templates, in alphabetical order. + */ + List * + pg_extension_templates(void) + { + List *templates = NIL; + Relation rel; + SysScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionTemplateRelationId, AccessShareLock); + + scandesc = systable_beginscan(rel, + ExtensionTemplateNameVersionIndexId, true, + SnapshotNow, 0, entry); + + while (HeapTupleIsValid(tuple = systable_getnext(scandesc))) + { + Form_pg_extension_template tmpl = + (Form_pg_extension_template) GETSTRUCT(tuple); + + bool isnull; + + Datum dvers = + heap_getattr(tuple, Anum_pg_extension_template_tplversion, + RelationGetDescr(rel), &isnull); + + char *version = isnull? NULL : text_to_cstring(DatumGetTextPP(dvers)); + + templates = lappend(templates, + list_make2(pstrdup(NameStr(tmpl->tplname)), + version)); + } + + systable_endscan(scandesc); + + heap_close(rel, AccessShareLock); + + return templates; + } *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 220,226 **** static void processCASbits(int cas_bits, int location, const char *constrType, AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt ! AlterRoleStmt AlterRoleSetStmt AlterDefaultPrivilegesStmt DefACLAction AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt --- 220,226 ---- AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt ! AlterRoleStmt AlterRoleSetStmt AlterTemplateStmt AlterDefaultPrivilegesStmt DefACLAction AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt *************** *** 229,239 **** static void processCASbits(int cas_bits, int location, const char *constrType, CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt CreateAssertStmt CreateTrigStmt CreateEventTrigStmt ! CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt ! DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt --- 229,239 ---- CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt CreateAssertStmt CreateTrigStmt CreateEventTrigStmt ! CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreateTemplateStmt CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt ! DropTemplateStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt *************** *** 266,271 **** static void processCASbits(int cas_bits, int location, const char *constrType, --- 266,274 ---- transaction_mode_item create_extension_opt_item alter_extension_opt_item + %type create_template_control create_template_control_plist + %type create_template_control_item + %type opt_lock lock_type cast_context %type vacuum_option_list vacuum_option_elem %type opt_force opt_or_replace *************** *** 720,725 **** stmt : --- 723,729 ---- | AlterCompositeTypeStmt | AlterRoleSetStmt | AlterRoleStmt + | AlterTemplateStmt | AlterTSConfigurationStmt | AlterTSDictionaryStmt | AlterUserMappingStmt *************** *** 757,762 **** stmt : --- 761,767 ---- | CreateUserStmt | CreateUserMappingStmt | CreatedbStmt + | CreateTemplateStmt | DeallocateStmt | DeclareCursorStmt | DefineStmt *************** *** 780,785 **** stmt : --- 785,791 ---- | DropUserStmt | DropUserMappingStmt | DropdbStmt + | DropTemplateStmt | ExecuteStmt | ExplainStmt | FetchStmt *************** *** 3440,3445 **** DropTableSpaceStmt: DROP TABLESPACE name --- 3446,3675 ---- /***************************************************************************** * * QUERY: + * CREATE TEMPLATE FOR EXTENSION name + * + *****************************************************************************/ + + CreateTemplateStmt: + CREATE TEMPLATE FOR EXTENSION name VERSION_P ColId_or_Sconst + WITH create_template_control AS Sconst + { + CreateTemplateStmt *n = makeNode(CreateTemplateStmt); + n->template = TEMPLATE_CREATE_EXTENSION; + n->extname = $5; + n->version = $7; + n->control = $9; + n->script = $11; + n->if_not_exists = false; + n->default_version = false; + $$ = (Node *) n; + } + | CREATE TEMPLATE FOR EXTENSION name + DEFAULT VERSION_P ColId_or_Sconst + WITH create_template_control AS Sconst + { + CreateTemplateStmt *n = makeNode(CreateTemplateStmt); + n->template = TEMPLATE_CREATE_EXTENSION; + n->extname = $5; + n->version = $8; + n->control = $10; + n->script = $12; + n->if_not_exists = false; + n->default_version = true; + $$ = (Node *) n; + } + | CREATE TEMPLATE FOR EXTENSION name + FROM ColId_or_Sconst TO ColId_or_Sconst AS Sconst + { + CreateTemplateStmt *n = makeNode(CreateTemplateStmt); + n->template = TEMPLATE_UPDATE_EXTENSION; + n->extname = $5; + n->from = $7; + n->to = $9; + n->control = NIL; + n->script = $11; + n->if_not_exists = false; + $$ = (Node *) n; + } + | CREATE TEMPLATE FOR EXTENSION name + FROM ColId_or_Sconst TO ColId_or_Sconst + WITH create_template_control AS Sconst + { + CreateTemplateStmt *n = makeNode(CreateTemplateStmt); + n->template = TEMPLATE_UPDATE_EXTENSION; + n->extname = $5; + n->from = $7; + n->to = $9; + n->control = $11; + n->script = $13; + n->if_not_exists = false; + $$ = (Node *) n; + } + ; + + /***************************************************************************** + * + * QUERY: + * ALTER TEMPLATE FOR EXTENSION name + * + * We only allow a single subcommand per command here. + * + *****************************************************************************/ + AlterTemplateStmt: + ALTER TEMPLATE FOR EXTENSION name + SET DEFAULT VERSION_P ColId_or_Sconst + { + AlterTemplateStmt *n = makeNode(AlterTemplateStmt); + n->template = TEMPLATE_CREATE_EXTENSION; + n->cmdtype = AET_SET_DEFAULT; + n->extname = $5; + n->version = $9; + n->missing_ok = false; + $$ = (Node *) n; + } + | ALTER TEMPLATE FOR EXTENSION name VERSION_P ColId_or_Sconst + AS Sconst + { + AlterTemplateStmt *n = makeNode(AlterTemplateStmt); + n->template = TEMPLATE_CREATE_EXTENSION; + n->cmdtype = AET_SET_SCRIPT; + n->extname = $5; + n->version = $7; + n->script = $9; + n->missing_ok = false; + $$ = (Node *) n; + } + | ALTER TEMPLATE FOR EXTENSION name VERSION_P ColId_or_Sconst + WITH create_template_control + { + AlterTemplateStmt *n = makeNode(AlterTemplateStmt); + n->template = TEMPLATE_CREATE_EXTENSION; + n->cmdtype = AET_UPDATE_CONTROL; + n->extname = $5; + n->version = $7; + n->control = $9; + n->missing_ok = false; + $$ = (Node *) n; + } + | ALTER TEMPLATE FOR EXTENSION name + FROM ColId_or_Sconst TO ColId_or_Sconst + AS Sconst + { + AlterTemplateStmt *n = makeNode(AlterTemplateStmt); + n->template = TEMPLATE_UPDATE_EXTENSION; + n->cmdtype = AET_SET_SCRIPT; + n->extname = $5; + n->from = $7; + n->to = $9; + n->script = $11; + n->missing_ok = false; + $$ = (Node *) n; + } + | ALTER TEMPLATE FOR EXTENSION name + FROM ColId_or_Sconst TO ColId_or_Sconst + WITH create_template_control + { + AlterTemplateStmt *n = makeNode(AlterTemplateStmt); + n->template = TEMPLATE_UPDATE_EXTENSION; + n->cmdtype = AET_UPDATE_CONTROL; + n->extname = $5; + n->from = $7; + n->to = $9; + n->control = $11; + n->missing_ok = false; + $$ = (Node *) n; + } + ; + + /***************************************************************************** + * + * QUERY: + * DROP TEMPLATE FOR EXTENSION name + * + *****************************************************************************/ + DropTemplateStmt: + DROP TEMPLATE FOR EXTENSION name + VERSION_P ColId_or_Sconst opt_drop_behavior + { + DropStmt *n = makeNode(DropStmt); + n->removeType = OBJECT_EXTENSION_TEMPLATE; + n->objects = list_make1(list_make1(makeString($5))); + n->arguments = list_make1(list_make1(makeString($7))); + n->behavior = $8; + n->missing_ok = false; + n->concurrent = false; + $$ = (Node *)n; + } + | DROP TEMPLATE FOR EXTENSION name + FROM ColId_or_Sconst TO ColId_or_Sconst opt_drop_behavior + { + DropStmt *n = makeNode(DropStmt); + n->removeType = OBJECT_EXTENSION_UPTMPL; + n->objects = list_make1(list_make1(makeString($5))); + n->arguments = list_make1(list_make2(makeString($7), + makeString($9))); + n->behavior = $10; + n->missing_ok = false; + n->concurrent = false; + $$ = (Node *)n; + } + ; + + create_template_control: + '(' create_template_control_plist ')' { $$ = $2; } + ; + + + create_template_control_plist: + create_template_control_item + { $$ = list_make1($1); } + | create_template_control_plist ',' create_template_control_item + { $$ = lappend($1, $3); } + | /* EMPTY */ + { $$ = NIL; } + ; + + create_template_control_item: + SCHEMA name + { + $$ = makeDefElem("schema", (Node *)makeString($2)); + } + | IDENT + { + /* + * We handle identifiers that aren't parser keywords with + * the following special-case codes, to avoid bloating the + * size of the main parser. + */ + if (strcmp($1, "superuser") == 0) + $$ = makeDefElem("superuser", (Node *)makeInteger(TRUE)); + else if (strcmp($1, "nosuperuser") == 0) + $$ = makeDefElem("superuser", (Node *)makeInteger(FALSE)); + else if (strcmp($1, "relocatable") == 0) + $$ = makeDefElem("relocatable", (Node *)makeInteger(TRUE)); + else if (strcmp($1, "norelocatable") == 0) + $$ = makeDefElem("relocatable", (Node *)makeInteger(FALSE)); + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized template option \"%s\"", $1), + parser_errposition(@1))); + } + | IDENT Sconst + { + if (strcmp($1, "requires") == 0) + $$ = makeDefElem("requires", (Node *)makeString($2)); + else + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("unrecognized template option \"%s\"", $1), + parser_errposition(@1))); + } + ; + + /***************************************************************************** + * + * QUERY: * CREATE EXTENSION extension * [ WITH ] [ SCHEMA schema ] [ VERSION version ] [ FROM oldversion ] * *** a/src/backend/tcop/utility.c --- b/src/backend/tcop/utility.c *************** *** 46,51 **** --- 46,52 ---- #include "commands/sequence.h" #include "commands/tablecmds.h" #include "commands/tablespace.h" + #include "commands/template.h" #include "commands/trigger.h" #include "commands/typecmds.h" #include "commands/user.h" *************** *** 224,229 **** check_xact_readonly(Node *parsetree) --- 225,232 ---- case T_CreateExtensionStmt: case T_AlterExtensionStmt: case T_AlterExtensionContentsStmt: + case T_CreateTemplateStmt: + case T_AlterTemplateStmt: case T_CreateFdwStmt: case T_AlterFdwStmt: case T_CreateForeignServerStmt: *************** *** 621,626 **** standard_ProcessUtility(Node *parsetree, --- 624,641 ---- ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree); break; + case T_CreateTemplateStmt: + if (isCompleteQuery) + EventTriggerDDLCommandStart(parsetree); + CreateTemplate((CreateTemplateStmt *) parsetree); + break; + + case T_AlterTemplateStmt: + if (isCompleteQuery) + EventTriggerDDLCommandStart(parsetree); + AlterTemplate((AlterTemplateStmt *)parsetree); + break; + case T_CreateFdwStmt: if (isCompleteQuery) EventTriggerDDLCommandStart(parsetree); *************** *** 1781,1786 **** CreateCommandTag(Node *parsetree) --- 1796,1809 ---- tag = "ALTER EXTENSION"; break; + case T_CreateTemplateStmt: + tag = "CREATE TEMPLATE FOR EXTENSION"; + break; + + case T_AlterTemplateStmt: + tag = "ALTER TEMPLATE FOR EXTENSION"; + break; + case T_CreateFdwStmt: tag = "CREATE FOREIGN DATA WRAPPER"; break; *************** *** 1861,1866 **** CreateCommandTag(Node *parsetree) --- 1884,1895 ---- case OBJECT_EXTENSION: tag = "DROP EXTENSION"; break; + case OBJECT_EXTENSION_TEMPLATE: + tag = "DROP TEMPLATE FOR EXTENSION"; + break; + case OBJECT_EXTENSION_UPTMPL: + tag = "DROP TEMPLATE FOR EXTENSION"; + break; case OBJECT_FUNCTION: tag = "DROP FUNCTION"; break; *************** *** 2411,2416 **** GetCommandLogLevel(Node *parsetree) --- 2440,2450 ---- lev = LOGSTMT_DDL; break; + case T_CreateTemplateStmt: + case T_AlterTemplateStmt: + lev = LOGSTMT_DDL; + break; + case T_CreateFdwStmt: case T_AlterFdwStmt: case T_CreateForeignServerStmt: *** a/src/include/catalog/dependency.h --- b/src/include/catalog/dependency.h *************** *** 146,151 **** typedef enum ObjectClass --- 146,154 ---- OCLASS_USER_MAPPING, /* pg_user_mapping */ OCLASS_DEFACL, /* pg_default_acl */ OCLASS_EXTENSION, /* pg_extension */ + OCLASS_EXTENSION_CONTROL, /* pg_extension_control */ + OCLASS_EXTENSION_TEMPLATE, /* pg_extension_template */ + OCLASS_EXTENSION_UPTMPL, /* pg_extension_uptmpl */ OCLASS_EVENT_TRIGGER, /* pg_event_trigger */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; *** a/src/include/catalog/indexing.h --- b/src/include/catalog/indexing.h *************** *** 307,312 **** DECLARE_UNIQUE_INDEX(pg_extension_oid_index, 3080, on pg_extension using btree(o --- 307,329 ---- DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3081, on pg_extension using btree(extname name_ops)); #define ExtensionNameIndexId 3081 + DECLARE_UNIQUE_INDEX(pg_extension_template_oid_index, 3180, on pg_extension_template using btree(oid oid_ops)); + #define ExtensionTemplateOidIndexId 3180 + + DECLARE_UNIQUE_INDEX(pg_extension_template_name_version_index, 3181, on pg_extension_template using btree(tplname name_ops, tplversion text_ops)); + #define ExtensionTemplateNameVersionIndexId 3181 + + DECLARE_UNIQUE_INDEX(pg_extension_uptmpl_oid_index, 3280, on pg_extension_uptmpl using btree(oid oid_ops)); + #define ExtensionUpTmplOidIndexId 3280 + + DECLARE_UNIQUE_INDEX(pg_extension_uptmpl_name_from_to_index, 3281, on pg_extension_uptmpl using btree(uptname name_ops, uptfrom text_ops, uptto text_ops)); + #define ExtensionUpTpmlNameFromToIndexId 3281 + + DECLARE_UNIQUE_INDEX(pg_extension_control_oid_index, 3380, on pg_extension_control using btree(oid oid_ops)); + #define ExtensionControlOidIndexId 3380 + + DECLARE_UNIQUE_INDEX(pg_extension_control_name_version_index, 3381, on pg_extension_control using btree(ctlname name_ops, ctlversion text_ops)); + #define ExtensionControlNameVersionIndexId 3381 DECLARE_UNIQUE_INDEX(pg_range_rngtypid_index, 3542, on pg_range using btree(rngtypid oid_ops)); #define RangeTypidIndexId 3542 *** /dev/null --- b/src/include/catalog/pg_extension_control.h *************** *** 0 **** --- 1,73 ---- + /*------------------------------------------------------------------------- + * + * pg_extension_control.h + * definition of the system "extension_control" relation + * (pg_extension_control) along with the relation's initial contents. + * + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_extension_control.h + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ + #ifndef PG_EXTENSION_CONTROL_H + #define PG_EXTENSION_CONTROL_H + + #include "catalog/genbki.h" + + /* ---------------- + * pg_extension_install_control definition. cpp turns this into + * typedef struct FormData_pg_extension_control + * ---------------- + */ + #define ExtensionControlRelationId 3379 + + CATALOG(pg_extension_control,3379) + { + NameData ctlname; /* extension name */ + Oid ctlowner; /* control owner */ + bool ctldefault; /* this version is the extension's default? */ + bool ctlrelocatable; /* extension is relocatable? */ + bool ctlsuperuser; /* extension is superuser only? */ + NameData ctlnamespace; /* namespace of contained objects */ + + #ifdef CATALOG_VARLEN /* variable-length fields start here */ + text ctlversion; /* version to install with this control */ + text ctlrequires; /* extension dependency list */ + #endif + } FormData_pg_extension_control; + + /* ---------------- + * Form_pg_extension_control corresponds to a pointer to a tuple with the + * format of pg_extension_control relation. + * ---------------- + */ + typedef FormData_pg_extension_control *Form_pg_extension_control; + + /* ---------------- + * compiler constants for pg_extension_control + * ---------------- + */ + + #define Natts_pg_extension_control 8 + #define Anum_pg_extension_control_ctlname 1 + #define Anum_pg_extension_control_ctlowner 2 + #define Anum_pg_extension_control_ctldefault 3 + #define Anum_pg_extension_control_ctlrelocatable 4 + #define Anum_pg_extension_control_ctlsuperuser 5 + #define Anum_pg_extension_control_ctlnamespace 6 + #define Anum_pg_extension_control_ctlversion 7 + #define Anum_pg_extension_control_ctlrequires 8 + + /* ---------------- + * pg_extension_control has no initial contents + * ---------------- + */ + + #endif /* PG_EXTENSION_CONTROL_H */ *** /dev/null --- b/src/include/catalog/pg_extension_template.h *************** *** 0 **** --- 1,65 ---- + /*------------------------------------------------------------------------- + * + * pg_extension_template.h + * definition of the system "extension_template" relation + * (pg_extension_template) along with the relation's initial contents. + * + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_extension_template.h + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ + #ifndef PG_EXTENSION_TEMPLATE_H + #define PG_EXTENSION_TEMPLATE_H + + #include "catalog/genbki.h" + + /* ---------------- + * pg_extension_install_template definition. cpp turns this into + * typedef struct FormData_pg_extension_template + * ---------------- + */ + #define ExtensionTemplateRelationId 3179 + + CATALOG(pg_extension_template,3179) + { + NameData tplname; /* extension name */ + Oid tplowner; /* template owner */ + + #ifdef CATALOG_VARLEN /* variable-length fields start here */ + text tplversion; /* version to install with this template */ + text tplscript; /* extension's install script */ + #endif + } FormData_pg_extension_template; + + /* ---------------- + * Form_pg_extension_template corresponds to a pointer to a tuple with the + * format of pg_extension_template relation. + * ---------------- + */ + typedef FormData_pg_extension_template *Form_pg_extension_template; + + /* ---------------- + * compiler constants for pg_extension_template + * ---------------- + */ + + #define Natts_pg_extension_template 4 + #define Anum_pg_extension_template_tplname 1 + #define Anum_pg_extension_template_tplowner 2 + #define Anum_pg_extension_template_tplversion 3 + #define Anum_pg_extension_template_tplscript 4 + + /* ---------------- + * pg_extension_template has no initial contents + * ---------------- + */ + + #endif /* PG_EXTENSION_TEMPLATE_H */ *** /dev/null --- b/src/include/catalog/pg_extension_uptmpl.h *************** *** 0 **** --- 1,67 ---- + /*------------------------------------------------------------------------- + * + * pg_extension_uptmpl.h + * definition of the system "extension_uptmpl" relation + * (pg_extension_uptmpl) along with the relation's initial contents. + * + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_extension_uptmpl.h + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ + #ifndef PG_EXTENSION_UPTMPL_H + #define PG_EXTENSION_UPTMPL_H + + #include "catalog/genbki.h" + + /* ---------------- + * pg_extension_install_uptmpl definition. cpp turns this into + * typedef struct FormData_pg_extension_uptmpl + * ---------------- + */ + #define ExtensionUpTmplRelationId 3279 + + CATALOG(pg_extension_uptmpl,3279) + { + NameData uptname; /* extension name */ + Oid uptowner; /* template owner */ + + #ifdef CATALOG_VARLEN /* variable-length fields start here */ + text uptfrom; /* version this template updates from */ + text uptto; /* version this template updated to */ + text uptscript; /* extension's update script */ + #endif + } FormData_pg_extension_uptmpl; + + /* ---------------- + * Form_pg_extension_uptmpl corresponds to a pointer to a tuple with the + * format of pg_extension_uptmpl relation. + * ---------------- + */ + typedef FormData_pg_extension_uptmpl *Form_pg_extension_uptmpl; + + /* ---------------- + * compiler constants for pg_extension_uptmpl + * ---------------- + */ + + #define Natts_pg_extension_uptmpl 5 + #define Anum_pg_extension_uptmpl_uptname 1 + #define Anum_pg_extension_uptmpl_uptowner 2 + #define Anum_pg_extension_uptmpl_uptfrom 3 + #define Anum_pg_extension_uptmpl_uptto 4 + #define Anum_pg_extension_uptmpl_uptscript 5 + + /* ---------------- + * pg_extension_uptmpl has no initial contents + * ---------------- + */ + + #endif /* PG_EXTENSION_UPTMPL_H */ *** a/src/include/commands/extension.h --- b/src/include/commands/extension.h *************** *** 26,31 **** --- 26,51 ---- extern bool creating_extension; extern Oid CurrentExtensionObject; + /* + * Internal data structure to hold the results of parsing a control file + */ + typedef struct ExtensionControl + { + char *name; /* name of the extension */ + char *directory; /* directory for script files */ + char *default_version; /* default install target version, if any */ + char *module_pathname; /* string to substitute for MODULE_PATHNAME */ + char *comment; /* comment, if any */ + char *schema; /* target schema (allowed if !relocatable) */ + bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */ + bool superuser; /* must be superuser to install? */ + int encoding; /* encoding of the script file, or -1 */ + List *requires; /* names of prerequisite extensions */ + bool is_template; /* true if we're using catalog templates */ + } ExtensionControl; + + extern ExtensionControl *read_extension_control_file(const char *extname); + extern void check_valid_extension_name(const char *extensionname); extern Oid CreateExtension(CreateExtensionStmt *stmt); *** /dev/null --- b/src/include/commands/template.h *************** *** 0 **** --- 1,60 ---- + /*------------------------------------------------------------------------- + * + * template.h + * Template management commands (create/drop template). + * + * + * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/template.h + * + *------------------------------------------------------------------------- + */ + #ifndef TEMPLATE_H + #define TEMPLATE_H + + #include "nodes/parsenodes.h" + + extern Oid CreateTemplate(CreateTemplateStmt *stmt); + extern Oid CreateExtensionTemplate(CreateTemplateStmt *stmt); + extern Oid CreateExtensionUpdateTemplate(CreateTemplateStmt *stmt); + + extern Oid AlterTemplate(AlterTemplateStmt *stmt); + extern Oid AlterExtensionTemplate(AlterTemplateStmt *stmt); + extern Oid AlterExtensionUpdateTemplate(AlterTemplateStmt *stmt); + + extern Oid get_template_oid(const char *extname, const char *version, + bool missing_ok); + extern bool can_create_extension_from_template(const char *extname, + bool missing_ok); + extern Oid get_uptmpl_oid(const char *extname, + const char *from, const char *to, + bool missing_ok); + + extern void RemoveExtensionControlById(Oid extControlOid); + extern void RemoveExtensionTemplateById(Oid extTemplateOid); + extern void RemoveExtensionUpTmplById(Oid extUpTmplOid); + + extern ExtensionControl *find_pg_extension_control(const char *extname, + const char *version, + bool missing_ok); + + extern ExtensionControl *find_default_pg_extension_control(const char *extname, + bool missing_ok); + + extern char *read_pg_extension_template_script(const char *extname, + const char *version); + extern char *read_pg_extension_uptmpl_script(const char *extname, + const char *from_version, + const char *version); + extern char *read_extension_template_script(const char *extname, + const char *from_version, + const char *version); + + extern List *list_pg_extension_template_versions(const char *extname); + extern List *list_pg_extension_update_versions(const char *extname); + extern List *pg_extension_default_controls(void); + extern List *pg_extension_templates(void); + + #endif /* TEMPLATE_H */ *** a/src/include/nodes/nodes.h --- b/src/include/nodes/nodes.h *************** *** 361,366 **** typedef enum NodeTag --- 361,368 ---- T_AlterExtensionContentsStmt, T_CreateEventTrigStmt, T_AlterEventTrigStmt, + T_CreateTemplateStmt, + T_AlterTemplateStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 1118,1123 **** typedef enum ObjectType --- 1118,1125 ---- OBJECT_DOMAIN, OBJECT_EVENT_TRIGGER, OBJECT_EXTENSION, + OBJECT_EXTENSION_TEMPLATE, + OBJECT_EXTENSION_UPTMPL, OBJECT_FDW, OBJECT_FOREIGN_SERVER, OBJECT_FOREIGN_TABLE, *************** *** 1625,1630 **** typedef struct AlterExtensionContentsStmt --- 1627,1674 ---- List *objargs; /* Arguments if needed (eg, for functions) */ } AlterExtensionContentsStmt; + typedef enum TemplateType + { + TEMPLATE_CREATE_EXTENSION, + TEMPLATE_UPDATE_EXTENSION + } TemplateType; + + typedef struct CreateTemplateStmt + { + NodeTag type; + TemplateType template; + char *extname; /* Extension's name */ + char *version; /* Version to create from the template */ + char *from; /* In case of an update template, we update */ + char *to; /* from version to version */ + List *control; /* List of DefElem nodes */ + char *script; /* Extension's install script */ + bool if_not_exists; /* just do nothing if it already exists? */ + bool default_version; /* default version of this extension */ + } CreateTemplateStmt; + + typedef enum AlterTemplateType + { + AET_SET_DEFAULT, + AET_SET_SCRIPT, + AET_UPDATE_CONTROL, + } AlterTemplateType; + + typedef struct AlterTemplateStmt + { + NodeTag type; + TemplateType template; + AlterTemplateType cmdtype; /* Type of command */ + char *extname; /* Extension's name */ + char *version; /* Extension's version */ + char *from; /* In case of an update template, we update */ + char *to; /* from version to version */ + List *control; /* List of DefElem nodes */ + char *script; /* Extension's install script */ + bool missing_ok; /* skip error if missing? */ + + } AlterTemplateStmt; + /* ---------------------- * Create/Alter FOREIGN DATA WRAPPER Statements * ---------------------- *** a/src/include/utils/acl.h --- b/src/include/utils/acl.h *************** *** 197,202 **** typedef enum AclObjectKind --- 197,203 ---- ACL_KIND_FOREIGN_SERVER, /* pg_foreign_server */ ACL_KIND_EVENT_TRIGGER, /* pg_event_trigger */ ACL_KIND_EXTENSION, /* pg_extension */ + ACL_KIND_TEMPLATE, /* pg_template */ MAX_ACL_KIND /* MUST BE LAST */ } AclObjectKind; *** /dev/null --- b/src/test/regress/expected/extension.out *************** *** 0 **** --- 1,97 ---- + -- first create some templates + CREATE TEMPLATE + FOR EXTENSION PAIR DEFAULT VERSION '1.0' + WITH (superuser, norelocatable, schema public) + AS $$ + CREATE TYPE pair AS ( k text, v text ); + + CREATE OR REPLACE FUNCTION pair(anyelement, text) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair'; + + CREATE OR REPLACE FUNCTION pair(text, anyelement) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair'; + + CREATE OR REPLACE FUNCTION pair(anyelement, anyelement) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair'; + + CREATE OR REPLACE FUNCTION pair(text, text) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;'; + $$; + -- we want to test alter extension update + CREATE TEMPLATE + FOR EXTENSION PAIR FROM '1.0' TO '1.1' + WITH (superuser, norelocatable, schema public) + AS $$ + CREATE OPERATOR ~> (LEFTARG = text, + RIGHTARG = anyelement, + PROCEDURE = pair); + + CREATE OPERATOR ~> (LEFTARG = anyelement, + RIGHTARG = text, + PROCEDURE = pair); + + CREATE OPERATOR ~> (LEFTARG = anyelement, + RIGHTARG = anyelement, + PROCEDURE = pair); + + CREATE OPERATOR ~> (LEFTARG = text, + RIGHTARG = text, + PROCEDURE = pair); + $$; + -- and we want to test update with a cycle + CREATE TEMPLATE + FOR EXTENSION PAIR FROM '1.1' TO '1.2' + AS + $$ + COMMENT ON EXTENSION pair IS 'Simple Key Value Text Type'; + $$; + CREATE EXTENSION pair; + \dx pair + List of installed extensions + Name | Version | Schema | Description + ------+---------+--------+------------- + pair | 1.0 | public | + (1 row) + + \dx+ pair + Objects in extension "pair" + Object Description + -------------------------------------- + function pair(anyelement,anyelement) + function pair(anyelement,text) + function pair(text,anyelement) + function pair(text,text) + type pair + (5 rows) + + ALTER EXTENSION pair UPDATE TO '1.2'; + \dx+ pair + Objects in extension "pair" + Object Description + -------------------------------------- + function pair(anyelement,anyelement) + function pair(anyelement,text) + function pair(text,anyelement) + function pair(text,text) + operator ~>(anyelement,anyelement) + operator ~>(anyelement,text) + operator ~>(text,anyelement) + operator ~>(text,text) + type pair + (9 rows) + + DROP EXTENSION pair; + -- error, we don't have control settings for 1.2 + ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.2'; + ERROR: pg_extension_control for extension "pair" version "1.2" does not exist + -- that's accepted + ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.1'; + -- but we don't know how to apply 1.0 -- 1.1 at install yet + CREATE EXTENSION pair; + ERROR: Missing Extension Template entry for extension "pair" version "1.1" + \dx pair + List of installed extensions + Name | Version | Schema | Description + ------+---------+--------+------------- + (0 rows) + *** a/src/test/regress/expected/sanity_check.out --- b/src/test/regress/expected/sanity_check.out *************** *** 104,109 **** SELECT relname, relhasindex --- 104,112 ---- pg_enum | t pg_event_trigger | t pg_extension | t + pg_extension_control | t + pg_extension_template | t + pg_extension_uptmpl | t pg_foreign_data_wrapper | t pg_foreign_server | t pg_foreign_table | t *************** *** 166,172 **** SELECT relname, relhasindex timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (155 rows) -- -- another sanity check: every system catalog that has OIDs should have --- 169,175 ---- timetz_tbl | f tinterval_tbl | f varchar_tbl | f ! (158 rows) -- -- another sanity check: every system catalog that has OIDs should have *** a/src/test/regress/parallel_schedule --- b/src/test/regress/parallel_schedule *************** *** 83,89 **** test: select_into select_distinct select_distinct_on select_implicit select_havi # ---------- # Another group of parallel tests # ---------- ! test: privileges security_label collate # ---------- # Another group of parallel tests --- 83,89 ---- # ---------- # Another group of parallel tests # ---------- ! test: privileges security_label collate extension # ---------- # Another group of parallel tests *** /dev/null --- b/src/test/regress/sql/extension.sql *************** *** 0 **** --- 1,73 ---- + -- first create some templates + CREATE TEMPLATE + FOR EXTENSION PAIR DEFAULT VERSION '1.0' + WITH (superuser, norelocatable, schema public) + AS $$ + CREATE TYPE pair AS ( k text, v text ); + + CREATE OR REPLACE FUNCTION pair(anyelement, text) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair'; + + CREATE OR REPLACE FUNCTION pair(text, anyelement) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair'; + + CREATE OR REPLACE FUNCTION pair(anyelement, anyelement) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair'; + + CREATE OR REPLACE FUNCTION pair(text, text) + RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;'; + $$; + + -- we want to test alter extension update + CREATE TEMPLATE + FOR EXTENSION PAIR FROM '1.0' TO '1.1' + WITH (superuser, norelocatable, schema public) + AS $$ + CREATE OPERATOR ~> (LEFTARG = text, + RIGHTARG = anyelement, + PROCEDURE = pair); + + CREATE OPERATOR ~> (LEFTARG = anyelement, + RIGHTARG = text, + PROCEDURE = pair); + + CREATE OPERATOR ~> (LEFTARG = anyelement, + RIGHTARG = anyelement, + PROCEDURE = pair); + + CREATE OPERATOR ~> (LEFTARG = text, + RIGHTARG = text, + PROCEDURE = pair); + $$; + + -- and we want to test update with a cycle + CREATE TEMPLATE + FOR EXTENSION PAIR FROM '1.1' TO '1.2' + AS + $$ + COMMENT ON EXTENSION pair IS 'Simple Key Value Text Type'; + $$; + + CREATE EXTENSION pair; + + \dx pair + \dx+ pair + + ALTER EXTENSION pair UPDATE TO '1.2'; + + \dx+ pair + + DROP EXTENSION pair; + + -- error, we don't have control settings for 1.2 + ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.2'; + + -- that's accepted + ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.1'; + + -- but we don't know how to apply 1.0 -- 1.1 at install yet + CREATE EXTENSION pair; + + \dx pair + +