Hi,

Now that the (at least as far as I know) last ordering problem in pg_dump has been solved [1], I'm going to attempt resurrecting this old thread.

It seemed to me that the biggest objections to this patch in the old discussions were directed at the implementation, which I have tried to improve. The attached patch implements the actual splitting in a new backup format.

The general output scheme looks like this:
   schemaname/OBJECT_TYPES/object_name.sql,

but there are some exceptions.

Overloaded functions are dumped into the same file. Object names are encoded into the POSIX Portable Filename Character Set ([a-z0-9._-]) by replacing any characters outside that set with an underscore.

Restoring the dump is supported through an index.sql file containing statements which include (through \i) the actual object files in the dump directory.

Any thoughts?  Objections on the idea or the implementation?


[1]: http://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=7b583b20b1c95acb621c71251150beef958bb603


Regards,
Marko Tiikkaja
*** 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;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to