pg_dump_split_v2.patch

text/plain

Filename: pg_dump_split_v2.patch
Type: text/plain
Part: 0
Message: Re: pg_dump --split patch

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: context
Series: patch v2
File+
src/bin/pg_dump/Makefile 1 0
src/bin/pg_dump/pg_backup_archiver.c 4 0
src/bin/pg_dump/pg_backup_archiver.h 1 0
src/bin/pg_dump/pg_backup.h 2 0
src/bin/pg_dump/pg_backup_split.c 1063 0
src/bin/pg_dump/pg_dump.c 6 0
*** a/src/bin/pg_dump/Makefile
--- b/src/bin/pg_dump/Makefile
***************
*** 19,25 **** include $(top_builddir)/src/Makefile.global
  override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
  
  OBJS=	pg_backup_archiver.o pg_backup_db.o pg_backup_custom.o \
! 	pg_backup_null.o pg_backup_tar.o \
  	pg_backup_directory.o dumpmem.o dumputils.o compress_io.o $(WIN32RES)
  
  KEYWRDOBJS = keywords.o kwlookup.o
--- 19,25 ----
  override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
  
  OBJS=	pg_backup_archiver.o pg_backup_db.o pg_backup_custom.o \
! 	pg_backup_null.o pg_backup_tar.o pg_backup_split.o \
  	pg_backup_directory.o dumpmem.o dumputils.o compress_io.o $(WIN32RES)
  
  KEYWRDOBJS = keywords.o kwlookup.o
*** a/src/bin/pg_dump/pg_backup.h
--- b/src/bin/pg_dump/pg_backup.h
***************
*** 50,56 **** typedef enum _archiveFormat
  	archCustom = 1,
  	archTar = 3,
  	archNull = 4,
! 	archDirectory = 5
  } ArchiveFormat;
  
  typedef enum _archiveMode
--- 50,57 ----
  	archCustom = 1,
  	archTar = 3,
  	archNull = 4,
! 	archDirectory = 5,
! 	archSplit = 6
  } ArchiveFormat;
  
  typedef enum _archiveMode
