*** 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;