Extensions, this time with a patch
Hi,
Please find attached a WIP patch for extension's pg_dump support in
PostgreSQL, following design decisions that we've outlined earlier at
this year's and last year's PGCon developer meetings.
What's in the patch?
An extension is a new SQL object with a catalog and two commands to
manage them (reserved to superuser):
CREATE EXTENSION <extension> ;
DROP EXTENSION [IF EXISTS] <extension> [ RESTRICT | CASCADE ];
The first command (create) will parse the "control" file from a fixed
place (`pg_control --sharedir`/contrib/<extension>.control) and insert
an entry in the pg_extension catalog. That gives us an Oid which we can
use in pg_depend. Once we have it, the command will execute the SQL
script at same/place/<extension>.sql and recordDependencyOn lots of
objects created here (not all of them, because it does not appear
necessary to).
The drop command will happily remove the extension's object and all its
"direct" dependencies. That's what's been installed by the script. If
there exists some object not created by the script and that depends on
the extension, CASCADE is needed (e.g. create table foo(kv hstore);).
With that tracking in place, pg_dump is now able to issue a single
command per extension, the CREATE command. All dependent objects are
filtered out of the dump in the pg_dump queries. There's a nasty corner
case here with schema, see pg_dump support.
Rough support for a new \dx command in psql is implemented, too.
PGXS has been adapted so that it produces automatically the control file
should it be missing, using $(MAJORVERSION) as the contrib's
version. That means that right after installing contrib, it's possible
to 'create extension <any of them>' automatically (thanks to a 2 lines
file).
Open Items :
- cfparser
To parse the control/registry file, I've piggybacked on the recovery
configuration file parsing. The function to parse each line was not
exported from xlog, I've made a new src/backend/utils/misc/cfparser.c
file and placed parseRecoveryCommandFileLine() in there.
Please find attached a separate patch implementing just that, in case
you want to review/apply that on its own. The main patch do contains
the change too.
- User Documentation. Where in the manual do I write it?
- Naming of the "control" file, <extension>.{control,registry,install}
Currently, inspired by debian/control, the file is called .control,
but maybe we want to choose something that the user can guess about
the purpose before reading the fine manual. I don't like .install
because that's not what it is. .registry?
- Handling of custom_variable_classes
The attached patch has a column to store the information but makes no
use whatsoever of it, the goal would be to append classes from the
extension's registry file and possibly setup the default values
there.
As I don't think this part has been agreed before, I send the patch
without the code for that, even if I suspect it would be a rather
short addition, and very worthwile too.
- User data tables
An extension can install plain relations, and that's fine. The
problem is when the data in there are changed after installing.
Because the name of the game here is to exclude the table from the
dumps, of course the data will not be in there.
The solution would be to offer extension's author a way to 'flag'
some tables as worthy of dumping, I think marking the dependency as
DEPENDENCY_NORMAL rather then DEPENDENCY_INTERNAL will do the trick:
SELECT pg_extension_flag_dump(oid);
- Extension Upgrading
Should this be done by means of 'create extension' or some other
command, like 'alter extension foo upgrade'? The command would run
the SQL script again, which would be responsible for any steps the
extension author might find necessary to run.
- Bugs to fix
There's at least one where drop extension leaves things behind,
although it uses performDeletion(). The dependencies are fine enough
so that the leftover objects are not part of the dump done before to
drop, though. I didn't investigate it at all, this mail is in the
"discuss early" tradition of the project.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
extension.v0.patchtext/x-patchDownload
*** 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 ----
*** 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 */
};
***************
*** 1148,1153 **** doDeletion(const ObjectAddress *object)
--- 1151,1160 ----
RemoveUserMappingById(object->objectId);
break;
+ case OCLASS_EXTENSION:
+ RemoveExtensionById(object->objectId);
+ break;
+
case OCLASS_DEFACL:
RemoveDefaultACLById(object->objectId);
break;
***************
*** 2082,2087 **** getObjectClass(const ObjectAddress *object)
--- 2089,2098 ----
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)
--- 2574,2591 ----
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"
***************
*** 1139,1145 **** heap_create_with_catalog(const char *relname,
/*
* Make a dependency link to force the relation to be deleted if its
* namespace is. Also make a dependency link to its owner, as well as
! * dependencies for any roles mentioned in the default ACL.
*
* For composite types, these dependencies are tracked for the pg_type
* entry, so we needn't record them here. Likewise, TOAST tables don't
--- 1140,1147 ----
/*
* Make a dependency link to force the relation to be deleted if its
* namespace is. Also make a dependency link to its owner, as well as
! * dependencies for any roles mentioned in the default ACL. Record a
! * dependancy to the extension we're part of if any.
*
* For composite types, these dependencies are tracked for the pg_type
* entry, so we needn't record them here. Likewise, TOAST tables don't
***************
*** 1186,1191 **** heap_create_with_catalog(const char *relname,
--- 1188,1198 ----
0, NULL,
nnewmembers, newmembers);
}
+
+ if (create_extension != NULL )
+ {
+ recordDependencyOn(&myself, create_extension, DEPENDENCY_INTERNAL);
+ }
}
/*
*** 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_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/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,456 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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
+ * dependant 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`/<extension>.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 <unistd.h>
+ #include <dirent.h>
+ #include <sys/types.h>
+ #include <sys/stat.h>
+
+ #include "nodes/parsenodes.h"
+ #include "miscadmin.h"
+ #include "catalog/pg_extension.h"
+ #include "commands/extension.h"
+ #include "storage/fd.h"
+ #include "utils/builtins.h"
+ #include "utils/cfparser.h"
+ #include "utils/rel.h"
+ #include "access/heapam.h"
+ #include "catalog/indexing.h"
+ #include "catalog/dependency.h"
+ #include "utils/memutils.h"
+ #include "utils/syscache.h"
+ #include "utils/tqual.h"
+ #include "utils/builtins.h"
+ #include "utils/fmgroids.h"
+ #include "access/sysattr.h"
+
+ /*
+ * See commands/extension.h for details.
+ */
+ ObjectAddress *create_extension = NULL;
+
+ 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;
+ }
+
+ /*
+ * 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.
+ */
+ ExtensionControlFile *
+ read_extension_control_file(const char *filename)
+ {
+ int64 fsize = -1;
+ struct stat fst;
+ FILE *file;
+ char line[MAXPGPATH];
+ bool syntaxError = false;
+ ExtensionControlFile *control =
+ (ExtensionControlFile *)palloc(sizeof(ExtensionControlFile));
+
+ /*
+ * 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;
+
+ if (!parseRecoveryCommandFileLine(line, &tok1, &tok2))
+ {
+ syntaxError = true;
+ break;
+ }
+ if (tok1 == NULL)
+ continue;
+
+ if (strcmp(tok1, "name") == 0)
+ {
+ control->name = pstrdup(tok2);
+ }
+ if (strcmp(tok1, "version") == 0)
+ {
+ control->version = pstrdup(tok2);
+ }
+ if (strcmp(tok1, "custom_class") == 0)
+ {
+ control->custom_class = pstrdup(tok2);
+ }
+ }
+
+ if( control->name == NULL )
+ ereport(FATAL,
+ (errmsg("Missing a name parameter in file: %s", filename)));
+
+ if (syntaxError)
+ ereport(FATAL,
+ (errmsg("syntax error in extension control file: %s", line),
+ errhint("Lines should have the format parameter = 'value'.")));
+
+ FreeFile(file);
+ return control;
+ }
+
+ /*
+ * 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));
+
+ /*
+ * 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);
+
+ /*
+ * Set the global create_extension so that any object created in the
+ * script has a dependancy recorded towards the extension here.
+ */
+ create_extension = (ObjectAddress *)palloc(sizeof(ObjectAddress));
+ create_extension->classId = ExtensionRelationId;
+ create_extension->objectId = extensionoid;
+ create_extension->objectSubId = 0;
+
+ elog(NOTICE,
+ "Installing extension '%s' from '%s'",
+ stmt->extname,
+ get_extension_install_filename(stmt->extname));
+
+ filename = cstring_to_text(get_extension_install_filename(stmt->extname));
+ DirectFunctionCall1(pg_execute_from_file, PointerGetDatum(filename));
+
+ /* reset the current_extension dependancy 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;
+ }
*** 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
***************
*** 3087,3092 **** _copyDiscardStmt(DiscardStmt *from)
--- 3087,3114 ----
return newnode;
}
+ static CreateExtensionStmt *
+ _copyCreateExtensionStmt(CreateExtensionStmt *from)
+ {
+ CreateExtensionStmt *newnode = makeNode(CreateExtensionStmt);
+
+ COPY_STRING_FIELD(extname);
+
+ 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)
{
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 1587,1592 **** _equalDropTableSpaceStmt(DropTableSpaceStmt *a, DropTableSpaceStmt *b)
--- 1587,1610 ----
}
static bool
+ _equalCreateExtensionStmt(CreateExtensionStmt *a, CreateExtensionStmt *b)
+ {
+ COMPARE_STRING_FIELD(extname);
+
+ 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)
{
*** 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,3128 ----
/*****************************************************************************
*
* QUERY:
+ * CREATE EXTENSION extension
+ *
+ *****************************************************************************/
+
+ CreateExtensionStmt: CREATE EXTENSION name
+ {
+ CreateExtensionStmt *n = makeNode(CreateExtensionStmt);
+ n->extname = $3;
+ $$ = (Node *) n;
+ }
+ ;
+
+ /*****************************************************************************
+ *
+ * QUERY :
+ * DROP EXTENSION <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/'
*
*****************************************************************************/
***************
*** 4184,4190 **** 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 ] <objname> |
--- 4226,4232 ----
*
* 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 ] <objname> |
***************
*** 4363,4368 **** comment_type:
--- 4405,4411 ----
| 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 <pgadmin@pse-consulting.de>
+ * Dimitri Fontaine <dimitri@2ndQuadrant.fr>
*
* IDENTIFICATION
* src/backend/utils/adt/genfile.c
***************
*** 30,35 ****
--- 31,46 ----
#include "utils/memutils.h"
#include "utils/timestamp.h"
+ #include "tcop/pquery.h"
+ #include "tcop/tcopprot.h"
+ #include "tcop/utility.h"
+ #include "access/transam.h"
+ #include "access/xact.h"
+ #include "utils/resowner.h"
+ #include "utils/snapmgr.h"
+ #include "parser/analyze.h"
+ #include "access/printtup.h"
+
typedef struct
{
char *location;
***************
*** 264,266 **** pg_ls_dir(PG_FUNCTION_ARGS)
--- 275,459 ----
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;
+
+ CommandDest dest = DestNone;
+ MemoryContext oldcontext;
+ List *parsetree_list;
+ ListCell *parsetree_item;
+ bool save_log_statement_stats = log_statement_stats;
+ bool was_logged = false;
+ bool isTopLevel;
+ char msec_str[32];
+
+ 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);
+
+ /*
+ elog(NOTICE, "pg_execute_from_file('%s') read %d/%d bytes:", filename, nbytes, fsize);
+ elog(NOTICE, "%s", query_string);
+ */
+
+ /*
+ * Code pasted from postgres.c:exec_simple_query, main differences are:
+ * - don't override unnamed portal, name it after filename instead
+ * - don't start nor finish a transaction
+ * - don't set stats or tracing markers
+ */
+ oldcontext = MemoryContextSwitchTo(MessageContext);
+ parsetree_list = pg_parse_query(query_string);
+ MemoryContextSwitchTo(oldcontext);
+
+ isTopLevel = false;
+
+ foreach(parsetree_item, parsetree_list)
+ {
+ Node *parsetree = (Node *) lfirst(parsetree_item);
+ bool snapshot_set = false;
+ const char *commandTag;
+ char completionTag[COMPLETION_TAG_BUFSIZE];
+ List *querytree_list,
+ *plantree_list;
+ Portal portal;
+ DestReceiver *receiver;
+ int16 format = 0; /* TEXT */
+
+ commandTag = CreateCommandTag(parsetree);
+
+ /* If we got a cancel signal in parsing or prior command, quit */
+ CHECK_FOR_INTERRUPTS();
+
+ /*
+ * Set up a snapshot if parse analysis/planning will need one.
+ */
+ if (analyze_requires_snapshot(parsetree))
+ {
+ PushActiveSnapshot(GetTransactionSnapshot());
+ snapshot_set = true;
+ }
+
+ /*
+ * OK to analyze, rewrite, and plan this query.
+ *
+ * Switch to appropriate context for constructing querytrees (again,
+ * these must outlive the execution context).
+ */
+ oldcontext = MemoryContextSwitchTo(MessageContext);
+
+ querytree_list = pg_analyze_and_rewrite(parsetree, query_string,
+ NULL, 0);
+
+ plantree_list = pg_plan_queries(querytree_list, 0, NULL);
+
+ /* Done with the snapshot used for parsing/planning */
+ if (snapshot_set)
+ PopActiveSnapshot();
+
+ /* If we got a cancel signal in analysis or planning, quit */
+ CHECK_FOR_INTERRUPTS();
+
+ /*
+ * Create a portal to run the query or queries in. Name if after the
+ * given filename. If there already is one, silently drop it.
+ */
+ portal = CreatePortal(filename, true, true);
+ /* Don't display the portal in pg_cursors */
+ portal->visible = false;
+
+ /*
+ * We don't have to copy anything into the portal, because everything
+ * we are passing here is in MessageContext, which will outlive the
+ * portal anyway.
+ */
+ PortalDefineQuery(portal,
+ NULL,
+ query_string,
+ commandTag,
+ plantree_list,
+ NULL);
+
+ /*
+ * Start the portal. No parameters here.
+ */
+ PortalStart(portal, NULL, InvalidSnapshot);
+ PortalSetResultFormat(portal, 1, &format);
+
+ /*
+ * Now we can create the destination receiver object.
+ */
+ receiver = CreateDestReceiver(dest);
+ if (dest == DestRemote)
+ SetRemoteDestReceiverParams(receiver, portal);
+
+ /*
+ * Switch back to transaction context for execution.
+ */
+ MemoryContextSwitchTo(oldcontext);
+
+ /*
+ * Run the portal to completion, and then drop it (and the receiver).
+ */
+ (void) PortalRun(portal,
+ FETCH_ALL,
+ isTopLevel,
+ receiver,
+ receiver,
+ completionTag);
+
+ (*receiver->rDestroy) (receiver);
+
+ PortalDrop(portal, false);
+
+ if (!IsA(parsetree, TransactionStmt) && lnext(parsetree_item) != NULL)
+ {
+ CommandCounterIncrement();
+ }
+ }
+ PG_RETURN_VOID();
+ }
*** 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,112 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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"
+
+ /*
+ * 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
+ 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;
+ }
*** 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,2569 ----
* 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) "
! ") "
! " 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);
***************
*** 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
*
--- 2680,2687 ----
* 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, "
--- 2696,2721 ----
/* 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, "
--- 2947,2966 ----
/* 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, "
--- 3139,3157 ----
/* 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);
--- 3257,3282 ----
/* 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, "
--- 3350,3372 ----
/* 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, "
--- 3512,3535 ----
/* 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
--- 3691,3734 ----
* 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, "
--- 5089,5110 ----
/* 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, "
--- 5258,5276 ----
/* 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);
--- 5864,5887 ----
/* 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);
--- 5960,5985 ----
/* 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);
--- 6056,6077 ----
/* 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);
--- 6143,6166 ----
/* 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);
--- 6233,6262 ----
/* 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);
--- 6334,6365 ----
/* 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)
--- 6836,6844 ----
{
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)
--- 6935,6986 ----
}
/*
+ * 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;\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
***************
*** 2720,2725 **** listSchemas(const char *pattern, bool verbose)
--- 2720,2770 ----
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.extname AS \"%s\", x.extversion AS \"%s\", "
+ "x.custom_class AS \"%s\", d.description as \"%s\" "
+ " FROM pg_catalog.pg_extension x \n"
+ " LEFT JOIN pg_description d on x.oid = d.objoid "
+ " and d.classoid = 3696",
+ gettext_noop("Name"),
+ gettext_noop("Version"),
+ gettext_noop("Custom Class"),
+ gettext_noop("Description"));
+
+ processSQLNamePattern(pset.db, &buf, pattern, false, 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;
*** 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");
*** 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,49 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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"
+
+ /*
+ * create_extension is only set when running a CREATE EXTENSION command, it
+ * allows to register the (INTERNAL) dependancies 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;
+
+ 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 */
+ } ExtensionControlFile;
+
+ char *get_extension_install_filename(const char *extname);
+ char *get_extension_control_filename(const char *extname);
+ char * extension_parse_control_line(const char *content, ExtensionControlFile *control);
+ ExtensionControlFile *read_extension_control_file(const char *filename);
+ 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
***************
*** 348,353 **** typedef enum NodeTag
--- 348,355 ----
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
***************
*** 1053,1058 **** typedef enum ObjectType
--- 1053,1059 ----
OBJECT_CONVERSION,
OBJECT_DATABASE,
OBJECT_DOMAIN,
+ OBJECT_EXTENSION,
OBJECT_FDW,
OBJECT_FOREIGN_SERVER,
OBJECT_FUNCTION,
***************
*** 1479,1484 **** typedef struct Constraint
--- 1480,1504 ----
} Constraint;
/* ----------------------
+ * Create/Drop Extension Statements
+ * ----------------------
+ */
+
+ typedef struct CreateExtensionStmt
+ {
+ NodeTag type;
+ char *extname;
+ } 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);
*** /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 parseRecoveryCommandFileLine(char *cmdline, char **key_p, char **value_p);
+
+ #endif /* CFPARSER_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,116 ----
all: all-lib
endif # MODULE_big
+ # create extension support
+ ifndef CONTROL
+ ifdef MODULE_big
+ CONTROL = $(MODULE_big).control
+ EXTENSION = $(MODULE_big)
+ else
+ CONTROL = $(MODULES).control
+ EXTENSION = $(MODULES)
+ endif
+ endif
! $(CONTROL):
! touch .control
! test ! -f $@ && echo "name = '$(EXTENSION)' \nversion = '$(MAJORVERSION)'" > $@
!
! 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)
--- 235,241 ----
rm -f regress.def
endif
endif # REGRESS
+ if [ -f .control ]; then rm -f .control $(CONTROL); fi
ifdef MODULE_big
clean: clean-lib
cfparser.v0.patchtext/x-patchDownload
*** 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 ----
*** 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,112 ----
+ /*-------------------------------------------------------------------------
+ *
+ * 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"
+
+ /*
+ * 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
+ 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;
+ }
*** /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 parseRecoveryCommandFileLine(char *cmdline, char **key_p, char **value_p);
+
+ #endif /* CFPARSER_H */
On Tue, Oct 12, 2010 at 08:57:09PM +0200, Dimitri Fontaine wrote:
Hi,
Please find attached a WIP patch for extension's pg_dump support in
PostgreSQL, following design decisions that we've outlined earlier at
this year's and last year's PGCon developer meetings.What's in the patch?
An extension is a new SQL object with a catalog and two commands to
manage them (reserved to superuser):CREATE EXTENSION <extension>�;
DROP EXTENSION [IF EXISTS] <extension> [ RESTRICT | CASCADE ];
Kudos!
- User Documentation. Where in the manual do I write it?
Parts belong in Server Administration, others in Server Programming.
- Extension Upgrading
Should this be done by means of 'create extension' or some other
command, like 'alter extension foo upgrade'? The command would
run the SQL script again, which would be responsible for any
steps the extension author might find necessary to run.
As people will want to up- or downgrade extensions to a particular
version, this should probably be something more like ALTER EXTENSION
... SET VERSION [version number | LATEST | PREVIOUS ]... or something
like that.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Excerpts from David Fetter's message of mié oct 13 11:27:56 -0300 2010:
On Tue, Oct 12, 2010 at 08:57:09PM +0200, Dimitri Fontaine wrote:
- Extension Upgrading
Should this be done by means of 'create extension' or some other
command, like 'alter extension foo upgrade'? The command would
run the SQL script again, which would be responsible for any
steps the extension author might find necessary to run.As people will want to up- or downgrade extensions to a particular
version, this should probably be something more like ALTER EXTENSION
... SET VERSION [version number | LATEST | PREVIOUS ]... or something
like that.
Does this mean that the control file should contain a version number in
the filename? Otherwise, I don't see how you'd have more than one
version of the control file.
Also, if upgrading is necessary, there will need to be one "upgrade"
control file that says how to upgrade from version N to N+1.
I don't think we should really support the downgrade case. It has the
potential to get too messy -- and for what gain?
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Alvaro Herrera <alvherre@commandprompt.com> writes:
Also, if upgrading is necessary, there will need to be one "upgrade"
control file that says how to upgrade from version N to N+1.
I don't think we should really support the downgrade case. It has the
potential to get too messy -- and for what gain?
I think we could leave that to the extension author to decide. Basically,
what is needed is a script file that says how to replace version M by
version N. If the author cares to supply a script for going from
M to M-1, it's no extra logic in the extension manager to be able to
apply that one. In many cases it wouldn't be very practical to do,
but then you just don't offer the script.
regards, tom lane
On Wed, Oct 13, 2010 at 11:36:02AM -0300, Alvaro Herrera wrote:
Excerpts from David Fetter's message of mi� oct 13 11:27:56 -0300 2010:
On Tue, Oct 12, 2010 at 08:57:09PM +0200, Dimitri Fontaine wrote:
- Extension Upgrading
Should this be done by means of 'create extension' or some other
command, like 'alter extension foo upgrade'? The command would
run the SQL script again, which would be responsible for any
steps the extension author might find necessary to run.As people will want to up- or downgrade extensions to a particular
version, this should probably be something more like ALTER EXTENSION
... SET VERSION [version number | LATEST | PREVIOUS ]... or something
like that.Does this mean that the control file should contain a version number in
the filename? Otherwise, I don't see how you'd have more than one
version of the control file.
Excellent idea!
Also, if upgrading is necessary, there will need to be one "upgrade"
control file that says how to upgrade from version N to N+1.
This, too, is an excellent idea.
I don't think we should really support the downgrade case. It has
the potential to get too messy -- and for what gain?
I think there should be something extension authors should be able to
provide for the downgrade case, given that an upgrade could cause a
system-wide failure.
Unfortunately, the extensions not provided with a downgrade option are
the ones likeliest to need it. Authors who write and test downgrade
code are much less likely to have their upgrades cause such failures
in the first place, but there's not much to do about that.
Cheers,
David.
--
David Fetter <david@fetter.org> http://fetter.org/
Phone: +1 415 235 3778 AIM: dfetter666 Yahoo!: dfetter
Skype: davidfetter XMPP: david.fetter@gmail.com
iCal: webcal://www.tripit.com/feed/ical/people/david74/tripit.ics
Remember to vote!
Consider donating to Postgres: http://www.postgresql.org/about/donate
Alvaro Herrera <alvherre@commandprompt.com> writes:
Does this mean that the control file should contain a version number in
the filename? Otherwise, I don't see how you'd have more than one
version of the control file.Also, if upgrading is necessary, there will need to be one "upgrade"
control file that says how to upgrade from version N to N+1.
I like both ideas. I'd like to propose that we get back to this part of
the feature later, after the first patch is in. After all, the main goal
is to support dump&restore of extensions. Let's do that first.
If some of you are interested, the development happens here:
http://git.postgresql.org/gitweb?p=postgresql-extension.git;a=shortlog;h=refs/heads/extension
I've written some basic documentation in the "Extending SQL"
chapter (and command man pages too). There's not so much to tell about
the new features, as the goal is for it to fix something quite basic.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Excerpts from Dimitri Fontaine's message of mié oct 13 18:11:21 -0300 2010:
I like both ideas. I'd like to propose that we get back to this part of
the feature later, after the first patch is in. After all, the main goal
is to support dump&restore of extensions. Let's do that first.
Okay. I looked at the code and I have to admit that it seems awkward to
have pg_dump left-joining everything against pg_depend and checking for
NULLs. I wondered if there was a simpler way to go about it, perhaps
using EXCEPT? No specific proposal though.
If some of you are interested, the development happens here:
http://git.postgresql.org/gitweb?p=postgresql-extension.git;a=shortlog;h=refs/heads/extension
Thanks. I managed to retrieve into an already-checked-out copy of HEAD
and it worked pretty well:
git remote add extensions git://git.postgresql.org/git/postgresql-extension.git
git fetch extensions extension:extension
then I could run "git diff master...extension" and see the complete
diff. Of course, I can also see each commit individually. Or
"git checkout extension".
Maybe it would be worthwhile to split the parts that parse a file and
execute from a file, and submit separately. It is obviously
self-contained and serves a useful purpose on its own. It also forces
you to think harder about renaming the parse function :-)
--
Álvaro Herrera <alvherre@commandprompt.com>
The PostgreSQL Company - Command Prompt, Inc.
PostgreSQL Replication, Consulting, Custom Development, 24x7 support
Alvaro Herrera <alvherre@commandprompt.com> writes:
Okay. I looked at the code and I have to admit that it seems awkward to
have pg_dump left-joining everything against pg_depend and checking for
NULLs. I wondered if there was a simpler way to go about it, perhaps
using EXCEPT? No specific proposal though.
Thanks for your time and review!
The LEFT JOIN WHERE right IS NULL is the first thing that I though
about, if it looks ugly I'll rework the queries, ok.
Maybe it would be worthwhile to split the parts that parse a file and
execute from a file, and submit separately. It is obviously
self-contained and serves a useful purpose on its own. It also forces
you to think harder about renaming the parse function :-)
The cfparser code is extracted from xlog.c and I left it as-is, so the
function is till named parseRecoveryCommandFileLine. If cfparser has a
file name is ok, I could rename the function cfParseOneLine?
I'll send separate patches for that and pg_execute_from_file() then.
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Dimitri Fontaine <dimitri@2ndQuadrant.fr> writes:
Open Items :
- cfparser
Still in attached v1 patch, but will repost separately, as proposed by
Álvaro.
- User Documentation. Where in the manual do I write it?
Chapter 35. Extending SQL looked like a good choice, there it is. Needs
to get expanded with latest additions.
- Naming of the "control" file,
<extension>.{control,registry,install}
Issue still to discuss. I'm happy with the current .control name.
- Handling of custom_variable_classes
This version of the patch address the point. The problem I wanted to
solve is that 'create extension' has no chance to edit the user
configuration, such as custom_variable_classes. Also, in the debian
world it is a policy violation for a package to modify another package's
configuration file.
So I wanted the extension mechanism to be able to append some classes to
the custom_variable_classes without touching the configuration
file. That's why pg_extension has this custom_class column.
The v1 patch, attached, rework this GUC so that it's SUSET. Invalid
variables are now kept aside while processing the configuration, so that
it's possible to scan them again when custom_variable_classes changes,
and to make them available.
Based on this change, postinit now will scan the pg_extension catalog
for the connected database and add all classes defined there. So that if
an extension register its own class, and the user defines some variables
in the configuration file, it's not necessary for him to edit the global
custom_variable_classes any more.
Also, at CREATE EXTENSION time, should an extension specific classes be
given, custom_variable_classes is changed on the fly, and default values
from the control file can get loaded too.
Current code is to be read as a proposal, open to discussion of course:
it seemed to me preferable to write it (having not heard back from my
earlier mail) as an opening.
- User data tables
An extension can install plain relations, and that's fine. The
problem is when the data in there are changed after installing.
Because the name of the game here is to exclude the table from the
dumps, of course the data will not be in there.The solution would be to offer extension's author a way to 'flag'
some tables as worthy of dumping, I think marking the dependency as
DEPENDENCY_NORMAL rather then DEPENDENCY_INTERNAL will do the trick:SELECT pg_extension_flag_dump(oid);
That's in the attached patch too.
- Extension Upgrading
That was a mistake to raise this subject this early, it seems wise to
defer that to a later patch. Much remains to be designed here.
- Bugs to fix
There's at least one where drop extension leaves things behind,
although it uses performDeletion(). The dependencies are fine enough
so that the leftover objects are not part of the dump done before to
drop, though.
That well seems to me to be an existing bug that I'm just putting in the
light. The reading of comments from findDependentObjects() makes me
think that following nested DEPENDENCY_INTERNAL levels is broken, but I
didn't have time to figure out how exactly, nor to try to fix.
So, v1 patch attached, and repository available too:
http://git.postgresql.org/gitweb?p=postgresql-extension.git;a=shortlog;h=refs/heads/extension
Regards,
--
Dimitri Fontaine
http://2ndQuadrant.fr PostgreSQL : Expertise, Formation et Support
Attachments:
extension.v1.patch.gzapplication/octet-streamDownload
�Az�Lextension.v1.patch �\{W�H������`@����8�1�]\��;�l���mt�%�O&�}������23��9��RWWWUWW������
f�l�jE!�6w[�c�=�������b���������v���CF/�?�8�c�y�;���34O��#n�m|�n���9���i��D����;������I;��913]�����w<|����1gi���� �%���5��_}�����������Tc�^I^���6��O������\��,H�TNf2�����Y�c�O��D�?g����b~:�����)�o�lIvN�������]7]]D�����@��}96�p$@�K{��]���:^D1�S��M��@�d����L��\�kIl��$2h�7���eRk�"S���G����
~�\��W��Z �|�
��_��5"�?�|�\����<Y�~q�����]O<e!w��G���2�G�f��,pM����,=��7�
G-�K��jWj�����+�]'�S}�� [�7�Lrr��uaR\3�NV�~��}U� ���Ib�n&f�5e������e�L����<�q�G�<Z$�3��7s!����W�����������l&H���/�I�k��t��Mw� �>����h�a��/&A��1�
N��`���B����
���@w`5���[y��'V���P�D�"�3�
.T2��i��U���HQ� ; #!�H�&��� ��<����,,�:����v�p�+�����6
z�q\��a6nl�M�����$�V�x�����<{��K����� �K�5��`Q�#8Q ����W�D!��?(��n�e��
uo��������9*��(�7��<��E~Z\�.Z�0'���~�h� ���v��W���x!7���I�"�WE��J�H���r�336o����
�m�V���c�a���Mu�,v�.X��������!wf
��� 8yL�0y4�����,%F����y��6��T�*/9�3�'/9��;%/�C?��H&$6-���yH�������������q�w ������b��EA�%~��9�(t-zk����%nm��\���!��q(��&a-��L������Y�b��nB�0L $�Sx a?�n ��B?�������i'��h1�.������^2?�?ne)��Y}��-vaz���q6�9�����X-M.%'n����q��BRKJ$a�G���S�3B�(��$��r_J�
�
L���N%$����,���T$<(7`4?��H�}������=wo���o����3j�O�R;v�5���
� ��u�s,!J�1TK
����4 �FBB]���F���j�`�H���Y�[0��<
���r
]���#QW6�����l ��I�?�^�JR<:�j/�vj�"�k�V��c���b]d� ���g��N?����)xX2����JW1�)(���k�ZQ�����A��'f�_P �01y�Wx/�/�M-�.o��2��f�T�P
n7q1�vb�\��K9{/f�:���I"tE"��F��j��
�f��������%�B��#��rY�(\M�!WNS��b�Y����H�����0�h"$��E���e������r������/����f�c2��l���U�E�#��e��]��B]���}�����@^�pN���R������%q#'�@;���U���FZ����-��5.��v�F�.�(W@�v�T�j�2h�L�Y������������K�����K�&��h�B�j���� X��]� b���e����(�W�<;�O6|������\ 2�{�����`�|�N����;���h0�
�7��?{��!O��(������� 1'���V��BU�����c`�/W�24j����0�L 8�>}q���{%�'
^��$�SJ=K90W�r w�3���~��4sa����
�����4Y����DQvJY����&%xQeh��^����F��9[���5��'��_<��$���D�H��������t ;0[���z�Z*��X�������tJ�G'������s��<9Qy��6����\�?��2��gj�
����"�������M�������K5��E��<�������
�������vO�u�`�(�^��o�~$��pF�'-���Z�K��J}��]���ow�����S��H{[�'+�+mr}��^���������9�5-���i�iw+M/2����?kZr~���<��3�Zj��me���r���$v��5K���jECM`/����9��\@@�f������D��9�M@pQz���>�l{���8_��p;s����w������g���2���qP�Bg� CnAP
X��6��5��q�.�@��G��,��\6h���������s1�ta� qz�,L��� {i���8��`&.�dW��`��{��T����y|9���}�.0(x)x\Az"W�:J��D��������A,�-��$R�_/v`V��`����FRW ����SZ�"@�`�:�?^\4�\�A���GEm�R\}�2"j��3]v�L��2�u@Q����'��(1y�$���q$�/6�w0��u!]�E�w,v��mk{\�-x��mH�2�x*�OR�u�� ����W 8l1����� ��bF���b��N�������'����pV�v=���a�V���ZS'��0���0^����Sp���>��]�CWJ.�o��+ �$����"���y��x������w��d�u�
�'�����@�������C�������x���^r�������I������ �A��Fkd���V^�.�B��[���I��R�����A[i����~��>l���~c���k��X��4]G����&��:K��Y���k�\
��q�#�W ���K?�I�6p���SC��F�P��,�8�M�/�sy-P�I4���&���]�cB�#e�W(�n2��G����r���@.ZlM��]�"N���xd��;"����oJ�D?���RGG���Y�N�e�������&�9�'>I^�e=SM1!&r��F��J�YW���9�X�����Kc&z��g�M8�I� �U����s��%3��'<���{��+_�b���-V.��M��<�����t�<{��\���r~����R��X�s-&g�$;���D&.���==�7���8�j�P��!�9�,��������[�&,7 S�����9�07a�gMb /w�����B��-l(&���{���X��DRy����t���������'�Q����i��?�G���F��p�F��3�� n�����
"�h�0+��S.�9�� �*���j�V'��nY����+�
t�[��&�d9���&�4x�H��[�P�fD�r�EejQIV9#w��ct����b�����B����XB���
�@3X-���{�\Mi-�+R��9 Y���T�M^RmI�]P����c= ��|^2�j�7��B�B�j "���M�Q�Bdn]m������;PI� �����
�;�&T���?N�����gX��pI���w��mh�W��wg���u������h0<������d�����\3��@?y���^�c�Wl�Lxv�r�>z����^�B���H��T����,g�K*jTk
�iA%#f:qV��[�� �9��[Q�b��]�<����z�
�1 q�S��T�����4���>��f�������N����~$�%t�/����HI�7�$����q������<Wa�7�������HZe����������������"�<����I�*����`=�qA�qrn �l[�,�PN�#Q*�cT8����Cl��H'���%�K�u
o-F�0�^���������A���A�3L���&�m�13_#����$�d5��1����'(�$�V��)����- ��FW#�G�O�� �<UC�������������0�y��R��7��E}�`����-Z�� �����,W��n�^��{��t�O�����[�R��% ����Rh% ��� ���|�[
��db ��������*/�tkc���"T����4���CP��?A�M�Mo�2�D*!��z��Eo5�j�!��|����i
�����(s�2l�/���upJ��8�|��(.9Q�������~���7�t��"���SJ���`a�!��k��@Q��N+���D��:&z�9X��Y�G�����!���~�K���D'M$�X��8���y_H��9�9������N�
vs���+;���:r��)�O���$������c������G�R���#����Y�^�]�U��^��[ 2����l�����{�y�Q��-��
|V{�<��~`�2�}R�;Z�,��+��z���;5X���������e��&������w!w��y4�d;�)����A��K��Y->v�n4��Td�9"���%����E�����$��k<�j�WQ����o��h��n������Of�_��R��' ���\��|�G-��x2�n��=m�M��1�6����0��!6SZ��#�E$��.�����%?�&��L <�7�t���k�
�r+����m�����nzo{��
��
�;A��������E��q�������u��;��:[N���t����%vET]B�.$Q���%��s���cp����AcU-Yq0d�����4�eUY6�k,�Y;N��E5m�r�e�Q=R�(��S�|B����]�kw�c��;e�o��E.5-������@ ��&3T��_K\}SgB�2bP��irf�_Y����n.Y���.Kz��<�Y�E]f��/*d�r��������*�����*��,\(������N��q3�������"�=A���w����Y�$�W-��:�x��C|�N�fj P���;���<�Eq����P��
s�.���6`s6��B���a������ ��A��$7ZDd��H�
z��l������gQ�@]��J7D�z��nx����:l8^���k����H�`O�O04��a��C�B���N�V�+~vu�X�y_g�sp��j���.�����
&���XI���.��G�2�F{>��<��������>�!�'�8GY�������������g�i�5�
�<Xg7�sU���67��
�9w��"������
���n���Y��D����� ���_B:�1�o�I��%�0�������JQf�������Q��r��7�=6�a�#��_����x�7���3�����m�t�4��|�6q����d�����w���}q&�.����E�e���W���[�����CT?�JK�-�E���y:���00��F:�����}X������[����XGxuz 5\V���N��2�`Ek%��K��(p+X�h����Z3]�/"j�~o/E|k �J� �5��neT�� ���\RXZP����d#
����XJ�}� ���,b�*R�=�U���f�jm��A����)��'^Y��XOz�A%M�s��� >�_��p��y��'����|N�%� ]��)o\�O��x����6����u)]�ct��E���N�����Tw���te�'
����m[��i<l,�G:m|�%M�?��]
_���9����q�i��$ f�i� ~M`�QE.>��?��S��;�S���p�=`�H���a�^�Atwg:�A���-��~&�?���o��h��'[��^�b[�iZ�]/���o���H���/�}Z��L�:���[�Z��)�O;�h�x4g�k�����
���u:����o��WYLw�+�O����/�L����L� ��w�QeT�����(�C
����x��� ������sX�&&y�j�|�Q�(%_�.�G#�G��zG�1%��o�[3��I�H2������������������b>�CdE�(LT��}��'�, �g�����,k-[�Q�����~���H*���|���mD�����=������m#����)���F�����=�,�:���e��m���%��FU���v���<