*** a/src/bin/pg_dump/pg_backup_archiver.c
--- b/src/bin/pg_dump/pg_backup_archiver.c
***************
*** 2125,2130 **** _allocAH(const char *FileSpec, const ArchiveFormat fmt,
--- 2125,2134 ----
  		case archTar:
  			InitArchiveFmt_Tar(AH);
  			break;
+ 		
+ 		case archSplit:
+ 			InitArchiveFmt_Split(AH);
+ 			break;
  
  		default:
  			exit_horribly(modulename, "unrecognized file format \"%d\"\n", fmt);
*** a/src/bin/pg_dump/pg_backup_archiver.h
--- b/src/bin/pg_dump/pg_backup_archiver.h
***************
*** 369,374 **** extern void InitArchiveFmt_Custom(ArchiveHandle *AH);
--- 369,375 ----
  extern void InitArchiveFmt_Null(ArchiveHandle *AH);
  extern void InitArchiveFmt_Directory(ArchiveHandle *AH);
  extern void InitArchiveFmt_Tar(ArchiveHandle *AH);
+ extern void InitArchiveFmt_Split(ArchiveHandle *AH);
  
  extern bool isValidTarHeader(char *header);
  
*** /dev/null
--- b/src/bin/pg_dump/pg_backup_split.c
***************
*** 0 ****
--- 1,1063 ----
+ /*-------------------------------------------------------------------------
+  *
+  * pg_backup_split.c
+  *
+  *  A split format dump is a directory, which contains all database objects
+  *  separated into .sql files, and an "index.sql" file with psql statements
+  *  to allow restoring the separated objects.
+  *
+  *-------------------------------------------------------------------------
+  */
+ 
+ #include "postgres_fe.h"
+ #include "libpq-fe.h"
+ #include "libpq/libpq-fs.h"
+ #include "pg_backup_archiver.h"
+ #include "dumpmem.h"
+ #include "dumputils.h"
+ 
+ #include <dirent.h>
+ #include <sys/stat.h>
+ 
+ typedef struct
+ {
+ 	char	   *filename;		/* filename excluding the directory (basename) */
+ 	DumpId		dumpId;			/* dump id of the TocEntry */
+ } lclTocEntry;
+ 
+ typedef struct
+ {
+ 	/*
+ 	 * Our archive location. This is basically what the user specified as his
+ 	 * backup file but of course here it is a directory.
+ 	 */
+ 	char	   *directory;
+ 
+ 	FILE	   *dataFH;			/* currently open data file */
+ 
+ 	lclTocEntry **sortedToc;	/* array of toc entires sorted by (filename, dumpId) */
+ } lclContext;
+ 
+ /* translator: this is a module name */
+ static const char *modulename = gettext_noop("split archiver");
+ 
+ 
+ /* prototypes for private functions */
+ static void _ArchiveEntry(ArchiveHandle *AH, TocEntry *te);
+ static void _StartData(ArchiveHandle *AH, TocEntry *te);
+ static void _EndData(ArchiveHandle *AH, TocEntry *te);
+ static size_t _WriteData(ArchiveHandle *AH, const void *data, size_t dLen);
+ static size_t _WriteBuf(ArchiveHandle *AH, const void *buf, size_t len);
+ static void _CloseArchive(ArchiveHandle *AH);
+ 
+ static void _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid);
+ static size_t _WriteBlobData(ArchiveHandle *AH, const void *data, size_t dLen);
+ static void _EndBlob(ArchiveHandle *AH, TocEntry *te, Oid oid);
+ 
+ static size_t _splitOut(ArchiveHandle *AH, const void *buf, size_t len);
+ 
+ static int lclTocEntryCmp(const void *av, const void *bv);
+ static bool should_add_index_entry(ArchiveHandle *AH, TocEntry *te);
+ static void create_sorted_toc(ArchiveHandle *AH);
+ static void get_object_description(ArchiveHandle *AH, TocEntry *te, PQExpBuffer buf);
+ static void add_ownership_information(ArchiveHandle *AH, TocEntry *te);
+ static void set_search_path(ArchiveHandle *AH, TocEntry *te);
+ static void write_split_directory(ArchiveHandle *AH);
+ 
+ static void create_schema_directory(ArchiveHandle *AH, const char *tag);
+ static void create_directory(ArchiveHandle *AH, const char *fmt, ...)
+ 	__attribute__((format(PG_PRINTF_ATTRIBUTE, 2, 3)));
+ static char *prepend_directory(ArchiveHandle *AH, const char *relativeFilename);
+ static char *encode_filename(const char *input);
+ static TocEntry *find_dependency(ArchiveHandle *AH, TocEntry *te);
+ static char *get_object_filename(ArchiveHandle *AH, TocEntry *t);
+ 
+ 
+ /*
+  *	Init routine required by ALL formats. This is a global routine
+  *	and should be declared in pg_backup_archiver.h
+  *
+  *	Its task is to create any extra archive context (using AH->formatData),
+  *	and to initialize the supported function pointers.
+  *
+  *	It should also prepare whatever its input source is for reading/writing,
+  *	and in the case of a read mode connection, it should load the Header & TOC.
+  */
+ void
+ InitArchiveFmt_Split(ArchiveHandle *AH)
+ {
+ 	lclContext *ctx;
+ 
+ 	/* Assuming static functions, this can be copied for each format. */
+ 	AH->ArchiveEntryPtr = _ArchiveEntry;
+ 	AH->StartDataPtr = _StartData;
+ 	AH->WriteDataPtr = _WriteData;
+ 	AH->EndDataPtr = _EndData;
+ 	AH->WriteBytePtr = NULL;
+ 	AH->ReadBytePtr = NULL;
+ 	AH->WriteBufPtr = _WriteBuf;
+ 	AH->ReadBufPtr = NULL;
+ 	AH->ClosePtr = _CloseArchive;
+ 	AH->ReopenPtr = NULL;
+ 	AH->PrintTocDataPtr = NULL;
+ 	AH->ReadExtraTocPtr = NULL;
+ 	AH->WriteExtraTocPtr = NULL;
+ 	AH->PrintExtraTocPtr = NULL;
+ 
+ 	AH->StartBlobsPtr = NULL;
+ 	AH->StartBlobPtr = _StartBlob;
+ 	AH->EndBlobPtr = _EndBlob;
+ 	AH->EndBlobsPtr = NULL;
+ 
+ 	AH->ClonePtr = NULL;
+ 	AH->DeClonePtr = NULL;
+ 
+ 	AH->CustomOutPtr = _splitOut;
+ 
+ 	/* Set up our private context */
+ 	ctx = (lclContext *) pg_malloc0(sizeof(lclContext));
+ 	AH->formatData = (void *) ctx;
+ 
+ 	ctx->dataFH = NULL;
+ 	ctx->sortedToc = NULL;
+ 
+ 	/* Initialize LO buffering */
+ 	AH->lo_buf_size = LOBBUFSIZE;
+ 	AH->lo_buf = (void *) pg_malloc(LOBBUFSIZE);
+ 
+ 	if (!AH->fSpec || strcmp(AH->fSpec, "") == 0)
+ 		exit_horribly(modulename, "no output directory specified\n");
+ 
+ 	if (AH->compression != 0)
+ 		exit_horribly(modulename, "split archive format does not support compression\n");
+ 
+ 	if (AH->mode != archModeWrite)
+         exit_horribly(modulename, "reading a split archive not supported; restore using psql\n");
+ 
+ 	ctx->directory = AH->fSpec;
+ 
+ 	if (mkdir(ctx->directory, 0700) < 0)
+ 		exit_horribly(modulename, "could not create directory \"%s\": %s\n",
+ 					  ctx->directory, strerror(errno));
+ 
+ 	create_directory(AH, "EXTENSIONS");
+ 	create_directory(AH, "BLOBS");
+ }
+ 
+ /*
+  * Custom output function to write output from ahprintf() to ctx->dataFH.
+  */
+ static size_t
+ _splitOut(ArchiveHandle *AH, const void *buf, size_t len)
+ {
+ 	lclContext *ctx = (lclContext *) AH->formatData;
+ 
+ 	if (!ctx->dataFH)
+ 		exit_horribly(modulename, "ctx->dataFH is NULL\n");
+ 
+ 	return fwrite(buf, 1, len, ctx->dataFH);
+ }
+ 
+ static void
+ create_schema_directory(ArchiveHandle *AH, const char *tag)
+ {
+ 	char *namespace = encode_filename(tag);
+ 
+ 	create_directory(AH, "%s", namespace);
+ 	create_directory(AH, "%s/AGGREGATES", namespace);
+ 	create_directory(AH, "%s/CHECK_CONSTRAINTS", namespace);
+ 	create_directory(AH, "%s/CONSTRAINTS", namespace);
+ 	create_directory(AH, "%s/FK_CONSTRAINTS", namespace);
+ 	create_directory(AH, "%s/FUNCTIONS", namespace);
+ 	create_directory(AH, "%s/INDEXES", namespace);
+ 	create_directory(AH, "%s/OPERATOR_CLASSES", namespace);
+ 	create_directory(AH, "%s/OPERATOR_FAMILIES", namespace);
+ 	create_directory(AH, "%s/RULES", namespace);
+ 	create_directory(AH, "%s/SEQUENCES", namespace);
+ 	create_directory(AH, "%s/SERVERS", namespace);
+ 	create_directory(AH, "%s/TABLEDATA", namespace);
+ 	create_directory(AH, "%s/TABLES", namespace);
+ 	create_directory(AH, "%s/TYPES", namespace);
+ 	create_directory(AH, "%s/TRIGGERS", namespace);
+ 	create_directory(AH, "%s/VIEWS", namespace);
+ }
+ 
+ /*
+  * Called by the Archiver when the dumper creates a new TOC entry.
+  *
+  * We determine the filename for this entry.
+ */
+ static void
+ _ArchiveEntry(ArchiveHandle *AH, TocEntry *te)
+ {
+ 	lclTocEntry *tctx;
+ 
+ 	tctx = (lclTocEntry *) pg_malloc0(sizeof(lclTocEntry));
+ 	tctx->dumpId = te->dumpId;
+ 	te->formatData = (void *) tctx;
+ 
+ 	tctx->filename = get_object_filename(AH, te);
+ }
+ 
+ 
+ /*
+  * Called by the archiver when saving TABLE DATA (not schema). This routine
+  * should save whatever format-specific information is needed to read
+  * the archive back.
+  *
+  * It is called just prior to the dumper's 'DataDumper' routine being called.
+  *
+  * We create the data file for writing and add any information necessary
+  * for restoring the table data.
+  */
+ static void
+ _StartData(ArchiveHandle *AH, TocEntry *te)
+ {
+ 	lclTocEntry *tctx = (lclTocEntry *) te->formatData;
+ 	lclContext *ctx = (lclContext *) AH->formatData;
+ 	char	   *fname;
+ 
+ 	fname = prepend_directory(AH, tctx->filename);
+ 
+ 	ctx->dataFH = fopen(fname, PG_BINARY_W);
+ 	if (ctx->dataFH == NULL)
+ 		exit_horribly(modulename, "could not open output file \"%s\": %s\n",
+ 					  fname, strerror(errno));
+ 
+ 	/* set the search path */
+ 	set_search_path(AH, te);
+ 
+ 	/*
+ 	 * If there's a COPY statement, add it to the beginning of the file.  If there
+ 	 * isn't one, this must be a --inserts dump and we don't need to add anything.
+ 	 */
+ 	if (te->copyStmt)
+ 		ahprintf(AH, "%s", te->copyStmt);
+ }
+ 
+ /*
+  * Called by archiver when dumper calls WriteData. Note that while the
+  * WriteData routine is generally called for both BLOB and TABLE data, we
+  * substitute our own _WriteBlob function when dealing with BLOBs.
+  *
+  * We write the data to the open data file.
+  */
+ static size_t
+ _WriteData(ArchiveHandle *AH, const void *data, size_t dLen)
+ {
+ 	lclContext *ctx = (lclContext *) AH->formatData;
+ 
+ 	if (dLen == 0)
+ 		return 0;
+ 
+ 	return fwrite(data, 1, dLen, ctx->dataFH);
+ }
+ 
+ /*
+  * Called by the archiver when a dumper's 'DataDumper' routine has
+  * finished.
+  *
+  * We close the data file.
+  */
+ static void
+ _EndData(ArchiveHandle *AH, TocEntry *te)
+ {
+ 	lclContext *ctx = (lclContext *) AH->formatData;
+ 
+ 	/* Close the file */
+ 	fclose(ctx->dataFH);
+ 
+ 	ctx->dataFH = NULL;
+ }
+ 
+ /*
+  * Write a buffer of data to the archive.
+  * Called by the archiver to write a block of bytes to a data file.
+  */
+ static size_t
+ _WriteBuf(ArchiveHandle *AH, const void *buf, size_t len)
+ {
+ 	lclContext *ctx = (lclContext *) AH->formatData;
+ 	size_t		res;
+ 
+ 	res = fwrite(buf, 1, len, ctx->dataFH);
+ 	if (res != len)
+ 		exit_horribly(modulename, "could not write to output file: %s\n",
+ 					  strerror(errno));
+ 
+ 	return res;
+ }
+ 
+ /*
+  * Close the archive.
+  *
+  * When writing the archive, this is the routine that actually starts
+  * the process of saving it to files. No data should be written prior
+  * to this point, since the user could sort the TOC after creating it.
+  *
+  * Usually when an archive is written, we should call WriteHead() and
+  * WriteToc().  But since we don't write a TOC file at all, we can just
+  * skip that and write the index file from the TocEntry array.  We do,
+  * however, use WriteDataChunks() to write the table data.
+  */
+ static void
+ _CloseArchive(ArchiveHandle *AH)
+ {
+ 	if (AH->mode == archModeWrite)
+ 	{
+ 		WriteDataChunks(AH);
+ 		write_split_directory(AH);
+ 	}
+ }
+ 
+ 
+ /*
+  * BLOB support
+  */
+ 
+ /*
+  * Called by the archiver when we're about to start dumping a blob.
+  *
+  * We create a file to write the blob to.
+  */
+ static void
+ _StartBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
+ {
+ 	lclContext *ctx = (lclContext *) AH->formatData;
+ 	char		fname[MAXPGPATH];
+ 
+ 	snprintf(fname, MAXPGPATH, "%s/BLOBS/%u.sql", ctx->directory, oid);
+ 	ctx->dataFH = fopen(fname, PG_BINARY_W);
+ 	if (ctx->dataFH == NULL)
+ 		exit_horribly(modulename, "could not open output file \"%s\": %s\n",
+ 					  fname, strerror(errno));
+ 
+ 	ahprintf(AH, "SELECT pg_catalog.lo_open('%u', %d);\n", oid, INV_WRITE);
+ 
+ 	/* Substitute a different function to deal with BLOB data */
+ 	AH->WriteDataPtr = _WriteBlobData;
+ }
+ 
+ /*
+  * Called by dumper via archiver from within a data dump routine.
+  * We substitute this for _WriteData while emitting a BLOB.
+  */
+ static size_t
+ _WriteBlobData(ArchiveHandle *AH, const void *data, size_t dLen)
+ {
+ 	if (dLen > 0)
+ 	{
+ 		PQExpBuffer buf = createPQExpBuffer();
+ 		appendByteaLiteralAHX(buf,
+ 							  (const unsigned char *) data,
+ 							  dLen,
+ 							  AH);
+ 		
+ 		ahprintf(AH, "SELECT pg_catalog.lowrite(0, %s);\n", buf->data);
+ 		destroyPQExpBuffer(buf);
+ 	}
+ 
+ 	return dLen;
+ }
+ 
+ /*
+  * Called by the archiver when the dumper is finished writing a blob.
+  *
+  * We close the blob file and write an entry to the blob TOC file for it.
+  */
+ static void
+ _EndBlob(ArchiveHandle *AH, TocEntry *te, Oid oid)
+ {
+ 	lclContext *ctx = (lclContext *) AH->formatData;
+ 
+ 	ahprintf(AH, "SELECT pg_catalog.lo_close(0);\n\n");
+ 
+ 	/* Close the BLOB data file itself */
+ 	fclose(ctx->dataFH);
+ 	ctx->dataFH = NULL;
+ 
+ 	/* Restore the pointer we substituted in _StartBlob() */
+ 	AH->WriteDataPtr = _WriteData;
+ }
+ 
+ 
+ static int
+ lclTocEntryCmp(const void *av, const void *bv)
+ {
+ 	int c;
+ 	lclTocEntry *a = *((lclTocEntry **) av);
+ 	lclTocEntry *b = *((lclTocEntry **) bv);
+ 
+ 	/* NULLs should sort last */
+ 	c = (b->filename != NULL) - (a->filename != NULL);
+ 	if (c != 0)
+ 		return c;
+ 
+ 	/* don't call strcmp() on NULLs */
+ 	if (a->filename != NULL && b->filename != NULL)
+ 	{
+ 		c = strcmp(a->filename, b->filename);
+ 		if (c != 0)
+ 			return c;
+ 	}
+ 
+ 	return a->dumpId - b->dumpId;
+ }
+ 
+ static bool
+ should_add_index_entry(ArchiveHandle *AH, TocEntry *te)
+ {
+ 	lclTocEntry **sortedToc;
+ 	lclTocEntry **pte;
+ 	lclTocEntry **key;
+ 	lclTocEntry *prevte;
+ 
+ 	key = (lclTocEntry **) &te->formatData;
+ 
+ 	sortedToc = ((lclContext *) AH->formatData)->sortedToc;
+ 	if (!sortedToc)
+ 		exit_horribly(modulename, "formatData->sortedToc is NULL\n");
+ 		
+ 	pte = (lclTocEntry **) bsearch(key, sortedToc,
+ 								   AH->tocCount, sizeof(lclTocEntry *), lclTocEntryCmp);
+ 
+ 	if (!pte)
+ 		exit_horribly(modulename, "binary search failed\n");
+ 
+ 	/* If there's no previous entry, always add an index entry */
+ 	if (pte == sortedToc)
+ 		return true;
+ 
+ 	/*
+ 	 * If there's a previous entry with the same filename, we don't want to add
+ 	 * an index entry for this TocEntry.  Note that NULLs sort last so the
+ 	 * previous entry's filename can never be NULL.
+ 	 */
+ 	prevte = *(pte - 1);
+ 	return strcmp(prevte->filename, (*key)->filename) != 0;
+ }
+ 
+ /*
+  * Create a list of lclTocEntries sorted by (filename, dumpId).  This list is
+  * used when creating the index file to make sure we don't include a file
+  * multiple times.
+  */
+ static void
+ create_sorted_toc(ArchiveHandle *AH)
+ {
+ 	int i;
+ 	lclContext *ctx;
+ 	TocEntry *te;
+ 
+ 	ctx = (lclContext *) AH->formatData;
+ 	/* sanity checks */
+ 	if (!ctx)
+ 		exit_horribly(modulename, "formatData not allocated\n");
+ 	if (ctx->sortedToc != NULL)
+ 		exit_horribly(modulename, "formatData->sortedToc not NULL\n");
+ 
+ 	ctx->sortedToc = (lclTocEntry **) pg_malloc0(sizeof(lclTocEntry *) * AH->tocCount);
+ 	for (i = 0, te = AH->toc->next; te != AH->toc; i++, te = te->next)
+ 		ctx->sortedToc[i] = (lclTocEntry *) te->formatData;
+ 
+ 	qsort(ctx->sortedToc, AH->tocCount, sizeof(lclTocEntry *), lclTocEntryCmp);
+ }
+ 
+ static void
+ get_object_description(ArchiveHandle *AH, TocEntry *te, PQExpBuffer buf)
+ {
+ 	const char *type = te->desc;
+ 
+ 	/* use ALTER TABLE for views and sequences */
+ 	if (strcmp(type, "VIEW") == 0 || strcmp(type, "SEQUENCE") == 0)
+ 		type = "TABLE";
+ 
+ 	/* must not call fmtId() on BLOBs */
+ 	if (strcmp(type, "BLOB") == 0)
+ 	{
+ 		appendPQExpBuffer(buf, "LARGE OBJECT %s ", te->tag);
+ 		return;
+ 	}
+ 
+ 	/* a number of objects that require no special treatment */
+ 	if (strcmp(type, "COLLATION") == 0 ||
+ 		strcmp(type, "CONVERSION") == 0 ||
+ 		strcmp(type, "DOMAIN") == 0 ||
+ 		strcmp(type, "DATABASE") == 0 ||
+ 		strcmp(type, "FOREIGN DATA WRAPPER") == 0 ||
+ 		strcmp(type, "FOREIGN TABLE") == 0 ||
+ 		strcmp(type, "INDEX") == 0 ||
+ 		strcmp(type, "LARGE OBJECT") == 0 ||
+ 		strcmp(type, "TABLE") == 0 ||
+ 		strcmp(type, "TEXT SEARCH CONFIGURATION") == 0 ||
+ 		strcmp(type, "TEXT SEARCH DICTIONARY") == 0 ||
+ 		strcmp(type, "TYPE") == 0 ||
+ 		strcmp(type, "PROCEDURAL LANGUAGE") == 0 ||
+ 		strcmp(type, "SCHEMA") == 0 ||
+ 		strcmp(type, "SERVER") == 0 ||
+ 		strcmp(type, "USER MAPPING") == 0)
+ 	{
+ 		appendPQExpBuffer(buf, "%s ", type);
+ 		if (te->namespace)
+ 			appendPQExpBuffer(buf, "%s.", fmtId(te->namespace));
+ 		appendPQExpBuffer(buf, "%s ", fmtId(te->tag));
+ 
+ 		return;
+ 	}
+ 
+ 	 /*
+ 	  * These object types require additional decoration.  Fortunately, the
+ 	  * information needed is exactly what's in the DROP command.
+ 	  */
+ 	if (strcmp(type, "AGGREGATE") == 0 ||
+ 		strcmp(type, "FUNCTION") == 0 ||
+ 		strcmp(type, "OPERATOR") == 0 ||
+ 		strcmp(type, "OPERATOR CLASS") == 0 ||
+ 		strcmp(type, "OPERATOR FAMILY") == 0)
+ 	{
+ 		/* chop "DROP " off the front and make a modifyable copy */
+ 		char *first = pg_strdup(te->dropStmt + 5);
+ 		char *last;
+ 
+ 		/* strip off any ';' or '\n' at the end */
+ 		last = first + strlen(first) - 1;
+ 		while (last >= first && (*last == '\n' || *last == ';'))
+ 			last--;
+ 		*(last + 1) = '\0';		
+ 
+ 		appendPQExpBuffer(buf, "%s ", first);
+ 
+ 		free(first);
+ 
+ 		return;
+ 	}
+ 
+ 	exit_horribly(modulename, "don't know how to set owner for object type %s\n", type);
+ }
+ 
+ static void
+ add_ownership_information(ArchiveHandle *AH, TocEntry *te)
+ {
+ 	PQExpBuffer temp;
+ 
+ 	/* skip objects that don't have an owner */
+ 	if (strcmp(te->desc, "ACL") == 0 ||
+ 		strcmp(te->desc, "CAST") == 0 ||
+ 		strcmp(te->desc, "COMMENT") == 0 ||
+ 		strcmp(te->desc, "CHECK CONSTRAINT") == 0 ||
+ 		strcmp(te->desc, "CONSTRAINT") == 0 ||
+ 		strcmp(te->desc, "DEFAULT") == 0 ||
+ 		strcmp(te->desc, "ENCODING") == 0 ||
+ 		strcmp(te->desc, "EXTENSION") == 0 ||
+ 		strcmp(te->desc, "FK CONSTRAINT") == 0 ||
+ 		strcmp(te->desc, "LARGE OBJECT") == 0 ||
+ 		strcmp(te->desc, "RULE") == 0 ||
+ 		strcmp(te->desc, "SEQUENCE OWNED BY") == 0 ||
+ 		strcmp(te->desc, "SEQUENCE SET") == 0 ||
+ 		strcmp(te->desc, "STDSTRINGS") == 0 ||
+ 		strcmp(te->desc, "TRIGGER") == 0)
+ 		return;
+ 
+ 	temp = createPQExpBuffer();
+ 	appendPQExpBuffer(temp, "ALTER ");
+ 	get_object_description(AH, te, temp);
+ 	appendPQExpBuffer(temp, "OWNER TO %s;", fmtId(te->owner));
+ 	ahprintf(AH, "%s\n\n", temp->data);
+ 	destroyPQExpBuffer(temp);
+ }
+ 
+ static void
+ set_search_path(ArchiveHandle *AH, TocEntry *te)
+ {
+ 	if (!te->namespace)
+ 		return;
+ 
+ 	/*
+ 	 * We want to add the namespace to information to each object regardless
+ 	 * of the previous object's namespace; that way it is easy to see when an
+ 	 * object is moved to another schema.
+ 	 */
+ 	if (strcmp(te->namespace, "pg_catalog") == 0)
+ 		ahprintf(AH, "SET search_path TO pg_catalog;\n\n");
+ 	else
+ 		ahprintf(AH, "SET search_path TO '%s', pg_catalog;\n\n", te->namespace);
+ }
+ 
+ /*
+  * Majority of the work is done here.  We scan through the list of TOC entries
+  * and write the object definitions into their respective files.  At the same
+  * time, we build the "index" file.
+  */
+ static void
+ write_split_directory(ArchiveHandle *AH)
+ {
+ 	lclContext *ctx;
+ 	TocEntry *te;
+ 	FILE *indexFH;
+ 	char buf[512];
+ 
+ 	ctx = (lclContext *) AH->formatData;
+ 
+ 	create_sorted_toc(AH);
+ 
+ 	indexFH = fopen(prepend_directory(AH, "index.sql"), "w");
+ 	if (!indexFH)
+ 		exit_horribly(modulename, "could not open index.sql: %s\n", strerror(errno));
+ 
+ 	snprintf(buf, sizeof(buf),	"\n-- PostgreSQL split database dump\n\n"
+ 								"BEGIN;\n"
+ 								"SET client_min_messages TO 'warning';\n"
+ 								"SET client_encoding TO '%s';\n"
+ 								"SET check_function_bodies TO false;\n\n",
+ 									pg_encoding_to_char(AH->public.encoding));
+ 	if (fwrite(buf, 1, strlen(buf), indexFH) != strlen(buf))
+ 		exit_horribly(modulename, "could not write to index file: %s\n", strerror(errno));
+ 
+ 	for (te = AH->toc->next; te != AH->toc; te = te->next)
+ 	{
+ 		lclTocEntry *tctx;
+ 		const char *filename;
+ 
+ 		tctx = (lclTocEntry *) te->formatData;
+ 
+ 		/* for TABLEDATA, the only thing we need to do is add an index entry */
+ 		if (strcmp(te->desc, "TABLE DATA") == 0)
+ 		{
+ 			snprintf(buf, sizeof(buf), "\\i %s\n", tctx->filename);
+ 			if (fwrite(buf, 1, strlen(buf), indexFH) != strlen(buf))
+ 				exit_horribly(modulename, "could not write index file: %s\n", strerror(errno));
+ 			continue;
+ 		}
+ 
+ 		/* skip data */
+ 		if (te->dataDumper)
+ 			continue;
+ 
+ 		/*  if there's no filename we need to skip this entry, see _ArchiveEntry() */
+ 		if (!tctx->filename)
+ 			continue;
+ 
+ 		/* add an index entry if necessary */
+ 		if (should_add_index_entry(AH, te))
+ 		{
+ 			snprintf(buf, sizeof(buf), "\\i %s\n", tctx->filename);
+ 			if (fwrite(buf, 1, strlen(buf), indexFH) != strlen(buf))
+ 				exit_horribly(modulename, "could not write index file: %s\n", strerror(errno));
+ 		}
+ 
+ 		/* 
+ 		 * Special case: don't try to re-create the "public" schema.  Note that we
+ 		 * still need to create the index entry because all schemas use the same
+ 		 * "schemaless.sql" file, so make sure that happens before we reach this
+ 		 * point.
+ 		 */
+ 		if (strcmp(te->desc, "SCHEMA") == 0 &&
+ 			strcmp(te->tag, "public") == 0)
+ 			continue;
+ 
+ 		filename = prepend_directory(AH, tctx->filename);
+ 
+ 		/* multiple objects could map to the same file, so open in "append" mode */
+ 		ctx->dataFH = fopen(filename, "a");
+ 		if (!ctx->dataFH)
+ 			exit_horribly(modulename, "could not open file \"%s\": %s\n",
+ 							filename, strerror(errno));
+ 
+ 		set_search_path(AH, te);
+ 
+ 		ahprintf(AH, "%s\n", te->defn);
+ 		
+ 		/*
+ 		 * Special case: add \i for BLOBs into the "blobs.sql" data file.  It's ugly
+ 		 * to have this here, but there really isn't any better place.
+ 		 */
+ 		if (strcmp(te->desc, "BLOB") == 0)
+ 			ahprintf(AH, "\\i BLOBS/%s.sql\n\n", te->tag);
+ 
+ 		add_ownership_information(AH, te);
+ 
+ 		fclose(ctx->dataFH);
+ 		ctx->dataFH = NULL;
+ 	}
+ 
+ 	if (fwrite("COMMIT;\n", 1, 8, indexFH) != 8)
+ 		exit_horribly(modulename, "could not write index file: %s\n", strerror(errno));
+ 
+ 	fclose(indexFH);
+ }
+ 
+ 
+ static void
+ create_directory(ArchiveHandle *AH, const char *fmt, ...)
+ {
+ 	va_list ap;
+ 	char reldir[MAXPGPATH];
+ 	char *directory;
+ 
+ 	va_start(ap, fmt);
+ 	vsnprintf(reldir, MAXPGPATH, fmt, ap);
+ 	va_end(ap);
+ 
+ 	directory = prepend_directory(AH, reldir);
+ 	if (mkdir(directory, 0700) < 0)
+ 		exit_horribly(modulename, "could not create directory \"%s\": %s\n",
+ 					  directory, strerror(errno));
+ }
+ 
+ 
+ static char *
+ prepend_directory(ArchiveHandle *AH, const char *relativeFilename)
+ {
+ 	lclContext *ctx = (lclContext *) AH->formatData;
+ 	static char buf[MAXPGPATH];
+ 	char	   *dname;
+ 
+ 	dname = ctx->directory;
+ 
+ 	if (strlen(dname) + 1 + strlen(relativeFilename) + 1 > MAXPGPATH)
+ 		exit_horribly(modulename, "file name too long: \"%s\"\n", dname);
+ 
+ 	strcpy(buf, dname);
+ 	strcat(buf, "/");
+ 	strcat(buf, relativeFilename);
+ 
+ 	return buf;
+ }
+ 
+ 
+ /*
+  * Encode a filename to fit in the "Portable Filename Character Set" in POSIX.
+  *
+  * Any character not part of that set will be replaced with '_'.  Also, because
+  * some file system are case insensitive, we need to lower-case all file names.
+  *
+  * Because we don't know what encoding the data is in, if we see multiple
+  * consecutive octets outside the set, we only output one underscore
+  * representing all of them.  That way one can easily compare the outputs of
+  * dumps taken on systems with different encoding.
+  */
+ static char *
+ encode_filename(const char *input)
+ {
+ 	static char buf[MAXPGPATH];
+ 	const char *p = input;
+ 	char *op = buf;
+ 	bool replaced_previous;
+ 
+ 	/*
+ 	 * The input filename should be at most 64 bytes (because it comes from the
+ 	 * "name" datatype), so this should never happen.
+ 	 */
+ 	if (strlen(input) >= MAXPGPATH)
+ 		exit_horribly(modulename, "file name too long: \"%s\"\n", input);
+ 
+ 	for (replaced_previous = false;;)
+ 	{
+ 		if (*p == 0)
+ 			break;
+ 
+ 		if (*p >= 'A' && *p <= 'Z')
+ 		{
+ 			*op++ = tolower(*p);
+ 			replaced_previous = false;
+ 		}
+ 		else if ((*p >= 'a' && *p <= 'z') ||
+ 				 (*p >= '0' && *p <= '9') ||
+ 				 *p == '.' || *p == '_' || *p == '-')
+ 		{
+ 			*op++ = *p;
+ 			replaced_previous = false;
+ 		}
+ 		else if (!replaced_previous)
+ 		{
+ 			*op++ = '_';
+ 			replaced_previous = true;
+ 		}
+ 
+ 		p++;
+ 	}
+ 
+ 	*op = '\0';
+ 
+ 	return buf;
+ }
+ 
+ /*
+  * Given a pointer to the start of an identifier, returns a pointer to one
+  * character past that identifier, or NULL if no valid identifier was found.
+  * Also we don't remove any escaped quotes inside quoted identifiers, so the
+  * caller should be prepared to deal with that.  In the (currently) only use of
+  * this function it won't matter, since double quotes will be replaced with a
+  * single underscore when encoding the filename.
+  */
+ static char *
+ skip_identifier(char *buf)
+ {
+ 	char *p = buf;
+ 	bool quoted = false;
+ 
+ 	if (*p == '"')
+ 		quoted = true;
+ 	/* without quotes, the first character needs special treatment */
+ 	else if (!((*p >= 'a' && *p <= 'z') || *p == '_'))
+ 		return NULL;
+ 	p++;
+ 
+ 	for (;;)
+ 	{
+ 		/*
+ 		 * If we're parsing a quoted identifier, stop at a quote unless it's escaped.
+ 		 * Also make sure we don't go past the end of the string.
+ 		 *
+ 		 * Or if we're not parsing a quoted identifier, stop whenever we encounter
+ 		 * any character which would require quotes.  Note that we don't care what
+ 		 * the character is; it's up to the caller to see whether it makes sense to
+ 		 * have that character in there.
+ 		 */
+ 		if (quoted)
+ 		{
+ 			if (*p == '"')
+ 			{
+ 				p++;
+ 				if (*p != '"')
+ 					return p;
+ 			}
+ 			else if (*p == '\0')
+ 				return NULL;
+ 		}
+ 		else if (!((*p >= 'a' && *p <= 'z') ||
+ 				  (*p >= '0' && *p <= '9') ||
+ 				  (*p == '_')))
+ 			return p;
+ 
+ 		p++;
+ 	}
+ }
+ 
+ static TocEntry *
+ find_dependency(ArchiveHandle *AH, TocEntry *te)
+ {
+ 	DumpId depId;
+ 	TocEntry *depte;
+ 
+ 	if (te->nDeps != 1)
+ 		exit_horribly(modulename, "unexpected number of dependencies (%d) for \"%s\" %d\n", te->nDeps, te->desc, te->dumpId);
+ 
+ 	depId = te->dependencies[0];
+ 
+ 	for (depte = te->prev; depte != te; depte = depte->prev)
+ 	{
+ 		if (depte->dumpId == depId)
+ 			return depte;
+ 	}
+ 
+ 	exit_horribly(modulename, "could not find dependency %d for \"%s\" %d\n", depId, te->desc, te->dumpId);
+ }
+ 
+ static char *
+ get_object_filename(ArchiveHandle *AH, TocEntry *te)
+ {
+ 	int i;
+ 	char path[MAXPGPATH];
+ 
+ 	/*
+ 	 * List of object types we can simply dump into  [schema/]OBJTYPE/tag.sql.
+ 	 * The first argument is the object type (te->desc) and the second one is
+ 	 * the subdirectory to dump to.
+ 	 */
+ 	const char * const object_types[][2] =
+ 	{
+ 		{ "AGGREGATE",			"AGGREGATES"		},
+ 		{ "CHECK CONSTRAINT",	"CHECK_CONSTRAINTS" },
+ 		{ "CONSTRAINT",			"CONSTRAINTS"		},
+ 		{ "EXTENSION",			"EXTENSIONS"		},
+ 		{ "FK CONSTRAINT",		"FK_CONSTRAINTS"	},
+ 		{ "INDEX",				"INDEXES"			},
+ 		{ "SEQUENCE",			"SEQUENCES"			},
+ 		{ "OPERATOR CLASS",		"OPERATOR_CLASSES"	},
+ 		{ "OPERATOR FAMILY",	"OPERATOR_FAMILIES"	},
+ 		{ "RULE",				"RULES"				},
+ 		{ "SERVER",				"SERVERS"			},
+ 		{ "TABLE",				"TABLES"			},
+ 		{ "TYPE",				"TYPES"				},
+ 		{ "TRIGGER",			"TRIGGERS"			},
+ 		{ "VIEW",				"VIEWS"				}
+ 	};
+ 
+ 
+ 	if (te->dataDumper)
+ 	{
+ 		if (strcmp(te->desc, "TABLE DATA") == 0)
+ 		{
+ 			snprintf(path, MAXPGPATH, "%s/TABLEDATA/%d.sql", encode_filename(te->namespace), te->dumpId);
+ 			return pg_strdup(path);
+ 		}
+ 		else if (strcmp(te->desc, "BLOBS") == 0)
+ 		{
+ 			/*
+ 			 * Return NULL for BLOBS.  The _*_Blob functions will know how to find the
+ 			 * correct files -- we don't since we don't know the oids yet.
+ 			 */
+ 			return NULL;
+ 		}
+ 
+ 		exit_horribly(modulename, "unknown data object %s\n", te->desc);
+ 	}
+ 
+ 	/*
+ 	 * There's no need to create a database; one should always exist when
+ 	 * restoring.
+ 	 */
+ 	if (strcmp(te->desc, "DATABASE") == 0)
+ 		return NULL;
+ 
+ 	if (strcmp(te->desc, "BLOB") == 0)
+ 	{
+ 		snprintf(path, MAXPGPATH, "blobs.sql");
+ 		return pg_strdup(path);
+ 	}
+ 
+ 	/* for schemas, create the directory before dumping the definition */
+ 	if (strcmp(te->desc, "SCHEMA") == 0)
+ 		create_schema_directory(AH, te->tag);
+ 
+ 	/* schemaless objects which don't depend on anything */
+ 	if (strcmp(te->desc, "COLLATION") == 0 ||
+ 		strcmp(te->desc, "ENCODING") == 0 ||
+ 		strcmp(te->desc, "PROCEDURAL LANGUAGE") == 0 ||
+ 		strcmp(te->desc, "SCHEMA") == 0 ||
+ 		strcmp(te->desc, "STDSTRINGS") == 0)
+ 		return pg_strdup("schemaless.sql");
+ 
+ 	/*
+ 	 * These objects depend on other objects so they can't be put into
+ 	 * schemaless.sql.
+ 	 */
+ 	if (strcmp(te->desc, "CAST") == 0)
+ 		return pg_strdup("casts.sql");
+ 	if (strcmp(te->desc, "CONVERSION") == 0)
+ 		return pg_strdup("conversions.sql");
+ 	if (strcmp(te->desc, "DEFAULT") == 0)
+ 		return pg_strdup("defaults.sql");
+ 	if (strcmp(te->desc, "USER MAPPING") == 0)
+ 		return pg_strdup("user_mappings.sql");
+ 	if (strcmp(te->desc, "OPERATOR") == 0)
+ 	{
+ 		snprintf(path, MAXPGPATH, "%s/operators.sql", encode_filename(te->namespace));
+ 		return pg_strdup(path);
+ 	}
+ 
+ 	/*
+ 	 * These objects should always contain dependency information.  Find the
+ 	 * object we depend  te  depends on, and dump them to the same file.
+ 	 */
+ 	if (strcmp(te->desc, "ACL") == 0 ||
+ 		strcmp(te->desc, "SEQUENCE SET") == 0 ||
+ 		strcmp(te->desc, "SEQUENCE OWNED BY") == 0 ||
+ 		strcmp(te->desc, "COMMENT") == 0)
+ 	{
+ 		TocEntry *depte;
+ 		lclTocEntry *depctx;
+ 
+ 		depte = find_dependency(AH, te);
+ 		depctx = (lclTocEntry *) depte->formatData;
+ 		if (!depctx)
+ 			exit_horribly(modulename, "unexpected NULL formatData\n");
+ 
+ 		/* no need to strdup() */
+ 		return depctx->filename;
+ 	}
+ 
+ 	if (strcmp(te->desc, "AGGREGATE") == 0 ||
+ 		strcmp(te->desc, "FUNCTION") == 0)
+ 	{
+ 		char *buf;
+ 		char *proname;
+ 		char *p;
+ 		char *fname;
+ 		PQExpBuffer temp;
+ 
+ 		/*
+ 		 * Parse the actual function/aggregate name from the DROP statement.  This is
+ 		 * easier than parsing it from the tag since the object name is never quoted
+ 		 * inside the tag so we can't reliably tell where the argument list begins.
+ 		 */
+ 		if (strncmp(te->dropStmt, "DROP FUNCTION ", 14) == 0)
+ 			buf = pg_strdup(te->dropStmt + 14);
+ 		else if (strncmp(te->dropStmt, "DROP AGGREGATE ", 15) == 0)
+ 			buf = pg_strdup(te->dropStmt + 15);
+ 		else
+ 			exit_horribly(modulename, "could not parse DROP statement \"%s\"\n", te->dropStmt);
+ 
+ 		proname = buf;
+ 
+ 		p = skip_identifier(proname);
+ 		if (!p)
+ 			exit_horribly(modulename, "could not parse DROP statement \"%s\"\n", te->dropStmt);
+ 		
+ 		/*
+ 		 * If there's a namespace, ignore it and find the end of the next identifier.
+ 		 * That should be the name of the function.
+ 		 */
+ 		if (*p == '.')
+ 		{
+ 			proname = p + 1;
+ 			p = skip_identifier(proname);
+ 			if (!p)
+ 				exit_horribly(modulename, "could not parse DROP statement \"%s\"\n", te->dropStmt);
+ 		}
+ 
+ 		/* the argument list should be right after the function name */
+ 		if (*p != '(')
+ 			exit_horribly(modulename, "could not parse DROP statement \"%s\"\n", te->dropStmt);
+ 
+ 		/*
+ 		 * Terminate the identifier before the argument list definition, removing
+ 		 * quotes if necessary.
+ 		 */
+ 		if (*proname == '"')
+ 		{
+ 			proname++;
+ 			*(p-1) = '\0';
+ 		}
+ 		else
+ 			*p = '\0';
+ 
+ 		temp = createPQExpBuffer();
+ 		/* must use 2 steps here because encode_filename() is nonreentrant */
+ 		appendPQExpBuffer(temp, "%s/", encode_filename(te->namespace));
+ 		appendPQExpBuffer(temp, "%sS/%s.sql", te->desc, encode_filename(proname));
+ 
+ 		fname = pg_strdup(temp->data);
+ 
+ 		destroyPQExpBuffer(temp);
+ 		free(buf);
+ 
+ 		return fname;
+ 	}
+ 
+ 	/* finally, see if it's any of the objects that require no special treatment */
+ 	for (i = 0; i < sizeof(object_types) / sizeof(object_types[0]); ++i)
+ 	{
+ 		if (strcmp(object_types[i][0], te->desc) == 0)
+ 		{
+ 			const char *objsubdir = object_types[i][1];
+ 
+ 			if (te->namespace)
+ 			{
+ 				/* must use 2 steps here because encode_filename() is nonreentrant */
+ 				char *namespace = pg_strdup(encode_filename(te->namespace));
+ 				snprintf(path, MAXPGPATH, "%s/%s/%s.sql", namespace,
+ 						 objsubdir, encode_filename(te->tag));
+ 				free(namespace);
+ 			}
+ 			else
+ 				snprintf(path, MAXPGPATH, "%s/%s.sql",
+ 						 objsubdir, encode_filename(te->tag));
+ 
+ 			return pg_strdup(path);
+ 		}
+ 	}
+ 
+ 	exit_horribly(modulename, "unknown object type \"%s\"\n", te->desc);
+ }
*** a/src/bin/pg_dump/pg_dump.c
--- b/src/bin/pg_dump/pg_dump.c
***************
*** 806,813 **** help(const char *progname)
  
  	printf(_("\nGeneral options:\n"));
  	printf(_("  -f, --file=FILENAME          output file or directory name\n"));
