Dumping an Extension's Script
Hi,
Please find attached to this email an RFC patch implementing the basics
of the pg_dump --extension-script option. After much discussion around
the concept of an inline extension, we decided last year that a good
first step would be pg_dump support for an extension's script.
The approach I've been using here is to dump the script from the catalog
current dependencies, which mean that a sequence of CREATE EXTENSION
followed by a number of ALTER EXTENSION … UPDATE … will be consolidated
into a single CREATE EXTENSION command in the dump, much the same as
with CREATE TABLE then ALTER TABLE … ADD COLUMN and the like.
Currently the option behavior is the following, that looks sane to me,
and is open for discussion: the dump's schema always include the CREATE
EXTENSION commands you need. The extensions listed in the -X option
(that you can use more than once) will get dumped with their's current
member objects in a script, inline.
To try the attached patch, you could do as following:
createdb foo
psql -c "create extension hstore" -d foo
pg_dump -X hstore -f /tmp/foo.sql foo
createdb bar
psql -1 -f /tmp/foo.sql -d bar
To be able to restore the dump, I've been adding some basic support to
the CREATE EXTENSION command so that it will find the data it needs from
the SQL command rather than the control file.
Note that the extension control file only contains information about how
to install an extension from a script file on disk. That's something we
don't need at all when installing the extension from a dump, using
either pg_restore or psql. We have some exceptions to that principle,
namely: requires (sets the search_path) and relocatable (found in the
catalogs, needs to survive dump/restore).
Given positive feedback on that way to attack the problem, the TODO list
includes:
- document the new pg_dump --extension-script switch
- add support for ALTER EXTENSION … WITH $$ <script here> $$;
The ALTER EXTENSION support is optional as far as pg_dump support goes,
it would be good to have it to make the User Interface complete.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
dump_extension_script.patchtext/x-patchDownload
*** a/src/backend/commands/extension.c
--- b/src/backend/commands/extension.c
***************
*** 75,80 **** typedef struct ExtensionControlFile
--- 75,81 ----
bool superuser; /* must be superuser to install? */
int encoding; /* encoding of the script file, or -1 */
List *requires; /* names of prerequisite extensions */
+ char *script;
} ExtensionControlFile;
/*
***************
*** 577,586 **** 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;
--- 578,587 ----
}
/*
! * Create an ExtensionControlFile with default values.
*/
static ExtensionControlFile *
! default_extension_control_file(const char *extname)
{
ExtensionControlFile *control;
***************
*** 593,598 **** read_extension_control_file(const char *extname)
--- 594,610 ----
control->superuser = true;
control->encoding = -1;
+ return control;
+ }
+
+ /*
+ * Read the primary control file for the specified extension.
+ */
+ static ExtensionControlFile *
+ read_extension_control_file(const char *extname)
+ {
+ ExtensionControlFile *control = default_extension_control_file(extname);
+
/*
* Parse the primary control file.
*/
***************
*** 858,866 **** 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);
--- 870,883 ----
CurrentExtensionObject = extensionOid;
PG_TRY();
{
! char *c_sql;
Datum t_sql;
+ if (control->script)
+ c_sql = control->script;
+ else
+ c_sql = read_extension_script_file(control, filename);
+
/* We use various functions that want to operate on text datums */
t_sql = CStringGetTextDatum(c_sql);
***************
*** 1178,1183 **** void
--- 1195,1203 ----
CreateExtension(CreateExtensionStmt *stmt)
{
DefElem *d_schema = NULL;
+ DefElem *d_script = NULL;
+ DefElem *d_requires = NULL;
+ DefElem *d_relocatable = NULL;
DefElem *d_new_version = NULL;
DefElem *d_old_version = NULL;
char *schemaName;
***************
*** 1229,1248 **** CreateExtension(CreateExtensionStmt *stmt)
errmsg("nested CREATE EXTENSION is not supported")));
/*
! * Read the primary control file. Note we assume that it does not contain
! * 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
*/
foreach(lc, stmt->options)
{
DefElem *defel = (DefElem *) lfirst(lc);
! if (strcmp(defel->defname, "schema") == 0)
{
if (d_schema)
ereport(ERROR,
--- 1249,1288 ----
errmsg("nested CREATE EXTENSION is not supported")));
/*
! * Read the statement option list.
! *
! * We need to read some of the options to know what to do next: if the
! * "script" one is in use, we don't want to read any control file.
*/
foreach(lc, stmt->options)
{
DefElem *defel = (DefElem *) lfirst(lc);
! if (strcmp(defel->defname, "script") == 0)
! {
! if (d_script)
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("conflicting or redundant options")));
! d_script = defel;
! }
! else if (strcmp(defel->defname, "relocatable") == 0)
! {
! if (d_relocatable)
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("conflicting or redundant options")));
! d_relocatable = defel;
! }
! else if (strcmp(defel->defname, "requires") == 0)
! {
! if (d_requires)
! ereport(ERROR,
! (errcode(ERRCODE_SYNTAX_ERROR),
! errmsg("conflicting or redundant options")));
! d_requires = defel;
! }
! else if (strcmp(defel->defname, "schema") == 0)
{
if (d_schema)
ereport(ERROR,
***************
*** 1270,1275 **** CreateExtension(CreateExtensionStmt *stmt)
--- 1310,1364 ----
elog(ERROR, "unrecognized option: %s", defel->defname);
}
+ if (d_script)
+ {
+ /*
+ * We are given the extension's script in the SQL command, so create a
+ * control file structure in memory, we won't have a control file on
+ * disk to use.
+ */
+ pcontrol = default_extension_control_file(stmt->extname);
+ pcontrol->script = strVal(d_script->arg);
+
+ if (d_relocatable)
+ pcontrol->relocatable = intVal(d_relocatable->arg) != 0;
+
+ if (d_requires)
+ {
+ /* Need a modifiable copy of string */
+ char *rawnames = pstrdup(strVal(d_requires->arg));
+
+ /* Parse string into list of identifiers */
+ if (!SplitIdentifierString(rawnames, ',', &pcontrol->requires))
+ {
+ /* syntax error in name list */
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"requires\" must be a list of extension names")));
+ }
+ }
+ }
+ else
+ {
+ /*
+ * If the extension script is not in the command, then the user is not
+ * the extension packager and we want to read about relocatable and
+ * requires in the control file, not in the SQL command.
+ */
+ if (d_relocatable || d_requires)
+ ereport(ERROR,
+ (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("parameter \"%s\" is only expected when using CREATE EXTENSION AS",
+ d_relocatable ? "relocatable" : "requires")));
+
+ /*
+ * Read the primary control file. Note we assume that it does not
+ * contain any non-ASCII data, so there is no need to worry about
+ * encoding at this point.
+ */
+ pcontrol = read_extension_control_file(stmt->extname);
+ }
+
/*
* Determine the version to install
*/
***************
*** 1332,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
--- 1421,1433 ----
}
/*
! * Fetch control parameters for installation target version. When the
! * script is given in the command string, no auxiliary file is expected.
*/
! if (pcontrol->script == NULL)
! control = read_extension_aux_control_file(pcontrol, versionName);
! else
! control = pcontrol;
/*
* Determine the target schema to install the extension into
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 3439,3444 **** DropTableSpaceStmt: DROP TABLESPACE name
--- 3439,3446 ----
* CREATE EXTENSION extension
* [ WITH ] [ SCHEMA schema ] [ VERSION version ] [ FROM oldversion ]
*
+ * CREATE EXTENSION extension ... AS $$ ... $$
+ *
*****************************************************************************/
CreateExtensionStmt: CREATE EXTENSION name opt_with create_extension_opt_list
***************
*** 3457,3462 **** CreateExtensionStmt: CREATE EXTENSION name opt_with create_extension_opt_list
--- 3459,3486 ----
n->options = $8;
$$ = (Node *) n;
}
+ | CREATE EXTENSION name opt_with create_extension_opt_list
+ AS Sconst
+ {
+ CreateExtensionStmt *n = makeNode(CreateExtensionStmt);
+ n->extname = $3;
+ n->if_not_exists = false;
+ n->options = lappend($5,
+ makeDefElem("script",
+ (Node *)makeString($7)));
+ $$ = (Node *) n;
+ }
+ | CREATE EXTENSION IF_P NOT EXISTS name
+ opt_with create_extension_opt_list AS Sconst
+ {
+ CreateExtensionStmt *n = makeNode(CreateExtensionStmt);
+ n->extname = $6;
+ n->if_not_exists = false;
+ n->options = lappend($8,
+ makeDefElem("script",
+ (Node *)makeString($10)));
+ $$ = (Node *) n;
+ }
;
create_extension_opt_list:
***************
*** 3479,3484 **** create_extension_opt_item:
--- 3503,3535 ----
{
$$ = makeDefElem("old_version", (Node *)makeString($2));
}
+ | IDENT Sconst
+ /*
+ * 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, "requires") == 0)
+ $$ = makeDefElem("requires", (Node *)makeString($2));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized extension option \"%s\"", $1),
+ parser_errposition(@1)));
+ }
+ | IDENT
+ {
+ 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 extension option \"%s\"", $1),
+ parser_errposition(@1)));
+ }
;
/*****************************************************************************
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 119,124 **** static SimpleOidList table_exclude_oids = {NULL, NULL};
--- 119,127 ----
static SimpleStringList tabledata_exclude_patterns = {NULL, NULL};
static SimpleOidList tabledata_exclude_oids = {NULL, NULL};
+ static SimpleStringList extension_include_patterns = {NULL, NULL};
+ static SimpleOidList extension_include_oids = {NULL, NULL};
+
/* default, if no "inclusion" switches appear, is to dump everything */
static bool include_everything = true;
***************
*** 150,155 **** static void expand_schema_name_patterns(Archive *fout,
--- 153,161 ----
static void expand_table_name_patterns(Archive *fout,
SimpleStringList *patterns,
SimpleOidList *oids);
+ static void expand_extension_name_patterns(Archive *fout,
+ SimpleStringList *patterns,
+ SimpleOidList *oids);
static NamespaceInfo *findNamespace(Archive *fout, Oid nsoid, Oid objoid);
static void dumpTableData(Archive *fout, TableDataInfo *tdinfo);
static void guessConstraintInheritance(TableInfo *tblinfo, int numTables);
***************
*** 165,173 **** static void dumpSecLabel(Archive *fout, const char *target,
static int findSecLabels(Archive *fout, Oid classoid, Oid objoid,
SecLabelItem **items);
static int collectSecLabels(Archive *fout, SecLabelItem **items);
! static void dumpDumpableObject(Archive *fout, DumpableObject *dobj);
static void dumpNamespace(Archive *fout, NamespaceInfo *nspinfo);
! static void dumpExtension(Archive *fout, ExtensionInfo *extinfo);
static void dumpType(Archive *fout, TypeInfo *tyinfo);
static void dumpBaseType(Archive *fout, TypeInfo *tyinfo);
static void dumpEnumType(Archive *fout, TypeInfo *tyinfo);
--- 171,181 ----
static int findSecLabels(Archive *fout, Oid classoid, Oid objoid,
SecLabelItem **items);
static int collectSecLabels(Archive *fout, SecLabelItem **items);
! static void dumpDumpableObject(Archive *fout, DumpableObject *dobj,
! DumpableObject **dobjs, int numObjs);
static void dumpNamespace(Archive *fout, NamespaceInfo *nspinfo);
! static void dumpExtension(Archive *fout, ExtensionInfo *extinfo,
! DumpableObject **dobjs, int numObjs);
static void dumpType(Archive *fout, TypeInfo *tyinfo);
static void dumpBaseType(Archive *fout, TypeInfo *tyinfo);
static void dumpEnumType(Archive *fout, TypeInfo *tyinfo);
***************
*** 322,327 **** main(int argc, char **argv)
--- 330,336 ----
{"superuser", required_argument, NULL, 'S'},
{"table", required_argument, NULL, 't'},
{"exclude-table", required_argument, NULL, 'T'},
+ {"extension-script", required_argument, NULL, 'X'},
{"no-password", no_argument, NULL, 'w'},
{"password", no_argument, NULL, 'W'},
{"username", required_argument, NULL, 'U'},
***************
*** 388,394 **** main(int argc, char **argv)
}
}
! while ((c = getopt_long(argc, argv, "abcCE:f:F:h:in:N:oOp:RsS:t:T:U:vwWxZ:",
long_options, &optindex)) != -1)
{
switch (c)
--- 397,403 ----
}
}
! while ((c = getopt_long(argc, argv, "abcCE:f:F:h:in:N:oOp:RsS:t:T:U:vwWxX:Z:",
long_options, &optindex)) != -1)
{
switch (c)
***************
*** 471,476 **** main(int argc, char **argv)
--- 480,489 ----
simple_string_list_append(&table_exclude_patterns, optarg);
break;
+ case 'X': /* include extension(s) script */
+ simple_string_list_append(&extension_include_patterns, optarg);
+ break;
+
case 'U':
username = pg_strdup(optarg);
break;
***************
*** 670,675 **** main(int argc, char **argv)
--- 683,692 ----
expand_table_name_patterns(fout, &tabledata_exclude_patterns,
&tabledata_exclude_oids);
+ /* Expand extension selection patterns into OID lists */
+ expand_extension_name_patterns(fout, &extension_include_patterns,
+ &extension_include_oids);
+
/* non-matching exclusion patterns aren't an error */
/*
***************
*** 746,752 **** main(int argc, char **argv)
/* Now the rearrangeable objects. */
for (i = 0; i < numObjs; i++)
! dumpDumpableObject(fout, dobjs[i]);
/*
* Set up options info to ensure we dump what we want.
--- 763,769 ----
/* Now the rearrangeable objects. */
for (i = 0; i < numObjs; i++)
! dumpDumpableObject(fout, dobjs[i], dobjs, numObjs);
/*
* Set up options info to ensure we dump what we want.
***************
*** 830,835 **** help(const char *progname)
--- 847,853 ----
printf(_(" -S, --superuser=NAME superuser user name to use in plain-text format\n"));
printf(_(" -t, --table=TABLE dump the named table(s) only\n"));
printf(_(" -T, --exclude-table=TABLE do NOT dump the named table(s)\n"));
+ printf(_(" -X, --extension-script dunp named extension(s) scripts\n"));
printf(_(" -x, --no-privileges do not dump privileges (grant/revoke)\n"));
printf(_(" --binary-upgrade for use by upgrade utilities only\n"));
printf(_(" --column-inserts dump data as INSERT commands with column names\n"));
***************
*** 1062,1067 **** expand_table_name_patterns(Archive *fout,
--- 1080,1132 ----
}
/*
+ * Find the OIDs of all extensions matching the given list of patterns,
+ * and append them to the given ExtensionMembers list.
+ */
+ static void
+ expand_extension_name_patterns(Archive *fout,
+ SimpleStringList *patterns,
+ SimpleOidList *oids)
+ {
+ PQExpBuffer query;
+ PGresult *res;
+ SimpleStringListCell *cell;
+ int i;
+
+ if (patterns->head == NULL)
+ return; /* nothing to do */
+
+ query = createPQExpBuffer();
+
+ /*
+ * We use UNION ALL rather than UNION; this might sometimes result in
+ * duplicate entries in the OID list, but we don't care.
+ */
+
+ for (cell = patterns->head; cell; cell = cell->next)
+ {
+ if (cell != patterns->head)
+ appendPQExpBuffer(query, "UNION ALL\n");
+ appendPQExpBuffer(query,
+ "SELECT e.oid"
+ "\nFROM pg_catalog.pg_extension e\n");
+ processSQLNamePattern(GetConnection(fout), query,
+ cell->val, false, false,
+ NULL, "e.extname", NULL, NULL);
+ }
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ for (i = 0; i < PQntuples(res); i++)
+ {
+ simple_oid_list_append(oids, atooid(PQgetvalue(res, i, 0)));
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+ }
+
+ /*
* selectDumpableNamespace: policy-setting subroutine
* Mark a namespace as to be dumped or not
*/
***************
*** 1208,1213 **** selectDumpableExtension(ExtensionInfo *extinfo)
--- 1273,1286 ----
extinfo->dobj.dump = false;
else
extinfo->dobj.dump = include_everything;
+
+ /*
+ * In any case, an extension can be included an inclusion switch
+ */
+ if (extension_include_oids.head &&
+ simple_oid_list_member(&extension_include_oids,
+ extinfo->dobj.catId.oid))
+ extinfo->dobj.dump = true;
}
/*
***************
*** 2737,2742 **** getExtensions(Archive *fout, int *numExtensions)
--- 2810,2816 ----
int i_extversion;
int i_extconfig;
int i_extcondition;
+ int i_extrequires;
/*
* Before 9.1, there are no extensions.
***************
*** 2752,2761 **** getExtensions(Archive *fout, int *numExtensions)
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
! appendPQExpBuffer(query, "SELECT x.tableoid, x.oid, "
! "x.extname, n.nspname, x.extrelocatable, x.extversion, x.extconfig, x.extcondition "
! "FROM pg_extension x "
! "JOIN pg_namespace n ON n.oid = x.extnamespace");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
--- 2826,2842 ----
/* Make sure we are in proper schema */
selectSourceSchema(fout, "pg_catalog");
! /* Get the extension and its requirements: extensions it depends on */
! appendPQExpBuffer(query,
! "SELECT x.tableoid, x.oid, x.extname, n.nspname, "
! "x.extrelocatable, x.extversion, x.extconfig, "
! "x.extcondition, "
! "array_to_string(array_agg(e.extname), ',') as extrequires "
! "FROM pg_extension x JOIN pg_namespace n ON n.oid = x.extnamespace "
! "LEFT JOIN pg_depend d on d.objid = x.oid "
! "and d.refclassid = 'pg_extension'::regclass "
! "LEFT JOIN pg_extension e ON e.oid = d.refobjid "
! "group by 1, 2, 3, 4, 5, 6, 7, 8");
res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
***************
*** 2771,2776 **** getExtensions(Archive *fout, int *numExtensions)
--- 2852,2858 ----
i_extversion = PQfnumber(res, "extversion");
i_extconfig = PQfnumber(res, "extconfig");
i_extcondition = PQfnumber(res, "extcondition");
+ i_extrequires = PQfnumber(res, "extrequires");
for (i = 0; i < ntups; i++)
{
***************
*** 2784,2789 **** getExtensions(Archive *fout, int *numExtensions)
--- 2866,2872 ----
extinfo[i].extversion = pg_strdup(PQgetvalue(res, i, i_extversion));
extinfo[i].extconfig = pg_strdup(PQgetvalue(res, i, i_extconfig));
extinfo[i].extcondition = pg_strdup(PQgetvalue(res, i, i_extcondition));
+ extinfo[i].extrequires = pg_strdup(PQgetvalue(res, i, i_extrequires));
/* Decide whether we want to dump it */
selectDumpableExtension(&(extinfo[i]));
***************
*** 7248,7254 **** collectComments(Archive *fout, CommentItem **items)
* ArchiveEntries (TOC objects) for each object to be dumped.
*/
static void
! dumpDumpableObject(Archive *fout, DumpableObject *dobj)
{
switch (dobj->objType)
{
--- 7331,7338 ----
* ArchiveEntries (TOC objects) for each object to be dumped.
*/
static void
! dumpDumpableObject(Archive *fout, DumpableObject *dobj,
! DumpableObject **dobjs, int numObjs)
{
switch (dobj->objType)
{
***************
*** 7256,7262 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj)
dumpNamespace(fout, (NamespaceInfo *) dobj);
break;
case DO_EXTENSION:
! dumpExtension(fout, (ExtensionInfo *) dobj);
break;
case DO_TYPE:
dumpType(fout, (TypeInfo *) dobj);
--- 7340,7346 ----
dumpNamespace(fout, (NamespaceInfo *) dobj);
break;
case DO_EXTENSION:
! dumpExtension(fout, (ExtensionInfo *) dobj, dobjs, numObjs);
break;
case DO_TYPE:
dumpType(fout, (TypeInfo *) dobj);
***************
*** 7431,7444 **** dumpNamespace(Archive *fout, NamespaceInfo *nspinfo)
* writes out to fout the queries to recreate an extension
*/
static void
! dumpExtension(Archive *fout, ExtensionInfo *extinfo)
{
PQExpBuffer q;
PQExpBuffer delq;
PQExpBuffer labelq;
char *qextname;
- /* Skip if not to be dumped */
if (!extinfo->dobj.dump || dataOnly)
return;
--- 7515,7528 ----
* writes out to fout the queries to recreate an extension
*/
static void
! dumpExtension(Archive *fout, ExtensionInfo *extinfo,
! DumpableObject **dobjs, int numObjs)
{
PQExpBuffer q;
PQExpBuffer delq;
PQExpBuffer labelq;
char *qextname;
if (!extinfo->dobj.dump || dataOnly)
return;
***************
*** 7450,7456 **** dumpExtension(Archive *fout, ExtensionInfo *extinfo)
appendPQExpBuffer(delq, "DROP EXTENSION %s;\n", qextname);
! if (!binary_upgrade)
{
/*
* In a regular dump, we use IF NOT EXISTS so that there isn't a
--- 7534,7611 ----
appendPQExpBuffer(delq, "DROP EXTENSION %s;\n", qextname);
! /* dump this extension's content */
! if (extension_include_oids.head
! && simple_oid_list_member(&extension_include_oids,
! extinfo->dobj.catId.oid))
! {
! Archive *eout; /* the extension's script file */
! ArchiveHandle *EH;
! TocEntry *te;
! int i;
!
! /* See comment for !binary_upgrade case for IF NOT EXISTS. */
! appendPQExpBuffer(q,
! "CREATE EXTENSION IF NOT EXISTS %s\n"
! " WITH SCHEMA %s\n"
! " VERSION '%s'\n"
! " %s\n"
! " REQUIRES '%s'\n"
! "AS $%s$\n",
! qextname,
! fmtId(extinfo->namespace),
! extinfo->extversion,
! extinfo->relocatable ? "relocatable":"norelocatable",
! extinfo->extrequires,
! qextname);
!
! /*
! * Have another archive for this extension: this allows us to simply
! * walk the extension's dependencies and use the existing pg_dump code
! * to get the object create statement to be added in the script.
! *
! */
! eout = CreateArchive(NULL, archNull, 0, archModeAppend);
!
! EH = (ArchiveHandle *) eout;
!
! /* grab existing connection and remote version information */
! EH->connection = ((ArchiveHandle *)fout)->connection;
! eout->remoteVersion = fout->remoteVersion;
!
! /* dump all objects for this extension, that have been sorted out in
! * the right order following dependencies etc */
! for (i = 0; i < numObjs; i++)
! {
! DumpableObject *dobj = dobjs[i];
!
! if (dobj->ext_member
! && dobj->extension_oid == extinfo->dobj.catId.oid)
! {
! bool dump = dobj->dump;
!
! /* force the DumpableObject here to be dumped */
! dobj->dump = true;
!
! /* Dump the Object */
! dumpDumpableObject(eout, dobj, dobjs, numObjs);
!
! /* now restore its old dump flag value */
! dobj->dump = dump;
! }
! }
!
! /* restore the eout Archive into the local buffer */
! for (te = EH->toc->next; te != EH->toc; te = te->next)
! {
! if (strlen(te->defn) > 0)
! appendPQExpBuffer(q, "%s", te->defn);
! }
! CloseArchive(eout);
!
! appendPQExpBuffer(q, "$%s$;\n", qextname);
! }
! else if (!binary_upgrade)
{
/*
* In a regular dump, we use IF NOT EXISTS so that there isn't a
***************
*** 14101,14106 **** getExtensionMembership(Archive *fout, ExtensionInfo extinfo[],
--- 14256,14280 ----
dobj->ext_member = true;
+ /* track the extension Oid in the object for later processing */
+ if (extension_include_oids.head
+ && simple_oid_list_member(&extension_include_oids, refobjId.oid))
+ {
+ dobj->extension_oid = refobjId.oid;
+ }
+
+ /* The Shell Type needs to be in the --extension-script */
+ if (dobj->objType == DO_TYPE)
+ {
+ TypeInfo *typeInfo = (TypeInfo *) dobj;
+
+ if (typeInfo->shellType)
+ {
+ typeInfo->shellType->dobj.ext_member = true;
+ typeInfo->shellType->dobj.extension_oid = dobj->extension_oid;
+ }
+ }
+
/*
* Normally, mark the member object as not to be dumped. But in
* binary upgrades, we still dump the members individually, since the
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
***************
*** 133,138 **** typedef struct _dumpableObject
--- 133,139 ----
struct _namespaceInfo *namespace; /* containing namespace, or NULL */
bool dump; /* true if we want to dump this object */
bool ext_member; /* true if object is member of extension */
+ Oid extension_oid; /* Oid of the extension woning this object */
DumpId *dependencies; /* dumpIds of objects this one depends on */
int nDeps; /* number of valid dependencies */
int allocDeps; /* allocated size of dependencies[] */
***************
*** 151,156 **** typedef struct _extensionInfo
--- 152,158 ----
char *namespace; /* schema containing extension's objects */
bool relocatable;
char *extversion;
+ char *extrequires;
char *extconfig; /* info about configuration tables */
char *extcondition;
} ExtensionInfo;
On Mon, Nov 12, 2012 at 11:00 AM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:
Please find attached to this email an RFC patch implementing the basics
of the pg_dump --extension-script option. After much discussion around
the concept of an inline extension, we decided last year that a good
first step would be pg_dump support for an extension's script.
Do you have a link to the original thread? I have to confess I don't
remember what the purpose of this was and, heh heh, there are no
documentation changes in the patch itself either.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
Please find attached to this email an RFC patch implementing the basics
of the pg_dump --extension-script option. After much discussion around
the concept of an inline extension, we decided last year that a good
first step would be pg_dump support for an extension's script.Do you have a link to the original thread? I have to confess I don't
remember what the purpose of this was and, heh heh, there are no
documentation changes in the patch itself either.
My notes include those links to the original thread:
http://archives.postgresql.org/message-id/3157.1327298440@sss.pgh.pa.us
http://archives.postgresql.org/pgsql-hackers/2012-01/msg01311.php
https://commitfest.postgresql.org/action/patch_view?id=746
I could of course work on documenting the changes prior to the
reviewing, the thing is that I've been taking a different implementation
route towards the pg_dump --extension-script idea we talked about, that
I think is much simpler than anything else.
So I'd like to know if that approach is deemed acceptable by the
Guardians Of The Code before expanding any more hour on this…
It basically boils down to this hunk in dumpExtension():
output CREATE EXTENSION x WITH … AS $x$
/*
* Have another archive for this extension: this allows us to simply
* walk the extension's dependencies and use the existing pg_dump code
* to get the object create statement to be added in the script.
*
*/
eout = CreateArchive(NULL, archNull, 0, archModeAppend);
EH = (ArchiveHandle *) eout;
/* grab existing connection and remote version information */
EH->connection = ((ArchiveHandle *)fout)->connection;
eout->remoteVersion = fout->remoteVersion;
/* dump all objects for this extension, that have been sorted out in
* the right order following dependencies etc */
...
/* restore the eout Archive into the local buffer */
for (te = EH->toc->next; te != EH->toc; te = te->next)
{
if (strlen(te->defn) > 0)
appendPQExpBuffer(q, "%s", te->defn);
}
CloseArchive(eout);
output $x$;
What do you think?
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
On Thu, Nov 15, 2012 at 3:09 PM, Dimitri Fontaine
<dimitri@2ndquadrant.fr> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
Please find attached to this email an RFC patch implementing the basics
of the pg_dump --extension-script option. After much discussion around
the concept of an inline extension, we decided last year that a good
first step would be pg_dump support for an extension's script.Do you have a link to the original thread? I have to confess I don't
remember what the purpose of this was and, heh heh, there are no
documentation changes in the patch itself either.My notes include those links to the original thread:
http://archives.postgresql.org/message-id/3157.1327298440@sss.pgh.pa.us
http://archives.postgresql.org/pgsql-hackers/2012-01/msg01311.php
https://commitfest.postgresql.org/action/patch_view?id=746I could of course work on documenting the changes prior to the
reviewing, the thing is that I've been taking a different implementation
route towards the pg_dump --extension-script idea we talked about, that
I think is much simpler than anything else.So I'd like to know if that approach is deemed acceptable by the
Guardians Of The Code before expanding any more hour on this…It basically boils down to this hunk in dumpExtension():
output CREATE EXTENSION x WITH … AS $x$
/*
* Have another archive for this extension: this allows us to simply
* walk the extension's dependencies and use the existing pg_dump code
* to get the object create statement to be added in the script.
*
*/
eout = CreateArchive(NULL, archNull, 0, archModeAppend);EH = (ArchiveHandle *) eout;
/* grab existing connection and remote version information */
EH->connection = ((ArchiveHandle *)fout)->connection;
eout->remoteVersion = fout->remoteVersion;/* dump all objects for this extension, that have been sorted out in
* the right order following dependencies etc */
.../* restore the eout Archive into the local buffer */
for (te = EH->toc->next; te != EH->toc; te = te->next)
{
if (strlen(te->defn) > 0)
appendPQExpBuffer(q, "%s", te->defn);
}
CloseArchive(eout);output $x$;
What do you think?
That approach seems likely to break things for the hoped-for parallel
pg_dump feature, though I'm not sure exactly in what way.
Beyond that, I think much of the appeal of the extension feature is
that it dumps as "CREATE EXTENSION hstore;" and nothing more. That
allows you to migrate a dump between systems with different but
compatible versions of the hstore and have things work as intended.
I'm not opposed to the idea of being able to make extensions without
files on disk work ... but I consider it a niche use case; the
behavior we have right now works well for me and hopefully for others
most of the time.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
That approach seems likely to break things for the hoped-for parallel
pg_dump feature, though I'm not sure exactly in what way.
Will the parallel dump solve the dependencies and extension membership
properties in parallel too?
Beyond that, I think much of the appeal of the extension feature is
that it dumps as "CREATE EXTENSION hstore;" and nothing more. That
allows you to migrate a dump between systems with different but
compatible versions of the hstore and have things work as intended.
Yes. That's the only use case supported so far. The contrib/ use case.
I'm not opposed to the idea of being able to make extensions without
files on disk work ... but I consider it a niche use case; the
behavior we have right now works well for me and hopefully for others
most of the time.
I hear you. I'm not doing that on my free time, it's not a hobby, I have
customers that want it bad enough to be willing to sponsor my work here.
I hope that helps you figuring about the use case being a niche or not.
The current extension support has been targeted at a single use case,
because that's how you bootstrap that kind of feature. We have request
for extensions that will not include a part written in C.
We've been around the topic last year, we spent much energy trying to
come up with something easy enough to accept as a first step in that
direction, and the conclusion at the time was that we want to be able to
dump an extension's script. That's what my current patch is all about.
More about use cases. Consider PL/Proxy. By the way, that should really
really get in core and be called a FOREIGN FUNCTION, but let's get back
on topic. So I have customers with between 8 and 256 plproxy partitions,
that means each database upgrade has to reach that many databases.
Now, I've built a automatic system that will fetch the PL function code
from the staging database area, put them into files depending on the
schema they live in, package those files into a single one that can be
used by the CREATE EXTENSION command, automatically create an upgrade
file to be able to ALTER EXTENSION … TO VERSION …, and create a bunch of
debian packages out of that (a single debian source package that will
build as many binary packages as we have extensions).
Then, the system will push those packages to an internal repository, run
apt-get update on all the database hosts, then connect to each partition
and run the upgrade command.
All of that could get simplified to getting the PL code into a single
SQL command then running it on all the members of the cluster by using a
plproxy RUN ON ALL command, now that it's a self-contained single SQL
command.
Of course that's only one use case, but that email is already only too
long for what it does: rehashing a story we already ran last year.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Robert Haas <robertmhaas@gmail.com> writes:
I'm not opposed to the idea of being able to make extensions without
files on disk work ... but I consider it a niche use case; the
behavior we have right now works well for me and hopefully for others
most of the time.
Apparently I'm not the only one doing extensions without anything to
compile, all SQL:
http://keithf4.com/extension_tips_3
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
On 19 November 2012 16:25, Robert Haas <robertmhaas@gmail.com> wrote:
Beyond that, I think much of the appeal of the extension feature is
that it dumps as "CREATE EXTENSION hstore;" and nothing more. That
allows you to migrate a dump between systems with different but
compatible versions of the hstore and have things work as intended.
I'm not opposed to the idea of being able to make extensions without
files on disk work ... but I consider it a niche use case; the
behavior we have right now works well for me and hopefully for others
most of the time.
Distributing software should only happen by files?
So why does Stackbuilder exist on the Windows binary?
Why does yum exist? What's wrong with ftp huh?
Why does CPAN?
I've a feeling this case might be a sensible way forwards, not a niche at all.
--
Simon Riggs http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
On 20.11.2012 21:25, Simon Riggs wrote:
On 19 November 2012 16:25, Robert Haas<robertmhaas@gmail.com> wrote:
Beyond that, I think much of the appeal of the extension feature is
that it dumps as "CREATE EXTENSION hstore;" and nothing more. That
allows you to migrate a dump between systems with different but
compatible versions of the hstore and have things work as intended.
I'm not opposed to the idea of being able to make extensions without
files on disk work ... but I consider it a niche use case; the
behavior we have right now works well for me and hopefully for others
most of the time.Distributing software should only happen by files?
So why does Stackbuilder exist on the Windows binary?
Why does yum exist? What's wrong with ftp huh?
Why does CPAN?
I've a feeling this case might be a sensible way forwards, not a niche at all.
I have to join Robert in scratching my head over this. I don't
understand what the use case is. Can you explain? I don't understand the
comparison with stackbuilder, yum, ftp and CPAN. CPAN seems close to
pgxn, but what does that have to do with this patch?
On 20.11.2012 11:08, Dimitri Fontaine wrote:
Apparently I'm not the only one doing extensions without anything to
compile, all SQL:
No doubt about that. I'm sure extensions written in pure SQL or PL/pgSQL
are very common. But what does that have to do with this patch?
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Heikki Linnakangas <hlinnakangas@vmware.com> writes:
No doubt about that. I'm sure extensions written in pure SQL or PL/pgSQL are
very common. But what does that have to do with this patch?
This patch is all about enabling users to create extension without
having to ship them as root on the file system of the database(s)
server(s) first.
When you're having to code your extension in C, you know you're in for
shipping an executable binary (.so, .dylib or .dll), and for security
reasons it's well understood that you will have to get root privileges
on the server's file system to ship your binary before to be able to ask
PostgreSQL to please load it and execute the code in there.
When you can code your extension using dynamic code such as SQL or
PL/pgSQL, PL/pythonu or PL/perl, there's absolutely no good reason to
have to do the "ship on the server's file system first" that I can see.
Technically creating an extension "inline" (sending its definition in
the CREATE EXTENSION query itself) solves the problem of having to
access the server's file system as root.
Then, next pg_dump will include "CREATE EXTENSION foo;" as usual and at
pg_restore time that access files on the file systems. But maybe you
still are not granted access to the server's file system as root on the
pg_restore target, right? So now you need to be able to include the
extension's script into the dump.
Now, we don't want to have more than one kind of extensions. That's what
we learnt all together when reviewing my proposal from last year. Having
more than one way to ship an extension is good, having two different
animals with two different incompatible behaviors named the same thing
is bad. The solution we found is then to be able to include an
extension's script into pg_dump's output, and that's what my current
patch implements, per last year review.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 05.12.2012 12:22, Dimitri Fontaine wrote:
Heikki Linnakangas<hlinnakangas@vmware.com> writes:
No doubt about that. I'm sure extensions written in pure SQL or PL/pgSQL are
very common. But what does that have to do with this patch?This patch is all about enabling users to create extension without
having to ship them as root on the file system of the database(s)
server(s) first.
...
When you can code your extension using dynamic code such as SQL or
PL/pgSQL, PL/pythonu or PL/perl, there's absolutely no good reason to
have to do the "ship on the server's file system first" that I can see.Technically creating an extension "inline" (sending its definition in
the CREATE EXTENSION query itself) solves the problem of having to
access the server's file system as root.
Ok, I'm with you this far.
Then, next pg_dump will include "CREATE EXTENSION foo;" as usual and at
pg_restore time that access files on the file systems. But maybe you
still are not granted access to the server's file system as root on the
pg_restore target, right? So now you need to be able to include the
extension's script into the dump.
Now you lost me. I can see the need to install an extension without
access to the filesystem - but it does not follow that you need to be
able to dump an extension script. In general, I think you're confusing
three things:
1. The way an extension is deployed. It could be by copying the files to
the file system, by sending them over libpq, or shipped in .rpms by the
OS, or something else.
2. The way an extension's files are laid out before it's deployed.
Typically, you want to keep an extension's source code (whether it's C
or SQL or plpython) in a version control system.
3. Being able to deploy extensions to the server without superuser or
root access
I think it would make this discussion a lot clearer if we keep those
concerns separate. It's useful to have a mechanism to deploy an
extension over libpq. It's not clear to me if you're envisioning to
change 2. I don't think we should; having a .sql file and a .control
file seems perfectly fine to me.
I'd suggest that we just need a way to upload an extension to the server
via libpq. Something like "UPLOAD EXTENSION foo", which goes into COPY
mode and you can stream over a zip file containing the .sql and .control
file that make up the extension. The server would unzip the file into
the right directory.
Now, point 3 is yet another issue. If you need to copy the extension
files to /usr/share/, you need root (or similar) access on the
filesystem. We could allow extensions to be located somewhere in the
data directory instead. Like $PGDATA/extensions. But again, that would
be an independent change from 1 and 2.
And I still don't understand why pg_dump needs to know about any of this...
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2012-12-05 19:13:10 +0200, Heikki Linnakangas wrote:
On 05.12.2012 12:22, Dimitri Fontaine wrote:
Heikki Linnakangas<hlinnakangas@vmware.com> writes:
No doubt about that. I'm sure extensions written in pure SQL or PL/pgSQL are
very common. But what does that have to do with this patch?This patch is all about enabling users to create extension without
having to ship them as root on the file system of the database(s)
server(s) first.
...
When you can code your extension using dynamic code such as SQL or
PL/pgSQL, PL/pythonu or PL/perl, there's absolutely no good reason to
have to do the "ship on the server's file system first" that I can see.Technically creating an extension "inline" (sending its definition in
the CREATE EXTENSION query itself) solves the problem of having to
access the server's file system as root.Ok, I'm with you this far.
Then, next pg_dump will include "CREATE EXTENSION foo;" as usual and at
pg_restore time that access files on the file systems. But maybe you
still are not granted access to the server's file system as root on the
pg_restore target, right? So now you need to be able to include the
extension's script into the dump.Now you lost me. I can see the need to install an extension without access
to the filesystem - but it does not follow that you need to be able to dump
an extension script. In general, I think you're confusing three things:1. The way an extension is deployed. It could be by copying the files to the
file system, by sending them over libpq, or shipped in .rpms by the OS, or
something else.2. The way an extension's files are laid out before it's deployed.
Typically, you want to keep an extension's source code (whether it's C or
SQL or plpython) in a version control system.3. Being able to deploy extensions to the server without superuser or root
accessI think it would make this discussion a lot clearer if we keep those
concerns separate. It's useful to have a mechanism to deploy an extension
over libpq. It's not clear to me if you're envisioning to change 2. I don't
think we should; having a .sql file and a .control file seems perfectly fine
to me.I'd suggest that we just need a way to upload an extension to the server via
libpq. Something like "UPLOAD EXTENSION foo", which goes into COPY mode and
you can stream over a zip file containing the .sql and .control file that
make up the extension. The server would unzip the file into the right
directory.
Not sure what is better here. Dimitri's way seems to be easier to manage
for people who maintain their database in update scripts and such and
your's seems to be a bit simpler from the backend perspective.
Now, point 3 is yet another issue. If you need to copy the extension files
to /usr/share/, you need root (or similar) access on the filesystem. We
could allow extensions to be located somewhere in the data directory
instead. Like $PGDATA/extensions. But again, that would be an independent
change from 1 and 2.
I think installing them into some global space is not a sensible
interim-step. Having a UPLOAD EXTENSION in one database affect all other
databases or even clusters (because you e.g. updated the version) would
be really confusing.
Which leads to:
And I still don't understand why pg_dump needs to know about any of this...
Extensions should be fully per-database and we want pg_dump backups to
be restorable into another database/clusters/servers. So having a mode
for pg_dump that actually makes dumps that are usable for recovering
after a disaster seems sensible to me. Otherwise you need to redeploy
from the VCS or whatever, which isn't really what you want when
restoring a database backup.
Comparing the situation to the one where you have extensions provided by
the packaging system or by /contrib or whatever doesn't seem to be all
that valid to me.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 05.12.2012 19:27, Andres Freund wrote:
And I still don't understand why pg_dump needs to know about any of this...
Extensions should be fully per-database and we want pg_dump backups to
be restorable into another database/clusters/servers. So having a mode
for pg_dump that actually makes dumps that are usable for recovering
after a disaster seems sensible to me. Otherwise you need to redeploy
from the VCS or whatever, which isn't really what you want when
restoring a database backup.
Ok - but that it yet another issue, not to be confused with how you
deploy extensions. If we are to have such a mode in pg_dump, it should
be able to dump *all* extensions, regardless of how they were deployed.
(ok, might be difficult for extensions that include .so files or
similar, but certainly for an extension that only contains a .sql file
and a .control file, it shouldn't matter how it was deployed).
And whether extension control files (or the same information stored in a
table or wherever) should be per-database or per cluster - that's *yet*
another separate issue. You could argue for either behavior.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2012-12-05 19:40:58 +0200, Heikki Linnakangas wrote:
On 05.12.2012 19:27, Andres Freund wrote:
And I still don't understand why pg_dump needs to know about any of this...
Extensions should be fully per-database and we want pg_dump backups to
be restorable into another database/clusters/servers. So having a mode
for pg_dump that actually makes dumps that are usable for recovering
after a disaster seems sensible to me. Otherwise you need to redeploy
from the VCS or whatever, which isn't really what you want when
restoring a database backup.Ok - but that it yet another issue, not to be confused with how you deploy
extensions. If we are to have such a mode in pg_dump, it should be able to
dump *all* extensions, regardless of how they were deployed. (ok, might be
difficult for extensions that include .so files or similar, but certainly
for an extension that only contains a .sql file and a .control file, it
shouldn't matter how it was deployed).
For me it seems pretty natural to support dumping extension the way they
got created. I.e. a plain CREATE EXTENSION ...; if the extension was
preinstalled and some form that includes the extension source if you
installed it via the connection.
Extensions that were installed in some global manner *should* not be
dumped with ones installed over the connection. E.g. dumping /contrib or
packaged modules seems to be a bad idea to me.
That would possibly be useful as a debugging tool, but I don't see much
point besides that.
And whether extension control files (or the same information stored in a
table or wherever) should be per-database or per cluster - that's *yet*
another separate issue. You could argue for either behavior.
What would be the case for the per-cluster in the case of uploaded
extensions?
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Heikki Linnakangas <hlinnakangas@vmware.com> writes:
Ok - but that it yet another issue, not to be confused with how you deploy
extensions. If we are to have such a mode in pg_dump, it should be able to
dump *all* extensions, regardless of how they were deployed. (ok, might be
difficult for extensions that include .so files or similar, but certainly
for an extension that only contains a .sql file and a .control file, it
shouldn't matter how it was deployed).
That's what you have in the current patch. Try
=> create extension 'hstore';
$ pg_dump --extension-script hstore
It works as far as the script is concerned, and the control file is not
needed any more because the script as dumped does not need it, except
for the two parameters 'require' and 'relocatable', that are added in
the SQL command.
The binary file is not taken care of by this mechanism. Remember that in
most cases pg_restore will not be granted to deploy it at the right
place anyway, for security reasons.
And whether extension control files (or the same information stored in a
table or wherever) should be per-database or per cluster - that's *yet*
another separate issue. You could argue for either behavior.
At the SQL level, extensions do live in a database. The only reason why
we currently have them on the file system is binary executables (.so,
.dylib, .dll). And those are not per database, not even per cluster, not
even per major version, they are *per server*. It's something that makes
me very sad, and that I want to have the chance to fix later, but that
won't happen in 9.3, and certainly not in that very patch…
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andres Freund <andres@2ndquadrant.com> writes:
On 2012-12-05 19:13:10 +0200, Heikki Linnakangas wrote:
And I still don't understand why pg_dump needs to know about any of this...
Extensions should be fully per-database and we want pg_dump backups to
be restorable into another database/clusters/servers.
Wait a minute. I haven't bought into either of those statements, and
most particularly not the first one.
Upthread, Dimitri claimed that he wasn't creating two different kinds of
extensions with this patch, but the more I read about it the more it
seems that he *is* making a fundamentally different kind of animal.
And I don't think it's necessarily a good idea, especially not if we
still call it an extension.
I kind of like Heikki's idea of leaving CREATE EXTENSION alone and
inventing a separate "UPLOAD EXTENSION" operation, but there's a problem
with that: in many, probably most, installations, the server does not
and should not have permission to scribble on the directories where the
extension scripts are stored. Possibly we could circumvent that by
creating an auxiliary extensions directory under $PGDATA. (But then
it starts to seem like pg_dumpall --- not pg_dump --- ought to include
those files in its output...)
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Heikki Linnakangas <hlinnakangas@vmware.com> writes:
And whether extension control files (or the same information stored in a
table or wherever) should be per-database or per cluster - that's *yet*
another separate issue. You could argue for either behavior.
I think anyone arguing for the former is confusing an installed
extension with a not-installed one. Maybe it would help if we adopted
different terminologies. Perhaps call the control+sql files a "template",
while using "extension" for the installed entity?
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2012-12-05 12:55:42 -0500, Tom Lane wrote:
Andres Freund <andres@2ndquadrant.com> writes:
On 2012-12-05 19:13:10 +0200, Heikki Linnakangas wrote:
And I still don't understand why pg_dump needs to know about any of this...
Extensions should be fully per-database and we want pg_dump backups to
be restorable into another database/clusters/servers.Wait a minute. I haven't bought into either of those statements, and
most particularly not the first one.
Ok.
Upthread, Dimitri claimed that he wasn't creating two different kinds of
extensions with this patch, but the more I read about it the more it
seems that he *is* making a fundamentally different kind of animal.
And I don't think it's necessarily a good idea, especially not if we
still call it an extension.
I have to admit I haven't read the whole discussion about this. And I
also have to say that I have no idea yet whether I like the current
implementation because I haven't looked at it yet. I just wanted to give
input to the separate problems Heikki listed. Because I wished for
something roughly like this for years...
To me it seems to be sensible that extensions which are preinstalled on
the system are global and extensions which a single user inside a single
database created are per database.
Imo that doesn't make them all that fundamentally different.
I kind of like Heikki's idea of leaving CREATE EXTENSION alone and
inventing a separate "UPLOAD EXTENSION" operation, but there's a problem
with that: in many, probably most, installations, the server does not
and should not have permission to scribble on the directories where the
extension scripts are stored. Possibly we could circumvent that by
creating an auxiliary extensions directory under $PGDATA. (But then
it starts to seem like pg_dumpall --- not pg_dump --- ought to include
those files in its output...)
UPLOAD EXTENSION seems to be a good idea.
But I really really would like them to go to a per-database directory
not a per-cluster one. Otherwise the coordination between different
database "owners" inside a cluster will get really hairy. I want to be
able to install different versions of an application into different
databases.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 05.12.2012 20:07, Tom Lane wrote:
Heikki Linnakangas<hlinnakangas@vmware.com> writes:
And whether extension control files (or the same information stored in a
table or wherever) should be per-database or per cluster - that's *yet*
another separate issue. You could argue for either behavior.I think anyone arguing for the former is confusing an installed
extension with a not-installed one. Maybe it would help if we adopted
different terminologies. Perhaps call the control+sql files a "template",
while using "extension" for the installed entity?
+1 on the naming.
You could still argue that templates should be per-database. It would
make life easier for someone who is database owner but not superuser,
for example, allowing you to install an extension that only affects your
own database (assuming we set up the permissions so that that's
possible, of course).
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
At the SQL level, extensions do live in a database. The only reason why
we currently have them on the file system is binary executables (.so,
.dylib, .dll). And those are not per database, not even per cluster, not
even per major version, they are *per server*. It's something that makes
me very sad, and that I want to have the chance to fix later, but that
won't happen in 9.3, and certainly not in that very patch…
I think you're wasting your time to imagine that that case will ever be
"fixed". Allowing the server to scribble on executable files would set
off all kinds of security alarm bells, and rightly so. If Postgres ever
did ship with such a thing, I rather imagine that I'd be required to
patch it out of Red Hat releases (not that SELinux wouldn't prevent
it from happening anyway).
I do see an argument for allowing SQL-only extensions to be installed
this way, since that doesn't allow the execution of anything the user
couldn't execute anyway. There's no need to worry about anything except
control and script files though.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 05.12.2012 20:13, Andres Freund wrote:
But I really really would like them to go to a per-database directory
not a per-cluster one. Otherwise the coordination between different
database "owners" inside a cluster will get really hairy. I want to be
able to install different versions of an application into different
databases.
Extension authors should be careful to maintain backwards-compatibility,
so that it would be enough to have the latest version installed. If you
break compatibility, you probably should rename the extension.
That said, I can understand that in practice you'd want to have
different versions installed at the same time, so that you don't need to
re-test everything when upgrading an extension, and don't need to trust
that the extension author didn't accidentally break
backwards-compatibility anyway.
If you really meant "different versions of an application", and not
"different versions of an extension", then it seems to me that you're
abusing the extension infrastructure for something else. If you have
some functions that you consider part of the application, even if those
functions might be useful in other applications too, you probably don't
want to treat them as an extension.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2012-12-05 20:15:42 +0200, Heikki Linnakangas wrote:
On 05.12.2012 20:07, Tom Lane wrote:
Heikki Linnakangas<hlinnakangas@vmware.com> writes:
And whether extension control files (or the same information stored in a
table or wherever) should be per-database or per cluster - that's *yet*
another separate issue. You could argue for either behavior.I think anyone arguing for the former is confusing an installed
extension with a not-installed one.
Not sure whether it would be the best design, but having something like
UPLOAD EXTENSION which can only exist in the installed form would be
enough for nearly all the use-cases I experienced.
Maybe it would help if we adopted
different terminologies. Perhaps call the control+sql files a "template",
while using "extension" for the installed entity?+1 on the naming.
+1 on the idea of naming them separately, I am not happy with template,
but then I don't have a better suggestion.
You could still argue that templates should be per-database. It would make
life easier for someone who is database owner but not superuser, for
example, allowing you to install an extension that only affects your own
database (assuming we set up the permissions so that that's possible, of
course).
+1. We could even have two variants, UPLOAD [GLOBAL]
EXTENSION/TEMPLATE. ISTM that we would need some kind of search path
anyway so adding that separation seems to be a minimal amount of
additional effort.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2012-12-05 20:23:29 +0200, Heikki Linnakangas wrote:
On 05.12.2012 20:13, Andres Freund wrote:
But I really really would like them to go to a per-database directory
not a per-cluster one. Otherwise the coordination between different
database "owners" inside a cluster will get really hairy. I want to be
able to install different versions of an application into different
databases.Extension authors should be careful to maintain backwards-compatibility, so
that it would be enough to have the latest version installed. If you break
compatibility, you probably should rename the extension.
In theory yes. In practice:
That said, I can understand that in practice you'd want to have different
versions installed at the same time, so that you don't need to re-test
everything when upgrading an extension, and don't need to trust that the
extension author didn't accidentally break backwards-compatibility anyway.
;)
If you really meant "different versions of an application", and not
"different versions of an extension", then it seems to me that you're
abusing the extension infrastructure for something else. If you have some
functions that you consider part of the application, even if those functions
might be useful in other applications too, you probably don't want to treat
them as an extension.
I was thinking of reusable parts of applications that might be used in
more than one application.
*But* I think this also is a good basis to encapsulate individual
non-shared parts of an application. Why not?
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2012-12-05 13:18:16 -0500, Tom Lane wrote:
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
At the SQL level, extensions do live in a database. The only reason why
we currently have them on the file system is binary executables (.so,
.dylib, .dll). And those are not per database, not even per cluster, not
even per major version, they are *per server*. It's something that makes
me very sad, and that I want to have the chance to fix later, but that
won't happen in 9.3, and certainly not in that very patch…
Maybe I am missing something, but you already can separate them per
major version. You co-wrote the debian infrastructure to do so for some
debian packages, so I am not sure what you mean here.
Adding some *NON WRITABLE* per-cluster library directory doesn't seem to
be as controversion as other suggestions.
I think you're wasting your time to imagine that that case will ever be
"fixed". Allowing the server to scribble on executable files would set
off all kinds of security alarm bells, and rightly so. If Postgres ever
did ship with such a thing, I rather imagine that I'd be required to
patch it out of Red Hat releases (not that SELinux wouldn't prevent
it from happening anyway).
+1
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Dec 5, 2012 at 5:22 AM, Dimitri Fontaine <dimitri@2ndquadrant.fr> wrote:
This patch is all about enabling users to create extension without
having to ship them as root on the file system of the database(s)
server(s) first.
Right, but it changes the way that existing extensions *dump*, which
seems to me to undo the use case that works now.
I mean, the advantage of dumping an extension as CREATE EXTENSION
hstore is that you can reload that dump on a different server with a
newer version of hstore installed, and it'll still work. If we go
back to dumping all of the SQL commands that compose that extension,
then it'll break again, in exactly the way things were broken before
we had extensions in the first place. Back in the bad old days, you'd
dump your old database (which had all of the SQL commands for say
hstore) and then reload it on your new database - and it would fail,
because the old SQL commands didn't match the new binaries. Oops.
With the extension mechanism, it all works just fine: the old database
emits CREATE EXTENSION hstore and the new database can execute that
just fine. You still have a problem if the extension has meanwhile
been changed in a backwards-incompatible way that doesn't work for
your application (i.e. you're using the => operator which has since
been removed) but hopefully that doesn't happen too often, and in any
event it seems relatively unavoidable. And it takes nothing away from
the problem that extensions DO solve, which is incompatibilities
between the SQL file and the shared library.
When you can code your extension using dynamic code such as SQL or
PL/pgSQL, PL/pythonu or PL/perl, there's absolutely no good reason to
have to do the "ship on the server's file system first" that I can see.Technically creating an extension "inline" (sending its definition in
the CREATE EXTENSION query itself) solves the problem of having to
access the server's file system as root.
True, but so does not putting the code into an extension at all. You
can just create loose functions and operators. It's unclear to me
what advantage the extension mechanism provides if there's no shared
library and no on-disk files involved.
Then, next pg_dump will include "CREATE EXTENSION foo;" as usual and at
pg_restore time that access files on the file systems. But maybe you
still are not granted access to the server's file system as root on the
pg_restore target, right? So now you need to be able to include the
extension's script into the dump.
Granting for the moment that there's a reason to call this an
extension at all, rather than a schema or just a bunch of random
CREATE commands, which is not obvious to me, yes, you need to include
it in the dump. But sure then the extension needs to be marked as
being, somehow, a different flavor of extension that can only use SQL
(not shlibs) and needs to be dumped in-line, because otherwise, as
noted above, we break things for the flavor of extensions we've
already got.
Also, even there, it seems to me that it ought to work something like this:
CREATE EXTENSION inline_extension NULL; -- create an extension with no members
CREATE FUNCTION blahblahblah ...
ALTER EXTENSION inline_extension ADD FUNCTION blahblab ...
and so on for all the other members
That is, the extension members should just become dumpable objects.
This seems quite bizarre since the whole point of extensions AIUI is
to avoid dumping the members, but it's better than what the patch
implements. In the patch, IIRC, you emit all the members as a
"separate" dump that gets enclosed by dollar quotes. This strikes me
as ugly, and I think you can construct circular dependency situations
in which it will fail outright.
Now, we don't want to have more than one kind of extensions. That's what
we learnt all together when reviewing my proposal from last year. Having
more than one way to ship an extension is good, having two different
animals with two different incompatible behaviors named the same thing
is bad. The solution we found is then to be able to include an
extension's script into pg_dump's output, and that's what my current
patch implements, per last year review.
I don't think I agree. I don't see a problem having more than one
kind of extensions, but I'm worried that you're trying to shoehorn
something that isn't really an extension into an extension-sized box.
And I sure don't want that to mean "let's break stuff that works right
now".
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Dec 5, 2012 at 12:47 PM, Andres Freund <andres@2ndquadrant.com> wrote:
For me it seems pretty natural to support dumping extension the way they
got created. I.e. a plain CREATE EXTENSION ...; if the extension was
preinstalled and some form that includes the extension source if you
installed it via the connection.Extensions that were installed in some global manner *should* not be
dumped with ones installed over the connection. E.g. dumping /contrib or
packaged modules seems to be a bad idea to me.That would possibly be useful as a debugging tool, but I don't see much
point besides that.
I agree with all of that.
What I can't quite figure out is - AIUI, extensions are a way of
bundling shared libraries with SQL scripts, and a way of managing the
dump and restore process. If you just have SQL, there's no bundling
to do, and if you reverse out the pg_dump changes (which is more or
less what's being proposed here), then what do you have left other
than the good feeling of being "part of an extension"? At that point,
it seems to me that you've gone to a lot of work to add a layer of
packaging that serves no real purpose.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2012-12-05 13:50:27 -0500, Robert Haas wrote:
On Wed, Dec 5, 2012 at 12:47 PM, Andres Freund <andres@2ndquadrant.com> wrote:
For me it seems pretty natural to support dumping extension the way they
got created. I.e. a plain CREATE EXTENSION ...; if the extension was
preinstalled and some form that includes the extension source if you
installed it via the connection.Extensions that were installed in some global manner *should* not be
dumped with ones installed over the connection. E.g. dumping /contrib or
packaged modules seems to be a bad idea to me.That would possibly be useful as a debugging tool, but I don't see much
point besides that.I agree with all of that.
What I can't quite figure out is - AIUI, extensions are a way of
bundling shared libraries with SQL scripts, and a way of managing the
dump and restore process. If you just have SQL, there's no bundling
to do, and if you reverse out the pg_dump changes (which is more or
less what's being proposed here), then what do you have left other
than the good feeling of being "part of an extension"? At that point,
it seems to me that you've gone to a lot of work to add a layer of
packaging that serves no real purpose.
Manageability.
E.g. for years I had a set of (trigger) functions to counted the number
of rows in a table in a lockless manner. That's used in 10+ applications
of former clients of mine. All (plpg)sql.
Imagine I want to ship an updated version that 1. removes some
*internal* functions, 2. adds some internal function. 3. adds a new
*external* function.
Now most of the clients use completely different development models and
completely different ways of manageing upgrades. I needed to integrate
my teensy module into all of them.
If we had a way to package it nicely they could just upload the
extension inside their own workflows and I (or they) would be freed from
integrating foreign update scripts into their workflow.
Imagine embedding a PGXN module into your application which is used on
many servers and doesn't need superuser privileges or anything. Same
thing.
That's not something all that uncommon is it?
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Dec 5, 2012 at 2:01 PM, Andres Freund <andres@2ndquadrant.com> wrote:
E.g. for years I had a set of (trigger) functions to counted the number
of rows in a table in a lockless manner. That's used in 10+ applications
of former clients of mine. All (plpg)sql.
Imagine I want to ship an updated version that 1. removes some
*internal* functions, 2. adds some internal function. 3. adds a new
*external* function.Now most of the clients use completely different development models and
completely different ways of manageing upgrades. I needed to integrate
my teensy module into all of them.If we had a way to package it nicely they could just upload the
extension inside their own workflows and I (or they) would be freed from
integrating foreign update scripts into their workflow.
OK, but let me play devil's advocate here. Under the status quo, if
they used loose database objects, they would need to execute some
database code that does this:
DROP FUNCTION internalfunc1(int);
CREATE FUNCTION internalfunc2(int);
CREATE FUNCTION externalfunc3(int);
IIUC, under this proposal, the client would instead need to execute
some SQL code that looks something this (I'm faking the syntax here,
forgive me, but the patch doesn't seem to contemplate ALTER):
ALTER EXTENSION myextension UPDATE TO 1.1 USING SCRIPT $$
ALTER EXTENSION myextension DROP FUNCTION internalfunc1(int);
DROP FUNCTION internalfunc1(int);
CREATE FUNCTION internalfunc2(int);
ALTER EXTENSION myextension ADD FUNCTION internalfunc2(int);
CREATE FUNCTION externalfunc3(int);
ALTER FUNCTION myextension ADD FUNCTION externalfunc3(int);
$$;
That doesn't really look like an improvement to me. What am I missing?
Imagine embedding a PGXN module into your application which is used on
many servers and doesn't need superuser privileges or anything. Same
thing.That's not something all that uncommon is it?
Not at all. I'm not questioning the use case at all; I'm questioning
whether extensions are the right tool for addressing it.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2012-12-05 14:10:34 -0500, Robert Haas wrote:
On Wed, Dec 5, 2012 at 2:01 PM, Andres Freund <andres@2ndquadrant.com> wrote:
E.g. for years I had a set of (trigger) functions to counted the number
of rows in a table in a lockless manner. That's used in 10+ applications
of former clients of mine. All (plpg)sql.
Imagine I want to ship an updated version that 1. removes some
*internal* functions, 2. adds some internal function. 3. adds a new
*external* function.Now most of the clients use completely different development models and
completely different ways of manageing upgrades. I needed to integrate
my teensy module into all of them.If we had a way to package it nicely they could just upload the
extension inside their own workflows and I (or they) would be freed from
integrating foreign update scripts into their workflow.OK, but let me play devil's advocate here. Under the status quo, if
they used loose database objects, they would need to execute some
database code that does this:DROP FUNCTION internalfunc1(int);
CREATE FUNCTION internalfunc2(int);
CREATE FUNCTION externalfunc3(int);
They would need to do exactly that if their database had version 1.1 and
they upgrade to 1.3 but not if they already had 1.2...
IIUC, under this proposal, the client would instead need to execute
some SQL code that looks something this (I'm faking the syntax here,
forgive me, but the patch doesn't seem to contemplate ALTER):ALTER EXTENSION myextension UPDATE TO 1.1 USING SCRIPT $$
ALTER EXTENSION myextension DROP FUNCTION internalfunc1(int);
DROP FUNCTION internalfunc1(int);
CREATE FUNCTION internalfunc2(int);
ALTER EXTENSION myextension ADD FUNCTION internalfunc2(int);
CREATE FUNCTION externalfunc3(int);
ALTER FUNCTION myextension ADD FUNCTION externalfunc3(int);
$$;That doesn't really look like an improvement to me. What am I missing?
They should be able to simply slurp the extension from a file, possibly
even install it outside their own update mechanism. Given that you don't
know which version was installed beforehand thats not really possible
without some infrastructure.
And they should be able to drop the extension again afterwards without
it leaving a trace. Nearly all I have seen out there fails at that, and
the extension mechanism provides tracking of that.
Imagine embedding a PGXN module into your application which is used on
many servers and doesn't need superuser privileges or anything. Same
thing.That's not something all that uncommon is it?
Not at all. I'm not questioning the use case at all; I'm questioning
whether extensions are the right tool for addressing it.
Do you have some alterantive suggestion?
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Dec 5, 2012 at 2:19 PM, Andres Freund <andres@2ndquadrant.com> wrote:
IIUC, under this proposal, the client would instead need to execute
some SQL code that looks something this (I'm faking the syntax here,
forgive me, but the patch doesn't seem to contemplate ALTER):ALTER EXTENSION myextension UPDATE TO 1.1 USING SCRIPT $$
ALTER EXTENSION myextension DROP FUNCTION internalfunc1(int);
DROP FUNCTION internalfunc1(int);
CREATE FUNCTION internalfunc2(int);
ALTER EXTENSION myextension ADD FUNCTION internalfunc2(int);
CREATE FUNCTION externalfunc3(int);
ALTER FUNCTION myextension ADD FUNCTION externalfunc3(int);
$$;That doesn't really look like an improvement to me. What am I missing?
They should be able to simply slurp the extension from a file, possibly
even install it outside their own update mechanism. Given that you don't
know which version was installed beforehand thats not really possible
without some infrastructure.And they should be able to drop the extension again afterwards without
it leaving a trace. Nearly all I have seen out there fails at that, and
the extension mechanism provides tracking of that.
Ah, OK. Well, it sounds like this might be a decent fit for the
TEMPLATE concept proposed upthread, then.
I have no objection whatsoever to the concept of storing the SQL and
control files somewhere that doesn't need access to the server
filesystem - in fact, I think I previously proposed allowing those to
be stored in a database table. You could do that with something like:
CREATE TEMPLATE yadda;
ALTER TEMPLATE yadda ADD FILE 'yadda--1.0.sql' CONTENT $$...$$;
...or whatever. And that'd be 100% fine with me, and it could dump
and restore just that way, and life would be good. Or at least, it
sounds to me like that would meet the requirements you are
articulating without breaking anything that works today. In fact, it
sounds pretty cool.
The root of my objections upthread, I think, is that the design this
patch puts on the table seems to me to conflate the extension (which
is always a database object) with the template (which is *currently*
always a filesystem object). I think that's bound to create some
problems. In the patch as it exists today, I think those problems are
going to leak out in the form of breaking some of the things for which
extensions can currently be used, but even if we address those points
I have a sneaking suspicion that there will be others.
For example, your point (in the portion of your email I'm not quoting
here) about an upgrade across multiple version is well-taken - you
need a different script depending on the version that's currently
installed. Fixing that, though, seems to require a catalog of upgrade
scripts, so that the server can look at the installed version and the
available scripts and decide how to proceed. That catalog currently
takes the form of separate files in the filesystem, but I don't see
any reason why we can't store it somewhere else. What I'm not sold on
is the idea of shuttling it across as part of CREATE/ALTER EXTENSION
statements. I'm willing to be persuaded, but right now I can't see
how that's ever going either robust or convenient. Making it part of
a separate SQL object type gets around that problem rather nicely,
IMHO.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas escribió:
I have no objection whatsoever to the concept of storing the SQL and
control files somewhere that doesn't need access to the server
filesystem - in fact, I think I previously proposed allowing those to
be stored in a database table. You could do that with something like:CREATE TEMPLATE yadda;
ALTER TEMPLATE yadda ADD FILE 'yadda--1.0.sql' CONTENT $$...$$;...or whatever.
This seems unnecessary to me. What the patch at hand does is take the
file (actually, the contents of the file) and execute it directly,
without installing anything on disk. The precise contents of the
extension is still tracked through pg_depend, so you can drop it without
having previously saved neither the control file or the SQL script. (In
fact, that's how DROP EXTENSION works currently.)
There's also the pg_dump side of things; with your proposal we would be
forced to move over the yadda--1.0.sql file from the old server to the
new one; or, equivalently, put the whole ALTER TEMPLATE .. CONTENT
command in the dump, which is equivalent to what Dimitri's patch does;
so there doesn't seem to be a point.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andres Freund <andres@2ndquadrant.com> writes:
Maybe I am missing something, but you already can separate them per
major version. You co-wrote the debian infrastructure to do so for some
debian packages, so I am not sure what you mean here.
The debian infrastructure I've help building is all about compiling an
extension source package once and having as many binary artefacts as you
have major versions of PostgreSQL lying around. So yes in debian you
can actually install such extensions at different on disk locations per
major version. Sorry for the confusion.
Adding some *NON WRITABLE* per-cluster library directory doesn't seem to
be as controversion as other suggestions.
Well, it means a per-initdb (user driven) location where to store binary
files, ask Tom what he thinks about that with his Red Hat Packager… Hat.
On 2012-12-05 13:18:16 -0500, Tom Lane wrote:
I think you're wasting your time to imagine that that case will ever be
"fixed". Allowing the server to scribble on executable files would set
off all kinds of security alarm bells, and rightly so. If Postgres ever
did ship with such a thing, I rather imagine that I'd be required to
patch it out of Red Hat releases (not that SELinux wouldn't prevent
it from happening anyway).
That part I did understand. I still can't be happy about it, but I won't
get back with any proposal where that's put into questions. That said,
while you're talking about it, what if it's an opt-in GUC?
I do see an argument for allowing SQL-only extensions to be installed
this way, since that doesn't allow the execution of anything the user
couldn't execute anyway. There's no need to worry about anything except
control and script files though.
[…please make sure you're not drinking (coffee) before reading further…]
Now if we can't fix the executable files situation, what about making
the C coded extensions not require an executable anymore? I'm thinking
about studying what it would take exactly to write a PL/C where the
PostgreSQL backend would basically compile the embedded C code at CREATE
FUNCTION time and store bytecode or binary in the probin column.
I've stumbled accross more than one "dynamic code" or "retargetable
compiler" thing already, and several of those even have compatible
licences. Maybe the most promising ones are PL/LLVM or embeding the QEMU
code transformation code (a fork of the tcc compiler).
So, we're talking about a PL/C language, in-core or extension, where you
could define say hstore without shipping any executable binary. Yeah,
I'm crazy that way. Now I'll get back to the main thread…
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2012-12-05 21:16:52 +0100, Dimitri Fontaine wrote:
Andres Freund <andres@2ndquadrant.com> writes:
Adding some *NON WRITABLE* per-cluster library directory doesn't seem to
be as controversion as other suggestions.Well, it means a per-initdb (user driven) location where to store binary
files, ask Tom what he thinks about that with his Red Hat Packager… Hat.
I think it might be different if the directory is non-writable and
connot be made writable by user running postgres.
I do see an argument for allowing SQL-only extensions to be installed
this way, since that doesn't allow the execution of anything the user
couldn't execute anyway. There's no need to worry about anything except
control and script files though.[…please make sure you're not drinking (coffee) before reading further…]
Now if we can't fix the executable files situation, what about making
the C coded extensions not require an executable anymore? I'm thinking
about studying what it would take exactly to write a PL/C where the
PostgreSQL backend would basically compile the embedded C code at CREATE
FUNCTION time and store bytecode or binary in the probin column.So, we're talking about a PL/C language, in-core or extension, where you
could define say hstore without shipping any executable binary. Yeah,
I'm crazy that way. Now I'll get back to the main thread…
Imo thats not a sensible thing to pursue.
It would seriously shorten the effort needed to run user-provided
code. Yes, you can execute unrestricted perl, python whatever already
but with selinux and similar things in place that won't allow you to run
your own binaries. And currently execute-or-write protection will
prevent you from executing compiled code. So you would have to disable
that as well...
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
So,
Lots of things are being said, most of them are really interesting and
some of them are just re-hashing what we said about a year ago in the
"Inline Extensions" thread, whose conclusion was that the key not to
have two different beasts (inline, file based) was for pg_dump to
process them all the same way.
Meanwhile, I think I need to address that reaction first:
Robert Haas <robertmhaas@gmail.com> writes:
What I can't quite figure out is - AIUI, extensions are a way of
bundling shared libraries with SQL scripts, and a way of managing the
dump and restore process.
Not quite.
Extensions are user defined CASCADE support.
It's all about pg_depend extensibility.
Extensions are a way to manage dependencies of SQL objects in a way that
allow you to manage them as a single entity. Now you can
CREATE EXTENSION
ALTER EXTENSION
DROP EXTENSION
and all you're doing is managing a bunch of SQL objects at once.
The fact that it allows to implement a working dump&restore of aborigen
extensions called contribs has been the first step, not the whole goal.
You will notice that there's nothing in the whole extension patch and
framework that refers to a "module", those little executable binary
files whose name depend on the OS PostgreSQL is running on, and that you
manage at the file system level, in postgresql.conf with some GUCs, and
with the LOAD command.
You will also notice that we have been *very* careful not to taint any
extension related SQL command with the notion of files. That part is
well separated away and only meant to be known by extension authors and
packagers, not by mere mortals such as DBAs or users. The current patch
is willing to push that a little further away, making it optional even
to extension authors.
Those two facts didn't just happen. And I was not alone in designing the
system that way. Let's continue the design and its implementation! :)
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andres Freund escribió:
On 2012-12-05 21:16:52 +0100, Dimitri Fontaine wrote:
Now if we can't fix the executable files situation, what about making
the C coded extensions not require an executable anymore? I'm thinking
about studying what it would take exactly to write a PL/C where the
PostgreSQL backend would basically compile the embedded C code at CREATE
FUNCTION time and store bytecode or binary in the probin column.
Imo thats not a sensible thing to pursue.
+1. Certainly a pg_dump patch's thread is not the place to propose it.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
+1. Certainly a pg_dump patch's thread is not the place to propose it.
Sure. Sorry about that, the goal of that previous message was to let
people come to understand better my whole vision of what is an
Extension, a contrib, and where we are what I wanted us to build.
I refined those ideas in another email though, so you can safely ignore
this sub-thread. I'll get back to the question of storing .so in a per
database location with an opt-in GUC later, when appropriate.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andres Freund <andres@2ndquadrant.com> writes:
On 2012-12-05 21:16:52 +0100, Dimitri Fontaine wrote:
Now if we can't fix the executable files situation, what about making
the C coded extensions not require an executable anymore? I'm thinking
about studying what it would take exactly to write a PL/C where the
PostgreSQL backend would basically compile the embedded C code at CREATE
FUNCTION time and store bytecode or binary in the probin column.So, we're talking about a PL/C language, in-core or extension, where you
could define say hstore without shipping any executable binary. Yeah,
I'm crazy that way. Now I'll get back to the main thread…
Imo thats not a sensible thing to pursue.
That would be another thing that Red Hat would refuse to ship, as would
any other distro with an ounce of concern about security. But in any
case there's no way that we could implement it portably.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
On 2012-12-05 13:18:16 -0500, Tom Lane wrote:
I think you're wasting your time to imagine that that case will ever be
"fixed". Allowing the server to scribble on executable files would set
off all kinds of security alarm bells, and rightly so. If Postgres ever
did ship with such a thing, I rather imagine that I'd be required to
patch it out of Red Hat releases (not that SELinux wouldn't prevent
it from happening anyway).
That part I did understand. I still can't be happy about it, but I won't
get back with any proposal where that's put into questions. That said,
while you're talking about it, what if it's an opt-in GUC?
GUC or no GUC, it'd still be letting an unprivileged network-exposed
application (PG) do something that's against any sane system-level
security policy. Lipstick is not gonna help this pig.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2012-12-05 16:20:41 -0500, Tom Lane wrote:
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
On 2012-12-05 13:18:16 -0500, Tom Lane wrote:
I think you're wasting your time to imagine that that case will ever be
"fixed". Allowing the server to scribble on executable files would set
off all kinds of security alarm bells, and rightly so. If Postgres ever
did ship with such a thing, I rather imagine that I'd be required to
patch it out of Red Hat releases (not that SELinux wouldn't prevent
it from happening anyway).That part I did understand. I still can't be happy about it, but I won't
get back with any proposal where that's put into questions. That said,
while you're talking about it, what if it's an opt-in GUC?GUC or no GUC, it'd still be letting an unprivileged network-exposed
application (PG) do something that's against any sane system-level
security policy. Lipstick is not gonna help this pig.
What about the non-writable per cluster directory? Thats something I've
actively wished for in the past when developing a C module thats also
used in other clusters.
Greetings,
Andres Freund
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andres Freund <andres@anarazel.de> writes:
On 2012-12-05 16:20:41 -0500, Tom Lane wrote:
GUC or no GUC, it'd still be letting an unprivileged network-exposed
application (PG) do something that's against any sane system-level
security policy. Lipstick is not gonna help this pig.
What about the non-writable per cluster directory? Thats something I've
actively wished for in the past when developing a C module thats also
used in other clusters.
I see no security objection to either per-cluster or per-database
script+control-file directories, as long as they can only contain
SQL scripts and not executable files.
If we allow such things to be installed by less-than-superusers,
we'll have to think carefully about what privileges are given
when running the script. I forget at the moment how much of that
we already worked out back in the 9.1 era; I remember it was discussed
but not whether we had a bulletproof solution.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2012-12-05 16:42:38 -0500, Tom Lane wrote:
Andres Freund <andres@anarazel.de> writes:
On 2012-12-05 16:20:41 -0500, Tom Lane wrote:
GUC or no GUC, it'd still be letting an unprivileged network-exposed
application (PG) do something that's against any sane system-level
security policy. Lipstick is not gonna help this pig.What about the non-writable per cluster directory? Thats something I've
actively wished for in the past when developing a C module thats also
used in other clusters.I see no security objection to either per-cluster or per-database
script+control-file directories, as long as they can only contain
SQL scripts and not executable files.
Well, I was explicitly talking about C code above. The question doesn't
really have to do too much with this thread, sorry. Given I am proposing
the directory to be explicitly read-only and under permission that don't
allow postgres to change that its not really suitable for this topic...
Greetings,
Andres Freund
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Dec 5, 2012 at 2:54 PM, Alvaro Herrera <alvherre@2ndquadrant.com> wrote:
Robert Haas escribió:
I have no objection whatsoever to the concept of storing the SQL and
control files somewhere that doesn't need access to the server
filesystem - in fact, I think I previously proposed allowing those to
be stored in a database table. You could do that with something like:CREATE TEMPLATE yadda;
ALTER TEMPLATE yadda ADD FILE 'yadda--1.0.sql' CONTENT $$...$$;...or whatever.
This seems unnecessary to me. What the patch at hand does is take the
file (actually, the contents of the file) and execute it directly,
without installing anything on disk. The precise contents of the
extension is still tracked through pg_depend, so you can drop it without
having previously saved neither the control file or the SQL script. (In
fact, that's how DROP EXTENSION works currently.)
Yeah, DROP will work. But what about ALTER .. UPDATE?
There's also the pg_dump side of things; with your proposal we would be
forced to move over the yadda--1.0.sql file from the old server to the
new one; or, equivalently, put the whole ALTER TEMPLATE .. CONTENT
command in the dump, which is equivalent to what Dimitri's patch does;
so there doesn't seem to be a point.
Well, there's certainly a point, because IIUC Dimitri's patch dumps
the file into the pg_dump output no matter whether the file originally
came from an SQL command or the filesystem. IMHO, anyone who thinks
that isn't going to break things rather badly isn't thinking hard
enough.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
Yeah, DROP will work. But what about ALTER .. UPDATE?
What about it?
Well, there's certainly a point, because IIUC Dimitri's patch dumps
the file into the pg_dump output no matter whether the file originally
came from an SQL command or the filesystem. IMHO, anyone who thinks
that isn't going to break things rather badly isn't thinking hard
enough.
Only if you ask for it using --extension-script. The default behaviour
didn't change, whether you decide to install your extension from the
file system or the PostgreSQL port.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine escribió:
Robert Haas <robertmhaas@gmail.com> writes:
Well, there's certainly a point, because IIUC Dimitri's patch dumps
the file into the pg_dump output no matter whether the file originally
came from an SQL command or the filesystem. IMHO, anyone who thinks
that isn't going to break things rather badly isn't thinking hard
enough.Only if you ask for it using --extension-script. The default behaviour
didn't change, whether you decide to install your extension from the
file system or the PostgreSQL port.
What happens on a normal pg_dump of the complete database? For
extensions that were installed using strings instead of files, do I get
a string back? Because if not, the restore is clearly going to fail
anyway.
I mean, clearly the user doesn't want to list the extensions, figure
which ones were installed by strings, and then do pg_dump
--extension-script on them.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
What happens on a normal pg_dump of the complete database? For
extensions that were installed using strings instead of files, do I get
a string back? Because if not, the restore is clearly going to fail
anyway.
The argument here is that the user would then have packaged its
extension as files in the meantime. If not, that's operational error. A
backup you didn't restore successfully isn't a backup anyway.
I mean, clearly the user doesn't want to list the extensions, figure
which ones were installed by strings, and then do pg_dump
--extension-script on them.
The idea is that the user did install the extensions that came by
strings. Last year the consensus was clearly for pg_dump not to
distinguish in between file based and string based extensions that are
exactly the same thing once installed in a database. That's the current
design.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2012-12-05 23:28:45 +0100, Dimitri Fontaine wrote:
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
What happens on a normal pg_dump of the complete database? For
extensions that were installed using strings instead of files, do I get
a string back? Because if not, the restore is clearly going to fail
anyway.The argument here is that the user would then have packaged its
extension as files in the meantime. If not, that's operational error. A
backup you didn't restore successfully isn't a backup anyway.
Uh. Wait. What? If that argument is valid, we don't need anything but
file based extensions.
I mean, clearly the user doesn't want to list the extensions, figure
which ones were installed by strings, and then do pg_dump
--extension-script on them.The idea is that the user did install the extensions that came by
strings. Last year the consensus was clearly for pg_dump not to
distinguish in between file based and string based extensions that are
exactly the same thing once installed in a database. That's the current
design.
I don't find that argument convincing in the slightest. Could I perhaps
convince you to dig up a reference? I would be interested in the
arguments for that design back then.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Andres Freund <andres@2ndquadrant.com> writes:
The argument here is that the user would then have packaged its
extension as files in the meantime. If not, that's operational error. A
backup you didn't restore successfully isn't a backup anyway.Uh. Wait. What? If that argument is valid, we don't need anything but
file based extensions.
Well, I've been trying to understand the consensus, and to implement it
in the simplest possible way. Maybe the default should be to activate
automatically --extension-script for extensions without control files?
The idea is that the user did install the extensions that came by
strings. Last year the consensus was clearly for pg_dump not to
distinguish in between file based and string based extensions that are
exactly the same thing once installed in a database. That's the current
design.I don't find that argument convincing in the slightest. Could I perhaps
convince you to dig up a reference? I would be interested in the
arguments for that design back then.
I think here it is:
http://archives.postgresql.org/pgsql-hackers/2012-01/msg01307.php
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Dec 5, 2012 at 5:08 PM, Dimitri Fontaine <dimitri@2ndquadrant.fr> wrote:
Robert Haas <robertmhaas@gmail.com> writes:
Yeah, DROP will work. But what about ALTER .. UPDATE?
What about it?
Well, with the design you have proposed, unless you have access to the
filesystem, it ain't gonna work. And if you have access to the
filesystem, then this whole discussion is moot.
Well, there's certainly a point, because IIUC Dimitri's patch dumps
the file into the pg_dump output no matter whether the file originally
came from an SQL command or the filesystem. IMHO, anyone who thinks
that isn't going to break things rather badly isn't thinking hard
enough.Only if you ask for it using --extension-script. The default behaviour
didn't change, whether you decide to install your extension from the
file system or the PostgreSQL port.
That doesn't impress me in the slightest. Suppose you have two
identically configured machines A and B on which you install hstore
(from the filesystem) and a hypothetical extension istore (via the
inline extension mechanism). Now, you take regular backups of machine
A, and one day it dies, so you want to restore onto machine B. Well,
if you didn't dump with --extension-script, then you've got an
incomplete backup, so you are hosed. And if you did dump with
--extension-script, then you're OK in that scenario, but the wheels
come off if you try to dump and restore onto machine C, which is
running a newer version of PostgreSQL with an updated hstore. To do
it right, you have to remember which extensions you installed which
way and dump exactly the right thing for each one. That can't be
good.
Like Andres, I'd like to see a reference to the thread where we
supposedly had consensus on this behavior. I don't really recall us
achieving consensus on anything, but if we did I have a hard time
believing it was this.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Dec 5, 2012 at 5:43 PM, Dimitri Fontaine <dimitri@2ndquadrant.fr> wrote:
I don't find that argument convincing in the slightest. Could I perhaps
convince you to dig up a reference? I would be interested in the
arguments for that design back then.I think here it is:
http://archives.postgresql.org/pgsql-hackers/2012-01/msg01307.php
Ah ha!
I had to read that twice to remember what I meant by it, so that may
be a sign that the original email wasn't any too clear. That having
been said, I think that the confusion is this: the second paragraph of
that email was intended to be interpreted *in the context* of the
proposal made in the first paragraph of the email, NOT as a separate
proposal.
In other words, the first paragraph is arguing for something like the
notion of an extension template - the ability to store the extension
files inside the server, in cases where you don't want them to appear
in the file system. But perhaps implemented using functions rather
than dedicated SQL syntax. But regardless of the concrete syntax, the
first paragraph is proposing that we have something conceptually
similar to:
CREATE TEMPLATE yadda;
ALTER TEMPLATE yadda ADD FILE 'yadda--1.0.sql' CONTENT $$...$$;
Given that context, the second paragraph is intended as a suggestion
that we should have something like pg_dump --no-templates -- which
would still emit any CREATE EXTENSION commands, but not any
CREATE/ALTER TEMPLATE commands - so if you relied on any templates in
setting up the old cluster, the new cluster would need to have the
files installed in the usual place. It was not a suggestion that we
shoehorn the file management into CREATE / ALTER EXTENSION as you are
proposing here; the first paragraph expresses my opinion, which hasn't
changed between then and now, that that's a bad design.
Ugh.
Is that any more clear than what I said before?
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
Well, with the design you have proposed, unless you have access to the
filesystem, it ain't gonna work. And if you have access to the
filesystem, then this whole discussion is moot.
I did mention that this version of the patch is only ready to host the
current design talk we have now. I intend to amend it with some inline
ALTER EXTENSION facility.
In the worked out example you gave in another mail of this thread, you
would have to remove any explicit ALTER EXTENSION … ADD … of course, as
in a classic script here.
You would have to fill in both the current and next version of the
extension I guess, as a defensive check, too.
That doesn't impress me in the slightest. Suppose you have two
identically configured machines A and B on which you install hstore
(from the filesystem) and a hypothetical extension istore (via the
inline extension mechanism). Now, you take regular backups of machine
A, and one day it dies, so you want to restore onto machine B. Well,
if you didn't dump with --extension-script, then you've got an
incomplete backup, so you are hosed. And if you did dump with
You didn't ever restore your backup? So you didn't know for sure you had
one. More seriously…
--extension-script, then you're OK in that scenario, but the wheels
come off if you try to dump and restore onto machine C, which is
running a newer version of PostgreSQL with an updated hstore. To do
it right, you have to remember which extensions you installed which
way and dump exactly the right thing for each one. That can't be
good.
In the patch we're talking about, the --extension-script is an
accumulative option that needs an argument, so you do
pg_dump --extension-script istore --extension-script foo
or if you're into short options
pg_dump -X istore -X foo -X bar
I'm not saying that design is perfect nor definitive, it's just what
happens to be in the patch, and it allows you to solve your problem. We
could default the --extension-script to any installed extension for
which we don't have a control file?
Like Andres, I'd like to see a reference to the thread where we
supposedly had consensus on this behavior. I don't really recall us
achieving consensus on anything, but if we did I have a hard time
believing it was this.
What I remember about the "consensus" from last year is:
- http://archives.postgresql.org/pgsql-hackers/2012-01/msg01307.php
- inline and file based extensions must be the same beast once in the
database
- pg_dump options should work the same against either kind
- it all boils down to designing a consistent dump behavior
Which is the angle I've been working on reaching this round. The other
thing we said is more about how to get the dump's content, and I
realised that it could be so much simpler than relying on any file
anywhere: pg_extension and pg_depend have all the information we need.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas <robertmhaas@gmail.com> writes:
In other words, the first paragraph is arguing for something like the
notion of an extension template - the ability to store the extension
files inside the server, in cases where you don't want them to appear
in the file system. But perhaps implemented using functions rather
than dedicated SQL syntax. But regardless of the concrete syntax, the
first paragraph is proposing that we have something conceptually
similar to:
CREATE TEMPLATE yadda;
ALTER TEMPLATE yadda ADD FILE 'yadda--1.0.sql' CONTENT $$...$$;
Given that context, the second paragraph is intended as a suggestion
that we should have something like pg_dump --no-templates -- which
would still emit any CREATE EXTENSION commands, but not any
CREATE/ALTER TEMPLATE commands - so if you relied on any templates in
setting up the old cluster, the new cluster would need to have the
files installed in the usual place. It was not a suggestion that we
shoehorn the file management into CREATE / ALTER EXTENSION as you are
proposing here; the first paragraph expresses my opinion, which hasn't
changed between then and now, that that's a bad design.
FWIW, the more I think about it the more I like the notion of treating
"extension templates" as a separate kind of object. I do see value in
storing them inside the database system: transactional safety, the
ability to identify an owner, etc etc. But conflating this
functionality with installed extensions is just going to create
headaches.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Robert Haas <robertmhaas@gmail.com> writes:
Well, there's certainly a point, because IIUC Dimitri's patch dumps
the file into the pg_dump output no matter whether the file originally
came from an SQL command or the filesystem. IMHO, anyone who thinks
that isn't going to break things rather badly isn't thinking hard
enough.
Only if you ask for it using --extension-script. The default behaviour
didn't change, whether you decide to install your extension from the
file system or the PostgreSQL port.
A dump-level option for that seems completely wrong in any case: it
breaks one of the fundamental design objectives for extensions, or
at least for extensions as originally conceived. It might be necessary
to do it this way for these new critters, but that just reinforces the
point that you're designing a new kind of object.
I think a separate kind of "extension template" object would make a lot
more sense.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
In the patch we're talking about, the --extension-script is an
accumulative option that needs an argument, so you do
pg_dump --extension-script istore --extension-script foo
or if you're into short options
pg_dump -X istore -X foo -X bar
My reaction to this is "you've *got* to be kidding". You're going
to put it on the user to remember which extensions are which, or
else he gets an unusable dump? I don't think we should have a switch
like this at all. pg_dump should do the right thing for each extension
without being told.
And, once more, I think keeping the dump behavior for extensions as-is
and inventing a different concept for the script-file-substitutes would
be better than conflating the cases.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane <tgl@sss.pgh.pa.us> writes:
CREATE TEMPLATE yadda;
ALTER TEMPLATE yadda ADD FILE 'yadda--1.0.sql' CONTENT $$...$$;FWIW, the more I think about it the more I like the notion of treating
"extension templates" as a separate kind of object. I do see value in
storing them inside the database system: transactional safety, the
ability to identify an owner, etc etc. But conflating this
functionality with installed extensions is just going to create
headaches.
I totally agree that the current proposal is somewhat of a mess, and
making a distinction between an extension and its packaging seems like a
good approach to the problem.
Tom Lane <tgl@sss.pgh.pa.us> writes:
A dump-level option for that seems completely wrong in any case: it
breaks one of the fundamental design objectives for extensions, or
at least for extensions as originally conceived. It might be necessary
to do it this way for these new critters, but that just reinforces the
point that you're designing a new kind of object.
Well what this template idea is saying to me is that once installed,
we're still talking about an extension, the exact same thing.
I think a separate kind of "extension template" object would make a lot
more sense.
I'm on board now. We still have some questions to answer, and here's a
worked out design proposal for implementing my understanding of your
"extension's template" idea:
- Extension Scripts are now stored in the catalogs, right?
problem: pg_extension_script(extension, version, fromversion, script)
what's the unique key when fromversion is nullable?
so I would propose to have instead:
pg_extension_install_script(extension, version, script)
unique(extension, version)
pg_extension_update_script(extension, oldversion, newversion, script)
unique(extension, oldversion, newversion)
- The control file should get in the catalogs too, and as it can get
some per-version changes, it needs to be stored separately:
pg_extension_control(extension, version, default_version,
default_full_version, module_pathname,
relocatable, superuser, schema, requires)
unique(extension, version)
We would do the secondary control file overriding at creation time.
- The naming "TEMPLATE" appears to me to be too much of a generic
naming for our usage here, so I'm not sure about it yet. On the other
hand the following proposal would certainly require to reserve new
keywords, which we want to avoid:
CREATE EXTENSION PARAMETERS FOR 'version' [ WITH ] key = val…
CREATE EXTENSION SCRIPT FOR 'version' AS $$ … $$;
CREATE EXTENSION SCRIPT FROM 'version' TO 'version' AS …
So maybe what we could do instead is something like the following:
ALTER EXTENSION … CONFIGURATION FOR 'version' SET param = value, …;
ALTER EXTENSION … SET SCRIPT FOR 'version' AS $$ … $$;
ALTER EXTENSION … SET SCRIPT FROM 'version' TO 'version' AS …
Oh actually TEMPLATE is already a keyword thanks to text search, so
another alternative would be the following, if we really really want
to avoid any new keyword in our grammar:
ALTER EXTENSION … CONFIGURATION FOR 'version' SET param = value, …;
ALTER EXTENSION … SET TEMPLATE FOR 'version' AS $$ … $$;
ALTER EXTENSION … SET TEMPLATE FROM 'version' TO 'version' AS …
That would mean that ALTER EXTENSION could create objects in other
catalogs for an extension that does not exists itself yet, but is now
known available (select * from pg_available_extensions()).
We already have commands that will create subsidiary objects in other
places in the catalogs (serial, composite types, array types) but all
of those are using the new object in the command itself. So that
would be new, but it allows for not having any new keyword here.
The $2.56 question being what would be the pg_dump policy of the
"extension templates" objects? I suppose the whole game here is to dump
them all by default, which would just work at pg_restore time too.
It's possible to filter templates out at dump or restore time if you
need to install a new set of templates for a given extension before to
run CREATE EXTENSION so that's ok.
Now, my understanding is that CREATE EXTENSION would check for templates
being already available in the catalogs and failing to find them would
have to do the extra steps of creating them from disk files as a
preparatory step, right? (backward compatibility requirement)
Finally, while we're talking about reflecting on-disk objects into the
catalogs, do we want to have a pg_module catalog where we list all
shared objects binaries we know about, with a boolean column to indicate
which of those we loaded in the current session, and by which extension
if any?
I don't think we could easily match a .so with an extension's template
so I won't be proposing that, but we could quite easily match them now
with extensions, because we're going to have to LOAD the module while
creating_extension = true.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Tom Lane <tgl@sss.pgh.pa.us> writes:
I think a separate kind of "extension template" object would make a lot
more sense.
I'm on board now. We still have some questions to answer, and here's a
worked out design proposal for implementing my understanding of your
"extension's template" idea:
- Extension Scripts are now stored in the catalogs, right?
Only for these new-style thingies. I am not suggesting breaking the
existing file-based implementation, only offering a parallel
catalog-based implementation too. We'd have to think about what to do
for name collisions --- probably having the catalog entry take
precedence is okay, but is there an argument for something else?
[ need separate catalogs for install scripts and update scripts ]
Check.
pg_extension_control(extension, version, default_version,
default_full_version, module_pathname,
relocatable, superuser, schema, requires)
Given that the feature is going to be restricted to pure-SQL extensions,
I'm pretty sure we can do without module_pathname, and maybe some other
things.
- The naming "TEMPLATE" appears to me to be too much of a generic
naming for our usage here, so I'm not sure about it yet.
Yeah, possibly, but I don't have a better idea yet. I don't like
either PARAMETERS or SCRIPT --- for one thing, those don't convey the
idea that this is an object in its own right rather than an attribute of
an extension.
Oh actually TEMPLATE is already a keyword thanks to text search,
Actually, given the text search precedent, I'm not sure why you're so
against TEMPLATE.
That would mean that ALTER EXTENSION could create objects in other
catalogs for an extension that does not exists itself yet, but is now
known available (select * from pg_available_extensions()).
Man, that is just horrid. It brings back exactly the confusion we're
trying to eliminate by using the "template" terminology. We don't want
it to look like manipulating a template has anything to do with altering
an extension of the same name (which might or might not even be
installed).
The $2.56 question being what would be the pg_dump policy of the
"extension templates" objects?
The ones that are catalog objects, not file objects, should be dumped
I think.
Now, my understanding is that CREATE EXTENSION would check for templates
being already available in the catalogs and failing to find them would
have to do the extra steps of creating them from disk files as a
preparatory step, right? (backward compatibility requirement)
Wrong. There is no reason whatsoever to load file-based stuff into
catalogs. That just adds complication and overhead to cases that work
already, and will break update cases (what happens when a package update
changes the files?).
I don't think we could easily match a .so with an extension's template
so I won't be proposing that, but we could quite easily match them now
with extensions, because we're going to have to LOAD the module while
creating_extension = true.
One more time: this mode has nothing to do with extensions that involve
a .so. It's for extensions that can be represented purely as scripts,
and thus are self-contained in the proposed catalog entries.
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane <tgl@sss.pgh.pa.us> writes:
Only for these new-style thingies. I am not suggesting breaking the
existing file-based implementation, only offering a parallel
catalog-based implementation too. We'd have to think about what to do
for name collisions --- probably having the catalog entry take
precedence is okay, but is there an argument for something else?
Yeah, well, I would have prefered to have two ways to fill-in the
templates then only work from the templates. That would solve the name
collision problem, and I guess would allow to share more code.
The other thing is that I want to support extensions that use both
models: say the prototype has been written in pl/pythonu but the next
version now is switching to C/.so…
[ need separate catalogs for install scripts and update scripts ]
Check.
Ok.
pg_extension_control(extension, version, default_version,
default_full_version, module_pathname,
relocatable, superuser, schema, requires)Given that the feature is going to be restricted to pure-SQL extensions,
I'm pretty sure we can do without module_pathname, and maybe some other
things.
I already removed "directory" from that list beause once in the catalogs
you don't care where the files might have been found. MODULE_PATHNAME is
about how to read the script that we would store in the catalogs, not
sure we can bypass that.
Yeah, possibly, but I don't have a better idea yet. I don't like
either PARAMETERS or SCRIPT --- for one thing, those don't convey the
idea that this is an object in its own right rather than an attribute of
an extension.
A template is something that needs to be instanciated with specific
parameters, and can get used any number of times with different sets of
parameters to build each time a new object. It's nothing like what we're
talking about, or I don't understand it at all.
My understanding is that we store the extension "sources" in our
catalogs so as to be able to execute them later. The only option we have
that looks like a template parameter would be the SCHEMA, the others are
about picking the right sources/script.
Actually, given the text search precedent, I'm not sure why you're so
against TEMPLATE.
See above, my understanding of your proposal is not matching the
definition I know of that term.
That would mean that ALTER EXTENSION could create objects in other
catalogs for an extension that does not exists itself yet, but is now
known available (select * from pg_available_extensions()).Man, that is just horrid. It brings back exactly the confusion we're
trying to eliminate by using the "template" terminology. We don't want
it to look like manipulating a template has anything to do with altering
an extension of the same name (which might or might not even be
installed).
I still can't help but thinking in terms of populating the "templates" one
way or the other and then using the "templates" to create or update the
extension itself.
We could maybe have a command akin to "yum update" or "apt-get update"
that would refresh the TEMPLATEs from disk (handling name conflicts,
file name parsing and control files parsing), and some options to the
EXTENSION commands to force a refresh before working?
So either
REFRESH EXTENSION TEMPLATES;
ALTER EXTENSION hstore UPDATE TO '1.2';
or
ALTER EXTENSION hstore UPDATE TO '1.2' WITH TEMPLATE REFRESH;
So my horrid proposal above would mean that the REFRESH option defaults
to true, and is also available to CREATE EXTENSION. I'm not sure how
much less horrid that makes it, but I sure hope it allows to better
explain / convey my vision about the thing.
The $2.56 question being what would be the pg_dump policy of the
"extension templates" objects?The ones that are catalog objects, not file objects, should be dumped
I think.
Agreed.
Wrong. There is no reason whatsoever to load file-based stuff into
catalogs. That just adds complication and overhead to cases that work
already, and will break update cases (what happens when a package update
changes the files?).
What happens if the extension that was a created from a template is now
maintained on-disk (switch from pl/perlu to C)? What if the extension
that was on-disk because you couldn't use a template in 9.1 and 9.2 now
wants to be managed by the template system?
What if the PGXN guys think template are a perfect solution to integrate
into their client tool but the debian and yum packagers prefer to ship
disk-based extensions? And you want to switch from one packaging system
to the other?
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
Please find attached a preliminary patch following the TEMPLATE ideas,
and thanks in particular to Tom and Heikki for a practical design about
how to solve that problem!
Tom Lane <tgl@sss.pgh.pa.us> writes:
- Extension Scripts are now stored in the catalogs, right?
Only for these new-style thingies. I am not suggesting breaking the
existing file-based implementation, only offering a parallel
catalog-based implementation too. We'd have to think about what to do
for name collisions --- probably having the catalog entry take
precedence is okay, but is there an argument for something else?
The attached patch is implementing TEMPLATEs only for these new-style
thingies. Conflicts are checked at template creation time, and at create
extension time we do the file system lookup first, so that's the winner.
[ need separate catalogs for install scripts and update scripts ]
Check.
You'll find the 3 of them in the attached unfinished patch (install,
update, control).
Actually, given the text search precedent, I'm not sure why you're so
against TEMPLATE.
So I called them TEMPLATE and I tried hard to leave that term open to
other uses. As a result the main syntax is
CREATE TEMPLATE FOR EXTENSION …
ALTER TEMPLATE FOR EXTENSION …
DROP TEMPLATE FOR EXTENSION …
No new keyword has been added to the parser in the making of this patch.
You'll find some usage examples in the regression tests part of the
patch, and the new commands have received the very minimum documentation
coverage. I intend to fill in the docs some more before calling it ready
for commit, of course.
I'm at a point where I need feedback before continuing though, and I
think Tom is in the best position to provide it given the previous
exchanges.
The $2.56 question being what would be the pg_dump policy of the
"extension templates" objects?The ones that are catalog objects, not file objects, should be dumped
I think.
So, the current version of the patch has no support for pg_dump and psql
yet, and most ALTER commands in the grammar are not yet implemented. In
the lacking list we can also add ALTER … OWNER TO / RENAME and COMMENT,
both for the new catalog objects and the extension to be created from
them.
I think we could transfer the COMMENT on the template from the
pg_extension_control (so that you can change the comment at upgrade) to
the extension, but wanted to talk about that first. The alternative is
to simply add a comment column to the pg_extension_control catalog,
along with a grammar rule to get the information from the commands.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Please find attached a preliminary patch following the TEMPLATE ideas,
FYI, I've added it to the commitfest:
https://commitfest.postgresql.org/action/patch_view?id=1032
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Dimitri Fontaine (dimitri@2ndQuadrant.fr) wrote:
Please find attached a preliminary patch following the TEMPLATE ideas,
and thanks in particular to Tom and Heikki for a practical design about
how to solve that problem!
Given that it's preliminary and v0 and big and whatnot, it seems like
it should be bounced to post-9.3. Even so, I did take a look through
it, probably mostly because I'd really like to see this feature. :)
What's with removing the OBJECT_TABLESPACE case? Given that
get_object_address_tmpl() seems to mainly just fall into a couple of
case statements to split out the different options, I'm not sure that
having that function is useful, or perhaps it should be 2 distinct
functions?
ExtensionControlFile seemed like a good name, just changing that to
"ExtensionControl" doesn't seem as nice, tho that's a bit of bike
shedding, I suppose.
I'm not sure we have a 'dile system'... :)
For my 2c, I wish we could do something better than having to support
both on-disk conf files and in-database configs. Don't have any
particular solution to that tho.
Also pretty sure we only have one catalog
('get_ext_ver_list_from_catalogs')
'Template' seems like a really broad term which might end up being
associated with things beyond extensions, yet there are a number of
places where you just use 'TEMPLATE', eg, ACL_KIND_TEMPLATE. Seems like
it might become an issue later.
Just a side-note, there's also some whitespace issues.
Also, no pg_dump/restore support..? Seems like that'd be useful..
That's just a real quick run-through with my notes. If this patch is
really gonna go into 9.3, I'll try to take a deeper look.
Thanks,
Stephen
On 2013-01-18 12:45:02 -0500, Stephen Frost wrote:
* Dimitri Fontaine (dimitri@2ndQuadrant.fr) wrote:
Please find attached a preliminary patch following the TEMPLATE ideas,
and thanks in particular to Tom and Heikki for a practical design about
how to solve that problem!Given that it's preliminary and v0 and big and whatnot, it seems like
it should be bounced to post-9.3. Even so, I did take a look through
it, probably mostly because I'd really like to see this feature. :)
To be fair, the patch start its life pretty early on in the cycle and
only got really reviewed (and I think updated) later. I just got
rewritten into this form based on review.
'Template' seems like a really broad term which might end up being
associated with things beyond extensions, yet there are a number of
places where you just use 'TEMPLATE', eg, ACL_KIND_TEMPLATE. Seems like
it might become an issue later.
I think Tom came up with that name and while several people (including
me and I think also Dim) didn't really like it nobody has come up with a
better name so far.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Andres Freund (andres@2ndquadrant.com) wrote:
On 2013-01-18 12:45:02 -0500, Stephen Frost wrote:
'Template' seems like a really broad term which might end up being
associated with things beyond extensions, yet there are a number of
places where you just use 'TEMPLATE', eg, ACL_KIND_TEMPLATE. Seems like
it might become an issue later.I think Tom came up with that name and while several people (including
me and I think also Dim) didn't really like it nobody has come up with a
better name so far.
'Extension Template' is fine, I was just objecting to places in the code
where it just says 'TEMPLATE'. I imagine we might have some 'XXX
Template' at some point in the future and then we'd have confusion
between "is this an *extension* template or an XXX template?".
Thanks,
Stephen
Stephen Frost <sfrost@snowman.net> writes:
'Extension Template' is fine, I was just objecting to places in the code
where it just says 'TEMPLATE'. I imagine we might have some 'XXX
Template' at some point in the future and then we'd have confusion
between "is this an *extension* template or an XXX template?".
We already do: see text search templates. The code tends to call those
TSTEMPLATEs, so I'd suggest ACL_KIND_EXTTEMPLATE or some such. I agree
with Stephen's objection to use of the bare word "template".
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane <tgl@sss.pgh.pa.us> writes:
We already do: see text search templates. The code tends to call those
TSTEMPLATEs, so I'd suggest ACL_KIND_EXTTEMPLATE or some such. I agree
with Stephen's objection to use of the bare word "template".
Yes, me too, but I had a hard time to convince myself of using such a
wordy notation. I will adjust the patch. Is that all I have to adjust
before finishing the command set support? :)
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
* Dimitri Fontaine (dimitri@2ndQuadrant.fr) wrote:
Tom Lane <tgl@sss.pgh.pa.us> writes:
We already do: see text search templates. The code tends to call those
TSTEMPLATEs, so I'd suggest ACL_KIND_EXTTEMPLATE or some such. I agree
with Stephen's objection to use of the bare word "template".Yes, me too, but I had a hard time to convince myself of using such a
wordy notation. I will adjust the patch. Is that all I have to adjust
before finishing the command set support? :)
I'm keeping a healthy distance away from *that*.. ;)
Thanks,
Stephen
Hi,
Please find attached a new version of the patch, answering to most of
your reviewing points. I'll post another version shortly with support
for pg_dump and alter owner/rename.
The main priority was to confirm that the implementation is conforming
to the rought specs and design we agreed before with Tom and Heikki, in
order to be able to adjust anything I would have misunderstood there.
Stephen Frost <sfrost@snowman.net> writes:
What's with removing the OBJECT_TABLESPACE case? Given that
Merge artifact or fat fingers, something like that.
ExtensionControlFile seemed like a good name, just changing that to
"ExtensionControl" doesn't seem as nice, tho that's a bit of bike
shedding, I suppose.
Yeah, well, the values in there now can be fetched from a catalog, so I
though I should reflect that change somehow. Will revert to the previous
name if that's the consensus.
I'm not sure we have a 'dile system'... :)
Also pretty sure we only have one catalog
('get_ext_ver_list_from_catalogs')
Fixed.
'Template' seems like a really broad term which might end up being
associated with things beyond extensions, yet there are a number of
places where you just use 'TEMPLATE', eg, ACL_KIND_TEMPLATE. Seems like
it might become an issue later.
Fixed. Any other straight TEMPLATE reference would be another error when
cleaning up the patch, though my reading tonight didn't catch'em.
Also, no pg_dump/restore support..? Seems like that'd be useful..
Yeah, that's the obvious next step. The design is that we absolutely
want to dump and restore those templates, that's the key here :)
That's just a real quick run-through with my notes. If this patch is
really gonna go into 9.3, I'll try to take a deeper look.
Thanks for that!
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Please find attached a new version of the patch, answering to most of
your reviewing points. I'll post another version shortly with support
for pg_dump and alter owner/rename.
So, as far as pg_dump is concerned, I have a trick question here.
We now have those new catalogs:
- pg_extension_control
- pg_extension_template
- pg_extension_uptmpl
When doing CREATE EXTENSION, if we did use a template to find the
control information about it, we record a dependency. The template and
update_template (named uptmpl to make the name shorter) catalog entries
also have a pg_depend dependency towards the pg_extension_control.
Now, at pg_dump time, I want to be dumping the templates for the
extension and for updating the extension *before* the extension itself.
It seems to me that the dependency as setup will guarantee that.
The trick is that I don't have anything to dump for a given control
entry itself. So I could either add some more commands so that pg_dump
can setup the control then the template for creating or updating an
extension, or just have a dumpExtensionControl() that does nothing.
I'm not sure about which one to pick. Did I explain the problem properly
enough for someone to chime in?
Now that I've written this in that email, I think I'm going to go for
the new command. But maybe we have some precedent for objects that we
list in pg_dump only for solving several steps dependency lookups?
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Now that I've written this in that email, I think I'm going to go for
the new command. But maybe we have some precedent for objects that we
list in pg_dump only for solving several steps dependency lookups?
Yes, pg_dump has lots of objects that might not appear in a dump.
The most recent examples are the section fence objects ...
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
Please find attached v2 of the Extension Templates patch, with pg_dump
support and assorted fixes. It's still missing ALTER RENAME and OWNER
facilities, and owner in the dump. There's a design point I want to
address with some input before getting there, though. Hence this email.
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
We now have those new catalogs:
- pg_extension_control
- pg_extension_template
- pg_extension_uptmpl
What I did here in pg_dump is adding a new dumpable object type
DO_EXTENSION_TEMPLATE where in fact we're fetching entries from
pg_extension_control and pg_extension_template and uptmpl.
The thing is that we now have a control entry for any script to play, so
that we can ALTER the control properties of any known target version.
Also, an extension installed from a template keeps a dependency towards
the control entry of that template, so that the dump is done with the
right ordering.
Now, the tricky part that's left over. Say that you have an extension
pair with 3 versions available, and those upgrade paths (edited for
brevity):
~# select * from pg_extension_update_paths('pair');
source | target | path
--------+--------+---------------
1.0 | 1.1 | 1.0--1.1
1.0 | 1.2 | 1.0--1.1--1.2
1.1 | 1.2 | 1.1--1.2
CREATE EXTENSION pair VERSION '1.2';
PostgreSQL didn't know how to do that before, and still does not. That
feature is implemented in another patch of mine for 9.3, quietly waiting
for attention to get back to it, and answering to a gripe initially
expressed by Robert:
https://commitfest.postgresql.org/action/patch_view?id=968
Given the ability to install an extension from a default_version then
apply the update path to what the user asked, we would have been able
to ship hstore 1.0 and 1.0--1.1 script in 9.2, without having to
consider dropping the 1.0 version yet.
Now, back to Extension Templates: the pg_dump output from the attached
patch is not smart enough to cope with an extension that has been
upgraded, it will only install the *default* version of it.
There are two ways that I see about addressing that point:
- implement default_full_version support for CREATE EXTENSION and have
it working both in the case of file based installation and template
based installation, then pg_dump work is really straightforward;
CREATE EXTENSION pair VERSION '1.2'; -- will install 1.0 then update
- add smarts into pg_dump to understand the shortest path of
installation and upgrade going from the current default_version to
the currently installed version of a template based extension so as
to be able to produce the right order of commands, as e.g.:
CREATE EXTENSION pair; -- default is 1.0
ALTER EXTENSION pair UPDATE TO '1.2'; -- updates to 1.1 then 1.2
As you might have guessed already, if I'm going to implement some smarts
in the system to cope with installation time update paths, I'd rather do
it once in the backend rather than hack it together in pg_dump only for
the template based case.
Should I merge the default_full_version patch into the Extension
Template one, or would we rather first see about commiting the default
one then the template one, or the other way around, or something else I
didn't think about?
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
Hi,
Please find attached v3 of the Extension Templates patch, with full
pg_dump support thanks to having merged default_full_version, appended
with some regression tests now that it's possible.
The patch also implements ALTER RENAME and OWNER facilities for those
new templates objects.
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Now, back to Extension Templates: the pg_dump output from the attached
patch is not smart enough to cope with an extension that has been
upgraded, it will only install the *default* version of it.
That's been fixed by merging in the default_full_version patch.
There are two ways that I see about addressing that point:
- implement default_full_version support for CREATE EXTENSION and have
it working both in the case of file based installation and template
based installation, then pg_dump work is really straightforward;CREATE EXTENSION pair VERSION '1.2'; -- will install 1.0 then update
And that just works at pg_restore time, automatically, without pg_dump
having to know anything about how.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
On 02/23/2013 12:03 AM, Dimitri Fontaine wrote:
Hi,
Please find attached v3 of the Extension Templates patch, with full
pg_dump support thanks to having merged default_full_version, appended
with some regression tests now that it's possible.
There hasn't been visible movement on this work since the 22'nd when you
posted v3 and it was flagged for further review. Nobody's stepped up,
can we get any interest in this?
What's your opinion on the state of this patch? Are you satisfied with
the proposed patch as it stands? Any particular areas you think need
attention in review or during final committer examination? Any security
concerns?
--
Craig Ringer http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Craig Ringer <craig@2ndquadrant.com> writes:
There hasn't been visible movement on this work since the 22'nd when you
posted v3 and it was flagged for further review. Nobody's stepped up,
can we get any interest in this?
I hope we can, it's a pretty important development as far as I'm
concerned, a building block for other improvements that won't need
further assistance from core code.
What's your opinion on the state of this patch? Are you satisfied with
the proposed patch as it stands? Any particular areas you think need
attention in review or during final committer examination? Any security
concerns?
I think the patch is ready for a commiter. What I think the commiter
will want to change is here:
- hstore changes
The patch reverts the hstore--1.1.sql changes to show that with the
default_major_version included before, we could have chosen to ship
with hstore--1.0.sql and hstore--1.0--1.1.sql and install 1.1 by
default in more recent releases
- docs
We might need to add some more high-level docs about the feature,
like a worked out example in the main Extension section (35.15), but
I felt time pressed and that's typically something that can be done
while in beta
- catalog names
This patch needs 3 new catalogs, named pg_extension_control,
pg_extension_template and pg_extension_uptmpl for the Templates you
use to Update an extension (not the same natural PK as the ones you
use to insert).
The decision to use 3 catalogs has been validated earlier by Tom.
The focus point is on the naming: uptmpl is meant to be as short as
possible while still being easy to understand. Is that the case?
- psql support
When compared to current EXTENSION facilities, psql support here
would mean the ability to see an extension's scripts and control
file from psql directly, and we didn't feel like we should add that
after tall. So there's no psql support in that patch, other than
including the TEMPLATEs in pg_available_extensions().
- pg_available_extension_versions() support
Oooops, I didn't add that yet. Follow-up patch needed. Do we want a
new full patch or just a patch on-top of that for later applying?
This patch certainly is big enough as it is…
- Assert() HeapTuple's catalog
In the function extract_ctlversion() I would like to be able to
Assert() that the given tuple is from the right catalog and didn't
see how to do that
Other than that, the patch implements 3 new catalogs and associated
commands, and route those commands in a way that the new grammar
additions are not tied to EXTENSION TEMPLATEs but rather generic as far
as TEMPLATEs are concerned.
Regards,
--
Dimitri Fontaine 06 63 07 10 78
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 2013-03-04 11:51:36 +0100, Dimitri Fontaine wrote:
- Assert() HeapTuple's catalog
In the function extract_ctlversion() I would like to be able to
Assert() that the given tuple is from the right catalog and didn't
see how to do that
->t_tableOid. Haven't read the patch, so I am not sure whether thats a
good check to make.
Its not 100% useful, because several places neglect to set tableOid but
I have patch to remedy that (as part of the logical decoding work).
I can send that patch separated from other stuff if there's interest.
Greetings,
Andres Freund
--
Andres Freund http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Dimitri Fontaine wrote:
Please find attached v3 of the Extension Templates patch, with full
pg_dump support thanks to having merged default_full_version, appended
with some regression tests now that it's possible.
Here's a rebased version; there were some merge conflicts with master.
I also fixed some compiler warnings. I haven't reviewed the patch in
any detail yet. One thing that jump at me from the code style
perspective is the strange way it deals with "isnull" from heap_getattr.
(I think most of these should just elog(ERROR) if a null attr is found).
Another thing is that I don't find the name "uptmpl" very clear.
We might wish to see about AtlerExtensionTemplateRename -- not only the
typo in the name but also the fact that it opens/closes the catalog for
each tuple to rename -- seems suboptimal.
Keeping the "template.c" file name seems wrong -- exttemplate.c maybe?
(I renamed the parse nodes to ExtTemplate)
There was a strange bug in pg_dump; it used "qto" where I thought
qversion was appropriate. I changed it (I looked at this hunk more
closely than most others because there was a compiler warning here, but
I didn't verify that it works.)
You seem to love using Capitalized Letters for some things in error
messages; I don't find these very pretty, and anyway they violate our
style guidelines. (I think these are in elog() not ereport() calls, but
still)
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
Attachments:
templates.v4.patchtext/x-diff; charset=us-asciiDownload
*** a/contrib/hstore/Makefile
--- b/contrib/hstore/Makefile
***************
*** 5,11 **** OBJS = hstore_io.o hstore_op.o hstore_gist.o hstore_gin.o hstore_compat.o \
crc32.o
EXTENSION = hstore
! DATA = hstore--1.1.sql hstore--1.0--1.1.sql hstore--unpackaged--1.0.sql
REGRESS = hstore
--- 5,11 ----
crc32.o
EXTENSION = hstore
! DATA = hstore--1.0.sql hstore--1.0--1.1.sql hstore--unpackaged--1.0.sql
REGRESS = hstore
*** a/contrib/hstore/hstore.control
--- b/contrib/hstore/hstore.control
***************
*** 1,5 ****
--- 1,6 ----
# hstore extension
comment = 'data type for storing sets of (key, value) pairs'
default_version = '1.1'
+ default_full_version = '1.0'
module_pathname = '$libdir/hstore'
relocatable = true
*** a/doc/src/sgml/extend.sgml
--- b/doc/src/sgml/extend.sgml
***************
*** 350,355 ****
--- 350,363 ----
</para>
<para>
+ 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 <firstterm>template</> facility in order to upload the necessary
+ script to the <productname>PostgreSQL</> server, all using the usual
+ clients and protocol.
+ </para>
+
+ <para>
The kinds of SQL objects that can be members of an extension are shown in
the description of <xref linkend="sql-alterextension">. Notably, objects
that are database-cluster-wide, such as databases, roles, and tablespaces,
***************
*** 423,428 ****
--- 431,451 ----
</varlistentry>
<varlistentry>
+ <term><varname>default_full_version</varname> (<type>string</type>)</term>
+ <listitem>
+ <para>
+ This option allows an extension author to avoid shiping all versions
+ scripts when shipping an extension. When a version is requested and
+ the matching script does not exist on disk,
+ set <replaceable>default_full_version</replaceable> to the first
+ script you still ship and PostgreSQL will apply the intermediate
+ upgrade script as per the <command>ALTER EXTENSION UPDATE</command>
+ command.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
<term><varname>comment</varname> (<type>string</type>)</term>
<listitem>
<para>
*** a/doc/src/sgml/ref/allfiles.sgml
--- b/doc/src/sgml/ref/allfiles.sgml
***************
*** 32,37 **** Complete list of usable sgml source files in this directory.
--- 32,38 ----
<!ENTITY alterSequence SYSTEM "alter_sequence.sgml">
<!ENTITY alterTable SYSTEM "alter_table.sgml">
<!ENTITY alterTableSpace SYSTEM "alter_tablespace.sgml">
+ <!ENTITY alterTemplateForExtension SYSTEM "alter_extension_template.sgml">
<!ENTITY alterTSConfig SYSTEM "alter_tsconfig.sgml">
<!ENTITY alterTSDictionary SYSTEM "alter_tsdictionary.sgml">
<!ENTITY alterTSParser SYSTEM "alter_tsparser.sgml">
***************
*** 76,81 **** Complete list of usable sgml source files in this directory.
--- 77,83 ----
<!ENTITY createTable SYSTEM "create_table.sgml">
<!ENTITY createTableAs SYSTEM "create_table_as.sgml">
<!ENTITY createTableSpace SYSTEM "create_tablespace.sgml">
+ <!ENTITY createTemplateForExtension SYSTEM "create_extension_template.sgml">
<!ENTITY createTrigger SYSTEM "create_trigger.sgml">
<!ENTITY createTSConfig SYSTEM "create_tsconfig.sgml">
<!ENTITY createTSDictionary SYSTEM "create_tsdictionary.sgml">
***************
*** 116,121 **** Complete list of usable sgml source files in this directory.
--- 118,124 ----
<!ENTITY dropServer SYSTEM "drop_server.sgml">
<!ENTITY dropTable SYSTEM "drop_table.sgml">
<!ENTITY dropTableSpace SYSTEM "drop_tablespace.sgml">
+ <!ENTITY dropTemplateForExtension SYSTEM "drop_extension_template.sgml">
<!ENTITY dropTrigger SYSTEM "drop_trigger.sgml">
<!ENTITY dropTSConfig SYSTEM "drop_tsconfig.sgml">
<!ENTITY dropTSDictionary SYSTEM "drop_tsdictionary.sgml">
*** /dev/null
--- b/doc/src/sgml/ref/alter_extension_template.sgml
***************
*** 0 ****
--- 1,149 ----
+ <!--
+ doc/src/sgml/ref/alter_extension_template.sgml
+ PostgreSQL documentation
+ -->
+
+ <refentry id="SQL-ALTEREXTENSIONTEMPLATE">
+ <refmeta>
+ <refentrytitle>ALTER TEMPLATE FOR EXTENSION</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>ALTER TEMPLATE FOR EXTENSION</refname>
+ <refpurpose>change the definition of a template for an extension</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-alterextensiontemplate">
+ <primary>ALTER TEMPLATE FOR EXTENSION</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+ <synopsis>
+ ALTER TEMPLATE FOR EXTENSION <replaceable>name</replaceable> SET DEFAULT VERSION <replaceable>version</replaceable>
+ ALTER TEMPLATE FOR EXTENSION <replaceable>name</replaceable> SET DEFAULT FULL VERSION <replaceable>full_version</replaceable>
+ ALTER TEMPLATE FOR EXTENSION <replaceable>name</replaceable> VERSION <replaceable>version</replaceable> WITH [ ([ <replaceable class="parameter">control_parameter</replaceable> ] [, ... ]) ]
+ ALTER TEMPLATE FOR EXTENSION <replaceable>name</replaceable> VERSION <replaceable>version</replaceable> AS <replaceable>script</replaceable>
+ ALTER TEMPLATE FOR EXTENSION <replaceable>name</replaceable> FROM <replaceable>from_version</replaceable> TO <replaceable>to_version</replaceable> AS <replaceable>script</replaceable>
+ ALTER TEMPLATE FOR EXTENSION <replaceable>name</replaceable> OWNER TO <replaceable>new_owner</replaceable>
+ ALTER TEMPLATE FOR EXTENSION <replaceable>name</replaceable> RENAME TO <replaceable>new_name</replaceable>
+
+ <phrase>where <replaceable class="parameter">control_parameter</replaceable> is one of:
+
+ SCHEMA <replaceable class="parameter">schema_name</replaceable>
+ SUPERUSER
+ NOSUPERUSER
+ RELOCATABLE
+ NORELOCATABLE
+ REQUIRES <replaceable class="parameter">requirements</replaceable>
+
+ </synopsis>
+ </refsynopsisdiv>
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>ALTER TEMPLATE FOR EXTENSION</command> changes the definition of
+ an extension template. Currently, the only supported functionality is to
+ change the template's default version.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><replaceable class="parameter">name</replaceable></term>
+ <listitem>
+ <para>
+ The name of an extension that already has templates.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">version</replaceable></term>
+ <listitem>
+ <para>
+ The version of the extension we want to install by default when using
+ its template.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">full_version</replaceable></term>
+ <listitem>
+ <para>
+ The version of the extension we want to install from by default when
+ using its template. For example, if you have an extension installation
+ scipt for version <literal>1.0</literal> and an upgrade script
+ for <literal>1.0--1.1</literal>, you can set the
+ default <literal>full_version</literal> to <literal>1.0</literal> so
+ that <productname>PostgreSQL</productname> knows to install
+ version <literal>1.1</literal> by first using
+ the <literal>1.0</literal> creation script then
+ the <literal>1.1</literal> upgrade script.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">script</replaceable></term>
+ <listitem>
+ <para>
+ The script to run when installing this version of the extension.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">control_parameters</replaceable></term>
+ <listitem>
+ <para>
+ For details about the control parameters meaning, please refer
+ to <xref linkend="extend-extension">.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_name</replaceable></term>
+ <listitem>
+ <para>
+ The new name of the aggregate function.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><replaceable class="parameter">new_owner</replaceable></term>
+ <listitem>
+ <para>
+ The new owner of the aggregate function.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ There is no <command>ALTER TEMPLATE FOR EXTENSION</command> statement in
+ the SQL standard.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-createextensiontemplate"></member>
+ <member><xref linkend="sql-dropextensiontemplate"></member>
+ </simplelist>
+ </refsect1>
+ </refentry>
*** /dev/null
--- b/doc/src/sgml/ref/create_extension_template.sgml
***************
*** 0 ****
--- 1,104 ----
+ <!--
+ doc/src/sgml/ref/create_extension_template.sgml
+ PostgreSQL documentation
+ -->
+
+ <refentry id="SQL-CREATEEXTENSIONTEMPLATE">
+ <refmeta>
+ <refentrytitle>CREATE TEMPLATE FOR EXTENSION</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>CREATE TEMPLATE FOR EXTENSION</refname>
+ <refpurpose>define a new template for extension</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-createtstemplate">
+ <primary>CREATE TEMPLATE FOR EXTENSION</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+ <synopsis>
+ CREATE TEMPLATE FOR EXTENSION <replaceable class="parameter">name</replaceable>
+ [ DEFAULT ] VERSION <replaceable class="parameter">version</replaceable>
+ [ WITH <replaceable class="parameter"> [ (
+ [ <replaceable class="parameter">control_parameter</replaceable> ] [, ... ]
+ ) ] ]
+ AS <replaceable class="parameter">script</replaceable>
+
+ CREATE TEMPLATE FOR EXTENSION <replaceable class="parameter">name</replaceable>
+ FROM <replaceable class="parameter">old_version</replaceable> TO <replaceable class="parameter">new_version</replaceable>
+ [ WITH <replaceable class="parameter"> [ (
+ [ <replaceable class="parameter">control_parameter</replaceable> ] [, ... ]
+ ) ] ]
+ AS <replaceable class="parameter">script</replaceable>
+
+
+ <phrase>where <replaceable class="parameter">control_parameter</replaceable> is one of:
+
+ SCHEMA <replaceable class="parameter">schema_name</replaceable>
+ SUPERUSER
+ NOSUPERUSER
+ RELOCATABLE
+ NORELOCATABLE
+ REQUIRES <replaceable class="parameter">requirements</replaceable>
+
+ </synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>CREATE TEMPLATE FOR EXTENSION</command> 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 <productname>PostgreSQL</productname> service.
+ </para>
+
+ <para>
+ Using the <command>CREATE TEMPLATE FOR EXTENSION</command> command you
+ can upload script to be run at <command>CREATE EXTENSION</command> time
+ and at <command>ALTER EXTENSION ... UPDATE</command> time.
+ </para>
+
+ <para>
+ Refer to <xref linkend="extend-extension"> for further information.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Control Parameters</title>
+
+ <para>
+ For details about the control parameters meaning, please refer
+ to <xref linkend="extend-extension">.
+ </para>
+
+ <para>
+ The arguments can appear in any order, not only the one shown above.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Compatibility</title>
+
+ <para>
+ There is no
+ <command>CREATE TEMPLATE FOR EXTENSION</command> statement in the SQL
+ standard.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-alterextensiontemplate"></member>
+ <member><xref linkend="sql-dropextensiontemplate"></member>
+ </simplelist>
+ </refsect1>
+ </refentry>
*** /dev/null
--- b/doc/src/sgml/ref/drop_extension_template.sgml
***************
*** 0 ****
--- 1,81 ----
+ <!--
+ doc/src/sgml/ref/drop_extension_template.sgml
+ PostgreSQL documentation
+ -->
+
+ <refentry id="SQL-DROPTSTEMPLATE">
+ <refmeta>
+ <refentrytitle>DROP TEMPLATE FOR EXTENSION</refentrytitle>
+ <manvolnum>7</manvolnum>
+ <refmiscinfo>SQL - Language Statements</refmiscinfo>
+ </refmeta>
+
+ <refnamediv>
+ <refname>DROP TEMPLATE FOR EXTENSION</refname>
+ <refpurpose>remove a template for an extension</refpurpose>
+ </refnamediv>
+
+ <indexterm zone="sql-droptstemplate">
+ <primary>DROP TEMPLATE FOR EXTENSION</primary>
+ </indexterm>
+
+ <refsynopsisdiv>
+ <synopsis>
+ DROP TEMPLATE FOR EXTENSION <replaceable class="PARAMETER">name</replaceable> VERSION <replaceable class="PARAMETER">version</replaceable> [ CASCADE | RESTRICT ]
+ DROP TEMPLATE FOR EXTENSION <replaceable class="PARAMETER">name</replaceable> FROM <replaceable class="PARAMETER">old_version</replaceable> TO <replaceable class="PARAMETER">new_version</replaceable> [ CASCADE | RESTRICT ]
+ </synopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <command>DROP TEMPLATE FOR EXTENSION</command> drops an existing template
+ for named extension
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Parameters</title>
+
+ <variablelist>
+ <varlistentry>
+ <term><replaceable class="parameter">name</replaceable></term>
+ <listitem>
+ <para>
+ The name of an existing text search template.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>CASCADE</literal></term>
+ <listitem>
+ <para>
+ Automatically drop objects that depend on the template.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><literal>RESTRICT</literal></term>
+ <listitem>
+ <para>
+ Refuse to drop the template if any objects depend on it. This is the
+ default.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+
+ <simplelist type="inline">
+ <member><xref linkend="sql-alterextensiontemplate"></member>
+ <member><xref linkend="sql-createextensiontemplate"></member>
+ </simplelist>
+ </refsect1>
+
+ </refentry>
*** a/doc/src/sgml/reference.sgml
--- b/doc/src/sgml/reference.sgml
***************
*** 60,65 ****
--- 60,66 ----
&alterServer;
&alterTable;
&alterTableSpace;
+ &alterTemplateForExtension;
&alterTSConfig;
&alterTSDictionary;
&alterTSParser;
***************
*** 104,109 ****
--- 105,111 ----
&createTable;
&createTableAs;
&createTableSpace;
+ &createTemplateForExtension;
&createTSConfig;
&createTSDictionary;
&createTSParser;
***************
*** 144,149 ****
--- 146,152 ----
&dropServer;
&dropTable;
&dropTableSpace;
+ &dropTemplateForExtension;
&dropTSConfig;
&dropTSDictionary;
&dropTSParser;
*** a/src/backend/catalog/Makefile
--- b/src/backend/catalog/Makefile
***************
*** 38,43 **** POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\
--- 38,44 ----
pg_authid.h pg_auth_members.h pg_shdepend.h pg_shdescription.h \
pg_ts_config.h pg_ts_config_map.h pg_ts_dict.h \
pg_ts_parser.h pg_ts_template.h pg_extension.h \
+ pg_extension_control.h pg_extension_template.h pg_extension_uptmpl.h \
pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \
pg_foreign_table.h \
pg_default_acl.h pg_seclabel.h pg_shseclabel.h pg_collation.h pg_range.h \
*** a/src/backend/catalog/aclchk.c
--- b/src/backend/catalog/aclchk.c
***************
*** 32,37 ****
--- 32,40 ----
#include "catalog/pg_default_acl.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
+ #include "catalog/pg_extension_control.h"
+ #include "catalog/pg_extension_template.h"
+ #include "catalog/pg_extension_uptmpl.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_language.h"
***************
*** 5042,5047 **** pg_extension_ownercheck(Oid ext_oid, Oid roleid)
--- 5045,5182 ----
}
/*
+ * Ownership check for an extension control (specified by OID).
+ */
+ bool
+ pg_extension_control_ownercheck(Oid ext_control_oid, Oid roleid)
+ {
+ Relation pg_extension_control;
+ ScanKeyData entry[1];
+ SysScanDesc scan;
+ HeapTuple tuple;
+ Oid ownerId;
+
+ /* Superusers bypass all permission checking. */
+ if (superuser_arg(roleid))
+ return true;
+
+ /* There's no syscache for pg_extension_control, so do it the hard way */
+ pg_extension_control =
+ heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ext_control_oid));
+
+ scan = systable_beginscan(pg_extension_control,
+ ExtensionControlOidIndexId, true,
+ SnapshotNow, 1, entry);
+
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension control with OID %u does not exist",
+ ext_control_oid)));
+
+ ownerId = ((Form_pg_extension_control) GETSTRUCT(tuple))->ctlowner;
+
+ systable_endscan(scan);
+ heap_close(pg_extension_control, AccessShareLock);
+
+ return has_privs_of_role(roleid, ownerId);
+ }
+
+ /*
+ * Ownership check for an extension template (specified by OID).
+ */
+ bool
+ pg_extension_template_ownercheck(Oid ext_template_oid, Oid roleid)
+ {
+ Relation pg_extension_template;
+ ScanKeyData entry[1];
+ SysScanDesc scan;
+ HeapTuple tuple;
+ Oid ownerId;
+
+ /* Superusers bypass all permission checking. */
+ if (superuser_arg(roleid))
+ return true;
+
+ /* There's no syscache for pg_extension_template, so do it the hard way */
+ pg_extension_template =
+ heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ext_template_oid));
+
+ scan = systable_beginscan(pg_extension_template,
+ ExtensionTemplateOidIndexId, true,
+ SnapshotNow, 1, entry);
+
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension template with OID %u does not exist",
+ ext_template_oid)));
+
+ ownerId = ((Form_pg_extension_template) GETSTRUCT(tuple))->tplowner;
+
+ systable_endscan(scan);
+ heap_close(pg_extension_template, AccessShareLock);
+
+ return has_privs_of_role(roleid, ownerId);
+ }
+
+ /*
+ * Ownership check for an extension update template (specified by OID).
+ */
+ bool
+ pg_extension_uptmpl_ownercheck(Oid ext_uptmpl_oid, Oid roleid)
+ {
+ Relation pg_extension_uptmpl;
+ ScanKeyData entry[1];
+ SysScanDesc scan;
+ HeapTuple tuple;
+ Oid ownerId;
+
+ /* Superusers bypass all permission checking. */
+ if (superuser_arg(roleid))
+ return true;
+
+ /* There's no syscache for pg_extension_uptmpl, so do it the hard way */
+ pg_extension_uptmpl =
+ heap_open(ExtensionUpTmplRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ext_uptmpl_oid));
+
+ scan = systable_beginscan(pg_extension_uptmpl,
+ ExtensionUpTmplOidIndexId, true,
+ SnapshotNow, 1, entry);
+
+ tuple = systable_getnext(scan);
+ if (!HeapTupleIsValid(tuple))
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension uptmpl with OID %u does not exist",
+ ext_uptmpl_oid)));
+
+ ownerId = ((Form_pg_extension_uptmpl) GETSTRUCT(tuple))->uptowner;
+
+ systable_endscan(scan);
+ heap_close(pg_extension_uptmpl, AccessShareLock);
+
+ return has_privs_of_role(roleid, ownerId);
+ }
+
+ /*
* Check whether specified role has CREATEROLE privilege (or is a superuser)
*
* Note: roles do not have owners per se; instead we use this test in
*** a/src/backend/catalog/dependency.c
--- b/src/backend/catalog/dependency.c
***************
*** 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"
***************
*** 1228,1233 **** doDeletion(const ObjectAddress *object, int flags)
--- 1232,1249 ----
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;
***************
*** 2289,2294 **** getObjectClass(const ObjectAddress *object)
--- 2305,2319 ----
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;
}
***************
*** 2930,2935 **** getObjectDescription(const ObjectAddress *object)
--- 2955,2996 ----
break;
}
+ case OCLASS_EXTENSION_CONTROL:
+ {
+ char *extname;
+
+ extname = get_extension_control_name(object->objectId);
+ if (!extname)
+ elog(ERROR, "cache lookup failed for control template for extension %u",
+ object->objectId);
+ appendStringInfo(&buffer, _("control template for extension %s"), extname);
+ break;
+ }
+
+ case OCLASS_EXTENSION_TEMPLATE:
+ {
+ char *extname;
+
+ extname = get_extension_template_name(object->objectId);
+ if (!extname)
+ elog(ERROR, "cache lookup failed for template for creating extension %u",
+ object->objectId);
+ appendStringInfo(&buffer, _("install template for extension %s"), extname);
+ break;
+ }
+
+ case OCLASS_EXTENSION_UPTMPL:
+ {
+ char *extname;
+
+ extname = get_extension_uptmpl_name(object->objectId);
+ if (!extname)
+ elog(ERROR, "cache lookup failed for template for updating extension %u",
+ object->objectId);
+ appendStringInfo(&buffer, _("update template for extension %s"), extname);
+ break;
+ }
+
case OCLASS_EVENT_TRIGGER:
{
HeapTuple tup;
*** 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"
***************
*** 159,164 **** static ObjectPropertyType ObjectProperty[] =
--- 163,201 ----
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_EXTCONTROL
+ },
+ {
+ ExtensionTemplateRelationId,
+ ExtensionTemplateOidIndexId,
+ -1,
+ -1,
+ Anum_pg_extension_template_tplname,
+ InvalidAttrNumber, /* extension doesn't belong to extnamespace */
+ Anum_pg_extension_template_tplowner,
+ InvalidAttrNumber,
+ ACL_KIND_EXTTEMPLATE
+ },
+ {
+ ExtensionUpTmplRelationId,
+ ExtensionUpTmplOidIndexId,
+ -1,
+ -1,
+ Anum_pg_extension_uptmpl_uptname,
+ InvalidAttrNumber, /* extension doesn't belong to extnamespace */
+ Anum_pg_extension_uptmpl_uptowner,
+ InvalidAttrNumber,
+ ACL_KIND_EXTUPTMPL
+ },
+ {
ForeignDataWrapperRelationId,
ForeignDataWrapperOidIndexId,
FOREIGNDATAWRAPPEROID,
***************
*** 394,399 **** static ObjectAddress get_object_address_type(ObjectType objtype,
--- 431,438 ----
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);
/*
***************
*** 475,480 **** get_object_address(ObjectType objtype, List *objname, List *objargs,
--- 514,524 ----
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);
***************
*** 721,731 **** 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);
--- 765,770 ----
***************
*** 756,761 **** get_object_address_unqualified(ObjectType objtype,
--- 795,805 ----
address.objectId = get_event_trigger_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;
default:
elog(ERROR, "unrecognized objtype: %d", (int) objtype);
/* placate compiler, which doesn't know elog won't return */
***************
*** 1069,1074 **** get_object_address_opcf(ObjectType objtype,
--- 1113,1194 ----
}
/*
+ * 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
***************
*** 1133,1138 **** check_object_ownership(Oid roleid, ObjectType objtype, ObjectAddress address,
--- 1253,1268 ----
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTENSION,
NameListToString(objname));
break;
+ case OBJECT_EXTENSION_TEMPLATE:
+ if (!pg_extension_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTTEMPLATE,
+ NameListToString(objname));
+ break;
+ case OBJECT_EXTENSION_UPTMPL:
+ if (!pg_extension_ownercheck(address.objectId, roleid))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTUPTMPL,
+ NameListToString(objname));
+ break;
case OBJECT_FDW:
if (!pg_foreign_data_wrapper_ownercheck(address.objectId, roleid))
aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_FDW,
*** a/src/backend/commands/Makefile
--- b/src/backend/commands/Makefile
***************
*** 19,25 **** OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
portalcmds.o prepare.o proclang.o \
schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
! tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
variable.o view.o
include $(top_srcdir)/src/backend/common.mk
--- 19,25 ----
indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \
portalcmds.o prepare.o proclang.o \
schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \
! template.o tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \
variable.o view.o
include $(top_srcdir)/src/backend/common.mk
*** a/src/backend/commands/alter.c
--- b/src/backend/commands/alter.c
***************
*** 46,51 ****
--- 46,52 ----
#include "commands/schemacmds.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+ #include "commands/template.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "commands/user.h"
***************
*** 62,67 ****
--- 63,69 ----
static Oid AlterObjectNamespace_internal(Relation rel, Oid objid, Oid nspOid);
+ static HeapTuple get_catalog_object_by_oid(Relation catalog, Oid objectId);
/*
* Raise an error to the effect that an object of the given name is already
***************
*** 146,156 **** report_namespace_conflict(Oid classId, const char *name, Oid nspOid)
* objectId: OID of object to be renamed
* new_name: CString representation of new name
*/
! static void
AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
{
Oid classId = RelationGetRelid(rel);
- int oidCacheId = get_object_catcache_oid(classId);
int nameCacheId = get_object_catcache_name(classId);
AttrNumber Anum_name = get_object_attnum_name(classId);
AttrNumber Anum_namespace = get_object_attnum_namespace(classId);
--- 148,157 ----
* objectId: OID of object to be renamed
* new_name: CString representation of new name
*/
! void
AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
{
Oid classId = RelationGetRelid(rel);
int nameCacheId = get_object_catcache_name(classId);
AttrNumber Anum_name = get_object_attnum_name(classId);
AttrNumber Anum_namespace = get_object_attnum_namespace(classId);
***************
*** 168,175 **** AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
bool *nulls;
bool *replaces;
! oldtup = SearchSysCache1(oidCacheId, ObjectIdGetDatum(objectId));
! if (!HeapTupleIsValid(oldtup))
elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
objectId, RelationGetRelationName(rel));
--- 169,176 ----
bool *nulls;
bool *replaces;
! oldtup = get_catalog_object_by_oid(rel, objectId);
! if (oldtup == NULL)
elog(ERROR, "cache lookup failed for object %u of catalog \"%s\"",
objectId, RelationGetRelationName(rel));
***************
*** 286,293 **** AlterObjectRename_internal(Relation rel, Oid objectId, const char *new_name)
pfree(nulls);
pfree(replaces);
heap_freetuple(newtup);
-
- ReleaseSysCache(oldtup);
}
/*
--- 287,292 ----
***************
*** 337,342 **** ExecRenameStmt(RenameStmt *stmt)
--- 336,344 ----
case OBJECT_TYPE:
return RenameType(stmt);
+ case OBJECT_EXTENSION_TEMPLATE:
+ return AtlerExtensionTemplateRename(stmt->subname, stmt->newname);
+
case OBJECT_AGGREGATE:
case OBJECT_COLLATION:
case OBJECT_CONVERSION:
***************
*** 694,699 **** ExecAlterOwnerStmt(AlterOwnerStmt *stmt)
--- 696,705 ----
return AlterEventTriggerOwner(strVal(linitial(stmt->object)),
newowner);
+ case OBJECT_EXTENSION_TEMPLATE:
+ return AtlerExtensionTemplateOwner(strVal(linitial(stmt->object)),
+ newowner);
+
/* Generic cases */
case OBJECT_AGGREGATE:
case OBJECT_COLLATION:
*** a/src/backend/commands/event_trigger.c
--- b/src/backend/commands/event_trigger.c
***************
*** 85,90 **** static event_trigger_support_data event_trigger_support[] = {
--- 85,91 ----
{ "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
***************
*** 37,48 ****
--- 37,50 ----
#include "catalog/pg_collation.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_extension.h"
+ #include "catalog/pg_extension_control.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_type.h"
#include "commands/alter.h"
#include "commands/comment.h"
#include "commands/extension.h"
#include "commands/schemacmds.h"
+ #include "commands/template.h"
#include "funcapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
***************
*** 61,83 **** bool creating_extension = false;
Oid CurrentExtensionObject = InvalidOid;
/*
- * Internal data structure to hold the results of parsing a control file
- */
- typedef struct ExtensionControlFile
- {
- char *name; /* name of the extension */
- char *directory; /* directory for script files */
- char *default_version; /* default install target version, if any */
- char *module_pathname; /* string to substitute for MODULE_PATHNAME */
- char *comment; /* comment, if any */
- char *schema; /* target schema (allowed if !relocatable) */
- bool relocatable; /* is ALTER EXTENSION SET SCHEMA supported? */
- bool superuser; /* must be superuser to install? */
- int encoding; /* encoding of the script file, or -1 */
- List *requires; /* names of prerequisite extensions */
- } ExtensionControlFile;
-
- /*
* Internal data structure for update path information
*/
typedef struct ExtensionVersionInfo
--- 63,68 ----
***************
*** 96,106 **** static List *find_update_path(List *evi_list,
ExtensionVersionInfo *evi_start,
ExtensionVersionInfo *evi_target,
bool reinitialize);
! static void get_available_versions_for_extension(ExtensionControlFile *pcontrol,
Tuplestorestate *tupstore,
TupleDesc tupdesc);
static void ApplyExtensionUpdates(Oid extensionOid,
! ExtensionControlFile *pcontrol,
const char *initialVersion,
List *updateVersions);
--- 81,91 ----
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);
--- 217,223 ----
/*
* 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;
--- 355,361 ----
}
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;
--- 378,384 ----
}
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;
--- 396,402 ----
}
static char *
! get_extension_script_filename(ExtensionControl *control,
const char *from_version, const char *version)
{
char *result;
***************
*** 432,437 **** get_extension_script_filename(ExtensionControlFile *control,
--- 417,435 ----
return result;
}
+ /*
+ * An extension version is said to be "full" when it has a full install script,
+ * so that we know we don't need any update sequences dances either from
+ * "unpackaged" or from "default_major_version".
+ */
+ static bool
+ extension_version_is_full(ExtensionControl *control, const char *version)
+ {
+ char *filename = get_extension_script_filename(control, NULL, version);
+
+ return access(filename, F_OK) == 0
+ || OidIsValid(get_template_oid(control->name, version, true));
+ }
/*
* Parse contents of primary or auxiliary control file, and fill in
***************
*** 443,449 **** get_extension_script_filename(ExtensionControlFile *control,
* worry about what encoding it's in; all values are expected to be ASCII.
*/
static void
! parse_extension_control_file(ExtensionControlFile *control,
const char *version)
{
char *filename;
--- 441,447 ----
* 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)
{
--- 481,487 ----
FreeFile(file);
/*
! * Convert the ConfigVariable list into ExtensionControl entries.
*/
for (item = head; item != NULL; item = item->next)
{
***************
*** 507,512 **** parse_extension_control_file(ExtensionControlFile *control,
--- 505,514 ----
control->default_version = pstrdup(item->value);
}
+ else if (strcmp(item->name, "default_full_version") == 0)
+ {
+ control->default_full_version = pstrdup(item->value);
+ }
else if (strcmp(item->name, "module_pathname") == 0)
{
control->module_pathname = pstrdup(item->value);
***************
*** 579,594 **** parse_extension_control_file(ExtensionControlFile *control,
/*
* Read the primary control file for the specified extension.
*/
! static ExtensionControlFile *
read_extension_control_file(const char *extname)
{
! ExtensionControlFile *control;
/*
* Set up default values. Pointer fields are initially null.
*/
! control = (ExtensionControlFile *) palloc0(sizeof(ExtensionControlFile));
control->name = pstrdup(extname);
control->relocatable = false;
control->superuser = true;
control->encoding = -1;
--- 581,598 ----
/*
* Read the primary control file for the specified extension.
*/
! ExtensionControl *
read_extension_control_file(const char *extname)
{
! ExtensionControl *control;
/*
* Set up default values. Pointer fields are initially null.
*/
! control = (ExtensionControl *) palloc0(sizeof(ExtensionControl));
! control->ctrlOid = InvalidOid;
control->name = pstrdup(extname);
+ control->is_template = false;
control->relocatable = false;
control->superuser = true;
control->encoding = -1;
***************
*** 604,623 **** read_extension_control_file(const char *extname)
/*
* Read the auxiliary control file for the specified extension and version.
*
! * Returns a new modified ExtensionControlFile struct; the original struct
* (reflecting just the primary control file) is not modified.
*/
! static ExtensionControlFile *
! read_extension_aux_control_file(const ExtensionControlFile *pcontrol,
const char *version)
{
! ExtensionControlFile *acontrol;
/*
* Flat-copy the struct. Pointer fields share values with original.
*/
! acontrol = (ExtensionControlFile *) palloc(sizeof(ExtensionControlFile));
! memcpy(acontrol, pcontrol, sizeof(ExtensionControlFile));
/*
* Parse the auxiliary control file, overwriting struct fields
--- 608,627 ----
/*
* 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;
--- 632,706 ----
}
/*
+ * Read the control properties for given extension, either from a file on the
+ * file system or if it does not exists there, from a template catalog in
+ * pg_extension_control, if it exists.
+ *
+ * In the file system case, we get the default properties for the extension and
+ * one of them is the default_version property that allows us to know which
+ * version to install. Knowing that we can then read the right auxilliary
+ * control file to override some defaults if needs be.
+ *
+ * When reading from the catalogs, we have in pg_extension_control at most a
+ * row per version, with the whole set of properties we need to apply. So once
+ * we found the current default version to install, we don't need to read and
+ * another set of properties and override them.
+ *
+ * In both cases we return the structure ExtensionControl, 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
--- 743,748 ----
***************
*** 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;
--- 752,758 ----
* 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;
--- 837,848 ----
* 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
--- 868,873 ----
***************
*** 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);
--- 922,944 ----
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();
{
--- 987,993 ----
/* 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);
--- 1076,1082 ----
* 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)
--- 1132,1189 ----
}
/*
+ * We scan pg_extension_template for all install scripts of given extension,
+ * then pg_extension_uptmpl for all update scripts of same extension.
+ */
+ static List *
+ get_ext_ver_list_from_catalog(ExtensionControl *control)
+ {
+ List *evi_list = NIL;
+ List *installable, *direct_update_paths;
+ ListCell *lc;
+
+ /* pg_extension_template contains install scripts */
+ installable = list_pg_extension_template_versions(control->name);
+
+ foreach(lc, installable)
+ {
+ ExtensionVersionInfo *evi;
+ char *vername = (char *) lfirst(lc);
+
+ evi = get_ext_ver_info(vername, &evi_list);
+ }
+
+ /* pg_extension_uptmpl contains upgrade scripts */
+ direct_update_paths = list_pg_extension_update_versions(control->name);
+
+ foreach(lc, direct_update_paths)
+ {
+ ExtensionVersionInfo *evi, *evi2;
+ char *vername = (char *) linitial(lfirst(lc));
+ char *vername2 = (char *) lsecond(lfirst(lc));
+
+ evi = get_ext_ver_info(vername, &evi_list);
+ evi2 = get_ext_ver_info(vername2, &evi_list);
+ evi->reachable = lappend(evi->reachable, evi2);
+ }
+ return evi_list;
+ }
+
+ /*
+ * We have to implement that function twice. The first implementation deals
+ * with control files and sql scripts on the file system while the second one
+ * deals with the catalogs pg_extension_template and pg_extension_uptmpl.
+ */
+ static List *
+ get_ext_ver_list(ExtensionControl *control)
+ {
+ if (control->is_template)
+ return get_ext_ver_list_from_catalog(control);
+ else
+ return get_ext_ver_list_from_files(control);
+ }
+
+ /*
* Given an initial and final version name, identify the sequence of update
* scripts that have to be applied to perform that update.
*
***************
*** 1060,1066 **** get_ext_ver_list(ExtensionControlFile *control)
* version is *not* included).
*/
static List *
! identify_update_path(ExtensionControlFile *control,
const char *oldVersion, const char *newVersion)
{
List *result;
--- 1191,1197 ----
* version is *not* included).
*/
static List *
! identify_update_path(ExtensionControl *control,
const char *oldVersion, const char *newVersion)
{
List *result;
***************
*** 1185,1197 **** CreateExtension(CreateExtensionStmt *stmt)
char *versionName;
char *oldVersionName;
Oid extowner = GetUserId();
! ExtensionControlFile *pcontrol;
! ExtensionControlFile *control;
List *updateVersions;
List *requiredExtensions;
List *requiredSchemas;
Oid extensionOid;
ListCell *lc;
/* Check extension name validity before any filesystem access */
check_valid_extension_name(stmt->extname);
--- 1316,1329 ----
char *versionName;
char *oldVersionName;
Oid extowner = GetUserId();
! ExtensionControl *pcontrol;
! ExtensionControl *control;
List *updateVersions;
List *requiredExtensions;
List *requiredSchemas;
Oid extensionOid;
ListCell *lc;
+ bool unpackaged = false, target_version_is_full = false;
/* Check extension name validity before any filesystem access */
check_valid_extension_name(stmt->extname);
***************
*** 1233,1239 **** CreateExtension(CreateExtensionStmt *stmt)
* any non-ASCII data, so there is no need to worry about encoding at this
* point.
*/
! pcontrol = read_extension_control_file(stmt->extname);
/*
* Read the statement option list
--- 1365,1371 ----
* 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)
--- 1404,1414 ----
/*
* Determine the version to install
+ *
+ * Note that in the case when we install an extension from a template, and
+ * when the target version to install is given in the SQL command, we could
+ * arrange the code to only scan pg_extension_control once: there's no need
+ * to read any primary control row in that case. There's no harm doing so.
*/
if (d_new_version && d_new_version->arg)
versionName = strVal(d_new_version->arg);
***************
*** 1287,1328 **** CreateExtension(CreateExtensionStmt *stmt)
check_valid_version_name(versionName);
/*
* Determine the (unpackaged) version to update from, if any, and then
* figure out what sequence of update scripts we need to apply.
*/
! if (d_old_version && d_old_version->arg)
{
! oldVersionName = strVal(d_old_version->arg);
! check_valid_version_name(oldVersionName);
! if (strcmp(oldVersionName, versionName) == 0)
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! errmsg("FROM version must be different from installation target version \"%s\"",
! versionName)));
! updateVersions = identify_update_path(pcontrol,
! oldVersionName,
! versionName);
! if (list_length(updateVersions) == 1)
{
! /*
! * Simple case where there's just one update script to run. We
! * will not need any follow-on update steps.
! */
! Assert(strcmp((char *) linitial(updateVersions), versionName) == 0);
! updateVersions = NIL;
}
else
{
! /*
! * Multi-step sequence. We treat this as installing the version
! * that is the target of the first script, followed by successive
! * updates to the later versions.
! */
! versionName = (char *) linitial(updateVersions);
! updateVersions = list_delete_first(updateVersions);
}
}
else
--- 1424,1511 ----
check_valid_version_name(versionName);
/*
+ * If we have a full script for the target version (or a create template),
+ * we don't need to care about unpackaged or default_major_version, nor
+ * about upgrade sequences.
+ */
+ if (extension_version_is_full(pcontrol, versionName))
+ {
+ target_version_is_full = true;
+ oldVersionName = NULL;
+ updateVersions = NIL;
+ }
+ /*
* Determine the (unpackaged) version to update from, if any, and then
* figure out what sequence of update scripts we need to apply.
+ *
+ * When we have a default_full_version and the target is different from it,
+ * apply the same algorithm to find a sequence of updates. If the user did
+ * ask for a target version that happens to be the same as the
+ * default_full_version, just install that one directly.
*/
! else if ((d_old_version && d_old_version->arg) || pcontrol->default_full_version)
{
! unpackaged = (d_old_version && d_old_version->arg);
! if (unpackaged)
! oldVersionName = strVal(d_old_version->arg);
! else
! oldVersionName = pcontrol->default_full_version;
! check_valid_version_name(oldVersionName);
! if (strcmp(oldVersionName, versionName) == 0)
{
! if (unpackaged)
! {
! ereport(ERROR,
! (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
! errmsg("FROM version must be different from installation target version \"%s\"",
! versionName)));
! }
! else
! {
! /*
! * CREATE EXTENSION ... VERSION = default_full_version, just
! * pretend we don't have a default_full_version for the
! * remaining of the code here, as that's the behavior we want
! * to see happening.
! */
! pcontrol->default_full_version = NULL;
! oldVersionName = NULL;
! updateVersions = NIL;
! }
}
else
{
! /* oldVersionName != versionName */
! updateVersions = identify_update_path(pcontrol,
! oldVersionName,
! versionName);
! }
!
! /* in the create from unpackaged case, reduce the update list */
! if (unpackaged)
! {
! if (list_length(updateVersions) == 1)
! {
! /*
! * Simple case where there's just one update script to run. We
! * will not need any follow-on update steps.
! */
! Assert(strcmp((char *) linitial(updateVersions), versionName) == 0);
! updateVersions = NIL;
! }
! else
! {
! /*
! * Multi-step sequence. We treat this as installing the version
! * that is the target of the first script, followed by successive
! * updates to the later versions.
! */
! versionName = (char *) linitial(updateVersions);
! updateVersions = list_delete_first(updateVersions);
! }
}
}
else
***************
*** 1334,1340 **** CreateExtension(CreateExtensionStmt *stmt)
/*
* Fetch control parameters for installation target version
*/
! control = read_extension_aux_control_file(pcontrol, versionName);
/*
* Determine the target schema to install the extension into
--- 1517,1523 ----
/*
* Fetch control parameters for installation target version
*/
! control = read_extension_aux_control(pcontrol, versionName);
/*
* Determine the target schema to install the extension into
***************
*** 1448,1454 **** CreateExtension(CreateExtensionStmt *stmt)
versionName,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
! requiredExtensions);
/*
* Apply any control-file comment on extension
--- 1631,1638 ----
versionName,
PointerGetDatum(NULL),
PointerGetDatum(NULL),
! requiredExtensions,
! control->ctrlOid);
/*
* Apply any control-file comment on extension
***************
*** 1458,1476 **** CreateExtension(CreateExtensionStmt *stmt)
/*
* Execute the installation script file
! */
! execute_extension_script(extensionOid, control,
! oldVersionName, versionName,
! requiredSchemas,
! schemaName, schemaOid);
!
! /*
* If additional update scripts have to be executed, apply the updates as
* though a series of ALTER EXTENSION UPDATE commands were given
*/
! ApplyExtensionUpdates(extensionOid, pcontrol,
! versionName, updateVersions);
return extensionOid;
}
--- 1642,1678 ----
/*
* Execute the installation script file
! *
* If additional update scripts have to be executed, apply the updates as
* though a series of ALTER EXTENSION UPDATE commands were given
*/
! if (target_version_is_full)
! {
! execute_extension_script(extensionOid, control,
! NULL, versionName,
! requiredSchemas,
! schemaName, schemaOid);
! }
! else if (pcontrol->default_full_version && !unpackaged)
! {
! execute_extension_script(extensionOid, control,
! NULL, oldVersionName,
! requiredSchemas,
! schemaName, schemaOid);
!
! ApplyExtensionUpdates(extensionOid, pcontrol,
! oldVersionName, updateVersions);
! }
! else
! {
! execute_extension_script(extensionOid, control,
! oldVersionName, versionName,
! requiredSchemas,
! schemaName, schemaOid);
+ ApplyExtensionUpdates(extensionOid, pcontrol,
+ versionName, updateVersions);
+ }
return extensionOid;
}
***************
*** 1491,1497 **** Oid
InsertExtensionTuple(const char *extName, Oid extOwner,
Oid schemaOid, bool relocatable, const char *extVersion,
Datum extConfig, Datum extCondition,
! List *requiredExtensions)
{
Oid extensionOid;
Relation rel;
--- 1693,1699 ----
InsertExtensionTuple(const char *extName, Oid extOwner,
Oid schemaOid, bool relocatable, const char *extVersion,
Datum extConfig, Datum extCondition,
! List *requiredExtensions, Oid ctrlOid)
{
Oid extensionOid;
Relation rel;
***************
*** 1561,1566 **** InsertExtensionTuple(const char *extName, Oid extOwner,
--- 1763,1781 ----
recordDependencyOn(&myself, &otherext, DEPENDENCY_NORMAL);
}
+
+ /* Record dependency on pg_extension_control, if created from a template */
+ if (OidIsValid(ctrlOid))
+ {
+ ObjectAddress pg_extension_control;
+
+ pg_extension_control.classId = ExtensionControlRelationId;
+ pg_extension_control.objectId = ctrlOid;
+ pg_extension_control.objectSubId = 0;
+
+ recordDependencyOn(&myself, &pg_extension_control, DEPENDENCY_NORMAL);
+ }
+
/* Post creation hook for new extension */
InvokeObjectPostCreateHook(ExtensionRelationId, extensionOid, 0);
***************
*** 1631,1643 **** 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))
--- 1846,1860 ----
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))
***************
*** 1680,1686 **** pg_available_extensions(PG_FUNCTION_ARGS)
{
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControlFile *control;
char *extname;
Datum values[3];
bool nulls[3];
--- 1897,1903 ----
{
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControl *control;
char *extname;
Datum values[3];
bool nulls[3];
***************
*** 1721,1726 **** pg_available_extensions(PG_FUNCTION_ARGS)
--- 1938,1966 ----
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);
***************
*** 1789,1795 **** 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))
--- 2029,2035 ----
{
while ((de = ReadDir(dir, location)) != NULL)
{
! ExtensionControl *control;
char *extname;
if (!is_extension_control_filename(de->d_name))
***************
*** 1824,1830 **** 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)
{
--- 2064,2070 ----
* read versions of one extension, add rows to tupstore
*/
static void
! get_available_versions_for_extension(ExtensionControl *pcontrol,
Tuplestorestate *tupstore,
TupleDesc tupdesc)
{
***************
*** 1838,1844 **** 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];
--- 2078,2084 ----
/* 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];
***************
*** 1935,1941 **** 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 */
--- 2175,2181 ----
MemoryContext per_query_ctx;
MemoryContext oldcontext;
List *evi_list;
! ExtensionControl *control;
ListCell *lc1;
/* Check extension name validity before any filesystem access */
***************
*** 1968,1974 **** 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);
--- 2208,2214 ----
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);
***************
*** 2585,2591 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
DefElem *d_new_version = NULL;
char *versionName;
char *oldVersionName;
! ExtensionControlFile *control;
Oid extensionOid;
Relation extRel;
ScanKeyData key[1];
--- 2825,2831 ----
DefElem *d_new_version = NULL;
char *versionName;
char *oldVersionName;
! ExtensionControl *control;
Oid extensionOid;
Relation extRel;
ScanKeyData key[1];
***************
*** 2651,2657 **** 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
--- 2891,2897 ----
* 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
***************
*** 2726,2732 **** ExecAlterExtensionStmt(AlterExtensionStmt *stmt)
*/
static void
ApplyExtensionUpdates(Oid extensionOid,
! ExtensionControlFile *pcontrol,
const char *initialVersion,
List *updateVersions)
{
--- 2966,2972 ----
*/
static void
ApplyExtensionUpdates(Oid extensionOid,
! ExtensionControl *pcontrol,
const char *initialVersion,
List *updateVersions)
{
***************
*** 2736,2742 **** ApplyExtensionUpdates(Oid extensionOid,
foreach(lcv, updateVersions)
{
char *versionName = (char *) lfirst(lcv);
! ExtensionControlFile *control;
char *schemaName;
Oid schemaOid;
List *requiredExtensions;
--- 2976,2982 ----
foreach(lcv, updateVersions)
{
char *versionName = (char *) lfirst(lcv);
! ExtensionControl *control;
char *schemaName;
Oid schemaOid;
List *requiredExtensions;
***************
*** 2755,2761 **** 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);
--- 2995,3001 ----
/*
* 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,2323 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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/alter.h"
+ #include "commands/extension.h"
+ #include "commands/template.h"
+ #include "funcapi.h"
+ #include "miscadmin.h"
+ #include "tcop/utility.h"
+ #include "utils/acl.h"
+ #include "utils/builtins.h"
+ #include "utils/fmgroids.h"
+ #include "utils/lsyscache.h"
+ #include "utils/rel.h"
+ #include "utils/snapmgr.h"
+ #include "utils/tqual.h"
+
+ static Oid InsertExtensionControlTuple(Oid owner,
+ ExtensionControl *control,
+ const char *version);
+
+ static Oid InsertExtensionTemplateTuple(Oid owner,
+ ExtensionControl *control,
+ const char *version,
+ const char *script);
+
+ static Oid InsertExtensionUpTmplTuple(Oid owner,
+ const char *extname,
+ ExtensionControl *control,
+ const char *from,
+ const char *to,
+ const char *script);
+
+ static Oid AlterTemplateSetDefault(const char *extname, const char *version);
+ static Oid AlterTemplateSetDefaultFull(const char *extname,
+ const char *version);
+ static Oid AlterTemplateSetControl(const char *extname,
+ const char *version,
+ List *options);
+
+ static Oid AlterTemplateSetScript(const char *extname,
+ const char *version, const char *script);
+ static Oid AlterUpTpmlSetScript(const char *extname,
+ const char *from,
+ const char *to,
+ const char *script);
+
+ static Oid modify_pg_extension_control_default(const char *extname,
+ const char *version,
+ bool value);
+
+ static Oid modify_pg_extension_control_default_full(const char *extname,
+ const char *version,
+ bool value);
+
+ static ExtensionControl *read_pg_extension_control(const char *extname,
+ Relation rel,
+ HeapTuple tuple);
+
+ /*
+ * The grammar accumulates control properties into a DefElem list that we have
+ * to process in multiple places.
+ */
+ static void
+ parse_statement_control_defelems(ExtensionControl *control, List *defelems)
+ {
+ ListCell *lc;
+ DefElem *d_schema = NULL;
+ DefElem *d_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(CreateExtTemplateStmt *stmt)
+ {
+ switch (stmt->tmpltype)
+ {
+ case TEMPLATE_CREATE_EXTENSION:
+ return CreateExtensionTemplate(stmt);
+
+ case TEMPLATE_UPDATE_EXTENSION:
+ return CreateExtensionUpdateTemplate(stmt);
+ }
+ /* keep compiler happy */
+ return InvalidOid;
+ }
+
+ /*
+ * CREATE TEMPLATE FOR EXTENSION
+ *
+ * Create a template for an extension's given version.
+ */
+ Oid
+ CreateExtensionTemplate(CreateExtTemplateStmt *stmt)
+ {
+ ExtensionControl *default_version;
+ Oid extTemplateOid;
+ Oid owner = GetUserId();
+ ExtensionControl *control;
+
+ /* 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.
+ */
+ default_version = find_default_pg_extension_control(control->name, true);
+
+ if (stmt->default_version)
+ {
+ if (default_version)
+ ereport(ERROR,
+ (errcode(ERRCODE_DUPLICATE_OBJECT),
+ errmsg("extension \"%s\" already has a default control template",
+ control->name),
+ errdetail("default version is \"%s\"",
+ default_version->default_version)));
+
+ /* no pre-existing */
+ control->default_version = pstrdup(stmt->version);
+ }
+ else
+ {
+ /*
+ * No explicit default has been given in the command, and we didn't
+ * find one in the catalogs (it must be the first time we hear about
+ * that very extension): we maintain our invariant that we must have a
+ * single line per extension in pg_extension_control where ctldefault
+ * is true.
+ */
+ if (default_version == NULL)
+ control->default_version = pstrdup(stmt->version);
+ }
+
+ /*
+ * In the control structure, find_default_pg_extension_control() has
+ * stuffed the current default full version of the extension, which might
+ * be different from the default version.
+ *
+ * When creating the first template for an extension, we don't have a
+ * default_full_version set yet. To maintain our invariant that we always
+ * have a single version of the extension templates always as the default
+ * full version, if no default_full_version has been found, forcibly set it
+ * now.
+ */
+ if (default_version == NULL ||
+ default_version->default_full_version == NULL)
+ {
+ control->default_full_version = stmt->version;
+ }
+
+ extTemplateOid = InsertExtensionTemplateTuple(owner,
+ control,
+ stmt->version,
+ stmt->script);
+
+ /* Check that we have a default version target now */
+ CommandCounterIncrement();
+ find_default_pg_extension_control(stmt->extname, false);
+
+ return extTemplateOid;
+ }
+
+ /*
+ * CREATE TEMPLATE FOR UPDATE OF EXTENSION
+ */
+ Oid
+ CreateExtensionUpdateTemplate(CreateExtTemplateStmt *stmt)
+ {
+ Oid owner = GetUserId();
+ ExtensionControl *control;
+
+ /* 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);
+ }
+ else
+ {
+ /*
+ *
+ * To allow for ALTER command to be able to change the control
+ * properties of the given extension for the target version (to) here,
+ * when no control properties have been given on the command line, copy
+ * those of the version we upgrade from (from).
+ */
+ control = find_pg_extension_control(stmt->extname, stmt->from, false);
+ }
+
+ return InsertExtensionUpTmplTuple(owner, stmt->extname, control,
+ stmt->from, stmt->to, stmt->script);
+ }
+
+ /*
+ * Utility function to build a text[] from the List *requires option.
+ */
+ static Datum
+ construct_control_requires_datum(List *requires)
+ {
+ Datum *datums;
+ int ndatums;
+ ArrayType *a;
+ ListCell *lc;
+
+ ndatums = list_length(requires);
+ datums = (Datum *) palloc(ndatums * sizeof(Datum));
+ ndatums = 0;
+ foreach(lc, requires)
+ {
+ char *curreq = (char *) lfirst(lc);
+
+ datums[ndatums++] =
+ DirectFunctionCall1(namein, CStringGetDatum(curreq));
+ }
+ a = construct_array(datums, ndatums,
+ NAMEOID,
+ NAMEDATALEN, false, 'c');
+
+ return PointerGetDatum(a);
+ }
+
+ /*
+ * 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;
+
+ /*
+ * Build and insert the pg_extension_control tuple
+ */
+ rel = heap_open(ExtensionControlRelationId, RowExclusiveLock);
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ values[Anum_pg_extension_control_ctlname - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(control->name));
+
+ values[Anum_pg_extension_control_ctlowner - 1] =
+ ObjectIdGetDatum(owner);
+
+ values[Anum_pg_extension_control_ctlrelocatable - 1] =
+ BoolGetDatum(control->relocatable);
+
+ values[Anum_pg_extension_control_ctlsuperuser - 1] =
+ BoolGetDatum(control->superuser);
+
+ if (control->schema == NULL)
+ nulls[Anum_pg_extension_control_ctlnamespace - 1] = true;
+ else
+ values[Anum_pg_extension_control_ctlnamespace - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum((control->schema)));
+
+ values[Anum_pg_extension_control_ctlversion - 1] =
+ CStringGetTextDatum(version);
+
+ /*
+ * We only register that this pg_extension_control row is the default for
+ * the given extension. Necessary controls must have been made before.
+ */
+ if (control->default_version == NULL)
+ values[Anum_pg_extension_control_ctldefault - 1] = false;
+ else
+ values[Anum_pg_extension_control_ctldefault - 1] = true;
+
+ if (control->default_full_version == NULL)
+ values[Anum_pg_extension_control_ctldefaultfull - 1] = false;
+ else
+ values[Anum_pg_extension_control_ctldefaultfull - 1] = true;
+
+ if (control->requires == NULL)
+ nulls[Anum_pg_extension_control_ctlrequires - 1] = true;
+ else
+ values[Anum_pg_extension_control_ctlrequires - 1] =
+ construct_control_requires_datum(control->requires);
+
+ tuple = heap_form_tuple(rel->rd_att, values, nulls);
+
+ extControlOid = simple_heap_insert(rel, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, RowExclusiveLock);
+
+ /*
+ * Record dependencies on owner.
+ *
+ * When we create the extension template and control file, the target
+ * extension, its schema and requirements usually do not exist in the
+ * database. Don't even think about registering a dependency from the
+ * template.
+ */
+ recordDependencyOnOwner(ExtensionControlRelationId, extControlOid, owner);
+
+ /* Post creation hook for new extension control */
+ InvokeObjectPostCreateHook(ExtensionControlRelationId, extControlOid, 0);
+ ;
+ 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 */
+ InvokeObjectPostCreateHook(ExtensionTemplateRelationId, extTemplateOid, 0);
+
+ return extTemplateOid;
+ }
+
+ /*
+ * InsertExtensionUpTmplTuple
+ *
+ * Insert the new pg_extension_uptmpl row and register its dependencies.
+ * Return the OID assigned to the new row.
+ */
+ static Oid
+ InsertExtensionUpTmplTuple(Oid owner,
+ const char *extname,
+ ExtensionControl *control,
+ const char *from,
+ const char *to,
+ const char *script)
+ {
+ Oid extControlOid, extUpTmplOid;
+ Relation rel;
+ Datum values[Natts_pg_extension_uptmpl];
+ bool nulls[Natts_pg_extension_uptmpl];
+ HeapTuple tuple;
+ ObjectAddress myself, ctrl;
+
+ /*
+ * First create the companion extension control entry, 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.
+ */
+ extControlOid = InsertExtensionControlTuple(owner, control, to);
+
+ /*
+ * Build and insert the pg_extension_uptmpl tuple
+ */
+ rel = heap_open(ExtensionUpTmplRelationId, RowExclusiveLock);
+
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+
+ values[Anum_pg_extension_uptmpl_uptname - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum(extname));
+ values[Anum_pg_extension_uptmpl_uptowner - 1] = ObjectIdGetDatum(owner);
+ values[Anum_pg_extension_uptmpl_uptfrom - 1] = CStringGetTextDatum(from);
+ values[Anum_pg_extension_uptmpl_uptto - 1] = CStringGetTextDatum(to);
+ values[Anum_pg_extension_uptmpl_uptscript - 1] = CStringGetTextDatum(script);
+
+ tuple = heap_form_tuple(rel->rd_att, values, nulls);
+
+ extUpTmplOid = simple_heap_insert(rel, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(rel, RowExclusiveLock);
+
+ /*
+ * Record dependencies on owner only.
+ *
+ * When we create the extension template and control file, the target
+ * extension, its schema and requirements usually do not exist in the
+ * database. Don't even think about registering a dependency from the
+ * template.
+ */
+ recordDependencyOnOwner(ExtensionUpTmplRelationId, extUpTmplOid, owner);
+
+ myself.classId = ExtensionUpTmplRelationId;
+ myself.objectId = extUpTmplOid;
+ myself.objectSubId = 0;
+
+ /* record he dependency between the control row and the template row */
+ ctrl.classId = ExtensionControlRelationId;
+ ctrl.objectId = extControlOid;
+ ctrl.objectSubId = 0;
+
+ recordDependencyOn(&ctrl, &myself, DEPENDENCY_INTERNAL);
+
+ /* Post creation hook for new extension control */
+ InvokeObjectPostCreateHook(ExtensionUpTmplRelationId, extUpTmplOid, 0);
+
+ return extUpTmplOid;
+ }
+
+ /*
+ * Lookup functions
+ */
+ char *
+ get_extension_control_name(Oid ctrlOid)
+ {
+ char *result;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(ctrlOid));
+
+ scandesc = systable_beginscan(rel, ExtensionControlOidIndexId, true,
+ SnapshotNow, 1, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ result = pstrdup(
+ NameStr(((Form_pg_extension_control) GETSTRUCT(tuple))->ctlname));
+ else
+ result = NULL;
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return result;
+ }
+
+ char *
+ get_extension_template_name(Oid tmplOid)
+ {
+ char *result;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(tmplOid));
+
+ scandesc = systable_beginscan(rel, ExtensionTemplateOidIndexId, true,
+ SnapshotNow, 1, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ result = pstrdup(
+ NameStr(((Form_pg_extension_template) GETSTRUCT(tuple))->tplname));
+ else
+ result = NULL;
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return result;
+ }
+
+ char *
+ get_extension_uptmpl_name(Oid tmplOid)
+ {
+ char *result;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionUpTmplRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ ObjectIdAttributeNumber,
+ BTEqualStrategyNumber, F_OIDEQ,
+ ObjectIdGetDatum(tmplOid));
+
+ scandesc = systable_beginscan(rel, ExtensionUpTmplOidIndexId, true,
+ SnapshotNow, 1, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ /* We assume that there can be at most one matching tuple */
+ if (HeapTupleIsValid(tuple))
+ result = pstrdup(
+ NameStr(((Form_pg_extension_uptmpl) GETSTRUCT(tuple))->uptname));
+ else
+ result = NULL;
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return result;
+ }
+
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION name VERSION version
+ *
+ * This implements high level routing for sub commands.
+ */
+ Oid
+ AlterTemplate(AlterExtTemplateStmt *stmt)
+ {
+ switch (stmt->tmpltype)
+ {
+ case TEMPLATE_CREATE_EXTENSION:
+ return AlterExtensionTemplate(stmt);
+
+ case TEMPLATE_UPDATE_EXTENSION:
+ return AlterExtensionUpdateTemplate(stmt);
+ }
+ /* keep compiler happy */
+ return InvalidOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION routing
+ */
+ Oid
+ AlterExtensionTemplate(AlterExtTemplateStmt *stmt)
+ {
+ switch (stmt->cmdtype)
+ {
+ case AET_SET_DEFAULT:
+ return AlterTemplateSetDefault(stmt->extname, stmt->version);
+
+ case AET_SET_DEFAULT_FULL:
+ return AlterTemplateSetDefaultFull(stmt->extname, stmt->version);
+
+ case AET_SET_SCRIPT:
+ return AlterTemplateSetScript(stmt->extname,
+ stmt->version,
+ stmt->script);
+
+ case AET_UPDATE_CONTROL:
+ return AlterTemplateSetControl(stmt->extname,
+ stmt->version,
+ stmt->control);
+ }
+ /* make compiler happy */
+ return InvalidOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION UPDATE routing
+ */
+ Oid
+ AlterExtensionUpdateTemplate(AlterExtTemplateStmt *stmt)
+ {
+ switch (stmt->cmdtype)
+ {
+ case AET_SET_DEFAULT:
+ case AET_SET_DEFAULT_FULL:
+ /* shouldn't happen */
+ elog(ERROR, "pg_extension_control is associated to a specific version of an extension, not an update script.");
+ break;
+
+ case AET_UPDATE_CONTROL:
+ /* shouldn't happen */
+ elog(ERROR, "pg_extension_control is associated to a specific version of an extension, not an update script.");
+ break;
+
+ case AET_SET_SCRIPT:
+ return AlterUpTpmlSetScript(stmt->extname,
+ stmt->from,
+ stmt->to,
+ stmt->script);
+
+ }
+ /* make compiler happy */
+ return InvalidOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION ... OWNER TO ...
+ *
+ * In fact we are going to change the owner of all the templates objects
+ * related to the extension name given: pg_extension_control entries,
+ * pg_extension_template entries and also pg_extension_uptmpl entries.
+ *
+ * There's no reason to be able to change the owner of only a part of an
+ * extension's template (the control but not the template, or just the upgrade
+ * script).
+ */
+ Oid
+ AtlerExtensionTemplateOwner(const char *extname, Oid newOwnerId)
+ {
+ ListCell *lc;
+
+ /* Alter owner of all pg_extension_control entries for extname */
+ foreach(lc, list_pg_extension_control_oids_for(extname))
+ {
+ Relation catalog;
+ Oid objectId = lfirst_oid(lc);
+
+ elog(DEBUG1, "alter owner of pg_extension_control %u", objectId);
+
+ catalog = heap_open(ExtensionControlRelationId, RowExclusiveLock);
+ AlterObjectOwner_internal(catalog, objectId, newOwnerId);
+ heap_close(catalog, RowExclusiveLock);
+ }
+
+ /* Alter owner of all pg_extension_template entries for extname */
+ foreach(lc, list_pg_extension_template_oids_for(extname))
+ {
+ Relation catalog;
+ Oid objectId = lfirst_oid(lc);
+
+ elog(DEBUG1, "alter owner of pg_extension_template %u", objectId);
+
+ catalog = heap_open(ExtensionTemplateRelationId, RowExclusiveLock);
+ AlterObjectOwner_internal(catalog, objectId, newOwnerId);
+ heap_close(catalog, RowExclusiveLock);
+ }
+
+ /* Alter owner of all pg_extension_uptmpl entries for extname */
+ foreach(lc, list_pg_extension_uptmpl_oids_for(extname))
+ {
+ Relation catalog;
+ Oid objectId = lfirst_oid(lc);
+
+ elog(DEBUG1, "alter owner of pg_extension_uptmpl %u", objectId);
+
+ catalog = heap_open(ExtensionUpTmplRelationId, RowExclusiveLock);
+ AlterObjectOwner_internal(catalog, objectId, newOwnerId);
+ heap_close(catalog, RowExclusiveLock);
+ }
+
+ /* which Oid to return here? */
+ return InvalidOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION ... RENAME TO ...
+ *
+ * There's no reason to be able to change the name of only a part of an
+ * extension's template (the control but not the template, or just the upgrade
+ * script).
+ */
+ Oid
+ AtlerExtensionTemplateRename(const char *extname, const char *newname)
+ {
+ ListCell *lc;
+
+ /* Rename all pg_extension_control entries for extname */
+ foreach(lc, list_pg_extension_control_oids_for(extname))
+ {
+ Relation catalog;
+ Oid objectId = lfirst_oid(lc);
+
+ elog(DEBUG1, "rename pg_extension_control %u", objectId);
+
+ catalog = heap_open(ExtensionControlRelationId, RowExclusiveLock);
+ AlterObjectRename_internal(catalog, objectId, newname);
+ heap_close(catalog, RowExclusiveLock);
+ }
+
+ /* Rename all pg_extension_template entries for extname */
+ foreach(lc, list_pg_extension_template_oids_for(extname))
+ {
+ Relation catalog;
+ Oid objectId = lfirst_oid(lc);
+
+ elog(DEBUG1, "rename pg_extension_template %u", objectId);
+
+ catalog = heap_open(ExtensionTemplateRelationId, RowExclusiveLock);
+ AlterObjectRename_internal(catalog, objectId, newname);
+ heap_close(catalog, RowExclusiveLock);
+ }
+
+ /* Rename all pg_extension_uptmpl entries for extname */
+ foreach(lc, list_pg_extension_uptmpl_oids_for(extname))
+ {
+ Relation catalog;
+ Oid objectId = lfirst_oid(lc);
+
+ elog(DEBUG1, "rename pg_extension_uptmpl %u", objectId);
+
+ catalog = heap_open(ExtensionUpTmplRelationId, RowExclusiveLock);
+ AlterObjectRename_internal(catalog, objectId, newname);
+ heap_close(catalog, RowExclusiveLock);
+ }
+
+ /* which Oid to return here? */
+ return InvalidOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION ... SET DEFAULT VERSION ...
+ *
+ * We refuse to run without a default, so we drop the current one when
+ * assigning a new one.
+ */
+ static Oid
+ AlterTemplateSetDefault(const char *extname, const char *version)
+ {
+ /* we need to know who's the default */
+ ExtensionControl *current =
+ find_default_pg_extension_control(extname, false);
+
+ if (!pg_extension_control_ownercheck(current->ctrlOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTCONTROL,
+ extname);
+
+ /* silently do nothing if the default is already set as wanted */
+ if (strcmp(current->default_version, version) == 0)
+ return current->ctrlOid;
+
+ /* set ctldefault to false on current default extension */
+ modify_pg_extension_control_default(current->name,
+ current->default_version,
+ false);
+
+ /* set ctldefault to true on new default extension */
+ return modify_pg_extension_control_default(extname, version, true);
+ }
+
+ /*
+ * Implement flipping the ctldefaultfull bit to given value.
+ */
+ static Oid
+ modify_pg_extension_control_default(const char *extname,
+ const char *version,
+ bool value)
+ {
+ Oid ctrlOid;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+ Datum values[Natts_pg_extension_control];
+ bool nulls[Natts_pg_extension_control];
+ bool repl[Natts_pg_extension_control];
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_control_ctlname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_control_ctlversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ 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 ... SET DEFAULT FULL VERSION ...
+ */
+ static Oid
+ AlterTemplateSetDefaultFull(const char *extname, const char *version)
+ {
+ /* we need to know who's the default */
+ ExtensionControl *current =
+ find_default_pg_extension_control(extname, false);
+
+ /* the target version must be an installation script */
+ Oid target = get_template_oid(extname, version, false);
+
+ (void) target; /* silence compiler */
+
+ /* only check the owner of one of those, as we maintain them all the same */
+ if (!pg_extension_control_ownercheck(current->ctrlOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTCONTROL,
+ extname);
+
+ /* silently do nothing if the default is already set as wanted */
+ if (strcmp(current->default_full_version, version) == 0)
+ return current->ctrlOid;
+
+ /* set ctldefault to false on current default extension */
+ modify_pg_extension_control_default_full(current->name,
+ current->default_full_version,
+ false);
+
+ /* set ctldefault to true on new default extension */
+ return modify_pg_extension_control_default_full(extname, version, true);
+ }
+
+ /*
+ * Implement flipping the ctldefaultfull bit to given value.
+ */
+ static Oid
+ modify_pg_extension_control_default_full(const char *extname,
+ const char *version,
+ bool value)
+ {
+ Oid ctrlOid;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+ Datum values[Natts_pg_extension_control];
+ bool nulls[Natts_pg_extension_control];
+ bool repl[Natts_pg_extension_control];
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_control_ctlname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_control_ctlversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ 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_ctldefaultfull - 1] = BoolGetDatum(value);
+ repl[Anum_pg_extension_control_ctldefaultfull - 1] = true;
+
+ tuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
+ values, nulls, repl);
+
+ simple_heap_update(rel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return ctrlOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION ... AS $$ ... $$
+ */
+ static Oid
+ AlterTemplateSetScript(const char *extname,
+ const char *version,
+ const char *script)
+ {
+ Oid extTemplateOid;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+ Datum values[Natts_pg_extension_template];
+ bool nulls[Natts_pg_extension_template];
+ bool repl[Natts_pg_extension_template];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_template_tplname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_template_tplversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionTemplateNameVersionIndexId, true,
+ SnapshotNow, 2, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ elog(ERROR,
+ "pg_extension_template for extension \"%s\" version \"%s\" does not exist",
+ extname, version);
+
+ extTemplateOid = HeapTupleGetOid(tuple);
+
+ /* check privileges */
+ if (!pg_extension_template_ownercheck(extTemplateOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTTEMPLATE,
+ extname);
+
+ /* Modify ctldefault in the pg_extension_control tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+ memset(repl, 0, sizeof(repl));
+
+ repl[Anum_pg_extension_template_tplscript - 1] = true;
+ values[Anum_pg_extension_template_tplscript - 1] =
+ CStringGetTextDatum(script);
+
+ tuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
+ values, nulls, repl);
+
+ simple_heap_update(rel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return extTemplateOid;
+ }
+
+ /*
+ * ALTER TEMPLATE FOR EXTENSION ... FROM ... TO ... AS $$ ... $$
+ */
+ static Oid
+ AlterUpTpmlSetScript(const char *extname,
+ const char *from,
+ const char *to,
+ const char *script)
+ {
+ Oid extUpTmplOid;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[3];
+ Datum values[Natts_pg_extension_uptmpl];
+ bool nulls[Natts_pg_extension_uptmpl];
+ bool repl[Natts_pg_extension_uptmpl];
+
+ rel = heap_open(ExtensionUpTmplRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_uptmpl_uptname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_uptmpl_uptfrom,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(from));
+
+ ScanKeyInit(&entry[2],
+ Anum_pg_extension_uptmpl_uptto,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(to));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionUpTpmlNameFromToIndexId, true,
+ SnapshotNow, 3, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ elog(ERROR,
+ "pg_extension_template for extension \"%s\" from version \"%s\" to version \"%s\" does not exist",
+ extname, from, to);
+
+ extUpTmplOid = HeapTupleGetOid(tuple);
+
+ /* check privileges */
+ if (!pg_extension_uptmpl_ownercheck(extUpTmplOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTUPTMPL,
+ extname);
+
+ /* Modify ctldefault in the pg_extension_control tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+ memset(repl, 0, sizeof(repl));
+
+ repl[Anum_pg_extension_uptmpl_uptscript - 1] = true;
+ values[Anum_pg_extension_uptmpl_uptscript - 1] =
+ CStringGetTextDatum(script);
+
+ tuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
+ values, nulls, repl);
+
+ simple_heap_update(rel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return extUpTmplOid;
+ }
+
+ /*
+ * AlterTemplateSetControl
+ */
+ static Oid
+ AlterTemplateSetControl(const char *extname,
+ const char *version,
+ List *options)
+ {
+ Oid ctrlOid;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+ Datum values[Natts_pg_extension_control];
+ bool nulls[Natts_pg_extension_control];
+ bool repl[Natts_pg_extension_control];
+
+ ExtensionControl *current_control;
+ ExtensionControl *new_control =
+ (ExtensionControl *) palloc0(sizeof(ExtensionControl));
+
+ /* parse the new control options given in the SQL command */
+ new_control->name = pstrdup(extname);
+ parse_statement_control_defelems(new_control, options);
+
+ /* now find the tuple we want to edit */
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_control_ctlname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_control_ctlversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ SnapshotNow, 2, entry);
+
+ tuple = systable_getnext(scandesc);
+
+ if (!HeapTupleIsValid(tuple)) /* should not happen */
+ elog(ERROR,
+ "pg_extension_control for extension \"%s\" version \"%s\" does not exist",
+ extname, version);
+
+ ctrlOid = HeapTupleGetOid(tuple);
+
+ /* check privileges */
+ if (!pg_extension_control_ownercheck(ctrlOid, GetUserId()))
+ aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_EXTCONTROL,
+ extname);
+
+ current_control = read_pg_extension_control(extname, rel, tuple);
+
+ /* Modify the pg_extension_control tuple */
+ memset(values, 0, sizeof(values));
+ memset(nulls, 0, sizeof(nulls));
+ memset(repl, 0, sizeof(repl));
+
+ /*
+ * We don't compare with the current value, we directly set what's been
+ * given in the new command, if anything was given
+ */
+ if (new_control->schema)
+ {
+ values[Anum_pg_extension_control_ctlnamespace - 1] =
+ DirectFunctionCall1(namein, CStringGetDatum((new_control->schema)));
+ repl[Anum_pg_extension_control_ctlnamespace - 1] = true;
+ }
+ if (new_control->requires)
+ {
+ values[Anum_pg_extension_control_ctlrequires - 1] =
+ construct_control_requires_datum(new_control->requires);
+ repl[Anum_pg_extension_control_ctlrequires - 1] = true;
+ }
+
+ /*
+ * We need to compare superuser and relocatable because those are bools,
+ * and we don't have a NULL pointer when they were omited in the command.
+ */
+ if (new_control->superuser != current_control->superuser)
+ {
+ values[Anum_pg_extension_control_ctlsuperuser - 1] =
+ BoolGetDatum(new_control->superuser);
+ repl[Anum_pg_extension_control_ctlsuperuser - 1] = true;
+ }
+ if (new_control->relocatable != current_control->relocatable)
+ {
+ values[Anum_pg_extension_control_ctlrelocatable - 1] =
+ BoolGetDatum(new_control->relocatable);
+ repl[Anum_pg_extension_control_ctlrelocatable - 1] = true;
+ }
+
+ tuple = heap_modify_tuple(tuple, RelationGetDescr(rel),
+ values, nulls, repl);
+
+ simple_heap_update(rel, &tuple->t_self, tuple);
+ CatalogUpdateIndexes(rel, tuple);
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return ctrlOid;
+ }
+
+ /*
+ * get_template_oid - given an extension name and version, look up the template
+ * OID
+ *
+ * If missing_ok is false, throw an error if extension name not found. If
+ * true, just return InvalidOid.
+ */
+ Oid
+ get_template_oid(const char *extname, const char *version, bool missing_ok)
+ {
+ Oid result;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_template_tplname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_template_tplversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionTemplateNameVersionIndexId, true,
+ 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);
+ }
+
+ /*
+ * Internal tool to get the version string of a pg_extension_control tuple.
+ */
+ static char *
+ extract_ctlversion(Relation rel, HeapTuple tuple)
+ {
+ bool isnull;
+ Datum dvers;
+
+ dvers = heap_getattr(tuple, Anum_pg_extension_control_ctlversion,
+ RelationGetDescr(rel), &isnull);
+ if (isnull)
+ elog(ERROR, "invalid null extension version");
+
+ return text_to_cstring(DatumGetTextPP(dvers));
+ }
+
+ /*
+ * read_pg_extension_control
+ *
+ * Read a pg_extension_control row and fill in an ExtensionControl
+ * structure with the right elements in there.
+ */
+ static ExtensionControl *
+ read_pg_extension_control(const char *extname, Relation rel, HeapTuple tuple)
+ {
+ Datum dreqs;
+ bool isnull;
+ Form_pg_extension_control ctrl =
+ (Form_pg_extension_control) GETSTRUCT(tuple);
+
+ ExtensionControl *control =
+ (ExtensionControl *) palloc0(sizeof(ExtensionControl));
+
+ /* Those fields are not null */
+ control->ctrlOid = HeapTupleGetOid(tuple);
+ control->name = pstrdup(extname);
+ control->is_template = true;
+ control->relocatable = ctrl->ctlrelocatable;
+ control->superuser = ctrl->ctlsuperuser;
+ control->schema = pstrdup(NameStr(ctrl->ctlnamespace));
+
+ /* set control->default_version */
+ if (ctrl->ctldefault)
+ control->default_version = extract_ctlversion(rel, tuple);
+
+ /* set control->default_full_version */
+ if (ctrl->ctldefaultfull)
+ {
+ /* avoid extracting it again if we just did so */
+ if (control->default_version)
+ control->default_full_version = control->default_version;
+ else
+ control->default_full_version = extract_ctlversion(rel, tuple);
+ }
+
+ /* now see about the dependencies array */
+ dreqs = heap_getattr(tuple, Anum_pg_extension_control_ctlrequires,
+ RelationGetDescr(rel), &isnull);
+
+ if (!isnull)
+ {
+ ArrayType *arr = DatumGetArrayTypeP(dreqs);
+ Datum *elems;
+ int i;
+ int nelems;
+
+ if (ARR_NDIM(arr) != 1 || ARR_HASNULL(arr) || ARR_ELEMTYPE(arr) != TEXTOID)
+ elog(ERROR, "expected 1-D text array");
+ deconstruct_array(arr, TEXTOID, -1, false, 'i', &elems, NULL, &nelems);
+
+ for (i = 0; i < nelems; ++i)
+ control->requires = lappend(control->requires,
+ TextDatumGetCString(elems[i]));
+
+ pfree(elems);
+ }
+ return control;
+ }
+
+ /*
+ * Find the pg_extension_control row for given extname and version, if any, and
+ * return a filled in ExtensionControl structure.
+ *
+ * In case we don't have any pg_extension_control row for given extname and
+ * version, return NULL.
+ */
+ ExtensionControl *
+ find_pg_extension_control(const char *extname,
+ const char *version,
+ bool missing_ok)
+ {
+ ExtensionControl *control = NULL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_control_ctlname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_control_ctlversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ 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, and its OID, for internal
+ * use (such as checking ACLs).
+ *
+ * In a single scan of the pg_extension_control catalog, also find out the
+ * default full version of that extension, needed in extension.c when doing
+ * multi steps CREATE EXTENSION.
+ */
+ ExtensionControl *
+ find_default_pg_extension_control(const char *extname, bool missing_ok)
+ {
+ ExtensionControl *control = NULL;
+ char *default_full_version = NULL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_control_ctlname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ SnapshotNow, 1, entry);
+
+ /* find all the control tuples for extname */
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ {
+ bool isnull;
+ Datum tmpdatum;
+ bool ctldefault;
+ bool ctldefaultfull;
+
+ tmpdatum = fastgetattr(tuple,
+ Anum_pg_extension_control_ctldefault,
+ RelationGetDescr(rel), &isnull);
+ if (isnull)
+ elog(ERROR, "invalid null ctldefault");
+ ctldefault = DatumGetBool(tmpdatum);
+
+ /* only one of these is the default */
+ if (ctldefault)
+ {
+ if (!control)
+ control = read_pg_extension_control(extname, rel, tuple);
+ else
+ /* should not happen */
+ elog(ERROR,
+ "extension \"%s\" has more than one default control template",
+ extname);
+ }
+
+ tmpdatum = fastgetattr(tuple,
+ Anum_pg_extension_control_ctldefaultfull,
+ RelationGetDescr(rel), &isnull);
+ if (isnull)
+ elog(ERROR, "invalid null ctldefaultfull");
+ ctldefaultfull = DatumGetBool(tmpdatum);
+
+ /* the default version and default full version might be different */
+ if (ctldefaultfull)
+ default_full_version = extract_ctlversion(rel, tuple);
+ }
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ /* we really need a single default version. */
+ if (!control && !missing_ok)
+ ereport(ERROR,
+ (errcode(ERRCODE_UNDEFINED_OBJECT),
+ errmsg("extension \"%s\" has no default control template",
+ extname)));
+
+ /* don't forget to add in the default full version */
+ if (control != NULL && default_full_version)
+ control->default_full_version = default_full_version;
+
+ return control;
+ }
+
+ /*
+ * read_pg_extension_template_script
+ *
+ * Return the script from the pg_extension_template catalogs.
+ */
+ char *
+ read_pg_extension_template_script(const char *extname, const char *version)
+ {
+ char *script;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[2];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_template_tplname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ ScanKeyInit(&entry[1],
+ Anum_pg_extension_template_tplversion,
+ BTEqualStrategyNumber, F_TEXTEQ,
+ CStringGetTextDatum(version));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionTemplateNameVersionIndexId, true,
+ 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;
+ }
+
+ List *
+ list_pg_extension_control_oids_for(const char *extname)
+ {
+ List *oids = NIL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionControlRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_control_ctlname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionControlNameVersionIndexId, true,
+ SnapshotNow, 1, entry);
+
+ /* find all the control tuples for extname */
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ oids = lappend_oid(oids, HeapTupleGetOid(tuple));
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return oids;
+ }
+
+ List *
+ list_pg_extension_template_oids_for(const char *extname)
+ {
+ List *oids = NIL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionTemplateRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_template_tplname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionTemplateNameVersionIndexId, true,
+ SnapshotNow, 1, entry);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ oids = lappend_oid(oids, HeapTupleGetOid(tuple));
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return oids;
+ }
+
+ List *
+ list_pg_extension_uptmpl_oids_for(const char *extname)
+ {
+ List *oids = NIL;
+ Relation rel;
+ SysScanDesc scandesc;
+ HeapTuple tuple;
+ ScanKeyData entry[1];
+
+ rel = heap_open(ExtensionUpTmplRelationId, AccessShareLock);
+
+ ScanKeyInit(&entry[0],
+ Anum_pg_extension_uptmpl_uptname,
+ BTEqualStrategyNumber, F_NAMEEQ,
+ CStringGetDatum(extname));
+
+ scandesc = systable_beginscan(rel,
+ ExtensionUpTpmlNameFromToIndexId, true,
+ SnapshotNow, 1, entry);
+
+ while (HeapTupleIsValid(tuple = systable_getnext(scandesc)))
+ oids = lappend_oid(oids, HeapTupleGetOid(tuple));
+
+ systable_endscan(scandesc);
+
+ heap_close(rel, AccessShareLock);
+
+ return oids;
+ }
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
***************
*** 228,234 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
! AlterRoleStmt AlterRoleSetStmt
AlterDefaultPrivilegesStmt DefACLAction
AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
--- 228,234 ----
AlterObjectSchemaStmt AlterOwnerStmt AlterSeqStmt AlterTableStmt
AlterExtensionStmt AlterExtensionContentsStmt AlterForeignTableStmt
AlterCompositeTypeStmt AlterUserStmt AlterUserMappingStmt AlterUserSetStmt
! AlterRoleStmt AlterRoleSetStmt AlterExtTemplateStmt
AlterDefaultPrivilegesStmt DefACLAction
AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt
ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt
***************
*** 237,247 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
! CreateUserStmt CreateUserMappingStmt CreateRoleStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
! DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
LockStmt NotifyStmt ExplainableStmt PreparableStmt
--- 237,247 ----
CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt
CreateFdwStmt CreateForeignServerStmt CreateForeignTableStmt
CreateAssertStmt CreateTrigStmt CreateEventTrigStmt
! CreateUserStmt CreateUserMappingStmt CreateRoleStmt CreateExtTemplateStmt
CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt
DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt
DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt
! DropTemplateStmt DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt
DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt
GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt
LockStmt NotifyStmt ExplainableStmt PreparableStmt
***************
*** 275,280 **** static Node *makeRecursiveViewSelect(char *relname, List *aliases, Node *query);
--- 275,283 ----
transaction_mode_item
create_extension_opt_item alter_extension_opt_item
+ %type <list> create_template_control create_template_control_plist
+ %type <defelt> create_template_control_item
+
%type <ival> opt_lock lock_type cast_context
%type <ival> vacuum_option_list vacuum_option_elem
%type <boolean> opt_force opt_or_replace
***************
*** 731,736 **** stmt :
--- 734,740 ----
| AlterCompositeTypeStmt
| AlterRoleSetStmt
| AlterRoleStmt
+ | AlterExtTemplateStmt
| AlterTSConfigurationStmt
| AlterTSDictionaryStmt
| AlterUserMappingStmt
***************
*** 769,774 **** stmt :
--- 773,779 ----
| CreateUserStmt
| CreateUserMappingStmt
| CreatedbStmt
+ | CreateExtTemplateStmt
| DeallocateStmt
| DeclareCursorStmt
| DefineStmt
***************
*** 792,797 **** stmt :
--- 797,803 ----
| DropUserStmt
| DropUserMappingStmt
| DropdbStmt
+ | DropTemplateStmt
| ExecuteStmt
| ExplainStmt
| FetchStmt
***************
*** 3563,3568 **** DropTableSpaceStmt: DROP TABLESPACE name
--- 3569,3800 ----
/*****************************************************************************
*
* QUERY:
+ * CREATE TEMPLATE FOR EXTENSION name
+ *
+ *****************************************************************************/
+
+ CreateExtTemplateStmt:
+ CREATE TEMPLATE FOR EXTENSION name VERSION_P ColId_or_Sconst
+ WITH create_template_control AS Sconst
+ {
+ CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->extname = $5;
+ n->version = $7;
+ n->control = $9;
+ n->script = $11;
+ n->if_not_exists = false;
+ n->default_version = false;
+ $$ = (Node *) n;
+ }
+ | CREATE TEMPLATE FOR EXTENSION name
+ DEFAULT VERSION_P ColId_or_Sconst
+ WITH create_template_control AS Sconst
+ {
+ CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->extname = $5;
+ n->version = $8;
+ n->control = $10;
+ n->script = $12;
+ n->if_not_exists = false;
+ n->default_version = true;
+ $$ = (Node *) n;
+ }
+ | CREATE TEMPLATE FOR EXTENSION name
+ FROM ColId_or_Sconst TO ColId_or_Sconst AS Sconst
+ {
+ CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+ n->tmpltype = 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
+ {
+ CreateExtTemplateStmt *n = makeNode(CreateExtTemplateStmt);
+ n->tmpltype = 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.
+ *
+ *****************************************************************************/
+ AlterExtTemplateStmt:
+ ALTER TEMPLATE FOR EXTENSION name
+ SET DEFAULT VERSION_P ColId_or_Sconst
+ {
+ AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->cmdtype = AET_SET_DEFAULT;
+ n->extname = $5;
+ n->version = $9;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
+ | ALTER TEMPLATE FOR EXTENSION name
+ SET DEFAULT FULL VERSION_P ColId_or_Sconst
+ {
+ AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->cmdtype = AET_SET_DEFAULT_FULL;
+ n->extname = $5;
+ n->version = $10;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
+ | ALTER TEMPLATE FOR EXTENSION name VERSION_P ColId_or_Sconst
+ AS Sconst
+ {
+ AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->cmdtype = AET_SET_SCRIPT;
+ n->extname = $5;
+ n->version = $7;
+ n->script = $9;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
+ | ALTER TEMPLATE FOR EXTENSION name VERSION_P ColId_or_Sconst
+ WITH create_template_control
+ {
+ AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+ n->tmpltype = TEMPLATE_CREATE_EXTENSION;
+ n->cmdtype = AET_UPDATE_CONTROL;
+ n->extname = $5;
+ n->version = $7;
+ n->control = $9;
+ n->missing_ok = false;
+ $$ = (Node *) n;
+ }
+ | ALTER TEMPLATE FOR EXTENSION name
+ FROM ColId_or_Sconst TO ColId_or_Sconst
+ AS Sconst
+ {
+ AlterExtTemplateStmt *n = makeNode(AlterExtTemplateStmt);
+ n->tmpltype = 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;
+ }
+ ;
+
+ /*****************************************************************************
+ *
+ * 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 if (strcmp($1, "requires") == 0)
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("template option \"%s\" takes an argument", $1),
+ parser_errposition(@1)));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized template option \"%s\"", $1),
+ parser_errposition(@1)));
+ }
+ | IDENT Sconst
+ {
+ if (strcmp($1, "requires") == 0)
+ $$ = makeDefElem("requires", (Node *)makeString($2));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_SYNTAX_ERROR),
+ errmsg("unrecognized template option \"%s\"", $1),
+ parser_errposition(@1)));
+ }
+ ;
+
+ /*****************************************************************************
+ *
+ * QUERY:
* CREATE EXTENSION extension
* [ WITH ] [ SCHEMA schema ] [ VERSION version ] [ FROM oldversion ]
*
***************
*** 7309,7314 **** RenameStmt: ALTER AGGREGATE func_name aggr_args RENAME TO name
--- 7541,7555 ----
n->missing_ok = false;
$$ = (Node *)n;
}
+ | ALTER TEMPLATE FOR EXTENSION name RENAME TO name
+ {
+ RenameStmt *n = makeNode(RenameStmt);
+ n->renameType = OBJECT_EXTENSION_TEMPLATE;
+ n->subname = $5;
+ n->newname = $8;
+ n->missing_ok = false;
+ $$ = (Node *)n;
+ }
;
opt_column: COLUMN { $$ = COLUMN; }
***************
*** 7712,7717 **** AlterOwnerStmt: ALTER AGGREGATE func_name aggr_args OWNER TO RoleId
--- 7953,7966 ----
n->newowner = $7;
$$ = (Node *)n;
}
+ | ALTER TEMPLATE FOR EXTENSION name OWNER TO RoleId
+ {
+ AlterOwnerStmt *n = makeNode(AlterOwnerStmt);
+ n->objectType = OBJECT_EXTENSION_TEMPLATE;
+ n->object = list_make1(makeString($5));
+ n->newowner = $8;
+ $$ = (Node *)n;
+ }
;
*** a/src/backend/tcop/utility.c
--- b/src/backend/tcop/utility.c
***************
*** 47,52 ****
--- 47,53 ----
#include "commands/sequence.h"
#include "commands/tablecmds.h"
#include "commands/tablespace.h"
+ #include "commands/template.h"
#include "commands/trigger.h"
#include "commands/typecmds.h"
#include "commands/user.h"
***************
*** 226,231 **** check_xact_readonly(Node *parsetree)
--- 227,234 ----
case T_CreateExtensionStmt:
case T_AlterExtensionStmt:
case T_AlterExtensionContentsStmt:
+ case T_CreateExtTemplateStmt:
+ case T_AlterExtTemplateStmt:
case T_CreateFdwStmt:
case T_AlterFdwStmt:
case T_CreateForeignServerStmt:
***************
*** 654,659 **** standard_ProcessUtility(Node *parsetree,
--- 657,674 ----
ExecAlterExtensionContentsStmt((AlterExtensionContentsStmt *) parsetree));
break;
+ case T_CreateExtTemplateStmt:
+ InvokeDDLCommandEventTriggers(
+ parsetree,
+ CreateTemplate((CreateExtTemplateStmt *) parsetree));
+ break;
+
+ case T_AlterExtTemplateStmt:
+ InvokeDDLCommandEventTriggers(
+ parsetree,
+ AlterTemplate((AlterExtTemplateStmt *) parsetree));
+ break;
+
case T_CreateFdwStmt:
InvokeDDLCommandEventTriggers(
parsetree,
***************
*** 1670,1675 **** AlterObjectTypeCommandTag(ObjectType objtype)
--- 1685,1693 ----
case OBJECT_MATVIEW:
tag = "ALTER MATERIALIZED VIEW";
break;
+ case OBJECT_EXTENSION_TEMPLATE:
+ tag = "ALTER TEMPLATE FOR EXTENSION";
+ break;
default:
tag = "???";
break;
***************
*** 1823,1828 **** CreateCommandTag(Node *parsetree)
--- 1841,1854 ----
tag = "ALTER EXTENSION";
break;
+ case T_CreateExtTemplateStmt:
+ tag = "CREATE TEMPLATE FOR EXTENSION";
+ break;
+
+ case T_AlterExtTemplateStmt:
+ tag = "ALTER TEMPLATE FOR EXTENSION";
+ break;
+
case T_CreateFdwStmt:
tag = "CREATE FOREIGN DATA WRAPPER";
break;
***************
*** 1906,1911 **** CreateCommandTag(Node *parsetree)
--- 1932,1943 ----
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;
***************
*** 2502,2507 **** GetCommandLogLevel(Node *parsetree)
--- 2534,2544 ----
lev = LOGSTMT_DDL;
break;
+ case T_CreateExtTemplateStmt:
+ case T_AlterExtTemplateStmt:
+ lev = LOGSTMT_DDL;
+ break;
+
case T_CreateFdwStmt:
case T_AlterFdwStmt:
case T_CreateForeignServerStmt:
*** a/src/bin/pg_dump/common.c
--- b/src/bin/pg_dump/common.c
***************
*** 83,88 **** getSchemaData(Archive *fout, int *numTablesPtr)
--- 83,89 ----
InhInfo *inhinfo;
CollInfo *collinfo;
int numExtensions;
+ int numExtensionTemplates;
int numAggregates;
int numInherits;
int numRules;
***************
*** 120,125 **** getSchemaData(Archive *fout, int *numTablesPtr)
--- 121,130 ----
getOwnedSeqs(fout, tblinfo, numTables);
if (g_verbose)
+ write_msg(NULL, "reading extension templates\n");
+ getExtensionTemplates(fout, &numExtensionTemplates);
+
+ if (g_verbose)
write_msg(NULL, "reading extensions\n");
extinfo = getExtensions(fout, &numExtensions);
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 168,173 **** static int collectSecLabels(Archive *fout, SecLabelItem **items);
--- 168,174 ----
static void dumpDumpableObject(Archive *fout, DumpableObject *dobj);
static void dumpNamespace(Archive *fout, NamespaceInfo *nspinfo);
static void dumpExtension(Archive *fout, ExtensionInfo *extinfo);
+ static void dumpExtensionTemplate(Archive *fout, ExtensionTemplateInfo *extinfo);
static void dumpType(Archive *fout, TypeInfo *tyinfo);
static void dumpBaseType(Archive *fout, TypeInfo *tyinfo);
static void dumpEnumType(Archive *fout, TypeInfo *tyinfo);
***************
*** 2911,2916 **** findNamespace(Archive *fout, Oid nsoid, Oid objoid)
--- 2912,3038 ----
*
* numExtensions is set to the number of extensions read in
*/
+ ExtensionTemplateInfo *
+ getExtensionTemplates(Archive *fout, int *numExtensionTemplates)
+ {
+ PGresult *res;
+ int ntups;
+ int i;
+ PQExpBuffer query;
+ ExtensionTemplateInfo *exttmplinfo;
+ int i_tableoid;
+ int i_oid;
+ int i_extname;
+ int i_namespace;
+ int i_isdefault;
+ int i_relocatable;
+ int i_superuser;
+ int i_requires;
+ int i_install;
+ int i_version;
+ int i_from;
+ int i_to;
+ int i_script;
+
+ /*
+ * Before 9.3, there are no extension templates.
+ */
+ if (fout->remoteVersion < 90300)
+ {
+ *numExtensionTemplates = 0;
+ return NULL;
+ }
+
+ query = createPQExpBuffer();
+
+ /* Make sure we are in proper schema */
+ selectSourceSchema(fout, "pg_catalog");
+
+ /*
+ * The main catalog object for an extension template is the
+ * pg_extension_control entry, from which thanks to pg_depend we fetch
+ * either the extension creation script or its upgrade script.
+ */
+ appendPQExpBuffer(query, "select c.tableoid, c.oid, "
+ "c.ctlname, c.ctldefault, c.ctlrelocatable, "
+ "c.ctlsuperuser, c.ctlnamespace, "
+ "array_to_string(c.ctlrequires, ',') as requires, "
+ "true as install, t.tplversion as version, "
+ "null as uptfrom, null as uptto, t.tplscript as script "
+ "from pg_extension_control c "
+ "join pg_depend d "
+ "on d.classid = 'pg_catalog.pg_extension_control'::regclass "
+ "and d.objid = c.oid "
+ "and d.refclassid = 'pg_catalog.pg_extension_template'::regclass "
+ "join pg_extension_template t on t.oid = d.refobjid "
+ "union all "
+ "select c.tableoid, c.oid, "
+ "c.ctlname, c.ctldefault, c.ctlrelocatable, "
+ "c.ctlsuperuser, c.ctlnamespace, "
+ "array_to_string(c.ctlrequires, ',') as requires, "
+ "false as install, null as version, "
+ "u.uptfrom, u.uptto, u.uptscript as script "
+ "from pg_extension_control c "
+ "join pg_depend d on d.classid = 'pg_catalog.pg_extension_control'::regclass "
+ "and d.objid = c.oid "
+ "and d.refclassid = 'pg_catalog.pg_extension_uptmpl'::regclass "
+ "join pg_extension_uptmpl u on u.oid = d.refobjid ");
+
+ res = ExecuteSqlQuery(fout, query->data, PGRES_TUPLES_OK);
+
+ ntups = PQntuples(res);
+
+ exttmplinfo = (ExtensionTemplateInfo *)
+ pg_malloc(ntups * sizeof(ExtensionTemplateInfo));
+
+ i_tableoid = PQfnumber(res, "tableoid");
+ i_oid = PQfnumber(res, "oid");
+ i_extname = PQfnumber(res, "ctlname");
+ i_isdefault = PQfnumber(res, "ctldefault");
+ i_relocatable = PQfnumber(res, "ctlrelocatable");
+ i_namespace = PQfnumber(res, "ctlnamespace");
+ i_requires = PQfnumber(res, "requires");
+ i_superuser = PQfnumber(res, "ctlsuperuser");
+ i_install = PQfnumber(res, "install");
+ i_version = PQfnumber(res, "version");
+ i_from = PQfnumber(res, "uptfrom");
+ i_to = PQfnumber(res, "uptto");
+ i_script = PQfnumber(res, "script");
+
+ for (i = 0; i < ntups; i++)
+ {
+ exttmplinfo[i].dobj.objType = DO_EXTENSION_TEMPLATE;
+ exttmplinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid));
+ exttmplinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid));
+ AssignDumpId(&exttmplinfo[i].dobj);
+ exttmplinfo[i].dobj.name = pg_strdup(PQgetvalue(res, i, i_extname));
+ exttmplinfo[i].namespace = pg_strdup(PQgetvalue(res, i, i_namespace));
+ exttmplinfo[i].isdefault = *(PQgetvalue(res, i, i_isdefault)) == 't';
+ exttmplinfo[i].relocatable = *(PQgetvalue(res, i, i_relocatable)) == 't';
+ exttmplinfo[i].superuser = *(PQgetvalue(res, i, i_superuser)) == 't';
+ exttmplinfo[i].install = *(PQgetvalue(res, i, i_install)) == 't';
+ exttmplinfo[i].requires = pg_strdup(PQgetvalue(res, i, i_requires));
+ exttmplinfo[i].version = pg_strdup(PQgetvalue(res, i, i_version));
+ exttmplinfo[i].from = pg_strdup(PQgetvalue(res, i, i_from));
+ exttmplinfo[i].to = pg_strdup(PQgetvalue(res, i, i_to));
+ exttmplinfo[i].script = pg_strdup(PQgetvalue(res, i, i_script));
+ }
+
+ PQclear(res);
+ destroyPQExpBuffer(query);
+
+ *numExtensionTemplates = ntups;
+
+ return exttmplinfo;
+ }
+
+ /*
+ * getExtensions:
+ * read all extensions in the system catalogs and return them in the
+ * ExtensionInfo* structure
+ *
+ * numExtensions is set to the number of extensions read in
+ */
ExtensionInfo *
getExtensions(Archive *fout, int *numExtensions)
{
***************
*** 7507,7512 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj)
--- 7629,7637 ----
case DO_NAMESPACE:
dumpNamespace(fout, (NamespaceInfo *) dobj);
break;
+ case DO_EXTENSION_TEMPLATE:
+ dumpExtensionTemplate(fout, (ExtensionTemplateInfo *) dobj);
+ break;
case DO_EXTENSION:
dumpExtension(fout, (ExtensionInfo *) dobj);
break;
***************
*** 7682,7687 **** dumpNamespace(Archive *fout, NamespaceInfo *nspinfo)
--- 7807,7917 ----
}
/*
+ * dumpExtensionTemplate
+ * writes out to fout the queries to recreate an extension
+ */
+ static void
+ dumpExtensionTemplate(Archive *fout, ExtensionTemplateInfo *exttmplinfo)
+ {
+ PQExpBuffer q;
+ PQExpBuffer delq;
+ PQExpBuffer labelq;
+ char *qextname,
+ *qversion,
+ *qfrom,
+ *qto;
+ bool upgrade; /* install or upgrade script? */
+
+ /* Skip if not to be dumped */
+ if (!exttmplinfo->dobj.dump || dataOnly)
+ return;
+
+ q = createPQExpBuffer();
+ delq = createPQExpBuffer();
+ labelq = createPQExpBuffer();
+
+ qextname = pg_strdup(fmtId(exttmplinfo->dobj.name));
+ upgrade = !exttmplinfo->install;
+
+ if (upgrade)
+ {
+ qfrom = pg_strdup(fmtId(exttmplinfo->from));
+ qto = pg_strdup(fmtId(exttmplinfo->to));
+ qversion = NULL;
+
+ appendPQExpBuffer(delq, "DROP TEMPLATE FOR EXTENSION %s FROM %s TO %s;\n",
+ qextname, qfrom, qto);
+ }
+ else
+ {
+ qversion = pg_strdup(fmtId(exttmplinfo->version));
+
+ appendPQExpBuffer(delq, "DROP TEMPLATE FOR EXTENSION %s VERSION %s;\n",
+ qextname, qversion);
+ }
+
+ appendPQExpBuffer(q, "CREATE TEMPLATE FOR EXTENSION %s", qextname);
+
+ if (upgrade)
+ appendPQExpBuffer(q, " FROM %s TO %s", qfrom, qto);
+ else
+ {
+ if (exttmplinfo->isdefault)
+ appendPQExpBuffer(q, " DEFAULT");
+
+ appendPQExpBuffer(q, " VERSION %s", qversion);
+ }
+
+ /* control options */
+ appendPQExpBuffer(q, "\n WITH (schema %s, %ssuperuser, %srelocatable",
+ fmtId(exttmplinfo->namespace),
+ exttmplinfo->superuser ? "" : "no",
+ exttmplinfo->relocatable ? "" : "no");
+
+ if (exttmplinfo->requires && strlen(exttmplinfo->requires))
+ appendPQExpBuffer(q, ", requires '%s'", exttmplinfo->requires);
+ appendPQExpBuffer(q, ")\n");
+
+ /* extension script (either install or upgrade script) */
+ appendPQExpBuffer(q, " AS\n$$%s$$;\n", exttmplinfo->script);
+
+ /*
+ * When the default version is not a create script, we need an extra ALTER
+ * statement here.
+ */
+ if (!upgrade && exttmplinfo->isdefault)
+ {
+ appendPQExpBuffer(q, "\nALTER TEMPLATE FOR EXTENSION %s ", qextname);
+ appendPQExpBuffer(q, "SET DEFAULT VERSION %s;\n", qversion);
+ }
+
+ appendPQExpBuffer(labelq, "TEMPLATE FOR EXTENSION %s", qextname);
+
+ ArchiveEntry(fout, exttmplinfo->dobj.catId, exttmplinfo->dobj.dumpId,
+ exttmplinfo->dobj.name,
+ NULL, NULL,
+ "",
+ false, "EXTENSION TEMPLATE", SECTION_PRE_DATA,
+ q->data, delq->data, NULL,
+ NULL, 0,
+ NULL, NULL);
+
+ /* Dump Extension Comments and Security Labels */
+ dumpComment(fout, labelq->data,
+ NULL, "",
+ exttmplinfo->dobj.catId, 0, exttmplinfo->dobj.dumpId);
+ dumpSecLabel(fout, labelq->data,
+ NULL, "",
+ exttmplinfo->dobj.catId, 0, exttmplinfo->dobj.dumpId);
+
+ free(qextname);
+
+ destroyPQExpBuffer(q);
+ destroyPQExpBuffer(delq);
+ destroyPQExpBuffer(labelq);
+ }
+
+ /*
* dumpExtension
* writes out to fout the queries to recreate an extension
*/
***************
*** 14689,14694 **** addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
--- 14919,14925 ----
{
case DO_NAMESPACE:
case DO_EXTENSION:
+ case DO_EXTENSION_TEMPLATE:
case DO_TYPE:
case DO_SHELL_TYPE:
case DO_FUNC:
*** a/src/bin/pg_dump/pg_dump.h
--- b/src/bin/pg_dump/pg_dump.h
***************
*** 79,84 **** typedef enum
--- 79,85 ----
/* When modifying this enum, update priority tables in pg_dump_sort.c! */
DO_NAMESPACE,
DO_EXTENSION,
+ DO_EXTENSION_TEMPLATE,
DO_TYPE,
DO_SHELL_TYPE,
DO_FUNC,
***************
*** 145,150 **** typedef struct _extensionInfo
--- 146,166 ----
char *extcondition;
} ExtensionInfo;
+ typedef struct _extensionTemplateInfo
+ {
+ DumpableObject dobj;
+ char *namespace; /* schema containing extension's objects */
+ bool isdefault; /* from pg_extension_control */
+ bool relocatable; /* from pg_extension_control */
+ bool superuser; /* from pg_extension_control */
+ char *requires; /* from pg_extension_control */
+ bool install; /* true if install template (not upgrade) */
+ char *version; /* from pg_extension_template */
+ char *from; /* from pg_extension_uptmpl */
+ char *to; /* from pg_extension_uptmpl */
+ char *script; /* both from template or uptmpl */
+ } ExtensionTemplateInfo;
+
typedef struct _typeInfo
{
DumpableObject dobj;
***************
*** 538,543 **** extern void sortDumpableObjectsByTypeOid(DumpableObject **objs, int numObjs);
--- 554,561 ----
*/
extern NamespaceInfo *getNamespaces(Archive *fout, int *numNamespaces);
extern ExtensionInfo *getExtensions(Archive *fout, int *numExtensions);
+ extern ExtensionTemplateInfo *getExtensionTemplates(Archive *fout,
+ int *numExtensionTemplates);
extern TypeInfo *getTypes(Archive *fout, int *numTypes);
extern FuncInfo *getFuncs(Archive *fout, int *numFuncs);
extern AggInfo *getAggregates(Archive *fout, int *numAggregates);
*** a/src/bin/pg_dump/pg_dump_sort.c
--- b/src/bin/pg_dump/pg_dump_sort.c
***************
*** 37,42 **** static const int oldObjectTypePriority[] =
--- 37,43 ----
{
1, /* DO_NAMESPACE */
1, /* DO_EXTENSION */
+ 1, /* DO_EXTENSION_TEMPLATE */
2, /* DO_TYPE */
2, /* DO_SHELL_TYPE */
2, /* DO_FUNC */
***************
*** 85,90 **** static const int newObjectTypePriority[] =
--- 86,92 ----
{
1, /* DO_NAMESPACE */
4, /* DO_EXTENSION */
+ 4, /* DO_EXTENSION_TEMPLATE */
5, /* DO_TYPE */
5, /* DO_SHELL_TYPE */
6, /* DO_FUNC */
***************
*** 1087,1092 **** describeDumpableObject(DumpableObject *obj, char *buf, int bufsize)
--- 1089,1099 ----
"SCHEMA %s (ID %d OID %u)",
obj->name, obj->dumpId, obj->catId.oid);
return;
+ case DO_EXTENSION_TEMPLATE:
+ snprintf(buf, bufsize,
+ "EXTENSION TEMPLATE %s (ID %d OID %u)",
+ obj->name, obj->dumpId, obj->catId.oid);
+ return;
case DO_EXTENSION:
snprintf(buf, bufsize,
"EXTENSION %s (ID %d OID %u)",
*** a/src/include/catalog/dependency.h
--- b/src/include/catalog/dependency.h
***************
*** 146,151 **** typedef enum ObjectClass
--- 146,154 ----
OCLASS_USER_MAPPING, /* pg_user_mapping */
OCLASS_DEFACL, /* pg_default_acl */
OCLASS_EXTENSION, /* pg_extension */
+ OCLASS_EXTENSION_CONTROL, /* pg_extension_control */
+ OCLASS_EXTENSION_TEMPLATE, /* pg_extension_template */
+ OCLASS_EXTENSION_UPTMPL, /* pg_extension_uptmpl */
OCLASS_EVENT_TRIGGER, /* pg_event_trigger */
MAX_OCLASS /* MUST BE LAST */
} ObjectClass;
*** a/src/include/catalog/indexing.h
--- b/src/include/catalog/indexing.h
***************
*** 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,75 ----
+ /*-------------------------------------------------------------------------
+ *
+ * pg_extension_control.h
+ * definition of the system "extension_control" relation
+ * (pg_extension_control) along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_extension_control.h
+ *
+ * NOTES
+ * the genbki.pl script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PG_EXTENSION_CONTROL_H
+ #define PG_EXTENSION_CONTROL_H
+
+ #include "catalog/genbki.h"
+
+ /* ----------------
+ * pg_extension_install_control definition. cpp turns this into
+ * typedef struct FormData_pg_extension_control
+ * ----------------
+ */
+ #define ExtensionControlRelationId 3379
+
+ CATALOG(pg_extension_control,3379)
+ {
+ NameData ctlname; /* extension name */
+ Oid ctlowner; /* control owner */
+ bool ctldefault; /* is this version the default? */
+ bool ctldefaultfull; /* is this version the default full version? */
+ bool ctlrelocatable; /* extension is relocatable? */
+ bool ctlsuperuser; /* extension is superuser only? */
+ NameData ctlnamespace; /* namespace of contained objects */
+
+ #ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text ctlversion; /* version to install with this control */
+ text ctlrequires[1]; /* extension dependency list */
+ #endif
+ } FormData_pg_extension_control;
+
+ /* ----------------
+ * Form_pg_extension_control corresponds to a pointer to a tuple with the
+ * format of pg_extension_control relation.
+ * ----------------
+ */
+ typedef FormData_pg_extension_control *Form_pg_extension_control;
+
+ /* ----------------
+ * compiler constants for pg_extension_control
+ * ----------------
+ */
+
+ #define Natts_pg_extension_control 9
+ #define Anum_pg_extension_control_ctlname 1
+ #define Anum_pg_extension_control_ctlowner 2
+ #define Anum_pg_extension_control_ctldefault 3
+ #define Anum_pg_extension_control_ctldefaultfull 4
+ #define Anum_pg_extension_control_ctlrelocatable 5
+ #define Anum_pg_extension_control_ctlsuperuser 6
+ #define Anum_pg_extension_control_ctlnamespace 7
+ #define Anum_pg_extension_control_ctlversion 8
+ #define Anum_pg_extension_control_ctlrequires 9
+
+ /* ----------------
+ * pg_extension_control has no initial contents
+ * ----------------
+ */
+
+ #endif /* PG_EXTENSION_CONTROL_H */
*** /dev/null
--- b/src/include/catalog/pg_extension_template.h
***************
*** 0 ****
--- 1,65 ----
+ /*-------------------------------------------------------------------------
+ *
+ * pg_extension_template.h
+ * definition of the system "extension_template" relation
+ * (pg_extension_template) along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_extension_template.h
+ *
+ * NOTES
+ * the genbki.pl script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PG_EXTENSION_TEMPLATE_H
+ #define PG_EXTENSION_TEMPLATE_H
+
+ #include "catalog/genbki.h"
+
+ /* ----------------
+ * pg_extension_install_template definition. cpp turns this into
+ * typedef struct FormData_pg_extension_template
+ * ----------------
+ */
+ #define ExtensionTemplateRelationId 3179
+
+ CATALOG(pg_extension_template,3179)
+ {
+ NameData tplname; /* extension name */
+ Oid tplowner; /* template owner */
+
+ #ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text tplversion; /* version to install with this template */
+ text tplscript; /* extension's install script */
+ #endif
+ } FormData_pg_extension_template;
+
+ /* ----------------
+ * Form_pg_extension_template corresponds to a pointer to a tuple with the
+ * format of pg_extension_template relation.
+ * ----------------
+ */
+ typedef FormData_pg_extension_template *Form_pg_extension_template;
+
+ /* ----------------
+ * compiler constants for pg_extension_template
+ * ----------------
+ */
+
+ #define Natts_pg_extension_template 4
+ #define Anum_pg_extension_template_tplname 1
+ #define Anum_pg_extension_template_tplowner 2
+ #define Anum_pg_extension_template_tplversion 3
+ #define Anum_pg_extension_template_tplscript 4
+
+ /* ----------------
+ * pg_extension_template has no initial contents
+ * ----------------
+ */
+
+ #endif /* PG_EXTENSION_TEMPLATE_H */
*** /dev/null
--- b/src/include/catalog/pg_extension_uptmpl.h
***************
*** 0 ****
--- 1,67 ----
+ /*-------------------------------------------------------------------------
+ *
+ * pg_extension_uptmpl.h
+ * definition of the system "extension_uptmpl" relation
+ * (pg_extension_uptmpl) along with the relation's initial contents.
+ *
+ *
+ * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/catalog/pg_extension_uptmpl.h
+ *
+ * NOTES
+ * the genbki.pl script reads this file and generates .bki
+ * information from the DATA() statements.
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PG_EXTENSION_UPTMPL_H
+ #define PG_EXTENSION_UPTMPL_H
+
+ #include "catalog/genbki.h"
+
+ /* ----------------
+ * pg_extension_install_uptmpl definition. cpp turns this into
+ * typedef struct FormData_pg_extension_uptmpl
+ * ----------------
+ */
+ #define ExtensionUpTmplRelationId 3279
+
+ CATALOG(pg_extension_uptmpl,3279)
+ {
+ NameData uptname; /* extension name */
+ Oid uptowner; /* template owner */
+
+ #ifdef CATALOG_VARLEN /* variable-length fields start here */
+ text uptfrom; /* version this template updates from */
+ text uptto; /* version this template updated to */
+ text uptscript; /* extension's update script */
+ #endif
+ } FormData_pg_extension_uptmpl;
+
+ /* ----------------
+ * Form_pg_extension_uptmpl corresponds to a pointer to a tuple with the
+ * format of pg_extension_uptmpl relation.
+ * ----------------
+ */
+ typedef FormData_pg_extension_uptmpl *Form_pg_extension_uptmpl;
+
+ /* ----------------
+ * compiler constants for pg_extension_uptmpl
+ * ----------------
+ */
+
+ #define Natts_pg_extension_uptmpl 5
+ #define Anum_pg_extension_uptmpl_uptname 1
+ #define Anum_pg_extension_uptmpl_uptowner 2
+ #define Anum_pg_extension_uptmpl_uptfrom 3
+ #define Anum_pg_extension_uptmpl_uptto 4
+ #define Anum_pg_extension_uptmpl_uptscript 5
+
+ /* ----------------
+ * pg_extension_uptmpl has no initial contents
+ * ----------------
+ */
+
+ #endif /* PG_EXTENSION_UPTMPL_H */
*** a/src/include/commands/alter.h
--- b/src/include/commands/alter.h
***************
*** 19,24 ****
--- 19,26 ----
#include "utils/relcache.h"
extern Oid ExecRenameStmt(RenameStmt *stmt);
+ extern void AlterObjectRename_internal(Relation rel,
+ Oid objectId, const char *new_name);
extern Oid ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt);
extern Oid AlterObjectNamespace_oid(Oid classId, Oid objid, Oid nspOid,
*** a/src/include/commands/extension.h
--- b/src/include/commands/extension.h
***************
*** 26,40 ****
extern bool creating_extension;
extern Oid CurrentExtensionObject;
extern Oid CreateExtension(CreateExtensionStmt *stmt);
extern void RemoveExtensionById(Oid extId);
extern Oid InsertExtensionTuple(const char *extName, Oid extOwner,
! Oid schemaOid, bool relocatable, const char *extVersion,
! Datum extConfig, Datum extCondition,
! List *requiredExtensions);
extern Oid ExecAlterExtensionStmt(AlterExtensionStmt *stmt);
--- 26,65 ----
extern bool creating_extension;
extern Oid CurrentExtensionObject;
+ /*
+ * Internal data structure to hold the extension control information, that we
+ * get either from parsing a control file or from the pg_extension_control
+ * catalog when working from Extension Templates.
+ */
+ typedef struct ExtensionControl
+ {
+ Oid ctrlOid; /* pg_control_extension oid, or invalidoid */
+ char *name; /* name of the extension */
+ char *directory; /* directory for script files */
+ char *default_version; /* default install target version, if any */
+ char *default_full_version; /* default install source version, if any */
+ char *module_pathname; /* string to substitute for module_pathname */
+ char *comment; /* comment, if any */
+ char *schema; /* target schema (allowed if !relocatable) */
+ bool relocatable; /* is alter extension set schema supported? */
+ bool superuser; /* must be superuser to install? */
+ int encoding; /* encoding of the script file, or -1 */
+ List *requires; /* names of prerequisite extensions */
+ bool is_template; /* true if we're using catalog templates */
+ } ExtensionControl;
+
+ extern ExtensionControl *read_extension_control_file(const char *extname);
+ extern void check_valid_extension_name(const char *extensionname);
extern Oid CreateExtension(CreateExtensionStmt *stmt);
extern void RemoveExtensionById(Oid extId);
extern Oid InsertExtensionTuple(const char *extName, Oid extOwner,
! Oid schemaOid, bool relocatable,
! const char *extVersion,
! Datum extConfig, Datum extCondition,
! List *requiredExtensions, Oid ctrlOid);
extern Oid ExecAlterExtensionStmt(AlterExtensionStmt *stmt);
*** /dev/null
--- b/src/include/commands/template.h
***************
*** 0 ****
--- 1,72 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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(CreateExtTemplateStmt *stmt);
+ extern Oid CreateExtensionTemplate(CreateExtTemplateStmt *stmt);
+ extern Oid CreateExtensionUpdateTemplate(CreateExtTemplateStmt *stmt);
+
+ extern char *get_extension_control_name(Oid ctrlOid);
+ extern char *get_extension_template_name(Oid tmplOid);
+ extern char *get_extension_uptmpl_name(Oid tmplOid);
+
+ extern Oid AtlerExtensionTemplateOwner(const char *extname, Oid newowner);
+ extern Oid AtlerExtensionTemplateRename(const char *extname,
+ const char *newname);
+
+ extern Oid AlterTemplate(AlterExtTemplateStmt *stmt);
+ extern Oid AlterExtensionTemplate(AlterExtTemplateStmt *stmt);
+ extern Oid AlterExtensionUpdateTemplate(AlterExtTemplateStmt *stmt);
+
+ extern Oid get_template_oid(const char *extname, const char *version,
+ bool missing_ok);
+ extern bool can_create_extension_from_template(const char *extname,
+ bool missing_ok);
+ extern Oid get_uptmpl_oid(const char *extname,
+ const char *from, const char *to,
+ bool missing_ok);
+
+ extern void RemoveExtensionControlById(Oid extControlOid);
+ extern void RemoveExtensionTemplateById(Oid extTemplateOid);
+ extern void RemoveExtensionUpTmplById(Oid extUpTmplOid);
+
+ extern ExtensionControl *find_pg_extension_control(const char *extname,
+ const char *version,
+ bool missing_ok);
+
+ extern ExtensionControl *find_default_pg_extension_control(const char *extname,
+ bool missing_ok);
+
+ extern char *read_pg_extension_template_script(const char *extname,
+ const char *version);
+ extern char *read_pg_extension_uptmpl_script(const char *extname,
+ const char *from_version,
+ const char *version);
+ extern char *read_extension_template_script(const char *extname,
+ const char *from_version,
+ const char *version);
+
+ extern List *list_pg_extension_template_versions(const char *extname);
+ extern List *list_pg_extension_update_versions(const char *extname);
+ extern List *pg_extension_default_controls(void);
+ extern List *pg_extension_templates(void);
+
+ extern List *list_pg_extension_control_oids_for(const char *extname);
+ extern List *list_pg_extension_template_oids_for(const char *extname);
+ extern List *list_pg_extension_uptmpl_oids_for(const char *extname);
+
+ #endif /* TEMPLATE_H */
*** a/src/include/nodes/nodes.h
--- b/src/include/nodes/nodes.h
***************
*** 362,367 **** typedef enum NodeTag
--- 362,369 ----
T_CreateEventTrigStmt,
T_AlterEventTrigStmt,
T_RefreshMatViewStmt,
+ T_CreateExtTemplateStmt,
+ T_AlterExtTemplateStmt,
/*
* TAGS FOR PARSE TREE NODES (parsenodes.h)
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
***************
*** 1129,1134 **** typedef enum ObjectType
--- 1129,1136 ----
OBJECT_DOMAIN,
OBJECT_EVENT_TRIGGER,
OBJECT_EXTENSION,
+ OBJECT_EXTENSION_TEMPLATE,
+ OBJECT_EXTENSION_UPTMPL,
OBJECT_FDW,
OBJECT_FOREIGN_SERVER,
OBJECT_FOREIGN_TABLE,
***************
*** 1638,1643 **** typedef struct AlterExtensionContentsStmt
--- 1640,1688 ----
List *objargs; /* Arguments if needed (eg, for functions) */
} AlterExtensionContentsStmt;
+ typedef enum ExtTemplateType
+ {
+ TEMPLATE_CREATE_EXTENSION,
+ TEMPLATE_UPDATE_EXTENSION
+ } ExtTemplateType;
+
+ typedef struct CreateExtTemplateStmt
+ {
+ NodeTag type;
+ ExtTemplateType tmpltype;
+ char *extname; /* Extension's name */
+ char *version; /* Version to create from the template */
+ char *from; /* In case of an update template, we update */
+ char *to; /* from version to version */
+ List *control; /* List of DefElem nodes */
+ char *script; /* Extension's install script */
+ bool if_not_exists; /* just do nothing if it already exists? */
+ bool default_version; /* default version of this extension */
+ } CreateExtTemplateStmt;
+
+ typedef enum AlterExtTemplateType
+ {
+ AET_SET_DEFAULT,
+ AET_SET_DEFAULT_FULL,
+ AET_SET_SCRIPT,
+ AET_UPDATE_CONTROL,
+ } AlterExtTemplateType;
+
+ typedef struct AlterExtTemplateStmt
+ {
+ NodeTag type;
+ ExtTemplateType tmpltype;
+ AlterExtTemplateType cmdtype; /* Type of command */
+ char *extname; /* Extension's name */
+ char *version; /* Extension's version */
+ char *from; /* In case of an update template, we update */
+ char *to; /* from version to version */
+ List *control; /* List of DefElem nodes */
+ char *script; /* Extension's install script */
+ bool missing_ok; /* skip error if missing? */
+
+ } AlterExtTemplateStmt;
+
/* ----------------------
* Create/Alter FOREIGN DATA WRAPPER Statements
* ----------------------
*** a/src/include/utils/acl.h
--- b/src/include/utils/acl.h
***************
*** 197,202 **** typedef enum AclObjectKind
--- 197,205 ----
ACL_KIND_FOREIGN_SERVER, /* pg_foreign_server */
ACL_KIND_EVENT_TRIGGER, /* pg_event_trigger */
ACL_KIND_EXTENSION, /* pg_extension */
+ ACL_KIND_EXTCONTROL, /* pg_extension_control */
+ ACL_KIND_EXTTEMPLATE, /* pg_extension_template */
+ ACL_KIND_EXTUPTMPL, /* pg_extension_uptmpl */
MAX_ACL_KIND /* MUST BE LAST */
} AclObjectKind;
***************
*** 325,330 **** extern bool pg_foreign_data_wrapper_ownercheck(Oid srv_oid, Oid roleid);
--- 328,336 ----
extern bool pg_foreign_server_ownercheck(Oid srv_oid, Oid roleid);
extern bool pg_event_trigger_ownercheck(Oid et_oid, Oid roleid);
extern bool pg_extension_ownercheck(Oid ext_oid, Oid roleid);
+ extern bool pg_extension_control_ownercheck(Oid ext_oid, Oid roleid);
+ extern bool pg_extension_template_ownercheck(Oid ext_template_oid, Oid roleid);
+ extern bool pg_extension_uptmpl_ownercheck(Oid ext_uptmpl_oid, Oid roleid);
extern bool has_createrole_privilege(Oid roleid);
#endif /* ACL_H */
*** /dev/null
--- b/src/test/regress/expected/extension.out
***************
*** 0 ****
--- 1,214 ----
+ -- 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';
+ $$;
+ -- test some ALTER commands
+ -- ok
+ ALTER TEMPLATE FOR EXTENSION pair VERSION '1.0' WITH (relocatable);
+ -- we don't have a version 1.3 known yet
+ ALTER TEMPLATE FOR EXTENSION pair VERSION '1.3' WITH (relocatable);
+ ERROR: pg_extension_control for extension "pair" version "1.3" does not exist
+ -- you can't set the default on an upgrade script, only an extension's version
+ ALTER TEMPLATE FOR EXTENSION pair FROM '1.0' TO '1.1' SET DEFAULT;
+ ERROR: syntax error at or near "SET"
+ LINE 1: ...R TEMPLATE FOR EXTENSION pair FROM '1.0' TO '1.1' SET DEFAUL...
+ ^
+ -- you can't set control properties on an upgrade script, only an
+ -- extension's version
+ ALTER TEMPLATE FOR EXTENSION pair FROM '1.0' TO '1.1' WITH (relocatable);
+ ERROR: syntax error at or near "WITH ("
+ LINE 1: ...R TEMPLATE FOR EXTENSION pair FROM '1.0' TO '1.1' WITH (relo...
+ ^
+ -- try to set the default full version to an unknown extension version
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT FULL VERSION '1.1';
+ ERROR: template for extension "pair" version "1.1" does not exist
+ -- now set it to the current one already, should silently do nothing
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT FULL VERSION '1.0';
+ -- you can actually change the script used to update, though
+ ALTER TEMPLATE FOR EXTENSION pair FROM '1.1' TO '1.2'
+ AS $$
+ COMMENT ON EXTENSION pair IS 'A Toy Key Value Text Type';
+ $$;
+ CREATE EXTENSION pair;
+ \dx pair
+ List of installed extensions
+ Name | Version | Schema | Description
+ ------+---------+--------+-------------
+ pair | 1.0 | public |
+ (1 row)
+
+ \dx+ pair
+ Objects in extension "pair"
+ Object Description
+ --------------------------------------
+ function pair(anyelement,anyelement)
+ function pair(anyelement,text)
+ function pair(text,anyelement)
+ function pair(text,text)
+ type pair
+ (5 rows)
+
+ ALTER EXTENSION pair UPDATE TO '1.2';
+ \dx+ pair
+ Objects in extension "pair"
+ Object Description
+ --------------------------------------
+ function pair(anyelement,anyelement)
+ function pair(anyelement,text)
+ function pair(text,anyelement)
+ function pair(text,text)
+ operator ~>(anyelement,anyelement)
+ operator ~>(anyelement,text)
+ operator ~>(text,anyelement)
+ operator ~>(text,text)
+ type pair
+ (9 rows)
+
+ DROP EXTENSION pair;
+ -- test with another full version that's not the default
+ CREATE TEMPLATE
+ FOR EXTENSION pair VERSION '1.3'
+ WITH (superuser, norelocatable, schema public)
+ AS $$
+ CREATE TYPE pair AS ( k text, v text );
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;';
+
+ CREATE OPERATOR ~> (LEFTARG = text,
+ RIGHTARG = anyelement,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = anyelement,
+ RIGHTARG = text,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = anyelement,
+ RIGHTARG = anyelement,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = text,
+ RIGHTARG = text,
+ PROCEDURE = pair);
+
+ COMMENT ON EXTENSION pair IS 'Simple Key Value Text Type';
+ $$;
+ -- that's ok
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.1';
+ -- that will install 1.0 then run the 1.0 -- 1.1 update script
+ CREATE EXTENSION pair;
+ \dx pair
+ List of installed extensions
+ Name | Version | Schema | Description
+ ------+---------+--------+-------------
+ pair | 1.1 | public |
+ (1 row)
+
+ DROP EXTENSION pair;
+ -- now that should install from the extension from the 1.3 template, even if
+ -- we have a default_major_version pointing to 1.0, because we actually have
+ -- a 1.3 create script.
+ CREATE EXTENSION pair VERSION '1.3';
+ \dx pair
+ List of installed extensions
+ Name | Version | Schema | Description
+ ------+---------+--------+----------------------------
+ pair | 1.3 | public | Simple Key Value Text Type
+ (1 row)
+
+ DROP EXTENSION pair;
+ -- and now let's ask for 1.3 by default while still leaving the
+ -- default_major_version at 1.0, so that it's possible to directly install
+ -- 1.2 if needed.
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.3';
+ CREATE EXTENSION pair;
+ \dx pair
+ List of installed extensions
+ Name | Version | Schema | Description
+ ------+---------+--------+----------------------------
+ pair | 1.3 | public | Simple Key Value Text Type
+ (1 row)
+
+ DROP EXTENSION pair;
+ CREATE EXTENSION pair VERSION '1.2';
+ \dx pair
+ List of installed extensions
+ Name | Version | Schema | Description
+ ------+---------+--------+---------------------------
+ pair | 1.2 | public | A Toy Key Value Text Type
+ (1 row)
+
+ DROP EXTENSION pair;
+ -- test owner change
+ CREATE ROLE regression_bob;
+ ALTER TEMPLATE FOR EXTENSION pair OWNER TO regression_bob;
+ select ctlname, rolname
+ from pg_extension_control c join pg_roles r on r.oid = c.ctlowner;
+ ctlname | rolname
+ ---------+----------------
+ pair | regression_bob
+ pair | regression_bob
+ pair | regression_bob
+ pair | regression_bob
+ (4 rows)
+
+ -- test renaming
+ ALTER TEMPLATE FOR EXTENSION pair RENAME TO keyval;
+ -- cleanup
+ DROP TEMPLATE FOR EXTENSION keyval FROM '1.1' TO '1.2';
+ DROP TEMPLATE FOR EXTENSION keyval FROM '1.0' TO '1.1';
+ DROP TEMPLATE FOR EXTENSION keyval VERSION '1.0';
+ DROP TEMPLATE FOR EXTENSION keyval VERSION '1.3';
+ DROP ROLE regression_bob;
*** a/src/test/regress/expected/sanity_check.out
--- b/src/test/regress/expected/sanity_check.out
***************
*** 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
*** /dev/null
--- b/src/test/regress/sql/extension.sql
***************
*** 0 ****
--- 1,171 ----
+ -- 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';
+ $$;
+
+ -- test some ALTER commands
+
+ -- ok
+ ALTER TEMPLATE FOR EXTENSION pair VERSION '1.0' WITH (relocatable);
+
+ -- we don't have a version 1.3 known yet
+ ALTER TEMPLATE FOR EXTENSION pair VERSION '1.3' WITH (relocatable);
+
+ -- you can't set the default on an upgrade script, only an extension's version
+ ALTER TEMPLATE FOR EXTENSION pair FROM '1.0' TO '1.1' SET DEFAULT;
+
+ -- you can't set control properties on an upgrade script, only an
+ -- extension's version
+ ALTER TEMPLATE FOR EXTENSION pair FROM '1.0' TO '1.1' WITH (relocatable);
+
+ -- try to set the default full version to an unknown extension version
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT FULL VERSION '1.1';
+
+ -- now set it to the current one already, should silently do nothing
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT FULL VERSION '1.0';
+
+ -- you can actually change the script used to update, though
+ ALTER TEMPLATE FOR EXTENSION pair FROM '1.1' TO '1.2'
+ AS $$
+ COMMENT ON EXTENSION pair IS 'A Toy Key Value Text Type';
+ $$;
+
+ CREATE EXTENSION pair;
+
+ \dx pair
+ \dx+ pair
+
+ ALTER EXTENSION pair UPDATE TO '1.2';
+
+ \dx+ pair
+
+ DROP EXTENSION pair;
+
+ -- test with another full version that's not the default
+ CREATE TEMPLATE
+ FOR EXTENSION pair VERSION '1.3'
+ WITH (superuser, norelocatable, schema public)
+ AS $$
+ CREATE TYPE pair AS ( k text, v text );
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(anyelement, anyelement)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair';
+
+ CREATE OR REPLACE FUNCTION pair(text, text)
+ RETURNS pair LANGUAGE SQL AS 'SELECT ROW($1, $2)::pair;';
+
+ CREATE OPERATOR ~> (LEFTARG = text,
+ RIGHTARG = anyelement,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = anyelement,
+ RIGHTARG = text,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = anyelement,
+ RIGHTARG = anyelement,
+ PROCEDURE = pair);
+
+ CREATE OPERATOR ~> (LEFTARG = text,
+ RIGHTARG = text,
+ PROCEDURE = pair);
+
+ COMMENT ON EXTENSION pair IS 'Simple Key Value Text Type';
+ $$;
+
+ -- that's ok
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.1';
+
+ -- that will install 1.0 then run the 1.0 -- 1.1 update script
+ CREATE EXTENSION pair;
+ \dx pair
+ DROP EXTENSION pair;
+
+ -- now that should install from the extension from the 1.3 template, even if
+ -- we have a default_major_version pointing to 1.0, because we actually have
+ -- a 1.3 create script.
+ CREATE EXTENSION pair VERSION '1.3';
+ \dx pair
+ DROP EXTENSION pair;
+
+ -- and now let's ask for 1.3 by default while still leaving the
+ -- default_major_version at 1.0, so that it's possible to directly install
+ -- 1.2 if needed.
+ ALTER TEMPLATE FOR EXTENSION pair SET DEFAULT VERSION '1.3';
+
+ CREATE EXTENSION pair;
+ \dx pair
+ DROP EXTENSION pair;
+
+ CREATE EXTENSION pair VERSION '1.2';
+ \dx pair
+ DROP EXTENSION pair;
+
+ -- test owner change
+ CREATE ROLE regression_bob;
+
+ ALTER TEMPLATE FOR EXTENSION pair OWNER TO regression_bob;
+
+ select ctlname, rolname
+ from pg_extension_control c join pg_roles r on r.oid = c.ctlowner;
+
+ -- test renaming
+ ALTER TEMPLATE FOR EXTENSION pair RENAME TO keyval;
+
+ -- cleanup
+ DROP TEMPLATE FOR EXTENSION keyval FROM '1.1' TO '1.2';
+ DROP TEMPLATE FOR EXTENSION keyval FROM '1.0' TO '1.1';
+ DROP TEMPLATE FOR EXTENSION keyval VERSION '1.0';
+ DROP TEMPLATE FOR EXTENSION keyval VERSION '1.3';
+ DROP ROLE regression_bob;
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Here's a rebased version; there were some merge conflicts with master.
Um ... what's with those hstore changes?
regards, tom lane
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Tom Lane <tgl@sss.pgh.pa.us> writes:
Um ... what's with those hstore changes?
Just showing how we could deal with shipping 1.1 in the future, it's not
meant to be applied as-is. Part of the feature set in there comes from
when Robert complained that we can't have
CREATE EXTENSION hstore;
install version 1.1 from 1.0 plus 1.0--1.1 upgrade path. It so happens
that with extension templates, that features is needed for pg_dump
purposes, so it's included in this patch.
I've left the hstore changes to show it off only.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Alvaro Herrera <alvherre@2ndquadrant.com> writes:
Here's a rebased version; there were some merge conflicts with master.
Thanks!
I also fixed some compiler warnings. I haven't reviewed the patch in
any detail yet. One thing that jump at me from the code style
perspective is the strange way it deals with "isnull" from heap_getattr.
(I think most of these should just elog(ERROR) if a null attr is found).
I think you're right here, yes.
Another thing is that I don't find the name "uptmpl" very clear.
Any suggestion is welcome, I don't like it very much either.
Keeping the "template.c" file name seems wrong -- exttemplate.c maybe?
(I renamed the parse nodes to ExtTemplate)
I've been wondering about that. My thinking is that we're providing a
new set of TEMPLATE objects and commands and the commands are designed
so that it's easy to add more objects in there. The implementation too
is quite open to that, with "routing" functions in template.c.
Now I've choosen to implement the Extension templates in the same file
because currently those are the only kind of "Templates" that we manage,
and this project seems to prefer big files rather than too many files.
That said, I'm not wedded to this choice.
There was a strange bug in pg_dump; it used "qto" where I thought
qversion was appropriate. I changed it (I looked at this hunk more
closely than most others because there was a compiler warning here, but
I didn't verify that it works.)
It's quite hard for me to spot the hunk you're talking about without
setting up a whole new branch to extract the work you did in the new
diff, but maybe I'm just missing a diff-between-diffs tool?
You seem to love using Capitalized Letters for some things in error
messages; I don't find these very pretty, and anyway they violate our
style guidelines. (I think these are in elog() not ereport() calls, but
still)
I'll make sure to remember about that, thanks.
Regards,
--
Dimitri Fontaine 06 63 07 10 78
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 15.03.2013 23:00, Alvaro Herrera wrote:
Dimitri Fontaine wrote:
Please find attached v3 of the Extension Templates patch, with full
pg_dump support thanks to having merged default_full_version, appended
with some regression tests now that it's possible.Here's a rebased version; there were some merge conflicts with master.
I also fixed some compiler warnings.
I'm quite worried about the security ramifications of this patch. Today,
if you're not sure if a system has e.g sslinfo installed, you can safely
just run "CREATE EXTENSION sslinfo". With this patch, that's no longer
true, because "foo" might not be the extension you're looking for.
Mallory might've done this:
create template for extension sslinfo version '1.0' with (schema public)
as $$ DO EVIL STUFF $$;
Now if you run "CREATE EXTENSION sslinfo" as superuser, you've been
compromised. This is not only a problem when sitting at a psql console,
it also just became really dangerous to run pg_dump backups without
ensuring that all the extensions are installed beforehand. That's
exactly the situation we wanted to avoid when extensions were introduced
in the first place.
Things get even more complicated if there's version 1.0 of sslinfo
already installed, and you create an extension template for sslinfo
version 1.1. Is that possible? How does it behave?
Below are some random bugs that I bumped into while testing. These could
be fixed, but frankly I think this should be rejected for security reasons.
Documentation doesn't build, multiple errors. In addition to the
reference pages, there should be a section in the main docs about these
templates.
postgres=# create template for extension myextension version '1.0' with () as 'foobar';
CREATE TEMPLATE FOR EXTENSION
postgres=# create extension myextension;
ERROR: syntax error at or near "foobar"
LINE 1: create extension myextension;
^
Confusing error message.
postgres=# create template for extension myextension version '1.0' with () as $$create table foobar(i int4) $$;
CREATE TEMPLATE FOR EXTENSION
postgres=# create extension myextension;
CREATE EXTENSION
postgres=# select * from foobar;
ERROR: relation "foobar" does not exist
LINE 1: select * from foobar;
^
Where did that table go?
postgres=# create template for extension myextension version '1.0' with () as $$ create function myfunc() returns int4 AS $f$ select 123; $f$ language sql; $$;
CREATE TEMPLATE FOR EXTENSION
postgres=# create extension myextension version '1.0';
CREATE EXTENSION
postgres=# select * from pg_namespace; nspname | nspowner | nspacl
--------------------+----------+-------------------------------
pg_toast | 10 |
pg_temp_1 | 10 |
pg_toast_temp_1 | 10 |
pg_catalog | 10 | {heikki=UC/heikki,=U/heikki}
public | 10 | {heikki=UC/heikki,=UC/heikki}
information_schema | 10 | {heikki=UC/heikki,=U/heikki}
1.0 | 10 |
(7 rows)
Ah, here... Where did that " 1.0" schema come from?
postgres=> create template for extension myextension version '1.0' with (schema public) as $$ create function evilfunc() returns int4 AS 'evilfunc' language internal; $$;
CREATE TEMPLATE FOR EXTENSION
postgres=> create extension myextension version '1.0';ERROR: permission denied for language internal
postgres=> drop template for extension myextension version '1.0';
ERROR: extension with OID 16440 does not exist
Something wrong with catalog caching.
$ make -s install
/usr/bin/install: cannot stat `./hstore--1.0.sql': No such file or directory
make: *** [install] Error 1
Installing hstore fails.
postgres=> create template for extension sslinfo version '1.0' with (schema public) as $$ create function evilfunc() returns int4 AS 'evilfunc' language internal; $$;
ERROR: extension "sslinfo" is already available
postgres=> create template for extension sslinfo2 version '1.0' with (schema public) as $$ create function evilfunc() returns int4 AS 'evilfunc' language internal; $$;
CREATE TEMPLATE FOR EXTENSION
postgres=> alter template for extension sslinfo2 rename to sslinfo;
ALTER TEMPLATE FOR EXTENSION
If we check for an existing extension at CREATE, should also check for
that in ALTER ... RENAME TO.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Mar 27, 2013 at 10:16 AM, Heikki Linnakangas
<hlinnakangas@vmware.com> wrote:
I'm quite worried about the security ramifications of this patch. Today, if
you're not sure if a system has e.g sslinfo installed, you can safely just
run "CREATE EXTENSION sslinfo". With this patch, that's no longer true,
because "foo" might not be the extension you're looking for. Mallory
might've done this:create template for extension sslinfo version '1.0' with (schema public) as
$$ DO EVIL STUFF $$;
Surely creating an extension template must be a superuser-only
operation, in which case this is an issue because Mallory could also
have just blown up the world directly if he's already a superuser
anyway.
If the current patch isn't enforcing that, it's 100% broken.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On 27.03.2013 16:16, Heikki Linnakangas wrote:
Below are some random bugs that I bumped into while testing. These could
be fixed, but frankly I think this should be rejected for security reasons.
Also:
pg_dump does not dump the owner of an extension template correctly.
- Heikki
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Robert Haas escribió:
On Wed, Mar 27, 2013 at 10:16 AM, Heikki Linnakangas
<hlinnakangas@vmware.com> wrote:I'm quite worried about the security ramifications of this patch. Today, if
you're not sure if a system has e.g sslinfo installed, you can safely just
run "CREATE EXTENSION sslinfo". With this patch, that's no longer true,
because "foo" might not be the extension you're looking for. Mallory
might've done this:create template for extension sslinfo version '1.0' with (schema public) as
$$ DO EVIL STUFF $$;Surely creating an extension template must be a superuser-only
operation, in which case this is an issue because Mallory could also
have just blown up the world directly if he's already a superuser
anyway.
Yeah .. (except "this is NOT an issue")
If the current patch isn't enforcing that, it's 100% broken.
Even if it's not enforcing that, it's not 100% broken, it only contains
one more bug we need to fix.
--
Álvaro Herrera http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
On Wed, Mar 27, 2013 at 10:32 AM, Alvaro Herrera
<alvherre@2ndquadrant.com> wrote:
Surely creating an extension template must be a superuser-only
operation, in which case this is an issue because Mallory could also
have just blown up the world directly if he's already a superuser
anyway.Yeah .. (except "this is NOT an issue")
If the current patch isn't enforcing that, it's 100% broken.
Even if it's not enforcing that, it's not 100% broken, it only contains
one more bug we need to fix.
Sure. I didn't mean that such a mistake would make the patch
unsalvageable, only that it would be disastrous from a security point
of view. But as you say, pretty easy to fix.
--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Hi,
Thanks you for testing and reporting those strange bugs, I should be
able to fix them by Tuesday at the earliest.
Heikki Linnakangas <hlinnakangas@vmware.com> writes:
create template for extension sslinfo version '1.0' with (schema public) as
$$ DO EVIL STUFF $$;
What you're saying is that we should restrict the capability to
superusers only, where I didn't think about those security implications
before and though that it wouldn't be a necessary limitation.
I will add the usual superuser() checks in the next version of the
patch.
Documentation doesn't build, multiple errors. In addition to the reference
pages, there should be a section in the main docs about these templates.
I really would like for a simpler setup to build documentation on my OS
of choice, and realize that's no excuse. Will clean that up in next
version of the patch.
postgres=# create template for extension myextension version '1.0' with () as 'foobar';
CREATE TEMPLATE FOR EXTENSION
postgres=# create extension myextension;
ERROR: syntax error at or near "foobar"
LINE 1: create extension myextension;
^Confusing error message.
Do we need to compute a different error message when applying the script
from a template or from a file on-disk? Also please keep in mind that
those error messages are typically to be seen by the extension's author.
postgres=# create template for extension myextension version '1.0' with () as $$create table foobar(i int4) $$;
CREATE TEMPLATE FOR EXTENSION
postgres=# create extension myextension;
CREATE EXTENSION
postgres=# select * from foobar;
ERROR: relation "foobar" does not exist
LINE 1: select * from foobar;
^Where did that table go?
Well that's not the answer I wanted to make, but:
select c.oid, relname, nspname
from pg_class c join pg_namespace n on n.oid = c.relnamespace
where relname ~ 'foobar';
oid | relname | nspname
-------+---------+-------------
41412 | foobar | 1.0
(1 row)
Ah, here... Where did that " 1.0" schema come from?
I need to sort that out. Didn't have that problem in my tests (included
in the regression tests), will add your test case and see about fixing
that bug in the next version of the patch.
postgres=> create template for extension myextension version '1.0' with
(schema public) as $$ create function evilfunc() returns int4 AS
evilfunc' language internal; $$;
CREATE TEMPLATE FOR EXTENSION
postgres=> create extension myextension version '1.0';ERROR: permission denied for language internal
postgres=> drop template for extension myextension version '1.0';
ERROR: extension with OID 16440 does not existSomething wrong with catalog caching.
Or something wrong with dependencies maybe… will have a look at that
too, and add some regression tests.
$ make -s install
/usr/bin/install: cannot stat `./hstore--1.0.sql': No such file or directory
make: *** [install] Error 1Installing hstore fails.
Works for me. Anyway that part was to show up how we could have been
managing the hstore 1.1 update in the past, I don't intend for it to get
commited unless specifically asked to do so.
I guess I should now remove hstore changes from the patch now, and will
do so in the next version of the patch.
If we check for an existing extension at CREATE, should also check for that
in ALTER ... RENAME TO.
Indeed. Will fix that too.
Also:
pg_dump does not dump the owner of an extension template correctly.
Will look into that too.
Thanks for your reviewing and testing, sorry to have missed those bugs.
The new version of the patch, early next week, will include fixes for
all of those and some more testing.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Heikki Linnakangas <hlinnakangas@vmware.com> writes:
I'm quite worried about the security ramifications of this patch. Today, if
you're not sure if a system has e.g sslinfo installed, you can safely just
run "CREATE EXTENSION sslinfo". With this patch, that's no longer true,
because "foo" might not be the extension you're looking for. Mallory
With the attached patch, you can't create a template for an extension
that is already available, so to protect against your scenario you only
have to make "sslinfo" available.
Please also note that when actually installing the "sslinfo" extension,
the one from the system will get prefered over the one from the
templates, should you have both available.
Now, I can see why you would still think it's not enough. Baring better
ideas, the current patch restricts the feature to superusers.
Documentation doesn't build, multiple errors. In addition to the reference
pages, there should be a section in the main docs about these templates.
I'm now working on that, setting up the documentation tool set.
postgres=# create template for extension myextension version '1.0' with () as 'foobar';
CREATE TEMPLATE FOR EXTENSION
postgres=# create extension myextension;
ERROR: syntax error at or near "foobar"
LINE 1: create extension myextension;
^Confusing error message.
Introducing a syntax error in hstore--1.1.sql leads to the same message.
Even if we agree that it must be changed, I think that's for another
patch.
~# create extension hstore;
ERROR: syntax error at or near "foobar" at character 115
STATEMENT: create extension hstore;
ERROR: syntax error at or near "foobar"
postgres=# create template for extension myextension version '1.0' with () as $$create table foobar(i int4) $$;
CREATE TEMPLATE FOR EXTENSION
postgres=# create extension myextension;
CREATE EXTENSION
postgres=# select * from foobar;
ERROR: relation "foobar" does not exist
LINE 1: select * from foobar;
^Where did that table go?
The control->schema was not properly initialized when not given by the
user. That's fixed, and I added a regression test.
Ah, here... Where did that " 1.0" schema come from?
Fixed too, was the same bug.
postgres=> create template for extension myextension version '1.0' with
(schema public) as $$ create function evilfunc() returns int4 AS
evilfunc' language internal; $$;
CREATE TEMPLATE FOR EXTENSION
postgres=> create extension myextension version '1.0';ERROR: permission denied for language internal
postgres=> drop template for extension myextension version '1.0';
ERROR: extension with OID 16440 does not existSomething wrong with catalog caching.
Works for me, I couldn't reproduce the bug here, working from Álvaro's
version 4 of the patch. Maybe he did already fix it, and you tested my
version 3?
$ make -s install
/usr/bin/install: cannot stat `./hstore--1.0.sql': No such file or directory
make: *** [install] Error 1Installing hstore fails.
Fixed in the attached. Seeing that makes me think that you actually used
Álvaro's version 4, though.
postgres=> create template for extension sslinfo version '1.0' with
(schema public) as $$ create function evilfunc() returns int4 AS
evilfunc' language internal; $$;
ERROR: extension "sslinfo" is already available
Expected.
postgres=> create template for extension sslinfo2 version '1.0' with
(schema public) as $$ create function evilfunc() returns int4 AS
evilfunc' language internal; $$;
CREATE TEMPLATE FOR EXTENSION
postgres=> alter template for extension sslinfo2 rename to sslinfo;
ALTER TEMPLATE FOR EXTENSIONIf we check for an existing extension at CREATE, should also check for that
in ALTER ... RENAME TO.
Indeed, good catch. Fixed in the attached version 5 of the patch.
I didn't add a regression test for that case, because we need to know
which extensions are available when we try to obtain this error, and I
don't know how to do that. We could create a template for the extension
foobar, then foobar2, then rename foobar2 to foobar, but that wouldn't
exercise the same code path.
Thanks again for your reviewing,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
Hi,
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Documentation doesn't build, multiple errors. In addition to the reference
pages, there should be a section in the main docs about these templates.I'm now working on that, setting up the documentation tool set.
Fixed in the attached version 6 of the patch.
I couldn't get to fix the documentation build tool chain on my main
workstation, so I had to revive a VM where I was lucky enough to find my
old setup was still working, all with shared directories, VPATH build
setup etc. Anyways.
I also expanded a little on the docs we did have, adding a section in
the main extension chapter and some example in the CREATE TEMPLATE FOR
EXTENSION reference page.
That version has no flaw that I'm aware of and implements the design we
reached after almost 2 years on the topic which is more complex than it
would appear at first sight. I hope that you will like the patch if not
the feature itself, as I have big plans for it in the near future.
Many thanks to all involved for helping with the design and the
reviewing, regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
On Wed, Apr 3, 2013 at 8:29 AM, Dimitri Fontaine <dimitri@2ndquadrant.fr> wrote:
Hi,
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Documentation doesn't build, multiple errors. In addition to the reference
pages, there should be a section in the main docs about these templates.I'm now working on that, setting up the documentation tool set.
Fixed in the attached version 6 of the patch.
just tried to build this one, but it doesn't apply cleanly anymore...
specially the ColId_or_Sconst contruct in gram.y
--
Jaime Casanova www.2ndQuadrant.com
Professional PostgreSQL: Soporte 24x7 y capacitación
Phone: +593 4 5107566 Cell: +593 987171157
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Jaime Casanova <jaime@2ndquadrant.com> writes:
just tried to build this one, but it doesn't apply cleanly anymore...
specially the ColId_or_Sconst contruct in gram.y
Will rebase tomorrow, thanks for the notice!
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers
Jaime Casanova <jaime@2ndquadrant.com> writes:
just tried to build this one, but it doesn't apply cleanly anymore...
specially the ColId_or_Sconst contruct in gram.y
Please find attached a new version of the patch, v7, rebased to current
master tree and with some more cleanup. I've been using the new grammar
entry NonReservedWord_or_Sconst, I'm not sure about that.
In particular it seems I had forgotten to fix the owner support in
pg_dump as noted by Heikki, it's now properly addressed in the attached
version.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
templates.v7.patch.gzapplication/octet-streamDownload
� �*�Q�=�s�6�?��
�/md����;�Qe9��c���\'���$$��"U���������@���I��i&�`����b������vCg;��_�o���[��o������z=��g�w�c�0*@x*8�c�{�o?��#;���A>��
zx?��F��^�0�'l����d.�y��@E�*v�27�� LX������o$�9�p��P|�v4�g�[�kl����CVgq8��f����@��������Eq��hx�����~�}�z���^2aP�<���#?�]���:��b'�F 6"��(t�N�C~v�I?��!"�yt�#��@�� ��V����� O:��%��x7�|V��O�|�7fa����w Q2��w�
���G�$7v�A��H)���.��h�z��������@Q��������K���m1v&��?�)> �qm��c�����7<�x���������/������[f�<<����4�<:������3�v�z�&^���F*�����t[Ter1#��4
xyCP�T�X/�2�B��^
dC�D�-4��;�)f���N�!�
T+| �Q����G����_�����q�5(�%�Ac��������cB#�8r�1b����Ejf a�C�P!�C�[�v��l����RW��{P�M?�(NI3�dc/ E���?h���0����p����8P�
�B���-5J�IX�`:�m�|�b��Al�&i���YNQ���0�F��'����3�N���1}��n����a�k�1�{!D�����{����2@�'���I�"��#0�P$K|;���>��W1� �/�M�>�/��zB/^�h�������`�8EI-�v:�e�|f��}���� h��dc�w_Y�{����?���_�j6I��#$D�^!��|��*�����A{�'�����r, '���i2��8�@�>���f� %�t%+*�)�0��f�|��
�>�\����`$]��NM�4�V4�0n� �P�?���9������>C;q�D��4��{=0��?Y:
<a���p4N%��[(�M�p�6 �D ,#Kd�2�.��=A��8`b�0p���P�����D� -�Y���h���;��v�������:��mU�a�E[��n���t{j��J���R2�Rb��bQ=d�z�
z�>�����Bc�F������.4OY=D�F�'���&�����Bl�%8'\/�F�-H �c5�O�i\u��_>m�� ���K��x�V���:"r�l
�CT�O�����m�J )LR�Eh�@.�(3�y$���*oSJJ�=�o�!v����#o��v
�M���o��c�#�S�&�����L*rtd�M��pn�qVd���q� kq9d��g+-1�i���&�Bz"�E^�_+E��+����[�d
X��)�i-3iJ�zhUw�KW��c��?�-n���:�2
X�+G�X�7&y�2%K�R[b�+���#�b!
!T�~�(�����d�vP�A����]~�@4Q��f�P�z�d�d���������%{���������������y|���B�M�@� qs
:����1RX=��E��k-��PKo+�!|��`<<;:��R�C/v���!��R-2��Gvb��6#�5����}��B��S�f4�F��<s@�i���DJ�Z��))�YT��:�?����*�*_��� �Y�s��2O�p{�$�T=��,��(�9��oV����X����;o\�n/;��Fk,����Rvq{y�(y������)c����V�0\�;�iQ�G��b[[[������������������������h�zpv�n \��Bts �C7|�j���W�w�)���>s4�l����O�I�z 8���`�^i����j�r����V���i�n��>\]�[���z�S���!j��O��Vc�&a���OIk[�%�����@W�[A�y��P���9�B��� �VL��x��t��WR��8��E��@@�����Qa�J%Lm�W�C�M��1��(��)-{#��R.��4�=%�{;�ppu�eJ;����3)�h�N3�����H����f��0w��w�6[�����k*�U��z<�F���Uvx����~���h�����w�T)(oJ�)6/�����<�����]���Rdb%'!vA{"��b1�G���}���'��5�J�bj�wE!����Yuk�,(�����n�������jmI�)��j@
�xL��RO����)2�]���z9�`�?z�� ���4���f�(���^�>�8������i`
�1���8�^�^ 'R6G�8�����Ir�y��Z��cf��cR�t�U��r; �Xf4�H� Q�4aq�\a�L%���qB3������!��((�S��o���LB�GrI�>��(-ma�,���vha�����i�r[x gC�DL�A
Fp-�9g5?�H�(���woV� f�J2�SqX�l�C(��h.
���E ���F�Yzq��������{����s�xlz��j��<&?+ �L ���3����A��� �c�b\��,;��'�bgb_>!?�)��OM�~ �
{��"[c1����g�[(����S{�N�db�OZ���b���c������`924�b�&�*k��{��L��k&�$Y��8�L�G���<C�[�O�t:���<�M���v��u�r�v��rJ��Cx��N��� ]��<��[#����kJ���!���.�N���Rb��-�8�qE_���3��\%4�P�����13��rWa���H}z��1����<%�Y�
2#�Q��%V�3d�+;�S�'�%���Q(0Rq������c�fu(h����l^��)�d�jN�
Y:K�uy��|��/���^��*��'���?� p��Z������y$�4�����]e:�L����?[0�~��t��b�]������5������/7
�
�U�'`���b����Nr���V�������w�b��W��j5:����@~Y�z{[{��%�����l�;�u����j��k�_c���(�����=�<~�~�O��I�3
j����X��q�����7r�[��?�����u�q~�j@9�$���O���n�����z}������������GAzF%_��1Sv>�+O��:8?�r\��}�������L�Gd^���w�G�9������Sp8��)���>1��f 7�oj�����Kl� �i��^k�k�
������V\���'�4���-����,Kn�s�E��3�)������pn�3�v6�z�<R�@��2_� ��4����"/�Om���Pu�69�x�6��GW4A�����L��G��w���ZbK��L�ZT��q�%[����N-<a_�)s_eX�������[M_�J����xKoa��w�C����_��N^r����^D�F�K�"�����
WD�p��F�0��Q�w�N��tv����+-b�9������v9�d��
s\���*@����T�����>��8�?��������C'ZI�����R�U�T�����|��
�-*�������������8�n���lb��yl��w=���v�-����?6��V�
���uG0���w���"��%*�$_��d�C�������Q���=wk����|{�(��>fOi�E��8��+� Q���;�Gi^5������A�a� ;�0l%W����B�2��(�
�{`������>D�����y��0����moq�,+W�mG&����z���P����%l������-g�j�M�����}����Y���/�.�U����jY+~�<��6�]6��0���dc�@�Lh����N���m�T���z�61
����+�`�@��!��t|�p�S�����z����%��k4� �kT�_�q�]��a{M�:�F<t���M'��B%q��y�� �n��Q(��
������p�M��o��oq1VL����;��O�A�������bc.�X?��n�:c�@V�������"�������:k��yl�`�@�����y +h �+HV��{����
���"^$��h��^(���NO����T��a�����N'�`v��{"�7A�7�� ���`�+���[JJ�M������_��'��W"n^R�N�x��E�WV�)ln��f��8�Wct���N�������`"+�E���S�[��@����n����H����O�~��^����$~�0Vd��� d%����x&W�����P��
R��>O����P+R�oRuk�?���VP��;^��4Z��������������F����q��j�w����F���Z2h9����� Jo��o�����fLN�8�NN��R��h�5�v��mt`Uv[�(�6���'�t��|�� e�!�t�0���+�F9�6��ww�^���l����ha�.
��q
�d���������9��v.�RC�������
���K�f�u��?c�����./���2��d�d{���b��;�k�D�l�z�f)�'��:O���� ���v������ej�����2�9��������������� F�UO�oJ����1�nu�iN�2���t�V��h]_6"�|����ge/�$t�U�����8%Q�t������� �^���8ic� �E��,������������_Jl����$����l�H��'Q��5���=���ad��j��D9��j ��9C7.o���S
t�1�
{Z?��e ! �o������dR��?���BcaV�s��{��~��~����r^q�W��|���`yX���x-:��:��'M�"�7�B�}�ND������7�K��^�/k�v7������:`-^�Z�.�e�lh�l��D���{Q������@?�N��\�V����U��i5��m�^���|��"caR����Ckw�*_<^X�G��g)������] ����hOY�B#I�t@E��5�<m&\�EiT~�f�M��S�S�����&T$?������4����}b�l1h3�_���W�����C��As�H���z���w*������u�k���q.R{c�s{@)�m�=U
^�y�~��������A[����O�F����P��#,����X��������&����"zeF:�X�-��~����cs���$>�u���=�}pu(�~+����-�F ^&��P�a�:��q����Q�]�� �_vl^��E�v����|Y@s��%���������r2�����4�<0U// �&��/ �����#|�.�sb�c�����t�-tQ��V��������o�nn`=�<7�9���*��
C;u��J����"Z�=�j,��������-f��%�������z�r_A�~� m�����3u��}LwmG������|�8��0�9�k6�H�k�d�V�w����'��� T:����������q���3��r��]�n�:�=L��@���� p�G;��H���I�� S�h��"W��>,�� �}i���+_"��8�i&U���LiMu���+���@��{=]|~����z��@i��f��U�G���Q�:��4M�4e��$Q;]��������w��_�m����ry��V�m�Xh���^fmO���f1`��3 �d�!� ����<���q2h�:����i�Ra.W�����Mc~��k�<~���}����M�2%�s�OW�
6C6�2��������`m�g��@@��~�:"�����'w��_�o�U�U���2%)�q��n��?���V���a'�!�g��������*���)��2-���Y�l�\8v�w�qS*��KNX�!h!6��u��L~�?��W[�"�����#��4��))�����)m��!{��r��,$}�mJ������������$����q�I$x7�i�r����pK4N7#��|��A?T$�k��7���E�:w���f��/mSGl\�k o���T��.� G�}y����~�Y��j��.��gw\��^��t����Nr��������W�����5l�r����K��w����n~�����Y�Brx�-����{����lQ�3�W��$nl�������g00���3�����q��Hj&���o�K]UR���$������Z�j��FC�:8*��ZXb�b���Bb��u,oS�7����ov��B�5`/�O���N�/�N�wF�������9�7x*>�����q�� ��!�
�gE��4Jv
E5A�t%J8#�]�A=�����L�,�;��}�ou�ouUW@���W
�)ul�f������(O�19,R�8�?&�$^sDi�S������ox�V�#���ho��:���^�Z� (|�V�-�KgS��^�^��� # z�f������������0.��:|��U,k_�"0')=�M8����Ds�*K}w���2)�yErl���vM�����tE4�����4������\�O=�p5�c�>�i�����/�T�{�6�Y���L��E�}J����Q4��O?��������O��P�D�K>������2*>&��� �`Q�f\ @��7�}|M4�/���3�?�N��Vi���G�&�R*d�ah|�/�����s9^���r��B��Q2�T?��\�YH�\(��SH�{�.u�&����D�p3$q�}Q>�D�'�g��!����Y�+j_W;fN�pu�FO��G�h*����
�oUK��*���U�S:��&�B���.��h�c���X�"�!��+4a�s�/��TU�l��iJ,c��{�#�33����o[���@a�=�P+_Kr� f�yj�n��g�GE�k>Nv�������Q�F��`,~����&��_���j��7�vK��"u��L��(d����_�j��F'����X#����?�N�~w�o����*���:�7`��q��60�(M�b�pb�>�O:��X�1^>��9�����J��ree&V���\��f6�z���0M?�����y �d��r�����_�_�����'�_�GX*�
���0�%�p�������^#��]2l�(�%I�>!$�������q?�8>�����r:N�A���%;|G3��� �;�w1.�J�()���%:GKD\H�.gy�.������i���e��?�r�77.�,��icY�� ��-��1~��|�N��a�y$jH������S���W0�c<J�m�}{�}���E�{bx1_�>�{�W�.���%��8��Cp��J������#�n��3Z!�.�z� �)|B.�3�B����$�XPN���� f�^�����!���MC�'S���<������������)b;O�>I��^��/������,��u$&����o���{$���dX�v(vN����
nH���y�yr��-���[�'����������rC���������[�0�Y4��w��p*+�s��yr���nOl��H�e9�eTS�e����������MF�u>���������� ����������4��*|��nw�N)�1�`8z_Z�p?v�����]��=��7��������������u��] ������G>2�����l�������q�h*��SE���'H�������)*�'pZr�p�_K^�Q��!�����@������ (f��i@P"�*0����%f�M
�F61�E.90JL��?�$�(�]\C��LD7Jm��L���:�AT]�!��Hx�-2��MBa|}�Y�E,��6�0��?�!YI4�jM��
���W�h���C=�J�~��
bv��Z������Q;������\|$��k�m�CV@�6;�������b2Yn�iF�e8_5~����*5F�G:�=���rD\�KesS ss*0����6 e8��Uq%,-�%�,�����R)$���+����C��c�s��N�� ��f|���5�g��Q=�mT�����3�z�:��L�������M��*�S"��:p0+��4�*�:�����\�&e&%��oe~�<�E]���T���hp��*������D������.� $���$����AK��1�P� �r���
������/p_�C��$�
-
!&��#+5v%8B�(�~J��o��/ ���`
I afU����u�A��5��5Y��_���<M0����CJ�/l�y�e��"���BWqf���t0��qT\��_�SL�I���)�Y�p��w;�A�n�|�������xK��_��%e`�T9gN}�;S�|b$�Y�>���t�n����L��w<�����NAs���h��>��eC�H��~
�����M%
�[����67�
Ac2���B��,��<)�������,3�VHU��L�
��f�,eoT���
��
��Q-s�\��~���Y"^P4 D�}�?&=����0��TMt�5�G�*�qr��+�O�r-���}��!F^�3����Q-��gc������a�1�K�����WO5��$�!���"�3����� @/���4&%����, >��iT�-�M��\�X6�����������o��g�`�aC�6hN���v��[$5������!#�@��M���
�f�H�*�H��}�!=��~Ud����x�u���eW&�z��v:����u=�����R�����Z�w�o"r
�Y.*s�����_*(��j*W���\���:��x��������.K�Q��py��x��rz�����������`�
�F�&?�7��k��6ya��AR����pyce��h4�;���&��W���]��fA��7�^0���C?�f �le�s��3�>�������pey��T52�$�+`eY[���D�#}�t��2��$�O�<J��^���<D���6"/
��z��<C�1O)��UL����9@nb|91�.MLs���?q�!� ����d4����x0�tM��o�����#�9��\9���&��%�����y��J@B������g�O�����H����%[aT���k�
�~�����v������?��A��d 0�� 0�.��0 �Y�
�0�/� !D�@�4�p�eL^YWV6� �q�Wi&�@��� ���4)��b�1+����a2�y,��+lo��j��'{�����hN��~�j"Z++�^�����h�w�H/jl��`��%��dq_�����e�}c�1f��L<��Iy�i�������5��_i��A@����\��_���u��<a?�����a���~�����
m�w���9�S���������s���o��/$�rD��)W��q�f���nU��*�\d�KY�H��%���K�(���8i�o6����&�#���o���[9�?~�f���r�K�G�pQ^�]��7���Z��>��C�Z��j��7�-�]�q��H�q!8%M&*��)�������U@���Tg�I0�2�\`��R+O��g�j�h���>�u�����T��p�k����.�:x��hU���
WLp;^�N�p�?�7�^6W`�=+�wl�s�R��*|����N��u��{��{YB/?{Y�`�@��M����#��!(���P���!�,9O@�@i~�,>
�
��o��T���^��R�'.���9���X��3���Q�z#p�E?_�& `�GH������Q���i�Rtv_����H=��:��4Rk���<����U�
�����QI7�AX�$�����o�����G���q���{���%�����^�~c����`D���o{��2+�I���o�4����#�}�*��3mC�P;��9BX��u�y��)%9k�;���ZE,E�6q H�XC�p�|�
�[hJ�@�s����~�T���(�@gg�����F}(�Az]%#���;����H�F�w)u�90�
A�VP(Y/g�����i�1IS�KN���?.�d�h@��\�TB�Po��6mX�*���K"!WL�Ln�8��&<N��E�E�p��d �^�I� *�f��ilB�9���@��`�$y� ���
D���L�R�(q*�������(�S0���k�c��Q�q�
n� �f��=���K`��(��L�>F�0�Ji"p��`��:
���*�p4����0:���)�'�H'C��K�`q�J�`�������3�^����VJ�K����A���/��W_A��Q
�v���'F|������`��\/�L�#I@.�1��rE�9.���E��9�wC� D�������D�q���@�O���O� �=,L�q��v����#��z��l���J�7H�����fuM���A��1@Y��J8�� ��Z5�f�PE�"��M����>�c+O(]K����6��� ��0)�cR$]����D���z
z��8� ���� �
��mR��A�\RR�+�I���Dl��X�/�,����*�}.��/- "<��TyO��VC����i�]�E���������RQ�L��k��2��pQ���2u�U@�.��V���G��~j~�T�9���yG����s%\�)"n�K�4�g��D������B��K�78X����v�=�pR��"hd�����$h�����g 7�tRl.���U�} T�[�zs'h�2�<� (����d.E0�h.Q6��E�jEH!���d��b�4��_<9����T"�� �����ze9\_�1"]�X
�6�l��W��4&X@��* ���4���,>G|�1S��FW=�a��1��<q'��������;g��U�DZ�0t�w9�[M�`})\�`m�m�L�Y`�T>�� �4D2�����hR6�l����b�W)��!&�z���2�hM���*�����l7�Rk*(��` �a�1F)��N~��]AQv:9{-`[�l����������{�rK�v�����o�������1���;���������j~�Uq(�yQ���4����4g��G�Y_�]���/�Q�R<��s~"C��#�s����[G���oC��0�����8"������i�O�Nv�����/��G�cm#�X_���**pZ����JO(�#��������h�~�"�9(�hT��fI���9��47W@�yP0�?P^VA\�a/�P�F�4R�E���q���\��!O�����Q�bM���VdWt<��� b�0(��/K��� >�r�������~���V`{lzV*��� >\����9���9�M�Mi>�ph�^�}���--m����� �/�r�m����%�,���1�~����D��y
��-\����c�z�lPf�
Ga{�d�;u�dm��������O�n�~�u��V���8���d[���b~����p HO!�-yc�D�86 �M�"KaKT��G���1��4���5��P��&d6��3<�Y. Q�q"� !��F�`�j�=�(�h�bq7k�FZ\Z]0X�}�� �lY�@���
u���9z �@��-%�o�v�X��d|�{�'i�;���6E��7�y�&���n�Xt��4W�$]����P�4�^�����/��2�Ka���CN��3�u�8J�/!k#R<�a?�><�
GEGtGG!��|���*^�gI&�m��"K�P_��)�����a��"TV~N�
J��y
bJ���B�+� +O�M`�i�4�U�a�P��c���N����wjt�-��L@Ib&���� �^(%����[IpP�F��1���V9����/b�$\��HA<A���f�>�p��Wp�+SVc_��E�d�4�?�Z�[�R��l� ��h�1
k@���wC�^Oe��-�}mS�������-�qq0z��"�����f9��t�����B<����x���Fp���OU�-��7��8�����L��DP�QZ<������\���@�������>O�Q|����a�b�r��f[\����������O��Jk����,��S��������})~Tz�����X������<�v-z���H�nd��o;��v CB?�]E9����W�'M�8����+My��n4"H%���=?�j��)`���#�.����Q<����z\���B��G{HJ���������y�'�`� �,�wMgy�&'5x��U?z��o��
IFg1�*���c����X&���1�����c���3OqW��n*C@���lN����oe�_�ly]Gy�������.����)6e�XZ �+����N\@��QlJc6J���3A)M��0E���\��Me�Vp���^�U���_�P��@���������]�e��<f{��!����@WiS/��P����N[�]�L���g�"���m�� ��� E���t0Vt���r�_}X^|# Ann�� =��(��:\ 7��5u��i�1�2^����v��X�_PB�F� �����s0dTn�e�W1�%�M����Eg�����M�s�R4�^j�����{��O�H���TaX��Q��$,e!|)c����[{�;�����w���QO����c����o���-�����Y�v�x`������T9(�>���!��ozp��q�c�" ����k&�tbQ�Sv#���/x��3Y��,V��n@0jaR��hQ<�Uh,�6*h�Y
6�/t�\�����K����2j���:|�BY��~� x'����4f
��� ������z��!7 ;G�#a�`�P��|������Zjv�-����>�XN_�s�PP���<���������cW�m�������{���9V�4�c�C�,�@,`M������m����w��b���3y�R#)]�!H�\��QCJ��D-�����&���>����X�G{)V{l���S���o�������/N�K���8�� �hx�fIqq ���-u���/�)��A�x���J<I�@�)�/����s�y�EUJ�/ �$L�oq'��]��k0�
M`\���Q��=c ������/7�_��s��qOxW�e<�
����RJl
���\��
�&y1&�:����b�q��fU���b� �#����u����n������tdY|���ZDr��t�����D�c�hd� ���1Z�O�E|����� zK�+�1��1��q�>p�0/w9�[���x�s)mmd$���kQ��%�����I��"_�5��N�q������zk.�l��Bw����P�C{��]��MZ�7cio����R��^����`u�W�n"Z���q����q��u
���k
]�������En��l�\p��'�j���0����_�?��j'�H�4����lx�@��,����4��X(�5#��� ��9~=��4h���W������S��.w���n��M_�M�� �&�5����`8�Q�7z
TYc����{?��v�j%� ]dzh/ i��V$R�`��"�����������'��#"�~(.����� \�<nr���;���|����� � �6�������/����j�~S�w�V��9���7D��_`��d�~��^gF�9;���� m����S�� ~�p�w��j^���Z7���� ��K
rw�f��f��$��A�y0�� <gf����H����n�wO��B;q%���S�j��;+��"T�7�xF4/�K��:�0����(����>d_a��/�%�T�����7���}]��c �f'9����E�U����3�qSQ>x�����jET��~�
v0��x+^�Z_]^���,�� ���aw�����������=����s���`�zv�2t ��CZ�n���4�����5d`^�F��z��a��]����;rOv����<��=Onj���)�_R��r�m�f�d�s�(����y��D���p�������� ��}�N4��z����T�RE>�p���+������N���=����u��X�
lw�����ZN�Bp��"01@�/,��f������7X�.�L�k@p��Y��I�]�����rq���G
�)
�����Z|��=�� ��G\��xv<�VV�i���pRkA�8w#D��{���j1����I'A;�d7���a`�o��e6V�����2����%�U�ass5;&�N�3b=J���]���]��4<����d�$�,������`�!�X�t�o�Y���Q�=|�5 Q�w�$YK@'$dqK�2���p�e�2��������?�gJg1z�#b��R;���w<����w��;���/��G����X���b��j
���h*0S<0��_�O������}�GC*oIw�cz���K�����)�1h�kV�;�(�gc[�+�J�w�����2��Uw�
�I_d�\
�������#drd#���C�Ae�vj� o��n2
�^����%�3`��a�{��
�`��;��0nL���h$����J����!Z*W���a !Fp���0� ��7���h�1�����{VX���o.�$�P�O���zL��J��?I��@����7�!%v[Z^�_�����l��=��l�dy��{<v;h?����FDU�~�
U#VEd���.������m�=__��;�~���\?��e��b�<���T��IN��3�)pm,��9�!�8`D\&[38:o
(����y
��XgV��4��9�,mt�llj����;\�M6�7O���HI��y�A��0�{���g���=�;�����a��EB���Y����#����_%E��$mi�F���x���4���-E>H���R!���x��_X~,�:������Z:�tr��%�W�����i�!���.�Go&���v(�b�����E�Y
�V��a��u!%
�,�ihv#7�F�f��Z��o�E�����}�����dcY@`cY?�nAw������������o����=�bgcS cSko���������ez��y��eN{���K���zH��
�c��
\�
������p/K*`����.o��lq8o���T@DM�8�c�9m(�����@K��e�wzM�=�k�3�4Sm���*�4�������bi#4�n����5��y��+�����L�����x�{�8��xG�U���pf�{i��v�KK�P��n��T`��z9�?���$���e��_B@�x4x)��^J��b���$�'q|�YY���yqW�T)�l�wq~�����s��x24}�r���Mp������Y.��WG+��3��d�9eP�i]���Kr�f�r;_S&�VA��6�^t����9��az
�-����~������V@v.SU��%�H�c��H���$�3�\3;����7��[���@�������)H������c�K�x1OBx]D������%�����
�"J�yE����"�(�Y���"�R}T��5��o*��c��Q?��@F4��:��Lj*���
�(�&uB4��!���N��S\���?#`J+K��P�o��[�����2����2)uX����I���s����i�/8�$`��K�.�3��V�6�!���8�+��G�Xt�/,~�DCu �%����b����<%3���E�& �]��Z���9H"^7��fS������@r��|?>3���'��S�)���5�EZSX�:|J���ic})�������*�����]�������S�^i���@�X� ��7r���� �_V�1���W0m��7�������M6�*�%�NE?�T(����4�l���M�$��� ��d��%�f���������
r:2�(=/�K1��;&���=:9m�v��X�^ ������s_} �o0�8R�����������$V���b��H�!BY�
i��i2B�L�z��q&�
*_Fe;�xE|���@��yq���a�5�VD��A�!?����:���'