*** a/doc/src/sgml/catalogs.sgml --- b/doc/src/sgml/catalogs.sgml *************** *** 2664,2669 **** --- 2664,2725 ---- + + <structname>pg_extension</structname> + + + pg_extension + + + + The catalog pg_extension stores information + about the installed extension. See for + details about extensions. + + + + <structname>pg_extension</> Columns + + + + + Name + Type + References + Description + + + + + + extname + name + + Extension name + + + + extversion + text + + Version string of the extension, as provided by its author. + + + + custom_class + text + + Extenion's entries that are dynamically added + to custom_variable_classes. + + + + + +
+
+ + <structname>pg_foreign_data_wrapper</structname> *************** *** 5926,5931 **** --- 5982,5992 ---- + pg_extension + available and installed extensions + + + pg_group groups of database users *************** *** 6135,6140 **** --- 6196,6271 ---- + + <structname>pg_extensions</structname> + + + pg_extensions + + + + The pg_extensions view lists the extensions that + are currently available and installed. + + + + <structname>pg_extensions</> Columns + + + + + Name + Type + Description + + + + + + name + text + The name of the extensions + + + + version + text + The version string of the extension + + + + custom_variable_classes + text + The coma-separated list of custom variable classes provided by + this extension + + + + comment + text + The comment field provided in the control + file, or NULL if none were provided. + + + + installed + boolean + + true if the extension is already + installed; false otherwise + + + + + +
+ + + The pg_extensions view is read only. + + +
+ <structname>pg_group</structname> *** a/doc/src/sgml/extend.sgml --- b/doc/src/sgml/extend.sgml *************** *** 336,339 **** --- 336,402 ---- + + Putting it all together: create extension + + + extension + + + + The way to put together a coherent set of custom SQL objects + is to create an extension. There are two sides of the + extension, the Operating System side contains several files and + the SQL one uses them. + + + + The CREATE EXTENSION command + relies on a couple + of files to be placed in SHAREDIR/contrib. + + + + + + The extension.control + file should contain the information necessary to register the + extension. The file format must follow the per-line key = + 'value' syntax, with comments supported on any line after + a # character. + + + This control file allows for registering the extension and should + provide values for the following keys: name and + version. + + + The control file can also contain + a custom_variable_classes key followed by any + number of custom settings, + named class.varname. + The custom_variable_classes value takes effect + at CREATE EXTENSION time, and is persistent. The + custom settings are set automatically too, but are not persistent. + + + + + Once registered, the extension is loaded by means of executing the + commands from + the extension.sql + file. + + + + + + The admin + function pg_extension_flag_dump + can be used to revert the default pg_dump policy + about objects that belong to an extension and force the flagged objects + to be part of the backups. + + + *** a/doc/src/sgml/func.sgml --- b/doc/src/sgml/func.sgml *************** *** 13897,13902 **** postgres=# SELECT * FROM pg_xlogfile_name_offset(pg_stop_backup()); --- 13897,13909 ---- record Return information about a file + + + pg_execute_from_file(filename text) + + void + Executes the SQL commands contained in a file + *************** *** 13935,13940 **** SELECT (pg_stat_file('filename')).modification; --- 13942,14038 ---- + + pg_execute_from_file + + + pg_execute_from_file makes the server + execute SQL commands to be found in a file. This function is + reserved to superusers. + + + + The functions shown in are + facilities for extension authors. For details about extensions, see + . + + + + Extension Related Functions + + + Name Return Type Description + + + + + + + pg_extension_flag_dump(objid oid) + + bool + Flag an extension's objectid as worthy + of pg_dump. + + + + pg_extension_with_user_data() + + bool + Return true only when the extension's + script should care for user data. + + + + +
+ + + pg_extension_flag_dump + + + pg_extension_flag_dump changes the dependency type between + an extension's and one of its object, given + by oid. The default behavior of an extension is to + skip objects in pg_dump, but in some cases that's not + what you want to achieve, as an extension author. If your extension + provides user data (in a table, for example), then flag this table so + that it's part of the backups, like so: + + SELECT pg_extension_flag_dump('schema.tablename'::regclass); + + + + This function can only be called when loading a SQL script + using the command CREATE + EXTENSION. See + and . + + + + pg_extension_with_user_data + + + pg_extension_with_user_data allows extension's author to + prepare installation scripts that will work well for initial + installation and when restoring from a pg_dump + backup, which issues CREATE EXTENSION foo WITH NO + DATA;. See for + details. One way to use it is as following: + + DO $$ + BEGIN + IF pg_extension_with_user_data() THEN + create schema foo; + create table foo.bar(id serial primary key); + perform pg_extension_flag_dump('foo.bar_id_seq'::regclass); + perform pg_extension_flag_dump('foo.bar::regclass); + END IF; + END; + $$; + + + The functions shown in manage advisory locks. For details about proper use of these functions, see *************** *** 13957,13962 **** SELECT (pg_stat_file('filename')).modification; --- 14055,14061 ---- void Obtain exclusive advisory lock + pg_advisory_lock(key1 int, key2 int) *** a/doc/src/sgml/ref/allfiles.sgml --- b/doc/src/sgml/ref/allfiles.sgml *************** *** 50,55 **** Complete list of usable sgml source files in this directory. --- 50,56 ---- + *************** *** 85,90 **** Complete list of usable sgml source files in this directory. --- 86,92 ---- + *** /dev/null --- b/doc/src/sgml/ref/create_extension.sgml *************** *** 0 **** --- 1,109 ---- + + + + + CREATE EXTENSION + 7 + SQL - Language Statements + + + + CREATE EXTENSION + define and install a new extension + + + + CREATE EXTENSION + + + + + CREATE EXTENSION extension_name [ WITH [ NO ] DATA ] + + + + + Description + + + CREATE EXTENSION registers a new extension. The + extension name must be distinct from the name of any existing extension + in the database. + + + + An extension allows superusers to load a script in the database. This + script will typically create new SQL objects such as + functions, data types, operators and index support methods. + + + + + + Parameters + + + + extension_name + + + The name of an extension to be + installed. PostgreSQL will register the + extension using details from the + file SHAREDIR/contrib/extension.control + file then load + the SHAREDIR/contrib/extension.sql + script. + + + + + + WITH NO DATA + + + This option is used by . As it's possible + to flag extension's objects so that they get dumped + (see ), this option allow + extension authors to prepare installation scripts that will avoid + creating user data when restoring. + + + + + + + + Examples + + + Install the hstore extension: + + CREATE EXTENSION hstore; + + + + + + Compatibility + + + CREATE EXTENSION is a PostgreSQL + extension. + + + + + See Also + + + + Extension Related Functions + Additional Supplied Modules + + + + *** /dev/null --- b/doc/src/sgml/ref/drop_extension.sgml *************** *** 0 **** --- 1,115 ---- + + + + + DROP EXTENSION + 7 + SQL - Language Statements + + + + DROP EXTENSION + remove a tablespace + + + + DROP EXTENSION + + + + + DROP EXTENSION [ IF EXISTS ] extension_name [ CASCADE | RESTRICT ] + + + + + Description + + + DROP EXTENSION removes an extension from the + system. This will also DROP all and + any SQL objects installed by the extension. + + + + An extension can only be dropped by a superuser. + + + + + Parameters + + + + + IF EXISTS + + + Do not throw an error if the extension does not exist. A notice is issued + in this case. + + + + + + extension_name + + + The name of an installed extension. + + + + + + CASCADE + + + Automatically drop objects that depend on the index. + + + + + + RESTRICT + + + Refuse to drop the index if any objects depend on it. This is + the default. + + + + + + + + Examples + + + To remove the extension hstore from the system: + + DROP EXTENSION hstore; + + + + + + Compatibility + + + DROP EXTENSION is a PostgreSQL + extension. + + + + + See Also + + + + + + + *** a/doc/src/sgml/reference.sgml --- b/doc/src/sgml/reference.sgml *************** *** 78,83 **** --- 78,84 ---- &createConversion; &createDatabase; &createDomain; + &createExtension; &createForeignDataWrapper; &createFunction; &createGroup; *************** *** 113,118 **** --- 114,120 ---- &dropConversion; &dropDatabase; &dropDomain; + &dropExtension; &dropForeignDataWrapper; &dropFunction; &dropGroup; *** a/src/backend/access/transam/xlog.c --- b/src/backend/access/transam/xlog.c *************** *** 55,60 **** --- 55,61 ---- #include "utils/guc.h" #include "utils/ps_status.h" #include "utils/relmapper.h" + #include "utils/cfparser.h" #include "pg_trace.h" *************** *** 5018,5117 **** str_time(pg_time_t tnow) } /* - * Parse one line from recovery.conf. 'cmdline' is the raw line from the - * file. If the line is parsed successfully, returns true, false indicates - * syntax error. On success, *key_p and *value_p are set to the parameter - * name and value on the line, respectively. If the line is an empty line, - * consisting entirely of whitespace and comments, function returns true - * and *keyp_p and *value_p are set to NULL. - * - * The pointers returned in *key_p and *value_p point to an internal buffer - * that is valid only until the next call of parseRecoveryCommandFile(). - */ - static bool - parseRecoveryCommandFileLine(char *cmdline, char **key_p, char **value_p) - { - char *ptr; - char *bufp; - char *key; - char *value; - static char *buf = NULL; - - *key_p = *value_p = NULL; - - /* - * Allocate the buffer on first use. It's used to hold both the parameter - * name and value. - */ - if (buf == NULL) - buf = malloc(MAXPGPATH + 1); - bufp = buf; - - /* Skip any whitespace at the beginning of line */ - for (ptr = cmdline; *ptr; ptr++) - { - if (!isspace((unsigned char) *ptr)) - break; - } - /* Ignore empty lines */ - if (*ptr == '\0' || *ptr == '#') - return true; - - /* Read the parameter name */ - key = bufp; - while (*ptr && !isspace((unsigned char) *ptr) && - *ptr != '=' && *ptr != '\'') - *(bufp++) = *(ptr++); - *(bufp++) = '\0'; - - /* Skip to the beginning quote of the parameter value */ - ptr = strchr(ptr, '\''); - if (!ptr) - return false; - ptr++; - - /* Read the parameter value to *bufp. Collapse any '' escapes as we go. */ - value = bufp; - for (;;) - { - if (*ptr == '\'') - { - ptr++; - if (*ptr == '\'') - *(bufp++) = '\''; - else - { - /* end of parameter */ - *bufp = '\0'; - break; - } - } - else if (*ptr == '\0') - return false; /* unterminated quoted string */ - else - *(bufp++) = *ptr; - - ptr++; - } - *(bufp++) = '\0'; - - /* Check that there's no garbage after the value */ - while (*ptr) - { - if (*ptr == '#') - break; - if (!isspace((unsigned char) *ptr)) - return false; - ptr++; - } - - /* Success! */ - *key_p = key; - *value_p = value; - return true; - } - - /* * See if there is a recovery command file (recovery.conf), and if so * read in parameters for archive recovery and XLOG streaming. * --- 5019,5024 ---- *************** *** 5147,5153 **** readRecoveryCommandFile(void) char *tok1; char *tok2; ! if (!parseRecoveryCommandFileLine(cmdline, &tok1, &tok2)) { syntaxError = true; break; --- 5054,5060 ---- char *tok1; char *tok2; ! if (!cfParseOneLine(cmdline, &tok1, &tok2)) { syntaxError = true; break; *** a/src/backend/catalog/Makefile --- b/src/backend/catalog/Makefile *************** *** 39,44 **** POSTGRES_BKI_SRCS = $(addprefix $(top_srcdir)/src/include/catalog/,\ --- 39,45 ---- pg_ts_parser.h pg_ts_template.h \ pg_foreign_data_wrapper.h pg_foreign_server.h pg_user_mapping.h \ pg_default_acl.h pg_seclabel.h \ + pg_extension.h \ toasting.h indexing.h \ ) *** a/src/backend/catalog/dependency.c --- b/src/backend/catalog/dependency.c *************** *** 34,39 **** --- 34,40 ---- #include "catalog/pg_database.h" #include "catalog/pg_default_acl.h" #include "catalog/pg_depend.h" + #include "catalog/pg_extension.h" #include "catalog/pg_foreign_data_wrapper.h" #include "catalog/pg_foreign_server.h" #include "catalog/pg_language.h" *************** *** 55,60 **** --- 56,62 ---- #include "commands/comment.h" #include "commands/dbcommands.h" #include "commands/defrem.h" + #include "commands/extension.h" #include "commands/proclang.h" #include "commands/schemacmds.h" #include "commands/seclabel.h" *************** *** 152,158 **** static const Oid object_classes[MAX_OCLASS] = { ForeignDataWrapperRelationId, /* OCLASS_FDW */ ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ ! DefaultAclRelationId /* OCLASS_DEFACL */ }; --- 154,161 ---- ForeignDataWrapperRelationId, /* OCLASS_FDW */ ForeignServerRelationId, /* OCLASS_FOREIGN_SERVER */ UserMappingRelationId, /* OCLASS_USER_MAPPING */ ! DefaultAclRelationId, /* OCLASS_DEFACL */ ! ExtensionRelationId /* OCLASS_EXTENSION */ }; *************** *** 549,558 **** findDependentObjects(const ObjectAddress *object, /* no problem */ break; case DEPENDENCY_INTERNAL: - /* * This object is part of the internal implementation of ! * another object. We have three cases: * * 1. At the outermost recursion level, disallow the DROP. (We * just ereport here, rather than proceeding, since no other --- 552,560 ---- /* no problem */ break; case DEPENDENCY_INTERNAL: /* * This object is part of the internal implementation of ! * another object. We have four cases: * * 1. At the outermost recursion level, disallow the DROP. (We * just ereport here, rather than proceeding, since no other *************** *** 596,602 **** findDependentObjects(const ObjectAddress *object, break; /* ! * 3. When recursing from anyplace else, transform this * deletion request into a delete of the other object. * * First, release caller's lock on this object and get --- 598,611 ---- break; /* ! * 3. When droping an extension, it's ok to drop its ! * dependencies first, even if they're internal. ! */ ! if (otherObject.classId == ExtensionRelationId) ! break; ! ! /* ! * 4. When recursing from anyplace else, transform this * deletion request into a delete of the other object. * * First, release caller's lock on this object and get *************** *** 1148,1153 **** doDeletion(const ObjectAddress *object) --- 1157,1166 ---- RemoveUserMappingById(object->objectId); break; + case OCLASS_EXTENSION: + RemoveExtensionById(object->objectId); + break; + case OCLASS_DEFACL: RemoveDefaultACLById(object->objectId); break; *************** *** 2082,2087 **** getObjectClass(const ObjectAddress *object) --- 2095,2104 ---- case DefaultAclRelationId: Assert(object->objectSubId == 0); return OCLASS_DEFACL; + + case ExtensionRelationId: + Assert(object->objectSubId == 0); + return OCLASS_EXTENSION; } /* shouldn't get here */ *************** *** 2563,2568 **** getObjectDescription(const ObjectAddress *object) --- 2580,2597 ---- break; } + case OCLASS_EXTENSION: + { + char *extension; + + extension = get_extension_name(object->objectId); + if (!extension) + elog(ERROR, "cache lookup failed for extension %u", + object->objectId); + appendStringInfo(&buffer, _("extension %s"), extension); + break; + } + case OCLASS_TBLSPACE: { char *tblspace; *** a/src/backend/catalog/heap.c --- b/src/backend/catalog/heap.c *************** *** 49,54 **** --- 49,55 ---- #include "catalog/pg_type.h" #include "catalog/pg_type_fn.h" #include "catalog/storage.h" + #include "commands/extension.h" #include "commands/tablecmds.h" #include "commands/typecmds.h" #include "miscadmin.h" *************** *** 914,919 **** heap_create_with_catalog(const char *relname, --- 915,921 ---- Oid old_type_oid; Oid new_type_oid; Oid new_array_oid = InvalidOid; + ObjectAddress myself; pg_class_desc = heap_open(RelationRelationId, RowExclusiveLock); *************** *** 1150,1165 **** heap_create_with_catalog(const char *relname, * Also, skip this in bootstrap mode, since we don't make dependencies * while bootstrapping. */ if (relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_TOASTVALUE && !IsBootstrapProcessingMode()) { ! ObjectAddress myself, ! referenced; - myself.classId = RelationRelationId; - myself.objectId = relid; - myself.objectSubId = 0; referenced.classId = NamespaceRelationId; referenced.objectId = relnamespace; referenced.objectSubId = 0; --- 1152,1167 ---- * Also, skip this in bootstrap mode, since we don't make dependencies * while bootstrapping. */ + myself.classId = RelationRelationId; + myself.objectId = relid; + myself.objectSubId = 0; + if (relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_TOASTVALUE && !IsBootstrapProcessingMode()) { ! ObjectAddress referenced; referenced.classId = NamespaceRelationId; referenced.objectId = relnamespace; referenced.objectSubId = 0; *************** *** 1189,1194 **** heap_create_with_catalog(const char *relname, --- 1191,1207 ---- } /* + * Record a dependency to the extension we're part of if any. We could + * bypass that step but it simplifies pg_dump queries a lot to have the + * direct dependency recorded here, as embedded recursive SQL ain't that + * easy to maintain: WHERE NOT EXISTS(WITH RECURSIVE ...). + */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } + + /* * Store any supplied constraints and defaults. * * NB: this may do a CommandCounterIncrement and rebuild the relcache *** a/src/backend/catalog/objectaddress.c --- b/src/backend/catalog/objectaddress.c *************** *** 28,33 **** --- 28,34 ---- #include "catalog/pg_constraint.h" #include "catalog/pg_conversion.h" #include "catalog/pg_database.h" + #include "catalog/pg_extension.h" #include "catalog/pg_language.h" #include "catalog/pg_largeobject.h" #include "catalog/pg_largeobject_metadata.h" *************** *** 47,52 **** --- 48,54 ---- #include "commands/dbcommands.h" #include "commands/defrem.h" #include "commands/proclang.h" + #include "commands/extension.h" #include "commands/tablespace.h" #include "commands/trigger.h" #include "nodes/makefuncs.h" *************** *** 128,133 **** get_object_address(ObjectType objtype, List *objname, List *objargs, --- 130,136 ---- address = get_object_address_relobject(objtype, objname, &relation); break; case OBJECT_DATABASE: + case OBJECT_EXTENSION: case OBJECT_TABLESPACE: case OBJECT_ROLE: case OBJECT_SCHEMA: *************** *** 266,271 **** get_object_address_unqualified(ObjectType objtype, List *qualname) --- 269,277 ---- case OBJECT_DATABASE: msg = gettext_noop("database name cannot be qualified"); break; + case OBJECT_EXTENSION: + msg = gettext_noop("extension name cannot be qualified"); + break; case OBJECT_TABLESPACE: msg = gettext_noop("tablespace name cannot be qualified"); break; *************** *** 298,303 **** get_object_address_unqualified(ObjectType objtype, List *qualname) --- 304,314 ---- address.objectId = get_database_oid(name, false); address.objectSubId = 0; break; + case OBJECT_EXTENSION: + address.classId = ExtensionRelationId; + address.objectId = get_extension_oid(name, false); + address.objectSubId = 0; + break; case OBJECT_TABLESPACE: address.classId = TableSpaceRelationId; address.objectId = get_tablespace_oid(name, false); *************** *** 635,640 **** object_exists(ObjectAddress address) --- 646,654 ---- case TSConfigRelationId: cache = TSCONFIGOID; break; + case ExtensionRelationId: + indexoid = ExtensionOidIndexId; + break; default: elog(ERROR, "unrecognized classid: %u", address.classId); } *** a/src/backend/catalog/pg_aggregate.c --- b/src/backend/catalog/pg_aggregate.c *************** *** 32,37 **** --- 32,38 ---- #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" + #include "commands/extension.h" static Oid lookup_agg_function(List *fnName, int nargs, Oid *input_types, *************** *** 293,298 **** AggregateCreate(const char *aggName, --- 294,305 ---- referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + + /* Depends on currently installed extension, if any */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } } /* *** a/src/backend/catalog/pg_conversion.c --- b/src/backend/catalog/pg_conversion.c *************** *** 30,35 **** --- 30,36 ---- #include "utils/rel.h" #include "utils/syscache.h" #include "utils/tqual.h" + #include "commands/extension.h" /* * ConversionCreate *************** *** 131,136 **** ConversionCreate(const char *conname, Oid connamespace, --- 132,143 ---- recordDependencyOnOwner(ConversionRelationId, HeapTupleGetOid(tup), conowner); + /* Depends on currently installed extension, if any */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } + heap_freetuple(tup); heap_close(rel, RowExclusiveLock); *** a/src/backend/catalog/pg_depend.c --- b/src/backend/catalog/pg_depend.c *************** *** 265,270 **** changeDependencyFor(Oid classId, Oid objectId, --- 265,325 ---- } /* + * Change the deptype of the given dependency. + * + * Returns the number of lines that we changed. + */ + long + changeDependencyTypeFor(Oid objid, + Oid refclassId, Oid refobjectId, int refobjsubid, + DependencyType expected, DependencyType newtype) + { + Relation depRel; + ScanKeyData key[3]; + SysScanDesc scan; + HeapTuple tup; + long count = 0; + + depRel = heap_open(DependRelationId, RowExclusiveLock); + + /* Now search for dependency records */ + ScanKeyInit(&key[0], + Anum_pg_depend_refclassid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(refclassId)); + ScanKeyInit(&key[1], + Anum_pg_depend_refobjid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(refobjectId)); + ScanKeyInit(&key[2], + Anum_pg_depend_refobjsubid, + BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(refobjsubid)); + scan = systable_beginscan(depRel, DependReferenceIndexId, true, + SnapshotNow, 3, key); + + while (HeapTupleIsValid((tup = systable_getnext(scan)))) + { + Form_pg_depend depform = (Form_pg_depend) GETSTRUCT(tup); + + if (depform->objid == objid && depform->deptype == expected) + { + count++; + + /* make a modifiable copy */ + tup = heap_copytuple(tup); + depform = (Form_pg_depend) GETSTRUCT(tup); + depform->deptype = newtype; + simple_heap_update(depRel, &tup->t_self, tup); + } + } + systable_endscan(scan); + heap_close(depRel, RowExclusiveLock); + return count; + } + + + /* * isObjectPinned() * * Test if an object is required for basic database functionality. *** a/src/backend/catalog/pg_namespace.c --- b/src/backend/catalog/pg_namespace.c *************** *** 21,26 **** --- 21,27 ---- #include "utils/builtins.h" #include "utils/rel.h" #include "utils/syscache.h" + #include "commands/extension.h" /* ---------------- *************** *** 75,79 **** NamespaceCreate(const char *nspName, Oid ownerId) --- 76,92 ---- /* Record dependency on owner */ recordDependencyOnOwner(NamespaceRelationId, nspoid, ownerId); + /* Record dependency on extension, if we're in a CREATE EXTENSION */ + if (create_extension != NULL ) + { + ObjectAddress myself; + + myself.classId = NamespaceRelationId; + myself.objectId = nspoid; + myself.objectSubId = 0; + + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } + return nspoid; } *** a/src/backend/catalog/pg_operator.c --- b/src/backend/catalog/pg_operator.c *************** *** 33,38 **** --- 33,39 ---- #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" + #include "commands/extension.h" static Oid OperatorGet(const char *operatorName, *************** *** 846,849 **** makeOperatorDependencies(HeapTuple tuple) --- 847,856 ---- /* Dependency on owner */ recordDependencyOnOwner(OperatorRelationId, HeapTupleGetOid(tuple), oper->oprowner); + + /* Dependency on extension */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } } *** a/src/backend/catalog/pg_proc.c --- b/src/backend/catalog/pg_proc.c *************** *** 35,40 **** --- 35,41 ---- #include "utils/builtins.h" #include "utils/lsyscache.h" #include "utils/syscache.h" + #include "commands/extension.h" Datum fmgr_internal_validator(PG_FUNCTION_ARGS); *************** *** 614,619 **** ProcedureCreate(const char *procedureName, --- 615,626 ---- nnewmembers, newmembers); } + /* dependency on extension */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } + heap_freetuple(tup); heap_close(rel, RowExclusiveLock); *** a/src/backend/catalog/pg_type.c --- b/src/backend/catalog/pg_type.c *************** *** 31,36 **** --- 31,38 ---- #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" + #include "commands/extension.h" + Oid binary_upgrade_next_pg_type_oid = InvalidOid; *************** *** 625,630 **** GenerateTypeDependencies(Oid typeNamespace, --- 627,638 ---- /* Normal dependency on the default expression. */ if (defaultExpr) recordDependencyOnExpr(&myself, defaultExpr, NIL, DEPENDENCY_NORMAL); + + /* dependency on extension */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } } /* *** a/src/backend/catalog/system_views.sql --- b/src/backend/catalog/system_views.sql *************** *** 150,155 **** CREATE VIEW pg_locks AS --- 150,158 ---- CREATE VIEW pg_cursors AS SELECT * FROM pg_cursor() AS C; + CREATE VIEW pg_extensions AS + SELECT * FROM pg_extensions() AS E; + CREATE VIEW pg_prepared_xacts AS SELECT P.transaction, P.gid, P.prepared, U.rolname AS owner, D.datname AS database *** a/src/backend/commands/Makefile --- b/src/backend/commands/Makefile *************** *** 14,20 **** include $(top_builddir)/src/Makefile.global OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ constraint.o conversioncmds.o copy.o \ ! dbcommands.o define.o discard.o explain.o foreigncmds.o functioncmds.o \ indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \ portalcmds.o prepare.o proclang.o \ schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \ --- 14,21 ---- OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ constraint.o conversioncmds.o copy.o \ ! dbcommands.o define.o discard.o \ ! extension.o explain.o foreigncmds.o functioncmds.o \ indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \ portalcmds.o prepare.o proclang.o \ schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \ *** a/src/backend/commands/comment.c --- b/src/backend/commands/comment.c *************** *** 142,147 **** CommentObject(CommentStmt *stmt) --- 142,153 ---- (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be superuser to comment on procedural language"))); break; + case OBJECT_EXTENSION: + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("must be superuser to comment on extension"))); + break; case OBJECT_OPCLASS: if (!pg_opclass_ownercheck(address.objectId, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_OPCLASS, *** /dev/null --- b/src/backend/commands/extension.c *************** *** 0 **** --- 1,915 ---- + /*------------------------------------------------------------------------- + * + * extension.c + * Commands to manipulate extensions + * + * Extensions in PostgreSQL allow user defined behavior plugged in at + * runtime. They will typically create SQL objects that you want to avoid + * dumping, issuing a single CREATE EXTENSION foo; command instead. + * + * All we need internally to manage an extension is an OID so that the + * dependent objects can get associated with it. An extension is created by + * populating the pg_extension catalog from a "control" file, containing + * some parameters. + * + * The extension control file format is the most simple name = value, we + * don't need anything more there. The SQL file to execute commands from is + * hardcoded to `pg_config --sharedir`/.install.sql. + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/extension.c + * + *------------------------------------------------------------------------- + */ + #include "postgres.h" + + #include + #include + #include + #include + + #include "access/genam.h" + #include "access/heapam.h" + #include "access/sysattr.h" + #include "access/xact.h" + #include "catalog/dependency.h" + #include "catalog/indexing.h" + #include "catalog/pg_depend.h" + #include "catalog/pg_extension.h" + #include "catalog/pg_type.h" + #include "commands/comment.h" + #include "commands/extension.h" + #include "commands/portalcmds.h" + #include "funcapi.h" + #include "nodes/parsenodes.h" + #include "miscadmin.h" + #include "storage/fd.h" + #include "utils/builtins.h" + #include "utils/cfparser.h" + #include "utils/fmgroids.h" + #include "utils/guc.h" + #include "utils/memutils.h" + #include "utils/rel.h" + #include "utils/syscache.h" + #include "utils/tqual.h" + + /* + * See commands/extension.h for details. + */ + ObjectAddress *create_extension = NULL; + bool create_extension_with_user_data = true; + + char * + get_extension_control_basepath(void) + { + char sharepath[MAXPGPATH]; + char *result; + + get_share_path(my_exec_path, sharepath); + result = palloc(MAXPGPATH); + snprintf(result, MAXPGPATH, "%s/contrib", sharepath); + + return result; + } + + char * + get_extension_install_filename(const char *extname) + { + char sharepath[MAXPGPATH]; + char *result; + + get_share_path(my_exec_path, sharepath); + result = palloc(MAXPGPATH); + snprintf(result, MAXPGPATH, "%s/contrib/%s.sql", sharepath, extname); + + return result; + } + + char * + get_extension_control_filename(const char *extname) + { + char sharepath[MAXPGPATH]; + char *result; + + get_share_path(my_exec_path, sharepath); + result = palloc(MAXPGPATH); + snprintf(result, MAXPGPATH, "%s/contrib/%s.control", sharepath, extname); + + return result; + } + + /* + * Given a relative pathname such as "name.control", return the full path to + * the control file. + */ + char * + get_extension_control_fullname(const char *filename) + { + char sharepath[MAXPGPATH]; + char *result; + + get_share_path(my_exec_path, sharepath); + result = palloc(MAXPGPATH); + snprintf(result, MAXPGPATH, "%s/contrib/%s", sharepath, filename); + + return result; + } + + /* + * The control file is supposed to be very short, half a dozen lines, and + * reading it is only allowed to superuser, so we don't even try to minimize + * memory allocation risks here. + * + * Also note that only happens in CREATE EXTENSION and pg_extensions(). + * + * To support listing available extensions, the parameter init_gucs can be + * set to false to inhibit any GUC processing. + */ + ExtensionControlFile * + read_extension_control_file(const char *filename, bool init_gucs) + { + int64 fsize = -1; + struct stat fst; + FILE *file; + char line[MAXPGPATH]; + bool syntaxError = false; + ExtensionControlFile *control = + (ExtensionControlFile *)palloc(sizeof(ExtensionControlFile)); + + /* default values, all fields are NULL til parsed */ + control->name = control->version = control->custom_class = control->comment = NULL; + + /* + * Stat the file to get its size, then read its content into memory so + * that we can "parse" it. + */ + if (stat(filename, &fst) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", filename))); + + fsize = Int64GetDatum((int64) fst.st_size); + + if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\" for reading: %m", + filename))); + + if (ferror(file)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read file \"%s\": %m", filename))); + + /* + * Prepare our ExtensionControlFile structure with default values, then + * "parse" the control file. + * + * The parsing uses directly the code for parsing Recovery Commands as + * we're abusing the exact same format here. + */ + control->name = NULL; + control->version = NULL; + control->custom_class = NULL; + + while (fgets(line, sizeof(line), file) != NULL) + { + char *tok1; + char *tok2; + char *dot; + + if (!cfParseOneLine(line, &tok1, &tok2)) + { + syntaxError = true; + break; + } + if (tok1 == NULL) + continue; + + dot = strchr(tok1, GUC_QUALIFIER_SEPARATOR); + + if (strcmp(tok1, "name") == 0) + { + control->name = pstrdup(tok2); + } + else if (strcmp(tok1, "version") == 0) + { + control->version = pstrdup(tok2); + } + else if (strcmp(tok1, "comment") == 0) + { + control->comment = pstrdup(tok2); + } + else if (strcmp(tok1, "custom_variable_classes") == 0) + { + if (control->name == NULL) + ereport(ERROR, + (errmsg("Please setup the extension name first, then " + "its custom_variable_classes."))); + + control->custom_class = (char *) + assign_custom_variable_classes(tok2, false, PGC_S_EXTENSION); + + /* + * We're in an interactive command, CREATE EXTENSION. + * The file parsing code only gets run at this time. + * + * Don't report to many details so as not to make it appear like + * the user could avoid the error message by tweaking the + * control file. + */ + if (init_gucs) + { + if (!add_extension_custom_variable_classes(control->name, tok2, true)) + ereport(ERROR, + (errmsg("Extension '%s' could not set " + "custom_variable_classes.", control->name))); + } + } + else if (dot != NULL && + is_custom_class(tok1, dot - tok1, control->custom_class)) + { + if (init_gucs) + { + add_placeholder_variable(tok1, LOG, true); + SetConfigOption(tok1, tok2, PGC_SUSET, PGC_S_EXTENSION); + elog(NOTICE, "Extension '%s' SET %s TO '%s': please edit '%s'.", + control->name, tok1, tok2, + GetConfigOption("config_file", true)); + } + } + else + ereport(ERROR, + (errmsg("Unsupported parameter '%s' in file: %s", + tok1, filename), + errhint("Be sure to have 'custom_variable_classes' set " + "in a line before any custom variable."))); + } + + if( control->name == NULL ) + ereport(ERROR, + (errmsg("Missing a name parameter in file: %s", filename))); + + if (syntaxError) + ereport(ERROR, + (errmsg("syntax error in extension control file: %s", line), + errhint("Lines should have the format parameter = 'value'."))); + + FreeFile(file); + return control; + } + + /* + * Add extension specific custom class to global custom_variable_classes + * + * That can get called either via the interactive CREATE EXTENSION command, + * or at backend startup time to set the custom_variable_classes. + * + * Please remind that the extension specific code need to set the + * custom_variable_classes for all installed extensions at backend startup + * time, but *NOT* the custom variables themselves. We offer this facility + * for extensions authors to register their custom class easily and in a + * decentralized way (no need to edit postgresql.conf), but the settings + * themselves will need to be set as usual by the users. + * + * Avoid useless verbosity when that's happening in the postinit. + */ + bool + add_extension_custom_variable_classes(const char *extname, + const char *ext_custom_classes, + bool verbose) + { + char *newval; + int g_cvc_len = 0; /* length of global custom_variable_classes */ + int e_cvc_len = 0; /* length of extension's custom_variable_classes */ + + if (ext_custom_classes == NULL) + /* we did it, right? */ + return true; + + /* + * Concat the extension's custom variable classes to the current global + * one, then act as if it were done from guc.c itself + * + * See src/backend/misc/README for why we use malloc() here. + */ + e_cvc_len = strlen(ext_custom_classes); + + if (custom_variable_classes != NULL) + { + g_cvc_len = strlen(custom_variable_classes); + newval = (char *)malloc((g_cvc_len + e_cvc_len + 2) * sizeof(char)); + sprintf(newval, "%s,%s", custom_variable_classes, ext_custom_classes); + } + else + newval = (char *)ext_custom_classes; + + /* FIXME: add PGC_EXTENSION so that we don't abuse PGC_SIGHUP here? */ + SetConfigOption("custom_variable_classes", + newval, PGC_SIGHUP, PGC_S_EXTENSION); + + if (verbose) + elog(NOTICE, + "Extension '%s' SET custom_variable_classes TO '%s'", + extname, custom_variable_classes); + + return true; + } + + /* + * Get all known extensions (for the current database, because we just + * seqscan the pg_extension catalog here) and init custom_variable_classes + * from there. + */ + void + ExtensionSetCVC(void) + { + Relation rel; + HeapScanDesc scandesc; + HeapTuple tuple; + bool isnull; + Datum classes; + + rel = heap_open(ExtensionRelationId, AccessShareLock); + + /* memset(values, 0, sizeof(values)); */ + /* MemSet(nulls, false, sizeof(nulls)); */ + + scandesc = heap_beginscan(rel, SnapshotNow, 0, NULL); + + while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL) + { + if (HeapTupleIsValid(tuple)) + { + classes = heap_getattr(tuple, + Anum_pg_extension_custom_class, + rel->rd_att, + &isnull); + + if (!isnull) + add_extension_custom_variable_classes( + NameStr(((Form_pg_extension) GETSTRUCT(tuple))->extname), + TextDatumGetCString(classes), + false /* be quiet */); + } + } + heap_endscan(scandesc); + heap_close(rel, AccessShareLock); + return; + } + + + /* + * Create an extension + * + * Only superusers can create an extension. + */ + void + CreateExtension(CreateExtensionStmt *stmt) + { + ExtensionControlFile *control; + Relation rel; + Datum values[Natts_pg_extension]; + bool nulls[Natts_pg_extension]; + HeapTuple tuple; + Oid extensionoid; + text *filename; + + /* Must be super user */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to create extension \"%s\"", + stmt->extname), + errhint("Must be superuser to create an extension."))); + + if( InvalidOid != get_extension_oid(stmt->extname, true) ) + { + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("extension \"%s\" already exists", stmt->extname))); + } + + control = read_extension_control_file( + get_extension_control_filename(stmt->extname), true); + + /* + * Insert tuple into pg_extension. + * + * We use ExclusiveLock here so that there's only CREATE EXTENSION + * possible at any given time. It's necessary because of the way the + * global variable create_extension is handled, and it seems like a + * restriction that's easy to live with. + */ + rel = heap_open(ExtensionRelationId, ExclusiveLock); + + memset(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + + values[Anum_pg_extension_extname - 1] = + DirectFunctionCall1(namein, CStringGetDatum(control->name)); + + if( control->version == NULL ) + nulls[Anum_pg_extension_extversion - 1] = true; + else + values[Anum_pg_extension_extversion - 1] = + CStringGetTextDatum(control->version); + + if( control->custom_class == NULL ) + nulls[Anum_pg_extension_custom_class - 1] = true; + else + values[Anum_pg_extension_custom_class - 1] = + CStringGetTextDatum(control->custom_class); + + tuple = heap_form_tuple(rel->rd_att, values, nulls); + extensionoid = simple_heap_insert(rel, tuple); + CatalogUpdateIndexes(rel, tuple); + heap_freetuple(tuple); + heap_close(rel, ExclusiveLock); + + /* + * Comment on extension + */ + if (control->comment != NULL) + CreateComments(extensionoid, ExtensionRelationId, 0, control->comment); + + /* + * Set the global create_extension so that any object created in the + * script has a dependency recorded towards the extension here. + */ + create_extension = (ObjectAddress *)palloc(sizeof(ObjectAddress)); + create_extension->classId = ExtensionRelationId; + create_extension->objectId = extensionoid; + create_extension->objectSubId = 0; + + /* + * Also set create_extension_with_user_data so that the function + * pg_extension_with_user_data returns the right value. + */ + create_extension_with_user_data = stmt->user_data; + + elog(NOTICE, + "Installing extension '%s' from '%s', %s user data", + stmt->extname, + get_extension_install_filename(stmt->extname), + create_extension_with_user_data ? "with" : "whithout"); + + filename = cstring_to_text(get_extension_install_filename(stmt->extname)); + DirectFunctionCall1(pg_execute_from_file, PointerGetDatum(filename)); + + /* reset the current_extension dependency tracker */ + pfree(create_extension); + create_extension = NULL; + + return; + } + + /* + * Drop an extension + */ + void + DropExtension(DropExtensionStmt *stmt) + { + char *extname = stmt->extname; + Relation rel; + HeapScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + Oid extensionoid; + ObjectAddress object; + + /* Must be super user */ + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("permission denied to drop extension \"%s\"", + stmt->extname), + errhint("Must be superuser to drop an extension."))); + + /* + * Find the target tuple + */ + rel = heap_open(ExtensionRelationId, RowExclusiveLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_extname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); + tuple = heap_getnext(scandesc, ForwardScanDirection); + + if (!HeapTupleIsValid(tuple)) + { + if (!stmt->missing_ok) + { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("extension \"%s\" does not exist", extname))); + } + else + { + ereport(NOTICE, + (errmsg("extension \"%s\" does not exist, skipping", + extname))); + /* XXX I assume I need one or both of these next two calls */ + heap_endscan(scandesc); + heap_close(rel, RowExclusiveLock); + } + return; + } + extensionoid = HeapTupleGetOid(tuple); + heap_endscan(scandesc); + heap_close(rel, RowExclusiveLock); + + /* + * Do the deletion + */ + object.classId = ExtensionRelationId; + object.objectId = extensionoid; + object.objectSubId = 0; + + performDeletion(&object, stmt->behavior); + + return; + } + + /* + * get_extension_oid - given an extension name, look up the OID + * + * If missing_ok is false, throw an error if extension name not found. If + * true, just return InvalidOid. + */ + Oid + get_extension_oid(const char *extname, bool missing_ok) + { + Oid result; + Relation rel; + HeapScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + Anum_pg_extension_extname, + BTEqualStrategyNumber, F_NAMEEQ, + CStringGetDatum(extname)); + scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); + tuple = heap_getnext(scandesc, ForwardScanDirection); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = HeapTupleGetOid(tuple); + else + result = InvalidOid; + + heap_endscan(scandesc); + heap_close(rel, AccessShareLock); + + if (!OidIsValid(result) && !missing_ok) + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("extension \"%s\" does not exist", + extname))); + + return result; + } + + /* + * get_extension_name - given an extension OID, look up the name + * + * Returns a palloc'd string, or NULL if no such tablespace. + */ + char * + get_extension_name(Oid ext_oid) + { + char *result; + Relation rel; + HeapScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + /* + * Search pg_extension. We use a heapscan here even though there is an + * index on oid, on the theory that pg_extension will usually have just a + * few entries and so an indexed lookup is a waste of effort. + */ + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(ext_oid)); + scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); + tuple = heap_getnext(scandesc, ForwardScanDirection); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = pstrdup(NameStr(((Form_pg_extension) GETSTRUCT(tuple))->extname)); + else + result = NULL; + + heap_endscan(scandesc); + heap_close(rel, AccessShareLock); + + return result; + } + + /* + * Drop extension by OID. This is called to clean up dependencies. + */ + char * + RemoveExtensionById(Oid extId) + { + char *result; + Relation rel; + HeapScanDesc scandesc; + HeapTuple tuple; + ScanKeyData entry[1]; + + /* + * Search pg_extension. We use a heapscan here even though there is an + * index on oid, on the theory that pg_extension will usually have just a + * few entries and so an indexed lookup is a waste of effort. + */ + rel = heap_open(ExtensionRelationId, AccessShareLock); + + ScanKeyInit(&entry[0], + ObjectIdAttributeNumber, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(extId)); + scandesc = heap_beginscan(rel, SnapshotNow, 1, entry); + tuple = heap_getnext(scandesc, ForwardScanDirection); + + /* We assume that there can be at most one matching tuple */ + if (HeapTupleIsValid(tuple)) + result = pstrdup(NameStr(((Form_pg_extension) GETSTRUCT(tuple))->extname)); + else + result = NULL; + + simple_heap_delete(rel, &tuple->t_self); + + heap_endscan(scandesc); + heap_close(rel, AccessShareLock); + + return result; + } + + /* + * Public (SQL callable) function to let the extension install script know + * if it should care for user data or not. The typical usage is from pg_dump + * that will issue + * CREATE EXTENSION foo WITH NO DATA; + * + * That allows any object flagged as worthy of dump to be restored by the + * script produced by pg_dump rather than installed with default content + * from the extension's script. + */ + Datum + pg_extension_with_user_data(PG_FUNCTION_ARGS) + { + if (create_extension == NULL) + ereport(ERROR, + (errmsg("This function can only be used from CREATE EXTENSION."))); + + PG_RETURN_BOOL(create_extension_with_user_data); + } + + /* + * Public (SQL callable) function for managing user object that you want to + * dump from an extension. + * + * pg_dump queries are filtering out objects that have an internal ('i') + * dependency with the extension's oid, so we provide a function to change + * the dependency fron 'i' to 'n'. As a result, pg_dump will consider such + * objects. + * + * update pg_depend + * set deptype = 'n' + * where refclassid = 'pg_extension'::regclass + * and refobjid = $ext_oid and oibjid = $1 + * and deptype = 'i' + */ + Datum + pg_extension_flag_dump(PG_FUNCTION_ARGS) + { + Oid objid = PG_GETARG_OID(0); + long count; + + /* + * CREATE EXTENSION is superuser only and we check we're in that code + * path, so we don't add another explicit check for superuser here. + */ + if (create_extension == NULL) + ereport(ERROR, + (errmsg("This function can only be used from CREATE EXTENSION."))); + + count = changeDependencyTypeFor(objid, + ExtensionRelationId, create_extension->objectId, 0, + DEPENDENCY_INTERNAL, DEPENDENCY_NORMAL); + + if (count == 0) + ereport(ERROR, + (errmsg("Can't find pg_depend entry for oid %u " + "referring to current extension.", objid))); + else if (count > 1) + ereport(ERROR, + (errmsg("Current extension has more than one " + "dependency with object id %u.", objid))); + + PG_RETURN_BOOL(true); + } + + /* + * read (seqscan) the pg_extension catalog and fill in the exts array (that + * we allocate --- the caller has to free it). returns the number of items + * added into the array. + */ + static int + extension_list_installed(ExtensionControlFile **exts, bool name_only) + { + Relation rel; + HeapScanDesc scandesc; + HeapTuple tuple; + bool visnull, cisnull; + Datum d_version, d_classes; + int num = 0, size = 10; + + *exts = (ExtensionControlFile *)palloc(size*sizeof(ExtensionControlFile)); + + rel = heap_open(ExtensionRelationId, AccessShareLock); + scandesc = heap_beginscan(rel, SnapshotNow, 0, NULL); + + while ((tuple = heap_getnext(scandesc, ForwardScanDirection)) != NULL) + { + if (HeapTupleIsValid(tuple)) + { + if( num == size ) + { + size += 10; + *exts = (ExtensionControlFile *) + repalloc(exts, size*sizeof(ExtensionControlFile)); + } + + (*exts)[num].name = + pstrdup(NameStr(((Form_pg_extension) + GETSTRUCT(tuple))->extname)); + + if (!name_only) + { + d_version = heap_getattr(tuple, + Anum_pg_extension_extversion, + rel->rd_att, + &visnull); + d_classes = heap_getattr(tuple, + Anum_pg_extension_custom_class, + rel->rd_att, + &cisnull); + + if (visnull) + (*exts)[num].version = NULL; + else + (*exts)[num].version = pstrdup(TextDatumGetCString(d_version)); + + if (cisnull) + (*exts)[num].custom_class = NULL; + else + (*exts)[num].custom_class = pstrdup(TextDatumGetCString(d_classes)); + + (*exts)[num].comment = + GetComment(HeapTupleGetOid(tuple), tuple->t_tableOid, 0); + } + num++; + } + } + heap_endscan(scandesc); + heap_close(rel, AccessShareLock); + + return num; + } + + /* + * This function lists all extensions, the one currently already installed + * but also the one available for installation. For those, we parse the + * control file and return the information that would be available in the + * pg_extension catalog. + * + * A system view pg_extensions provides the user interface to this SRF + */ + Datum + pg_extensions(PG_FUNCTION_ARGS) + { + FuncCallContext *funcctx; + struct dirent *de; + extension_fctx *fctx; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to get extensions listings")))); + + if (SRF_IS_FIRSTCALL()) + { + MemoryContext oldcontext; + TupleDesc tupdesc; + ExtensionControlFile *installed; + int num_ext_installed; + + funcctx = SRF_FIRSTCALL_INIT(); + oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx); + + tupdesc = CreateTemplateTupleDesc(5, false); + TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name", + NAMEOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 2, "version", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 3, "custom_variable_classes", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 4, "comment", + TEXTOID, -1, 0); + TupleDescInitEntry(tupdesc, (AttrNumber) 5, "installed", + BOOLOID, -1, 0); + + funcctx->tuple_desc = BlessTupleDesc(tupdesc); + + /* Get an array of installed extensions, but only their names */ + num_ext_installed = extension_list_installed(&installed, true); + + fctx = palloc(sizeof(extension_fctx)); + fctx->dir.location = get_extension_control_basepath(); + fctx->dir.dirdesc = AllocateDir(fctx->dir.location); + fctx->num_installed = num_ext_installed; + fctx->installed = installed; + + if (!fctx->dir.dirdesc) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open directory \"%s\": %m", + fctx->dir.location))); + + funcctx->user_fctx = fctx; + MemoryContextSwitchTo(oldcontext); + } + + funcctx = SRF_PERCALL_SETUP(); + fctx = (extension_fctx *) funcctx->user_fctx; + + while ((de = ReadDir(fctx->dir.dirdesc, fctx->dir.location)) != NULL) + { + ExtensionControlFile *control; + Datum values[5]; + bool nulls[5]; + bool is_installed = false; + int i = 0; + HeapTuple tuple; + + if (strcmp(de->d_name, ".") == 0 || + strcmp(de->d_name, "..") == 0 || + strrchr(de->d_name, '.') == NULL || + strcmp(strrchr(de->d_name, '.'), ".control") != 0) + continue; + + control = read_extension_control_file( + get_extension_control_fullname(de->d_name), false); + + memset(values, 0, sizeof(values)); + MemSet(nulls, false, sizeof(nulls)); + + values[0] = DirectFunctionCall1(namein, CStringGetDatum(control->name)); + + if( control->version == NULL ) + nulls[1] = true; + else + values[1] = CStringGetTextDatum(control->version); + + if( control->custom_class == NULL ) + nulls[2] = true; + else + values[2] = CStringGetTextDatum(control->custom_class); + + if( control->comment == NULL ) + nulls[3] = true; + else + values[3] = CStringGetTextDatum(control->comment); + + /* + * We add a boolean "installed" to the pg_extension struct. As we + * expect installations to typically have very few extensions + * installed, we simple rescan the same array each time. + */ + while( !is_installed && i < fctx->num_installed ) + is_installed = 0 == strcmp(fctx->installed[i++].name, + control->name); + + values[4] = BoolGetDatum(is_installed); + + tuple = heap_form_tuple(funcctx->tuple_desc, values, nulls); + SRF_RETURN_NEXT(funcctx, HeapTupleGetDatum(tuple)); + } + FreeDir(fctx->dir.dirdesc); + SRF_RETURN_DONE(funcctx); + } *** a/src/backend/commands/foreigncmds.c --- b/src/backend/commands/foreigncmds.c *************** *** 32,37 **** --- 32,38 ---- #include "utils/lsyscache.h" #include "utils/rel.h" #include "utils/syscache.h" + #include "commands/extension.h" /* *************** *** 411,416 **** CreateForeignDataWrapper(CreateFdwStmt *stmt) --- 412,423 ---- referenced.objectId = fdwvalidator; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* dependency on extension */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } } recordDependencyOnOwner(ForeignDataWrapperRelationId, fdwId, ownerId); *************** *** 696,701 **** CreateForeignServer(CreateForeignServerStmt *stmt) --- 703,714 ---- recordDependencyOnOwner(ForeignServerRelationId, srvId, ownerId); + /* dependency on extension */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } + heap_close(rel, NoLock); } *************** *** 967,972 **** CreateUserMapping(CreateUserMappingStmt *stmt) --- 980,991 ---- /* Record the mapped user dependency */ recordDependencyOnOwner(UserMappingRelationId, umId, useId); + /* dependency on extension */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } + heap_close(rel, NoLock); } *** a/src/backend/commands/functioncmds.c --- b/src/backend/commands/functioncmds.c *************** *** 61,67 **** #include "utils/rel.h" #include "utils/syscache.h" #include "utils/tqual.h" ! static void AlterFunctionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId); --- 61,67 ---- #include "utils/rel.h" #include "utils/syscache.h" #include "utils/tqual.h" ! #include "commands/extension.h" static void AlterFunctionOwner_internal(Relation rel, HeapTuple tup, Oid newOwnerId); *************** *** 1744,1749 **** CreateCast(CreateCastStmt *stmt) --- 1744,1754 ---- recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } + heap_freetuple(tuple); heap_close(relation, RowExclusiveLock); *** a/src/backend/commands/opclasscmds.c --- b/src/backend/commands/opclasscmds.c *************** *** 42,47 **** --- 42,48 ---- #include "utils/rel.h" #include "utils/syscache.h" #include "utils/tqual.h" + #include "commands/extension.h" /* *************** *** 306,311 **** CreateOpFamily(char *amname, char *opfname, Oid namespaceoid, Oid amoid) --- 307,318 ---- /* dependency on owner */ recordDependencyOnOwner(OperatorFamilyRelationId, opfamilyoid, GetUserId()); + /* dependency on extension */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } + heap_close(rel, RowExclusiveLock); return opfamilyoid; *************** *** 693,698 **** DefineOpClass(CreateOpClassStmt *stmt) --- 700,711 ---- /* dependency on owner */ recordDependencyOnOwner(OperatorClassRelationId, opclassoid, GetUserId()); + /* dependency on extension */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } + heap_close(rel, RowExclusiveLock); } *** a/src/backend/commands/proclang.c --- b/src/backend/commands/proclang.c *************** *** 36,41 **** --- 36,42 ---- #include "utils/rel.h" #include "utils/syscache.h" #include "utils/tqual.h" + #include "commands/extension.h" typedef struct *************** *** 425,430 **** create_proc_lang(const char *languageName, bool replace, --- 426,437 ---- recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + /* dependency on extension */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } + heap_close(rel, RowExclusiveLock); } *** a/src/backend/commands/tsearchcmds.c --- b/src/backend/commands/tsearchcmds.c *************** *** 46,51 **** --- 46,52 ---- #include "utils/rel.h" #include "utils/syscache.h" #include "utils/tqual.h" + #include "commands/extension.h" static void MakeConfigurationMapping(AlterTSConfigurationStmt *stmt, *************** *** 154,159 **** makeParserDependencies(HeapTuple tuple) --- 155,166 ---- referenced.objectId = prs->prsheadline; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + + /* dependency on extension */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } } /* *************** *** 423,428 **** makeDictionaryDependencies(HeapTuple tuple) --- 430,441 ---- referenced.objectId = dict->dicttemplate; referenced.objectSubId = 0; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); + + /* dependency on extension, if in create extension */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } } /* *************** *** 965,970 **** makeTSTemplateDependencies(HeapTuple tuple) --- 978,989 ---- referenced.objectId = tmpl->tmplinit; recordDependencyOn(&myself, &referenced, DEPENDENCY_NORMAL); } + + /* dependency on extension, if in create extension */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } } /* *************** *** 1289,1294 **** makeConfigurationDependencies(HeapTuple tuple, bool removeOld, --- 1308,1319 ---- /* Record 'em (this includes duplicate elimination) */ record_object_address_dependencies(&myself, addrs, DEPENDENCY_NORMAL); + /* dependencies on extension being created, if any */ + if (create_extension != NULL ) + { + recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL); + } + free_object_addresses(addrs); } *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** *** 3115,3120 **** _copyDiscardStmt(DiscardStmt *from) --- 3115,3143 ---- return newnode; } + static CreateExtensionStmt * + _copyCreateExtensionStmt(CreateExtensionStmt *from) + { + CreateExtensionStmt *newnode = makeNode(CreateExtensionStmt); + + COPY_STRING_FIELD(extname); + COPY_SCALAR_FIELD(user_data); + + return newnode; + } + + static DropExtensionStmt * + _copyDropExtensionStmt(DropExtensionStmt *from) + { + DropExtensionStmt *newnode = makeNode(DropExtensionStmt); + + COPY_STRING_FIELD(extname); + COPY_SCALAR_FIELD(missing_ok); + COPY_SCALAR_FIELD(behavior); + + return newnode; + } + static CreateTableSpaceStmt * _copyCreateTableSpaceStmt(CreateTableSpaceStmt *from) { *************** *** 4124,4129 **** copyObject(void *from) --- 4147,4158 ---- case T_DropTableSpaceStmt: retval = _copyDropTableSpaceStmt(from); break; + case T_CreateExtensionStmt: + retval = _copyCreateExtensionStmt(from); + break; + case T_DropExtensionStmt: + retval = _copyDropExtensionStmt(from); + break; case T_AlterTableSpaceOptionsStmt: retval = _copyAlterTableSpaceOptionsStmt(from); break; *** a/src/backend/nodes/equalfuncs.c --- b/src/backend/nodes/equalfuncs.c *************** *** 1590,1595 **** _equalDropTableSpaceStmt(DropTableSpaceStmt *a, DropTableSpaceStmt *b) --- 1590,1614 ---- } static bool + _equalCreateExtensionStmt(CreateExtensionStmt *a, CreateExtensionStmt *b) + { + COMPARE_STRING_FIELD(extname); + COMPARE_SCALAR_FIELD(user_data); + + return true; + } + + static bool + _equalDropExtensionStmt(DropExtensionStmt *a, DropExtensionStmt *b) + { + COMPARE_STRING_FIELD(extname); + COMPARE_SCALAR_FIELD(missing_ok); + COMPARE_SCALAR_FIELD(behavior); + + return true; + } + + static bool _equalAlterTableSpaceOptionsStmt(AlterTableSpaceOptionsStmt *a, AlterTableSpaceOptionsStmt *b) { *************** *** 2754,2759 **** equal(void *a, void *b) --- 2773,2784 ---- case T_DiscardStmt: retval = _equalDiscardStmt(a, b); break; + case T_CreateExtensionStmt: + retval = _equalCreateExtensionStmt(a, b); + break; + case T_DropExtensionStmt: + retval = _equalDropExtensionStmt(a, b); + break; case T_CreateTableSpaceStmt: retval = _equalCreateTableSpaceStmt(a, b); break; *** a/src/backend/parser/gram.y --- b/src/backend/parser/gram.y *************** *** 190,196 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ AlterDefaultPrivilegesStmt DefACLAction AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt ! CreateDomainStmt CreateGroupStmt CreateOpClassStmt CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateAssertStmt CreateTrigStmt --- 190,196 ---- AlterDefaultPrivilegesStmt DefACLAction AnalyzeStmt ClosePortalStmt ClusterStmt CommentStmt ConstraintsSetStmt CopyStmt CreateAsStmt CreateCastStmt ! CreateDomainStmt CreateExtensionStmt CreateGroupStmt CreateOpClassStmt CreateOpFamilyStmt AlterOpFamilyStmt CreatePLangStmt CreateSchemaStmt CreateSeqStmt CreateStmt CreateTableSpaceStmt CreateFdwStmt CreateForeignServerStmt CreateAssertStmt CreateTrigStmt *************** *** 198,204 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ 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 --- 198,204 ---- CreatedbStmt DeclareCursorStmt DefineStmt DeleteStmt DiscardStmt DoStmt DropGroupStmt DropOpClassStmt DropOpFamilyStmt DropPLangStmt DropStmt DropAssertStmt DropTrigStmt DropRuleStmt DropCastStmt DropRoleStmt ! DropUserStmt DropdbStmt DropTableSpaceStmt DropFdwStmt DropExtensionStmt DropForeignServerStmt DropUserMappingStmt ExplainStmt FetchStmt GrantStmt GrantRoleStmt IndexStmt InsertStmt ListenStmt LoadStmt LockStmt NotifyStmt ExplainableStmt PreparableStmt *************** *** 484,490 **** static RangeVar *makeRangeVarFromAnyName(List *names, int position, core_yyscan_ DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT ! EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTERNAL EXTRACT FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS --- 484,490 ---- DICTIONARY DISABLE_P DISCARD DISTINCT DO DOCUMENT_P DOMAIN_P DOUBLE_P DROP EACH ELSE ENABLE_P ENCODING ENCRYPTED END_P ENUM_P ESCAPE EXCEPT ! EXCLUDE EXCLUDING EXCLUSIVE EXECUTE EXISTS EXPLAIN EXTENSION EXTERNAL EXTRACT FALSE_P FAMILY FETCH FIRST_P FLOAT_P FOLLOWING FOR FORCE FOREIGN FORWARD FREEZE FROM FULL FUNCTION FUNCTIONS *************** *** 680,685 **** stmt : --- 680,686 ---- | CreateCastStmt | CreateConversionStmt | CreateDomainStmt + | CreateExtensionStmt | CreateFdwStmt | CreateForeignServerStmt | CreateFunctionStmt *************** *** 705,710 **** stmt : --- 706,712 ---- | DoStmt | DropAssertStmt | DropCastStmt + | DropExtensionStmt | DropFdwStmt | DropForeignServerStmt | DropGroupStmt *************** *** 3081,3086 **** opt_procedural: --- 3083,3129 ---- /***************************************************************************** * * QUERY: + * CREATE EXTENSION extension [ WITH [ NO ] DATA ] + * + *****************************************************************************/ + + CreateExtensionStmt: CREATE EXTENSION name opt_with_data + { + CreateExtensionStmt *n = makeNode(CreateExtensionStmt); + n->extname = $3; + n->user_data = $4; + $$ = (Node *) n; + } + ; + + /***************************************************************************** + * + * QUERY : + * DROP EXTENSION [ RESTRICT | CASCADE ] + * + ****************************************************************************/ + + DropExtensionStmt: DROP EXTENSION name opt_drop_behavior + { + DropExtensionStmt *n = makeNode(DropExtensionStmt); + n->extname = $3; + n->missing_ok = false; + n->behavior = $4; + $$ = (Node *) n; + } + | DROP EXTENSION IF_P EXISTS name opt_drop_behavior + { + DropExtensionStmt *n = makeNode(DropExtensionStmt); + n->extname = $5; + n->missing_ok = true; + n->behavior = $6; + $$ = (Node *) n; + } + ; + + /***************************************************************************** + * + * QUERY: * CREATE TABLESPACE tablespace LOCATION '/path/to/tablespace/' * *****************************************************************************/ *************** *** 4185,4191 **** opt_restart_seqs: * * COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW | * CONVERSION | LANGUAGE | OPERATOR CLASS | LARGE OBJECT | ! * CAST | COLUMN | SCHEMA | TABLESPACE | ROLE | * TEXT SEARCH PARSER | TEXT SEARCH DICTIONARY | * TEXT SEARCH TEMPLATE | * TEXT SEARCH CONFIGURATION ] | --- 4228,4234 ---- * * COMMENT ON [ [ DATABASE | DOMAIN | INDEX | SEQUENCE | TABLE | TYPE | VIEW | * CONVERSION | LANGUAGE | OPERATOR CLASS | LARGE OBJECT | ! * CAST | COLUMN | SCHEMA | TABLESPACE | EXTENSION | ROLE | * TEXT SEARCH PARSER | TEXT SEARCH DICTIONARY | * TEXT SEARCH TEMPLATE | * TEXT SEARCH CONFIGURATION ] | *************** *** 4364,4369 **** comment_type: --- 4407,4413 ---- | VIEW { $$ = OBJECT_VIEW; } | CONVERSION_P { $$ = OBJECT_CONVERSION; } | TABLESPACE { $$ = OBJECT_TABLESPACE; } + | EXTENSION { $$ = OBJECT_EXTENSION; } | ROLE { $$ = OBJECT_ROLE; } ; *** a/src/backend/tcop/utility.c --- b/src/backend/tcop/utility.c *************** *** 32,37 **** --- 32,38 ---- #include "commands/defrem.h" #include "commands/discard.h" #include "commands/explain.h" + #include "commands/extension.h" #include "commands/lockcmds.h" #include "commands/portalcmds.h" #include "commands/prepare.h" *************** *** 175,180 **** check_xact_readonly(Node *parsetree) --- 176,182 ---- case T_CreateConversionStmt: case T_CreatedbStmt: case T_CreateDomainStmt: + case T_CreateExtensionStmt: case T_CreateFunctionStmt: case T_CreateRoleStmt: case T_IndexStmt: *************** *** 194,199 **** check_xact_readonly(Node *parsetree) --- 196,202 ---- case T_DropCastStmt: case T_DropStmt: case T_DropdbStmt: + case T_DropExtensionStmt: case T_DropTableSpaceStmt: case T_RemoveFuncStmt: case T_DropRoleStmt: *************** *** 557,562 **** standard_ProcessUtility(Node *parsetree, --- 560,573 ---- } break; + case T_CreateExtensionStmt: + CreateExtension((CreateExtensionStmt *) parsetree); + break; + + case T_DropExtensionStmt: + DropExtension((DropExtensionStmt *) parsetree); + break; + case T_CreateTableSpaceStmt: PreventTransactionChain(isTopLevel, "CREATE TABLESPACE"); CreateTableSpace((CreateTableSpaceStmt *) parsetree); *************** *** 1498,1503 **** CreateCommandTag(Node *parsetree) --- 1509,1522 ---- tag = "CREATE TABLE"; break; + case T_CreateExtensionStmt: + tag = "CREATE EXTENSION"; + break; + + case T_DropExtensionStmt: + tag = "DROP EXTENSION"; + break; + case T_CreateTableSpaceStmt: tag = "CREATE TABLESPACE"; break; *************** *** 2292,2297 **** GetCommandLogLevel(Node *parsetree) --- 2311,2324 ---- lev = LOGSTMT_DDL; break; + case T_CreateExtensionStmt: + lev = LOGSTMT_DDL; + break; + + case T_DropExtensionStmt: + lev = LOGSTMT_DDL; + break; + case T_CreateTableSpaceStmt: lev = LOGSTMT_DDL; break; *** a/src/backend/utils/adt/genfile.c --- b/src/backend/utils/adt/genfile.c *************** *** 7,12 **** --- 7,13 ---- * Copyright (c) 2004-2010, PostgreSQL Global Development Group * * Author: Andreas Pflug + * Dimitri Fontaine * * IDENTIFICATION * src/backend/utils/adt/genfile.c *************** *** 21,41 **** #include #include "catalog/pg_type.h" #include "funcapi.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "postmaster/syslogger.h" #include "storage/fd.h" #include "utils/builtins.h" #include "utils/memutils.h" #include "utils/timestamp.h" - typedef struct - { - char *location; - DIR *dirdesc; - } directory_fctx; - /* * Convert a "text" filename argument to C string, and check it's allowable. --- 22,38 ---- #include #include "catalog/pg_type.h" + #include "executor/spi.h" #include "funcapi.h" #include "mb/pg_wchar.h" #include "miscadmin.h" #include "postmaster/syslogger.h" #include "storage/fd.h" #include "utils/builtins.h" + #include "utils/genfile.h" #include "utils/memutils.h" #include "utils/timestamp.h" /* * Convert a "text" filename argument to C string, and check it's allowable. *************** *** 264,266 **** pg_ls_dir(PG_FUNCTION_ARGS) --- 261,331 ---- SRF_RETURN_DONE(funcctx); } + + /* + * Read a file then execute the SQL commands it contains. + */ + Datum + pg_execute_from_file(PG_FUNCTION_ARGS) + { + text *filename_t = PG_GETARG_TEXT_P(0); + char *filename; + FILE *file; + int64 fsize = -1, nbytes; + struct stat fst; + char *query_string = NULL; + + if (!superuser()) + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + (errmsg("must be superuser to get file information")))); + + /* + * Only superuser can call pg_execute_from_file, and CREATE EXTENSION + * uses that too. Don't double check the PATH. Also note that + * extension's install files are not in $PGDATA but `pg_config + * --sharedir`. + */ + filename = text_to_cstring(filename_t); + + if (stat(filename, &fst) < 0) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not stat file \"%s\": %m", filename))); + + fsize = Int64GetDatum((int64) fst.st_size); + + if ((file = AllocateFile(filename, PG_BINARY_R)) == NULL) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not open file \"%s\" for reading: %m", + filename))); + + if (ferror(file)) + ereport(ERROR, + (errcode_for_file_access(), + errmsg("could not read file \"%s\": %m", filename))); + + query_string = (char *)palloc((fsize+1)*sizeof(char)); + memset(query_string, 0, fsize+1); + nbytes = fread(query_string, 1, (size_t) fsize, file); + pg_verifymbstr(query_string, nbytes, false); + FreeFile(file); + + /* + * We abuse some internal knowledge from spi.h here. As we don't know + * which queries are going to get executed, we don't know what to expect + * as an OK return code from SPI_execute(). We assume that + * SPI_OK_CONNECT, SPI_OK_FINISH and SPI_OK_FETCH are quite improbable, + * though, and the errors are negatives. So a valid return code is + * considered to be SPI_OK_UTILITY or anything from there. + */ + SPI_connect(); + if (SPI_execute(query_string, false, 0) < SPI_OK_UTILITY) + ereport(ERROR, + (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("File '%s' contains invalid query", filename))); + SPI_finish(); + + PG_RETURN_VOID(); + } *** a/src/backend/utils/init/postinit.c --- b/src/backend/utils/init/postinit.c *************** *** 337,342 **** CheckMyDatabase(const char *name, bool am_superuser) --- 337,345 ---- SetConfigOption("client_encoding", GetDatabaseEncodingName(), PGC_BACKEND, PGC_S_DEFAULT); + /* Add extension dependent entries to custom_variable_classes */ + ExtensionSetCVC(); + /* assign locale variables */ collate = NameStr(dbform->datcollate); ctype = NameStr(dbform->datctype); *** a/src/backend/utils/misc/Makefile --- b/src/backend/utils/misc/Makefile *************** *** 15,21 **** include $(top_builddir)/src/Makefile.global override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS) OBJS = guc.o help_config.o pg_rusage.o ps_status.o superuser.o tzparser.o \ ! rbtree.o # This location might depend on the installation directories. Therefore # we can't subsitute it into pg_config.h. --- 15,21 ---- override CPPFLAGS := -I. -I$(srcdir) $(CPPFLAGS) OBJS = guc.o help_config.o pg_rusage.o ps_status.o superuser.o tzparser.o \ ! rbtree.o cfparser.o # This location might depend on the installation directories. Therefore # we can't subsitute it into pg_config.h. *** /dev/null --- b/src/backend/utils/misc/cfparser.c *************** *** 0 **** --- 1,113 ---- + /*------------------------------------------------------------------------- + * + * cfparser.c + * Function for parsing RecoveryCommandFile lines + * + * This very simple file format (varible = value) is now also used in the + * extension control file format + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/utils/misc/cfparser.c + * + *------------------------------------------------------------------------- + */ + + #include "postgres.h" + #include "utils/cfparser.h" + + /* + * Parse one line from recovery.conf. 'cmdline' is the raw line from the + * file. If the line is parsed successfully, returns true, false indicates + * syntax error. On success, *key_p and *value_p are set to the parameter + * name and value on the line, respectively. If the line is an empty line, + * consisting entirely of whitespace and comments, function returns true + * and *keyp_p and *value_p are set to NULL. + * + * The pointers returned in *key_p and *value_p point to an internal buffer + * that is valid only until the next call of parseRecoveryCommandFile(). + */ + bool + cfParseOneLine(char *cmdline, char **key_p, char **value_p) + { + char *ptr; + char *bufp; + char *key; + char *value; + static char *buf = NULL; + + *key_p = *value_p = NULL; + + /* + * Allocate the buffer on first use. It's used to hold both the parameter + * name and value. + */ + if (buf == NULL) + buf = malloc(MAXPGPATH + 1); + bufp = buf; + + /* Skip any whitespace at the beginning of line */ + for (ptr = cmdline; *ptr; ptr++) + { + if (!isspace((unsigned char) *ptr)) + break; + } + /* Ignore empty lines */ + if (*ptr == '\0' || *ptr == '#') + return true; + + /* Read the parameter name */ + key = bufp; + while (*ptr && !isspace((unsigned char) *ptr) && + *ptr != '=' && *ptr != '\'') + *(bufp++) = *(ptr++); + *(bufp++) = '\0'; + + /* Skip to the beginning quote of the parameter value */ + ptr = strchr(ptr, '\''); + if (!ptr) + return false; + ptr++; + + /* Read the parameter value to *bufp. Collapse any '' escapes as we go. */ + value = bufp; + for (;;) + { + if (*ptr == '\'') + { + ptr++; + if (*ptr == '\'') + *(bufp++) = '\''; + else + { + /* end of parameter */ + *bufp = '\0'; + break; + } + } + else if (*ptr == '\0') + return false; /* unterminated quoted string */ + else + *(bufp++) = *ptr; + + ptr++; + } + *(bufp++) = '\0'; + + /* Check that there's no garbage after the value */ + while (*ptr) + { + if (*ptr == '#') + break; + if (!isspace((unsigned char) *ptr)) + return false; + ptr++; + } + + /* Success! */ + *key_p = key; + *value_p = value; + return true; + } *** a/src/backend/utils/misc/guc-file.l --- b/src/backend/utils/misc/guc-file.l *************** *** 206,221 **** ProcessConfigFile(GucContext context) * We have to consider three cases for custom variables: * * 1. The class name is not valid according to the (new) setting ! * of custom_variable_classes. If so, reject. We don't care ! * which side is at fault. */ if (!is_custom_class(item->name, sep - item->name, cvc)) { ! ereport(elevel, ! (errcode(ERRCODE_UNDEFINED_OBJECT), ! errmsg("unrecognized configuration parameter \"%s\"", ! item->name))); ! goto cleanup_list; } /* * 2. There is no GUC entry. If we called set_config_option then --- 206,228 ---- * We have to consider three cases for custom variables: * * 1. The class name is not valid according to the (new) setting ! * of custom_variable_classes. If so, we would reject, but to ! * support custom_variable_classes being PGC_SUSET, we maintain ! * a list of not-yet-valid items. ! * ! * This list will be processed at assign_custom_variable_classes ! * time: each time cvc changes, we have candidates to ! * consider. Such a time is for example ExtensionSetCVC() from ! * backend post init, where we read additional cvc in ! * pg_extension catalog. */ if (!is_custom_class(item->name, sep - item->name, cvc)) { ! if ( find_invalid_option(item->name, item->value, true, elevel) == NULL) ! goto cleanup_list; ! ! /* don't set_config_option() as it's an invalid item */ ! continue; } /* * 2. There is no GUC entry. If we called set_config_option then *************** *** 314,319 **** ProcessConfigFile(GucContext context) --- 321,330 ---- { char *pre_value = NULL; + /* this list contains invalid placeholders, skip them here */ + if (find_invalid_option(item->name, NULL, false, elevel) != NULL) + continue; + /* In SIGHUP cases in the postmaster, report changes */ if (context == PGC_SIGHUP && !IsUnderPostmaster) { *** a/src/backend/utils/misc/guc.c --- b/src/backend/utils/misc/guc.c *************** *** 162,169 **** static bool assign_session_replication_role(int newval, bool doit, GucSource source); static const char *show_num_temp_buffers(void); static bool assign_phony_autocommit(bool newval, bool doit, GucSource source); - static const char *assign_custom_variable_classes(const char *newval, bool doit, - GucSource source); static bool assign_debug_assertions(bool newval, bool doit, GucSource source); static bool assign_bonjour(bool newval, bool doit, GucSource source); static bool assign_ssl(bool newval, bool doit, GucSource source); --- 162,167 ---- *************** *** 427,433 **** static char *log_timezone_string; static char *timezone_abbreviations_string; static char *XactIsoLevel_string; static char *data_directory; - static char *custom_variable_classes; static int max_function_args; static int max_index_keys; static int max_identifier_length; --- 425,430 ---- *************** *** 442,447 **** static int effective_io_concurrency; --- 439,446 ---- char *role_string; char *session_authorization_string; + /* should be static, but commands/extension.c needs to get at these */ + char *custom_variable_classes; /* * Displayable names for context types (enum GucContext) *************** *** 468,473 **** const char *const GucSource_Names[] = --- 467,473 ---- /* PGC_S_DEFAULT */ "default", /* PGC_S_ENV_VAR */ "environment variable", /* PGC_S_FILE */ "configuration file", + /* PGC_S_EXTENSION */ "extension", /* PGC_S_ARGV */ "command line", /* PGC_S_DATABASE */ "database", /* PGC_S_USER */ "user", *************** *** 2602,2608 **** static struct config_string ConfigureNamesString[] = }, { ! {"custom_variable_classes", PGC_SIGHUP, CUSTOM_OPTIONS, gettext_noop("Sets the list of known custom variable classes."), NULL, GUC_LIST_INPUT | GUC_LIST_QUOTE --- 2602,2608 ---- }, { ! {"custom_variable_classes", PGC_SUSET, CUSTOM_OPTIONS, gettext_noop("Sets the list of known custom variable classes."), NULL, GUC_LIST_INPUT | GUC_LIST_QUOTE *************** *** 2916,2927 **** static const char *const map_old_guc_names[] = { --- 2916,2930 ---- * Actual lookup of variables is done through this single, sorted array. */ static struct config_generic **guc_variables; + static struct config_generic **guc_invalid_ph; /* Current number of variables contained in the vector */ static int num_guc_variables; + static int num_guc_invalid_ph = 0; /* Vector capacity */ static int size_guc_variables; + static int size_guc_invalid_ph = 0; static bool guc_dirty; /* TRUE if need to do commit/abort work */ *************** *** 3191,3211 **** build_guc_variables(void) size_guc_variables = size_vars; qsort((void *) guc_variables, num_guc_variables, sizeof(struct config_generic *), guc_var_compare); } /* * Add a new GUC variable to the list of known variables. The * list is expanded if needed. */ static bool ! add_guc_variable(struct config_generic * var, int elevel) { ! if (num_guc_variables + 1 >= size_guc_variables) { /* * Increase the vector by 25% */ ! int size_vars = size_guc_variables + size_guc_variables / 4; struct config_generic **guc_vars; if (size_vars == 0) --- 3194,3230 ---- size_guc_variables = size_vars; qsort((void *) guc_variables, num_guc_variables, sizeof(struct config_generic *), guc_var_compare); + + /* + * Also build guc_invalid_ph, but leave it empty of course + */ + num_guc_invalid_ph = 0; + size_guc_invalid_ph = 10; + guc_invalid_ph = (struct config_generic **) + guc_malloc(FATAL, size_guc_invalid_ph * sizeof(struct config_generic *)); } /* * Add a new GUC variable to the list of known variables. The * list is expanded if needed. + * + * When valid is true, we're dealing with guc_variables, when it's not we're + * dealing with currently invalid placeholders, maintained in guc_invalid_ph. */ static bool ! add_guc_variable(struct config_generic * var, int elevel, bool valid) { ! int num = valid ? num_guc_variables : num_guc_invalid_ph; ! int size = valid ? size_guc_variables : size_guc_invalid_ph; ! ! struct config_generic **gvars = valid ? guc_variables : guc_invalid_ph; ! ! if (num + 1 >= size) { /* * Increase the vector by 25% */ ! int size_vars = size + size / 4; struct config_generic **guc_vars; if (size_vars == 0) *************** *** 3217,3243 **** add_guc_variable(struct config_generic * var, int elevel) else { guc_vars = (struct config_generic **) ! guc_realloc(elevel, guc_variables, size_vars * sizeof(struct config_generic *)); } if (guc_vars == NULL) return false; /* out of memory */ ! guc_variables = guc_vars; ! size_guc_variables = size_vars; } ! guc_variables[num_guc_variables++] = var; ! qsort((void *) guc_variables, num_guc_variables, sizeof(struct config_generic *), guc_var_compare); return true; } /* ! * Create and add a placeholder variable. It's presumed to belong ! * to a valid custom variable class at this point. */ ! static struct config_generic * ! add_placeholder_variable(const char *name, int elevel) { size_t sz = sizeof(struct config_string) + sizeof(char *); struct config_string *var; --- 3236,3274 ---- else { guc_vars = (struct config_generic **) ! guc_realloc(elevel, gvars, size_vars * sizeof(struct config_generic *)); } if (guc_vars == NULL) return false; /* out of memory */ ! gvars = guc_vars; ! if (valid) ! size_guc_variables = size_vars; ! else ! size_guc_invalid_ph = size_vars; } ! gvars[num++] = var; ! if (valid) ! num_guc_variables = num; ! else ! num_guc_invalid_ph = num; ! ! qsort((void *) gvars, num, sizeof(struct config_generic *), guc_var_compare); + return true; } /* ! * Create and add a placeholder variable. It's presumed to belong to a valid ! * custom variable class at this point, unless valid is false. In that case ! * we add it to the guc_invalid_ph variable. ! * ! * We use that from src/backend/commands/extension.c too. */ ! struct config_generic * ! add_placeholder_variable(const char *name, int elevel, bool valid) { size_t sz = sizeof(struct config_string) + sizeof(char *); struct config_string *var; *************** *** 3269,3275 **** add_placeholder_variable(const char *name, int elevel) */ var->variable = (char **) (var + 1); ! if (!add_guc_variable((struct config_generic *) var, elevel)) { free((void *) gen->name); free(var); --- 3300,3306 ---- */ var->variable = (char **) (var + 1); ! if (!add_guc_variable((struct config_generic *) var, elevel, valid)) { free((void *) gen->name); free(var); *************** *** 3285,3291 **** add_placeholder_variable(const char *name, int elevel) * formatted the way that assign_custom_variable_classes does it, ie, * no whitespace. NULL is valid for custom_var_classes. */ ! static bool is_custom_class(const char *name, int dotPos, const char *custom_var_classes) { bool result = false; --- 3316,3322 ---- * formatted the way that assign_custom_variable_classes does it, ie, * no whitespace. NULL is valid for custom_var_classes. */ ! bool is_custom_class(const char *name, int dotPos, const char *custom_var_classes) { bool result = false; *************** *** 3362,3374 **** find_option(const char *name, bool create_placeholders, int elevel) if (dot != NULL && is_custom_class(name, dot - name, custom_variable_classes)) ! return add_placeholder_variable(name, elevel); } /* Unknown name */ return NULL; } /* * comparator for qsorting and bsearching guc_variables array --- 3393,3454 ---- if (dot != NULL && is_custom_class(name, dot - name, custom_variable_classes)) ! return add_placeholder_variable(name, elevel, true); } /* Unknown name */ return NULL; } + /* + * Same as find_option, but uses guc_invalid_ph rather than guc_variables: + * that allows to support custom_variable_classes as PGC_SIGHUP, as changes + * to it will be able to trigger a traversal of known invalid place holders. + * + * We copy find_option() so as to minimize changes in existing call sites + * that don't care at all with that subtelty. + */ + static struct config_generic * + find_invalid_option(const char *name, const char *value, + bool create_placeholders, int elevel) + { + const char **key = &name; + struct config_generic **res; + char *dot = strchr(name, GUC_QUALIFIER_SEPARATOR); + + Assert(name); + + /* + * We're only dealing with invalid placeholders here. + */ + if (dot == NULL) + return NULL; + + /* + * By equating const char ** with struct config_generic *, we are assuming + * the name field is first in config_generic. + */ + res = (struct config_generic **) bsearch((void *) &key, + (void *) guc_invalid_ph, + num_guc_invalid_ph, + sizeof(struct config_generic *), + guc_var_compare); + if (res) + return *res; + + if (create_placeholders) + { + char *newval = guc_strdup(elevel, value); + struct config_generic *gconf = + add_placeholder_variable(name, elevel, false); + struct config_string *conf = (struct config_string *) gconf; + + conf->boot_val = newval; + InitializeOneGUCOption(gconf); + return gconf; + } + return NULL; + } /* * comparator for qsorting and bsearching guc_variables array *************** *** 5899,5905 **** define_custom_variable(struct config_generic * variable) * make sure it's initialized to its default value. */ InitializeOneGUCOption(variable); ! add_guc_variable(variable, ERROR); return; } --- 5979,5985 ---- * make sure it's initialized to its default value. */ InitializeOneGUCOption(variable); ! add_guc_variable(variable, ERROR, true); return; } *************** *** 7701,7707 **** assign_phony_autocommit(bool newval, bool doit, GucSource source) return true; } ! static const char * assign_custom_variable_classes(const char *newval, bool doit, GucSource source) { /* --- 7781,7790 ---- return true; } ! /* ! * The function is exported so that extension.c is able to abuse it ! */ ! const char * assign_custom_variable_classes(const char *newval, bool doit, GucSource source) { /* *************** *** 7755,7760 **** assign_custom_variable_classes(const char *newval, bool doit, GucSource source) --- 7838,7878 ---- /* GUC wants the result malloc'd */ newval = guc_strdup(LOG, buf.data); + /* + * Now that the value changed, rescan the invalid GUCs we recorded while + * reading the configuration file, chances are that some of them are now + * perfectly fine. Assuming the list of invalid custom variables should + * be rather short, we don't bother cleaning it when a element of it + * becomes valid. + * + * Note that this happen at each backend postinit, scanning pg_extension + * custom classes to add them dynamically to what's in the main file. + */ + if (doit) + { + int i; + for (i = 0; i < num_guc_invalid_ph; i++) + { + struct config_generic *igconf = guc_invalid_ph[i]; + struct config_string *iconf = (struct config_string *) igconf; + const char *dot = strchr(igconf->name, GUC_QUALIFIER_SEPARATOR); + + if (is_custom_class(igconf->name, dot - igconf->name, newval)) + { + /* + * Make a valid placeholder out of the invalid we have. + */ + struct config_generic *gconf = + add_placeholder_variable(igconf->name, LOG, true); + struct config_string *conf = (struct config_string *) gconf; + char *newval = guc_strdup(LOG, iconf->boot_val); + + conf->boot_val = newval; + InitializeOneGUCOption(gconf); + } + } + } + pfree(buf.data); return newval; } *** a/src/bin/pg_dump/common.c --- b/src/bin/pg_dump/common.c *************** *** 78,83 **** static int strInArray(const char *pattern, char **arr, int arr_size); --- 78,84 ---- TableInfo * getSchemaData(int *numTablesPtr) { + ExtensionInfo *extinfo; NamespaceInfo *nsinfo; AggInfo *agginfo; InhInfo *inhinfo; *************** *** 94,99 **** getSchemaData(int *numTablesPtr) --- 95,101 ---- FdwInfo *fdwinfo; ForeignServerInfo *srvinfo; DefaultACLInfo *daclinfo; + int numExtensions; int numNamespaces; int numAggregates; int numInherits; *************** *** 112,117 **** getSchemaData(int *numTablesPtr) --- 114,123 ---- int numDefaultACLs; if (g_verbose) + write_msg(NULL, "reading extensions\n"); + extinfo = getExtensions(&numExtensions); + + if (g_verbose) write_msg(NULL, "reading schemas\n"); nsinfo = getNamespaces(&numNamespaces); *** a/src/bin/pg_dump/pg_dump.c --- b/src/bin/pg_dump/pg_dump.c *************** *** 157,162 **** static int findSecLabels(Archive *fout, Oid classoid, Oid objoid, --- 157,163 ---- SecLabelItem **items); static int collectSecLabels(Archive *fout, SecLabelItem **items); static void dumpDumpableObject(Archive *fout, DumpableObject *dobj); + static void dumpExtension(Archive *fout, ExtensionInfo *extinfo); static void dumpNamespace(Archive *fout, NamespaceInfo *nspinfo); static void dumpType(Archive *fout, TypeInfo *tyinfo); static void dumpBaseType(Archive *fout, TypeInfo *tyinfo); *************** *** 2376,2381 **** binary_upgrade_set_relfilenodes(PQExpBuffer upgrade_buffer, Oid pg_class_oid, --- 2377,2456 ---- } /* + * 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(int *numExtensions) + { + PGresult *res; + int ntups; + int i; + PQExpBuffer query; + ExtensionInfo *extinfo = NULL; + int i_tableoid; + int i_oid; + int i_extname; + int i_extversion; + int i_extcclass; + + /* + * Before 9.1, there are no extensions. + */ + if (g_fout->remoteVersion < 90100) + { + *numExtensions = 0; + return extinfo; + } + + query = createPQExpBuffer(); + + /* Make sure we are in proper schema */ + selectSourceSchema("pg_catalog"); + + /* + * we fetch all namespaces including system ones, so that every object we + * read in can be linked to a containing namespace. + */ + appendPQExpBuffer(query, + "SELECT tableoid, oid, extname, extversion, custom_class " + "FROM pg_extension"); + + res = PQexec(g_conn, query->data); + check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); + + ntups = PQntuples(res); + + extinfo = (ExtensionInfo *) malloc(ntups * sizeof(ExtensionInfo)); + + i_tableoid = PQfnumber(res, "tableoid"); + i_oid = PQfnumber(res, "oid"); + i_extname = PQfnumber(res, "extname"); + i_extversion = PQfnumber(res, "extversion"); + i_extcclass = PQfnumber(res, "custom_class"); + + for (i = 0; i < ntups; i++) + { + extinfo[i].dobj.objType = DO_EXTENSION; + extinfo[i].dobj.catId.tableoid = atooid(PQgetvalue(res, i, i_tableoid)); + extinfo[i].dobj.catId.oid = atooid(PQgetvalue(res, i, i_oid)); + AssignDumpId(&extinfo[i].dobj); + extinfo[i].dobj.name = strdup(PQgetvalue(res, i, i_extname)); + extinfo[i].extversion = strdup(PQgetvalue(res, i, i_extversion)); + extinfo[i].custom_class = strdup(PQgetvalue(res, i, i_extcclass)); + } + + PQclear(res); + destroyPQExpBuffer(query); + + *numExtensions = ntups; + return extinfo; + } + + /* * getNamespaces: * read all namespaces in the system catalogs and return them in the * NamespaceInfo* structure *************** *** 2439,2448 **** getNamespaces(int *numNamespaces) * we fetch all namespaces including system ones, so that every object we * read in can be linked to a containing namespace. */ ! appendPQExpBuffer(query, "SELECT tableoid, oid, nspname, " ! "(%s nspowner) AS rolname, " ! "nspacl FROM pg_namespace", ! username_subquery); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 2514,2570 ---- * we fetch all namespaces including system ones, so that every object we * read in can be linked to a containing namespace. */ ! if (g_fout->remoteVersion >= 90100) ! { ! /* ! * So we want the namespaces, but we want to filter out any ! * namespace created by an extension's script. That's unless the ! * user went over his head and created objects into the extension's ! * schema: we now want the schema not to be filtered out to avoid: ! * ! * pg_dump: schema with OID 77869 does not exist ! */ ! appendPQExpBuffer(query, "SELECT n.tableoid, n.oid, n.nspname, " ! "(%s nspowner) AS rolname, " ! "n.nspacl " ! " FROM pg_namespace n " ! " WHERE n.nspname != 'information_schema' " ! " AND CASE WHEN (SELECT count(*) " ! " FROM pg_depend " ! " WHERE refobjid = n.oid and deptype != 'p') > 0 " ! " THEN EXISTS( " ! "WITH RECURSIVE depends AS ( " ! " select n.oid as nsp, objid, refobjid, array[refobjid] as deps " ! " from pg_depend " ! " where refobjid = n.oid and deptype != 'p' " ! " UNION ALL " ! " select p.nsp, p.objid, d.refobjid, deps || d.refobjid " ! " from pg_depend d JOIN depends p ON d.objid = p.objid " ! " where d.deptype != 'p' and not d.refobjid = any(deps) " ! " and not (d.refclassid = 'pg_extension'::regclass and d.deptype = 'n') " ! ") " ! " SELECT nsp, objid, array_agg(distinct refobjid) " ! " FROM depends " ! "GROUP BY nsp, objid " ! " HAVING NOT array_agg(distinct refobjid) && array(select oid from pg_extension) " ! ") " ! " ELSE true " ! " END " ! "UNION ALL " ! "SELECT n.tableoid, n.oid, n.nspname, " ! "(%s nspowner) AS rolname, " ! "nspacl FROM pg_namespace n " ! "WHERE n.nspname = 'information_schema'", ! username_subquery, ! username_subquery); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, nspname, " ! "(%s nspowner) AS rolname, " ! "nspacl FROM pg_namespace", ! username_subquery); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 2506,2512 **** findNamespace(Oid nsoid, Oid objoid) if (nsoid == nsinfo->dobj.catId.oid) return nsinfo; } ! write_msg(NULL, "schema with OID %u does not exist\n", nsoid); exit_nicely(); } else --- 2628,2635 ---- if (nsoid == nsinfo->dobj.catId.oid) return nsinfo; } ! write_msg(NULL, "schema with OID %u does not exist, " ! "but is needed for object %u\n", nsoid, objoid); exit_nicely(); } else *************** *** 2559,2565 **** getTypes(int *numTypes) * we include even the built-in types because those may be used as array * elements by user-defined types * ! * we filter out the built-in types when we dump out the types * * same approach for undefined (shell) types and array types * --- 2682,2689 ---- * we include even the built-in types because those may be used as array * elements by user-defined types * ! * we filter out the built-in types when we dump out the types, and from ! * 9.1 we also filter out types that depend on an extension * * same approach for undefined (shell) types and array types * *************** *** 2574,2580 **** getTypes(int *numTypes) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 80300) { appendPQExpBuffer(query, "SELECT tableoid, oid, typname, " "typnamespace, " --- 2698,2723 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT t.tableoid, t.oid, t.typname, " ! "t.typnamespace, " ! "(%s typowner) AS rolname, " ! "t.typinput::oid AS typinput, " ! "t.typoutput::oid AS typoutput, t.typelem, t.typrelid, " ! "CASE WHEN t.typrelid = 0 THEN ' '::\"char\" " ! "ELSE (SELECT relkind FROM pg_class WHERE oid = t.typrelid) END AS typrelkind, " ! "t.typtype, t.typisdefined, " ! "t.typname[0] = '_' AND t.typelem != 0 AND " ! "(SELECT typarray FROM pg_type te WHERE oid = t.typelem) = t.oid AS isarray " ! "FROM pg_type t " ! "LEFT JOIN pg_depend d ON d.objid = t.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else if (g_fout->remoteVersion >= 80300) { appendPQExpBuffer(query, "SELECT tableoid, oid, typname, " "typnamespace, " *************** *** 2806,2812 **** getOperators(int *numOprs) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, oprname, " "oprnamespace, " --- 2949,2968 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT o.tableoid, o.oid, o.oprname, " ! "o.oprnamespace, " ! "(%s oprowner) AS rolname, " ! "o.oprcode::oid AS oprcode " ! "FROM pg_operator o " ! "LEFT JOIN pg_depend d ON d.objid = o.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, oprname, " "oprnamespace, " *************** *** 2985,2991 **** getOpclasses(int *numOpclasses) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, opcname, " "opcnamespace, " --- 3141,3159 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT o.tableoid, o.oid, o.opcname, " ! "o.opcnamespace, " ! "(%s opcowner) AS rolname " ! "FROM pg_opclass o " ! "LEFT JOIN pg_depend d ON d.objid = o.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, opcname, " "opcnamespace, " *************** *** 3091,3101 **** getOpfamilies(int *numOpfamilies) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, opfname, " ! "opfnamespace, " ! "(%s opfowner) AS rolname " ! "FROM pg_opfamily", ! username_subquery); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 3259,3284 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT o.tableoid, o.oid, o.opfname, " ! "o.opfnamespace, " ! "(%s opfowner) AS rolname " ! "FROM pg_opfamily o " ! "LEFT JOIN pg_depend d ON d.objid = o.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, opfname, " ! "opfnamespace, " ! "(%s opfowner) AS rolname " ! "FROM pg_opfamily", ! username_subquery); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 3169,3175 **** getAggregates(int *numAggs) /* find all user-defined aggregates */ ! if (g_fout->remoteVersion >= 80200) { appendPQExpBuffer(query, "SELECT tableoid, oid, proname AS aggname, " "pronamespace AS aggnamespace, " --- 3352,3374 ---- /* find all user-defined aggregates */ ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT a.tableoid, a.oid, a.proname AS aggname, " ! "a.pronamespace AS aggnamespace, " ! "a.pronargs, a.proargtypes, " ! "(%s proowner) AS rolname, " ! "a.proacl AS aggacl " ! "FROM pg_proc a " ! "LEFT JOIN pg_depend d ON d.objid = a.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL AND proisagg " ! "AND pronamespace != " ! "(SELECT oid FROM pg_namespace WHERE nspname = 'pg_catalog')", ! username_subquery); ! } ! else if (g_fout->remoteVersion >= 80200) { appendPQExpBuffer(query, "SELECT tableoid, oid, proname AS aggname, " "pronamespace AS aggnamespace, " *************** *** 3315,3321 **** getFuncs(int *numFuncs) /* find all user-defined funcs */ ! if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, proname, prolang, " --- 3514,3537 ---- /* find all user-defined funcs */ ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, ! "SELECT f.tableoid, f.oid, f.proname, f.prolang, " ! "f.pronargs, f.proargtypes, f.prorettype, f.proacl, " ! "f.pronamespace, " ! "(%s proowner) AS rolname " ! "FROM pg_proc f " ! "LEFT JOIN pg_depend d ON d.objid = f.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL AND NOT proisagg " ! "AND pronamespace != " ! "(SELECT oid FROM pg_namespace " ! "WHERE nspname = 'pg_catalog')", ! username_subquery); ! } ! else if (g_fout->remoteVersion >= 70300) { appendPQExpBuffer(query, "SELECT tableoid, oid, proname, prolang, " *************** *** 3477,3483 **** getTables(int *numTables) * we cannot correctly identify inherited columns, owned sequences, etc. */ ! if (g_fout->remoteVersion >= 90000) { /* * Left join to pick up dependency info linking sequences to their --- 3693,3736 ---- * we cannot correctly identify inherited columns, owned sequences, etc. */ ! if (g_fout->remoteVersion >= 90100) ! { ! /* ! * Left join to pick up dependency info linking sequences to their ! * owning column, if any (note this dependency is AUTO as of 8.2) ! */ ! appendPQExpBuffer(query, ! "SELECT c.tableoid, c.oid, c.relname, " ! "c.relacl, c.relkind, c.relnamespace, " ! "(%s c.relowner) AS rolname, " ! "c.relchecks, c.relhastriggers, " ! "c.relhasindex, c.relhasrules, c.relhasoids, " ! "c.relfrozenxid, " ! "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, " ! "d.refobjid AS owning_tab, " ! "d.refobjsubid AS owning_col, " ! "(SELECT spcname FROM pg_tablespace t WHERE t.oid = c.reltablespace) AS reltablespace, " ! "array_to_string(c.reloptions, ', ') AS reloptions, " ! "array_to_string(array(SELECT 'toast.' || x FROM unnest(tc.reloptions) x), ', ') AS toast_reloptions " ! "FROM pg_class c " ! "LEFT JOIN pg_depend d ON " ! "(c.relkind = '%c' AND " ! "d.classid = c.tableoid AND d.objid = c.oid AND " ! "d.objsubid = 0 AND " ! "d.refclassid = c.tableoid AND d.deptype = 'a') " ! "LEFT JOIN pg_class tc ON (c.reltoastrelid = tc.oid) " ! "LEFT JOIN pg_depend dx ON dx.objid = c.oid and dx.deptype = 'i' " ! "and dx.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON dx.refobjid = x.oid " ! "WHERE x.oid IS NULL " ! "AND c.relkind in ('%c', '%c', '%c', '%c') " ! "ORDER BY c.oid", ! username_subquery, ! RELKIND_SEQUENCE, ! RELKIND_RELATION, RELKIND_SEQUENCE, ! RELKIND_VIEW, RELKIND_COMPOSITE_TYPE); ! } ! else if (g_fout->remoteVersion >= 90000) { /* * Left join to pick up dependency info linking sequences to their *************** *** 4838,4844 **** getProcLangs(int *numProcLangs) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90000) { /* pg_language has a laninline column */ appendPQExpBuffer(query, "SELECT tableoid, oid, " --- 5091,5112 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! /* pg_language has a laninline column */ ! appendPQExpBuffer(query, "SELECT l.tableoid, l.oid, " ! "l.lanname, l.lanpltrusted, l.lanplcallfoid, " ! "l.laninline, l.lanvalidator, l.lanacl, " ! "(%s lanowner) AS lanowner " ! "FROM pg_language l " ! "LEFT JOIN pg_depend d ON d.objid = l.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL and lanispl " ! "ORDER BY oid", ! username_subquery); ! } ! else if (g_fout->remoteVersion >= 90000) { /* pg_language has a laninline column */ appendPQExpBuffer(query, "SELECT tableoid, oid, " *************** *** 4992,4998 **** getCasts(int *numCasts) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 80400) { appendPQExpBuffer(query, "SELECT tableoid, oid, " "castsource, casttarget, castfunc, castcontext, " --- 5260,5278 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT c.tableoid, c.oid, " ! "c.castsource, c.casttarget, c.castfunc, c.castcontext, " ! "c.castmethod " ! "FROM pg_cast c " ! "LEFT JOIN pg_depend d ON d.objid = c.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL " ! "ORDER BY 3,4"); ! } ! else if (g_fout->remoteVersion >= 80400) { appendPQExpBuffer(query, "SELECT tableoid, oid, " "castsource, casttarget, castfunc, castcontext, " *************** *** 5586,5595 **** getTSParsers(int *numTSParsers) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, prsname, prsnamespace, " ! "prsstart::oid, prstoken::oid, " ! "prsend::oid, prsheadline::oid, prslextype::oid " ! "FROM pg_ts_parser"); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 5866,5889 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT p.tableoid, p.oid, p.prsname, p.prsnamespace, " ! "p.prsstart::oid, p.prstoken::oid, " ! "p.prsend::oid, p.prsheadline::oid, p.prslextype::oid " ! "FROM pg_ts_parser p " ! "LEFT JOIN pg_depend d ON d.objid = p.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL"); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, prsname, prsnamespace, " ! "prsstart::oid, prstoken::oid, " ! "prsend::oid, prsheadline::oid, prslextype::oid " ! "FROM pg_ts_parser"); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 5668,5678 **** getTSDictionaries(int *numTSDicts) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, dictname, " ! "dictnamespace, (%s dictowner) AS rolname, " ! "dicttemplate, dictinitoption " ! "FROM pg_ts_dict", ! username_subquery); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 5962,5987 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT tsd.tableoid, tsd.oid, tsd.dictname, " ! "tsd.dictnamespace, (%s dictowner) AS rolname, " ! "tsd.dicttemplate, tsd.dictinitoption " ! "FROM pg_ts_dict tsd " ! "LEFT JOIN pg_depend d ON d.objid = tsd.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, dictname, " ! "dictnamespace, (%s dictowner) AS rolname, " ! "dicttemplate, dictinitoption " ! "FROM pg_ts_dict", ! username_subquery); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 5749,5757 **** getTSTemplates(int *numTSTemplates) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, tmplname, " ! "tmplnamespace, tmplinit::oid, tmpllexize::oid " ! "FROM pg_ts_template"); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 6058,6079 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT t.tableoid, t.oid, t.tmplname, " ! "t.tmplnamespace, t.tmplinit::oid, t.tmpllexize::oid " ! "FROM pg_ts_template t " ! "LEFT JOIN pg_depend d ON d.objid = t.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL"); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, tmplname, " ! "tmplnamespace, tmplinit::oid, tmpllexize::oid " ! "FROM pg_ts_template"); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 5823,5832 **** getTSConfigurations(int *numTSConfigs) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, cfgname, " ! "cfgnamespace, (%s cfgowner) AS rolname, cfgparser " ! "FROM pg_ts_config", ! username_subquery); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 6145,6168 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT c.tableoid, c.oid, c.cfgname, " ! "c.cfgnamespace, (%s cfgowner) AS rolname, c.cfgparser " ! "FROM pg_ts_config c " ! "LEFT JOIN pg_depend d ON d.objid = c.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, cfgname, " ! "cfgnamespace, (%s cfgowner) AS rolname, cfgparser " ! "FROM pg_ts_config", ! username_subquery); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 5899,5911 **** getForeignDataWrappers(int *numForeignDataWrappers) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, " ! "(%s fdwowner) AS rolname, fdwvalidator::pg_catalog.regproc, fdwacl," ! "array_to_string(ARRAY(" ! " SELECT option_name || ' ' || quote_literal(option_value) " ! " FROM pg_options_to_table(fdwoptions)), ', ') AS fdwoptions " ! "FROM pg_foreign_data_wrapper", ! username_subquery); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 6235,6264 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT fdw.tableoid, fdw.oid, fdw.fdwname, " ! "(%s fdwowner) AS rolname, fdw.fdwvalidator::pg_catalog.regproc, fdwacl," ! "array_to_string(ARRAY(" ! " SELECT option_name || ' ' || quote_literal(option_value) " ! " FROM pg_options_to_table(fdw.fdwoptions)), ', ') AS fdwoptions " ! "FROM pg_foreign_data_wrapper fdw " ! "LEFT JOIN pg_depend d ON d.objid = fdw.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, fdwname, " ! "(%s fdwowner) AS rolname, fdwvalidator::pg_catalog.regproc, fdwacl," ! "array_to_string(ARRAY(" ! " SELECT option_name || ' ' || quote_literal(option_value) " ! " FROM pg_options_to_table(fdwoptions)), ', ') AS fdwoptions " ! "FROM pg_foreign_data_wrapper", ! username_subquery); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 5983,5996 **** getForeignServers(int *numForeignServers) /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! appendPQExpBuffer(query, "SELECT tableoid, oid, srvname, " ! "(%s srvowner) AS rolname, " ! "srvfdw, srvtype, srvversion, srvacl," ! "array_to_string(ARRAY(" ! " SELECT option_name || ' ' || quote_literal(option_value) " ! " FROM pg_options_to_table(srvoptions)), ', ') AS srvoptions " ! "FROM pg_foreign_server", ! username_subquery); res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); --- 6336,6367 ---- /* Make sure we are in proper schema */ selectSourceSchema("pg_catalog"); ! if (g_fout->remoteVersion >= 90100) ! { ! appendPQExpBuffer(query, "SELECT fs.tableoid, fs.oid, fs.srvname, " ! "(%s srvowner) AS rolname, " ! "fs.srvfdw, fs.srvtype, fs.srvversion, fs.srvacl," ! "array_to_string(ARRAY(" ! " SELECT option_name || ' ' || quote_literal(option_value) " ! " FROM pg_options_to_table(fs.srvoptions)), ', ') AS srvoptions " ! "FROM pg_foreign_server fs " ! "LEFT JOIN pg_depend d ON d.objid = fs.oid and d.deptype = 'i' " ! "and d.refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass " ! "LEFT JOIN pg_extension x ON d.refobjid = x.oid " ! "WHERE x.oid IS NULL", ! username_subquery); ! } ! else ! { ! appendPQExpBuffer(query, "SELECT tableoid, oid, srvname, " ! "(%s srvowner) AS rolname, " ! "srvfdw, srvtype, srvversion, srvacl," ! "array_to_string(ARRAY(" ! " SELECT option_name || ' ' || quote_literal(option_value) " ! " FROM pg_options_to_table(srvoptions)), ', ') AS srvoptions " ! "FROM pg_foreign_server", ! username_subquery); ! } res = PQexec(g_conn, query->data); check_sql_result(res, g_conn, query->data, PGRES_TUPLES_OK); *************** *** 6467,6472 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj) --- 6838,6846 ---- { switch (dobj->objType) { + case DO_EXTENSION: + dumpExtension(fout, (ExtensionInfo *) dobj); + break; case DO_NAMESPACE: dumpNamespace(fout, (NamespaceInfo *) dobj); break; *************** *** 6563,6568 **** dumpDumpableObject(Archive *fout, DumpableObject *dobj) --- 6937,6988 ---- } /* + * dumpExtension + * writes out to fout the queries to recreate an extension + */ + static void + dumpExtension(Archive *fout, ExtensionInfo *extinfo) + { + PQExpBuffer q; + PQExpBuffer delq; + char *qextname; + + /* Skip if not to be dumped */ + if (!extinfo->dobj.dump || dataOnly) + return; + + q = createPQExpBuffer(); + delq = createPQExpBuffer(); + + qextname = strdup(fmtId(extinfo->dobj.name)); + + appendPQExpBuffer(delq, "DROP EXTENSION %s;\n", qextname); + + appendPQExpBuffer(q, "CREATE EXTENSION %s WITH NO DATA;\n", qextname); + + ArchiveEntry(fout, extinfo->dobj.catId, extinfo->dobj.dumpId, + extinfo->dobj.name, + NULL, NULL, + "", + false, "EXTENSION", SECTION_PRE_DATA, + q->data, delq->data, NULL, + extinfo->dobj.dependencies, extinfo->dobj.nDeps, + NULL, NULL); + + /* Dump Extension Comments */ + resetPQExpBuffer(q); + appendPQExpBuffer(q, "EXTENSION %s", qextname); + dumpComment(fout, q->data, + NULL, "", + extinfo->dobj.catId, 0, extinfo->dobj.dumpId); + + free(qextname); + + destroyPQExpBuffer(q); + destroyPQExpBuffer(delq); + } + + /* * dumpNamespace * writes out to fout the queries to recreate a user-defined namespace */ *** a/src/bin/pg_dump/pg_dump.h --- b/src/bin/pg_dump/pg_dump.h *************** *** 88,93 **** typedef struct SimpleStringList --- 88,94 ---- typedef enum { /* When modifying this enum, update priority tables in pg_dump_sort.c! */ + DO_EXTENSION, DO_NAMESPACE, DO_TYPE, DO_SHELL_TYPE, *************** *** 132,137 **** typedef struct _dumpableObject --- 133,146 ---- int allocDeps; /* allocated size of dependencies[] */ } DumpableObject; + typedef struct _extensionInfo + { + DumpableObject dobj; + char *extname; + char *extversion; + char *custom_class; + } ExtensionInfo; + typedef struct _namespaceInfo { DumpableObject dobj; *************** *** 511,516 **** extern void sortDumpableObjectsByTypeOid(DumpableObject **objs, int numObjs); --- 520,526 ---- /* * version specific routines */ + extern ExtensionInfo *getExtensions(int *numExtensions); extern NamespaceInfo *getNamespaces(int *numNamespaces); extern TypeInfo *getTypes(int *numTypes); extern FuncInfo *getFuncs(int *numFuncs); *** a/src/bin/pg_dump/pg_dump_sort.c --- b/src/bin/pg_dump/pg_dump_sort.c *************** *** 28,33 **** static const char *modulename = gettext_noop("sorter"); --- 28,34 ---- */ static const int oldObjectTypePriority[] = { + 1, /* DO_EXTENSION */ 1, /* DO_NAMESPACE */ 2, /* DO_TYPE */ 2, /* DO_SHELL_TYPE */ *************** *** 65,70 **** static const int oldObjectTypePriority[] = --- 66,72 ---- */ static const int newObjectTypePriority[] = { + 1, /* DO_EXTENSION */ 1, /* DO_NAMESPACE */ 3, /* DO_TYPE */ 3, /* DO_SHELL_TYPE */ *************** *** 1018,1023 **** describeDumpableObject(DumpableObject *obj, char *buf, int bufsize) --- 1020,1030 ---- { switch (obj->objType) { + case DO_EXTENSION: + snprintf(buf, bufsize, + "EXTENSION %s (ID %d OID %u)", + obj->name, obj->dumpId, obj->catId.oid); + return; case DO_NAMESPACE: snprintf(buf, bufsize, "SCHEMA %s (ID %d OID %u)", *** a/src/bin/psql/command.c --- b/src/bin/psql/command.c *************** *** 488,493 **** exec_command(const char *cmd, --- 488,496 ---- break; } break; + case 'x': /* Extensions */ + success = listExtensions(pattern, show_verbose); + break; default: status = PSQL_CMD_UNKNOWN; } *** a/src/bin/psql/describe.c --- b/src/bin/psql/describe.c *************** *** 2735,2740 **** listSchemas(const char *pattern, bool verbose) --- 2735,2787 ---- return true; } + /* + * \dx + * + * Describes extensions + */ + bool + listExtensions(const char *pattern, bool verbose) + { + PQExpBufferData buf; + PGresult *res; + printQueryOpt myopt = pset.popt; + + initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, + "SELECT x.name AS \"%s\", x.version AS \"%s\", " + "x.custom_variable_classes AS \"%s\", " + "x.comment as \"%s\" " + " FROM pg_catalog.pg_extensions x ", + gettext_noop("Name"), + gettext_noop("Version"), + gettext_noop("Custom Variable Classes"), + gettext_noop("Description")); + + if (!verbose) + appendPQExpBuffer(&buf, "WHERE installed "); + + processSQLNamePattern(pset.db, &buf, pattern, !verbose, false, + NULL, "x.extname", NULL, + NULL); + + appendPQExpBuffer(&buf, "ORDER BY 1;"); + + res = PSQLexec(buf.data, false); + termPQExpBuffer(&buf); + if (!res) + return false; + + myopt.nullPrint = NULL; + myopt.title = _("List of extensions"); + myopt.translate_header = true; + + printQuery(res, &myopt, pset.queryFout, pset.logfile); + + PQclear(res); + return true; + } + /* * \dFp *** a/src/bin/psql/describe.h --- b/src/bin/psql/describe.h *************** *** 54,59 **** extern bool listTSDictionaries(const char *pattern, bool verbose); --- 54,62 ---- /* \dFt */ extern bool listTSTemplates(const char *pattern, bool verbose); + /* \dx */ + bool listExtensions(const char *pattern, bool verbose); + /* \l */ extern bool listAllDbs(bool verbose); *** a/src/bin/psql/help.c --- b/src/bin/psql/help.c *************** *** 219,224 **** slashUsage(unsigned short int pager) --- 219,225 ---- fprintf(output, _(" \\dT[S+] [PATTERN] list data types\n")); fprintf(output, _(" \\du[+] [PATTERN] list roles (users)\n")); fprintf(output, _(" \\dv[S+] [PATTERN] list views\n")); + fprintf(output, _(" \\dx [PATTERN] list extensions\n")); fprintf(output, _(" \\l[+] list all databases\n")); fprintf(output, _(" \\sf[+] FUNCNAME show a function's definition\n")); fprintf(output, _(" \\z [PATTERN] same as \\dp\n")); *** a/src/include/catalog/dependency.h --- b/src/include/catalog/dependency.h *************** *** 138,143 **** typedef enum ObjectClass --- 138,144 ---- OCLASS_FOREIGN_SERVER, /* pg_foreign_server */ OCLASS_USER_MAPPING, /* pg_user_mapping */ OCLASS_DEFACL, /* pg_default_acl */ + OCLASS_EXTENSION, /* pg_extension */ MAX_OCLASS /* MUST BE LAST */ } ObjectClass; *************** *** 197,202 **** extern long changeDependencyFor(Oid classId, Oid objectId, --- 198,207 ---- Oid refClassId, Oid oldRefObjectId, Oid newRefObjectId); + extern long changeDependencyTypeFor(Oid objid, + Oid refclassId, Oid refobjectId, int refobjsubid, + DependencyType expected, DependencyType newtype); + extern bool sequenceIsOwned(Oid seqId, Oid *tableId, int32 *colId); extern void markSequenceUnowned(Oid seqId); *** a/src/include/catalog/indexing.h --- b/src/include/catalog/indexing.h *************** *** 284,289 **** DECLARE_UNIQUE_INDEX(pg_db_role_setting_databaseid_rol_index, 2965, on pg_db_rol --- 284,295 ---- DECLARE_UNIQUE_INDEX(pg_seclabel_object_index, 3597, on pg_seclabel using btree(objoid oid_ops, classoid oid_ops, objsubid int4_ops, provider text_ops)); #define SecLabelObjectIndexId 3597 + DECLARE_UNIQUE_INDEX(pg_extension_oid_index, 3695, on pg_extension using btree(oid oid_ops)); + #define ExtensionOidIndexId 3695 + + DECLARE_UNIQUE_INDEX(pg_extension_name_index, 3697, on pg_extension using btree(extname name_ops)); + #define ExtensionNameIndexId 3697 + /* last step of initialization script: build the indexes declared above */ BUILD_INDICES *** /dev/null --- b/src/include/catalog/pg_extension.h *************** *** 0 **** --- 1,61 ---- + /*------------------------------------------------------------------------- + * + * pg_extension.h + * definition of the system "extension" relation (pg_extension) + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/catalog/pg_extension.h + * + * NOTES + * the genbki.pl script reads this file and generates .bki + * information from the DATA() statements. + * + *------------------------------------------------------------------------- + */ + #ifndef PG_EXTENSION_H + #define PG_EXTENSION_H + + #include "catalog/genbki.h" + + /* ---------------- + * pg_extension definition. cpp turns this into + * typedef struct FormData_pg_extension + * ---------------- + */ + #define ExtensionRelationId 3696 + + CATALOG(pg_extension,3696) + { + NameData extname; /* name of the extension */ + text extversion; /* version "name" of the extension */ + text custom_class; /* custom class used by the extension */ + } FormData_pg_extension; + + /* ---------------- + * Form_pg_extension corresponds to a pointer to a tuple with + * the format of pg_extension relation. + * ---------------- + */ + typedef FormData_pg_extension *Form_pg_extension; + + /* ---------------- + * compiler constants for pg_extension + * ---------------- + */ + + #define Natts_pg_extension 3 + #define Anum_pg_extension_extname 1 + #define Anum_pg_extension_extversion 2 + #define Anum_pg_extension_custom_class 3 + + /* ---------------- + * initial contents of pg_extension + * ---------------- + */ + + /* btree + DATA(insert ("PostgreSQL" PG_VERSION _null_)); + */ + #endif /* PG_EXTENSION_H */ *** a/src/include/catalog/pg_proc.h --- b/src/include/catalog/pg_proc.h *************** *** 3386,3399 **** DESCR("reload configuration files"); DATA(insert OID = 2622 ( pg_rotate_logfile PGNSP PGUID 12 1 0 0 f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ )); DESCR("rotate log file"); ! DATA(insert OID = 2623 ( pg_stat_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2249 "25" "{25,20,1184,1184,1184,1184,16}" "{i,o,o,o,o,o,o}" "{filename,size,access,modification,change,creation,isdir}" _null_ pg_stat_file _null_ _null_ _null_ )); DESCR("return file information"); ! DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ )); DESCR("read text from a file"); ! DATA(insert OID = 2625 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ )); DESCR("list all files in a directory"); ! DATA(insert OID = 2626 ( pg_sleep PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2278 "701" _null_ _null_ _null_ _null_ pg_sleep _null_ _null_ _null_ )); DESCR("sleep for the specified time in seconds"); DATA(insert OID = 2971 ( text PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "16" _null_ _null_ _null_ _null_ booltext _null_ _null_ _null_ )); DESCR("convert boolean to text"); --- 3386,3401 ---- DATA(insert OID = 2622 ( pg_rotate_logfile PGNSP PGUID 12 1 0 0 f f f t f v 0 0 16 "" _null_ _null_ _null_ _null_ pg_rotate_logfile _null_ _null_ _null_ )); DESCR("rotate log file"); ! DATA(insert OID = 2623 ( pg_stat_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2249 "25" "{25,20,1184,1184,1184,1184,16}" "{i,o,o,o,o,o,o}" "{filename,size,access,modification,change,creation,isdir}" _null_ pg_stat_file _null_ _null_ _null_ )); DESCR("return file information"); ! DATA(insert OID = 2624 ( pg_read_file PGNSP PGUID 12 1 0 0 f f f t f v 3 0 25 "25 20 20" _null_ _null_ _null_ _null_ pg_read_file _null_ _null_ _null_ )); DESCR("read text from a file"); ! DATA(insert OID = 2625 ( pg_ls_dir PGNSP PGUID 12 1 1000 0 f f f t t v 1 0 25 "25" _null_ _null_ _null_ _null_ pg_ls_dir _null_ _null_ _null_ )); DESCR("list all files in a directory"); ! DATA(insert OID = 2626 ( pg_sleep PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2278 "701" _null_ _null_ _null_ _null_ pg_sleep _null_ _null_ _null_ )); DESCR("sleep for the specified time in seconds"); + DATA(insert OID = 3627 ( pg_execute_from_file PGNSP PGUID 12 1 0 0 f f f t f v 1 0 2278 "25" _null_ _null_ _null_ _null_ pg_execute_from_file _null_ _null_ _null_ )); + DESCR("execute queries read from a file"); DATA(insert OID = 2971 ( text PGNSP PGUID 12 1 0 0 f f f t f i 1 0 25 "16" _null_ _null_ _null_ _null_ booltext _null_ _null_ _null_ )); DESCR("convert boolean to text"); *************** *** 4850,4855 **** DESCR("fetch the last row value"); --- 4852,4864 ---- DATA(insert OID = 3114 ( nth_value PGNSP PGUID 12 1 0 0 f t f t f i 2 0 2283 "2283 23" _null_ _null_ _null_ _null_ window_nth_value _null_ _null_ _null_ )); DESCR("fetch the Nth row value"); + /* Extensions */ + DATA(insert OID = 3995 ( pg_extensions PGNSP PGUID 12 1 100 0 f f f t t s 0 0 2249 "" "{19,25,25,25,16}" "{o,o,o,o,o}" "{name,version,custom_variable_classes,comment,installed}" _null_ pg_extensions _null_ _null_ _null_ )); + DESCR("flag an extension's object to be part of pg_dump"); + DATA(insert OID = 3996 ( pg_extension_flag_dump PGNSP PGUID 12 1 0 0 f f f t f v 1 0 16 "26" _null_ _null_ _null_ _null_ pg_extension_flag_dump _null_ _null_ _null_ )); + DESCR("flag an extension's object to be part of pg_dump"); + DATA(insert OID = 3997 ( pg_extension_with_user_data PGNSP PGUID 12 1 0 0 f f f t f s 0 0 16 "" _null_ _null_ _null_ _null_ pg_extension_with_user_data _null_ _null_ _null_ )); + DESCR("return true if the extension install script should create user data"); /* * Symbolic values for provolatile column: these indicate whether the result *** a/src/include/catalog/toasting.h --- b/src/include/catalog/toasting.h *************** *** 43,48 **** extern void BootstrapToastTable(char *relName, --- 43,49 ---- DECLARE_TOAST(pg_attrdef, 2830, 2831); DECLARE_TOAST(pg_constraint, 2832, 2833); DECLARE_TOAST(pg_description, 2834, 2835); + DECLARE_TOAST(pg_extension, 3698, 3699); DECLARE_TOAST(pg_proc, 2836, 2837); DECLARE_TOAST(pg_rewrite, 2838, 2839); DECLARE_TOAST(pg_seclabel, 3598, 3599); *** /dev/null --- b/src/include/commands/extension.h *************** *** 0 **** --- 1,68 ---- + /*------------------------------------------------------------------------- + * + * extension.h + * Extension management commands (create/drop extension). + * + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/commands/extension.h + * + *------------------------------------------------------------------------- + */ + #ifndef EXTENSION_H + #define EXTENSION_H + + #include "catalog/objectaddress.h" + #include "utils/genfile.h" + + + /* + * create_extension is only set when running a CREATE EXTENSION command, it + * allows to register the (INTERNAL) dependencies between the pg_extension + * row and the SQL objects created by its installation script. + * + * For that to work out, all CREATE commands have been modified so that they + * will inquire about create_extension and call recordDependencyOn() when + * it's set. + */ + extern ObjectAddress *create_extension; + bool create_extension_with_user_data; + + typedef struct ExtensionControlFile + { + char *name; /* name of the extension */ + char *version; /* version "name" of the extension */ + char *custom_class; /* custom class used by the extension */ + char *comment; /* the control file can also contain a comment */ + } ExtensionControlFile; + + typedef struct extension_fctx + { + directory_fctx dir; + int num_installed; + ExtensionControlFile *installed; + } extension_fctx; + + char *get_extension_control_basepath(void); + char *get_extension_install_filename(const char *extname); + char *get_extension_control_filename(const char *extname); + char *get_extension_control_fullname(const char *filename); + char * extension_parse_control_line(const char *content, ExtensionControlFile *control); + bool add_extension_custom_variable_classes(const char *extname, + const char *ext_custom_classes, + bool verbose); + ExtensionControlFile *read_extension_control_file(const char *filename, + bool init_gucs); + + extern void ExtensionSetCVC(void); + extern void CreateExtension(CreateExtensionStmt *stmt); + extern void DropExtension(DropExtensionStmt *stmt); + + Oid get_extension_oid(const char *extname, bool missing_ok); + char * get_extension_name(Oid ext_oid); + char * RemoveExtensionById(Oid extId); + + + #endif /* EXTENSION_H */ *** a/src/include/nodes/nodes.h --- b/src/include/nodes/nodes.h *************** *** 351,356 **** typedef enum NodeTag --- 351,358 ---- T_DropUserMappingStmt, T_AlterTableSpaceOptionsStmt, T_SecLabelStmt, + T_CreateExtensionStmt, + T_DropExtensionStmt, /* * TAGS FOR PARSE TREE NODES (parsenodes.h) *** a/src/include/nodes/parsenodes.h --- b/src/include/nodes/parsenodes.h *************** *** 1056,1061 **** typedef enum ObjectType --- 1056,1062 ---- OBJECT_CONVERSION, OBJECT_DATABASE, OBJECT_DOMAIN, + OBJECT_EXTENSION, OBJECT_FDW, OBJECT_FOREIGN_SERVER, OBJECT_FUNCTION, *************** *** 1482,1487 **** typedef struct Constraint --- 1483,1508 ---- } Constraint; /* ---------------------- + * Create/Drop Extension Statements + * ---------------------- + */ + + typedef struct CreateExtensionStmt + { + NodeTag type; + char *extname; + bool user_data; + } CreateExtensionStmt; + + typedef struct DropExtensionStmt + { + NodeTag type; + char *extname; + bool missing_ok; /* skip error if missing? */ + DropBehavior behavior; /* drop behavior - cascade/restrict */ + } DropExtensionStmt; + + /* ---------------------- * Create/Drop Table Space Statements * ---------------------- */ *** a/src/include/parser/kwlist.h --- b/src/include/parser/kwlist.h *************** *** 150,155 **** PG_KEYWORD("exclusive", EXCLUSIVE, UNRESERVED_KEYWORD) --- 150,156 ---- PG_KEYWORD("execute", EXECUTE, UNRESERVED_KEYWORD) PG_KEYWORD("exists", EXISTS, COL_NAME_KEYWORD) PG_KEYWORD("explain", EXPLAIN, UNRESERVED_KEYWORD) + PG_KEYWORD("extension", EXTENSION, UNRESERVED_KEYWORD) PG_KEYWORD("external", EXTERNAL, UNRESERVED_KEYWORD) PG_KEYWORD("extract", EXTRACT, COL_NAME_KEYWORD) PG_KEYWORD("false", FALSE_P, RESERVED_KEYWORD) *** a/src/include/utils/builtins.h --- b/src/include/utils/builtins.h *************** *** 442,447 **** extern Datum pg_relation_filepath(PG_FUNCTION_ARGS); --- 442,448 ---- extern Datum pg_stat_file(PG_FUNCTION_ARGS); extern Datum pg_read_file(PG_FUNCTION_ARGS); extern Datum pg_ls_dir(PG_FUNCTION_ARGS); + extern Datum pg_execute_from_file(PG_FUNCTION_ARGS); /* misc.c */ extern Datum current_database(PG_FUNCTION_ARGS); *************** *** 1054,1057 **** extern Datum pg_prepared_statement(PG_FUNCTION_ARGS); --- 1055,1063 ---- /* utils/mmgr/portalmem.c */ extern Datum pg_cursor(PG_FUNCTION_ARGS); + /* commands/extension.c */ + extern Datum pg_extensions(PG_FUNCTION_ARGS); + extern Datum pg_extension_with_user_data(PG_FUNCTION_ARGS); + extern Datum pg_extension_flag_dump(PG_FUNCTION_ARGS); + #endif /* BUILTINS_H */ *** /dev/null --- b/src/include/utils/cfparser.h *************** *** 0 **** --- 1,18 ---- + /*------------------------------------------------------------------------- + * + * cfparser.h + * Function for parsing RecoveryCommandFile lines + * + * Portions Copyright (c) 1996-2010, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/utils/cfparser.h + * + *------------------------------------------------------------------------- + */ + #ifndef CFPARSER_H + #define CFPARSER_H + + bool cfParseOneLine(char *cmdline, char **key_p, char **value_p); + + #endif /* CFPARSER_H */ *** /dev/null --- b/src/include/utils/genfile.h *************** *** 0 **** --- 1,22 ---- + /*-------------------------------------------------------------------- + * genfile.h + * + * External declarations pertaining to backend/utils/misc/genfile.c + * + * Copyright (c) 2000-2010, PostgreSQL Global Development Group + * + * src/include/utils/genfile.h + *-------------------------------------------------------------------- + */ + #ifndef GENFILE_H + #define GENFILE_H + + #include + + typedef struct + { + char *location; + DIR *dirdesc; + } directory_fctx; + + #endif /* GENFILE_H */ *** a/src/include/utils/guc.h --- b/src/include/utils/guc.h *************** *** 85,90 **** typedef enum --- 85,91 ---- PGC_S_DEFAULT, /* wired-in default */ PGC_S_ENV_VAR, /* postmaster environment variable */ PGC_S_FILE, /* postgresql.conf */ + PGC_S_EXTENSION, /* extension control file */ PGC_S_ARGV, /* postmaster command line */ PGC_S_DATABASE, /* per-database setting */ PGC_S_USER, /* per-user setting */ *************** *** 315,318 **** extern const char *assign_search_path(const char *newval, --- 316,329 ---- extern bool assign_xlog_sync_method(int newval, bool doit, GucSource source); + /* + * The following function is in guc.c but also used from extension.c + */ + char *custom_variable_classes; + extern const char *assign_custom_variable_classes(const char *newval, bool doit, + GucSource source); + bool is_custom_class(const char *name, + int dotPos, const char *custom_var_classes); + struct config_generic *add_placeholder_variable(const char *name, int elevel, bool valid); + #endif /* GUC_H */ *** a/src/makefiles/pgxs.mk --- b/src/makefiles/pgxs.mk *************** *** 93,102 **** include $(top_srcdir)/src/Makefile.shlib all: all-lib endif # MODULE_big ! install: all installdirs ! ifneq (,$(DATA)$(DATA_built)) ! @for file in $(addprefix $(srcdir)/, $(DATA)) $(DATA_built); do \ echo "$(INSTALL_DATA) $$file '$(DESTDIR)$(datadir)/$(datamoduledir)'"; \ $(INSTALL_DATA) $$file '$(DESTDIR)$(datadir)/$(datamoduledir)'; \ done --- 93,126 ---- all: all-lib endif # MODULE_big + # create extension support + ifndef CONTROL + ifndef EXTENSION + ifdef DATA_built + EXTENSION = $(basename $(notdir $(firstword $(DATA_built)))) + else ifdef DATA + EXTENSION = $(basename $(notdir $(firstword $(DATA)))) + endif # DATA + endif # EXTENSION + ifndef EXTVERSION + EXTVERSION = $(MAJORVERSION) + endif + ifdef EXTENSION + CONTROL = $(EXTENSION).control + endif + endif ! $(CONTROL): ! # create .control to keep track that we created the control file ! test ! -f $@ && touch .control ! test -f .control && (echo "name = '$(EXTENSION)'"; echo "version = '$(EXTVERSION)'") > $@ ! ifdef EXTCOMMENT ! test -f .control && echo "comment = '$(EXTCOMMENT)'" >> $@ ! endif ! ! install: all installdirs $(CONTROL) ! ifneq (,$(DATA)$(DATA_built)$(CONTROL)) ! @for file in $(addprefix $(srcdir)/, $(DATA)) $(DATA_built) $(CONTROL); do \ echo "$(INSTALL_DATA) $$file '$(DESTDIR)$(datadir)/$(datamoduledir)'"; \ $(INSTALL_DATA) $$file '$(DESTDIR)$(datadir)/$(datamoduledir)'; \ done *************** *** 221,226 **** ifeq ($(PORTNAME), win) --- 245,251 ---- rm -f regress.def endif endif # REGRESS + if [ -f .control ]; then rm -f .control $(CONTROL); fi ifdef MODULE_big clean: clean-lib