! 	printf(_("  -F, --format=c|d|t|p         output file format (custom, directory, tar,\n"
! 			 "                               plain text (default))\n"));
  	printf(_("  -v, --verbose                verbose mode\n"));
  	printf(_("  -V, --version                output version information, then exit\n"));
  	printf(_("  -Z, --compress=0-9           compression level for compressed formats\n"));
--- 806,813 ----
  
  	printf(_("\nGeneral options:\n"));
  	printf(_("  -f, --file=FILENAME          output file or directory name\n"));
! 	printf(_("  -F, --format=c|d|t|s|p       output file format (custom, directory, tar,\n"
! 			 "                               split directory, plain text (default))\n"));
  	printf(_("  -v, --verbose                verbose mode\n"));
  	printf(_("  -V, --version                output version information, then exit\n"));
  	printf(_("  -Z, --compress=0-9           compression level for compressed formats\n"));
***************
*** 957,962 **** parseArchiveFormat(const char *format, ArchiveMode *mode)
--- 957,966 ----
  		archiveFormat = archTar;
  	else if (pg_strcasecmp(format, "tar") == 0)
  		archiveFormat = archTar;
+ 	else if (pg_strcasecmp(format, "s") == 0)
+ 		archiveFormat = archSplit;
+ 	else if (pg_strcasecmp(format, "split") == 0)
+ 		archiveFormat = archSplit;
  	else
  		exit_horribly(NULL, "invalid output format \"%s\" specified\n", format);
  	return archiveFormat;