Create a FETCH_COUNT parameter that causes psql to execute SELECT-like
authorTom Lane
Tue, 29 Aug 2006 22:25:08 +0000 (22:25 +0000)
committerTom Lane
Tue, 29 Aug 2006 22:25:08 +0000 (22:25 +0000)
queries via a cursor, fetching a limited number of rows at a time and
therefore not risking exhausting memory.  A disadvantage of the scheme
is that 'aligned' output mode will align each group of rows independently
leading to odd-looking output, but all the other output formats work
reasonably well.  Chris Mair, with some additional hacking by moi.

doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/common.c
src/bin/psql/print.c
src/bin/psql/print.h
src/bin/psql/settings.h
src/bin/psql/startup.c

index 97809bb7b19aeb62e9308fb5ea4fc78573d76945..d6528d0bc10d14cef586cf345fab8f4664f44891 100644 (file)
@@ -1,5 +1,5 @@
 
 
@@ -2007,6 +2007,33 @@ bar
         
       
 
+      
+        FETCH_COUNT
+        
+        
+        If this variable is set to an integer value > 0,
+        the results of SELECT queries are fetched
+        and displayed in groups of that many rows, rather than the
+        default behavior of collecting the entire result set before
+        display.  Therefore only a
+        limited amount of memory is used, regardless of the size of
+        the result set.  Settings of 100 to 1000 are commonly used
+        when enabling this feature.
+        Keep in mind that when using this feature, a query may
+        fail after having already displayed some rows.
+        
+        
+        
+        Although you can use any output format with this feature,
+        the default aligned format tends to look bad
+        because each group of FETCH_COUNT rows
+        will be formatted separately, leading to varying column
+        widths across the row groups.  The other output formats work better.
+        
+        
+        
+      
+
       
         HISTCONTROL
         
index 3e9d23d1956be60df762e7e02e2d9ccd64873cbb..87e5ebc1fdd3ef2f102b98dbe2452eaf2e725b4e 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2006, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.126 2006/08/29 15:19:50 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.127 2006/08/29 22:25:07 tgl Exp $
  */
 #include "postgres_fe.h"
 #include "common.h"
@@ -28,6 +28,7 @@
 #include "command.h"
 #include "copy.h"
 #include "mb/pg_wchar.h"
+#include "mbprint.h"
 
 
 /* Workarounds for Windows */
@@ -53,7 +54,9 @@ typedef struct _timeb TimevalStruct;
 #endif
 
 
+static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
 static bool command_no_begin(const char *query);
+static bool is_select_command(const char *query);
 
 /*
  * "Safe" wrapper around strdup()
@@ -450,18 +453,15 @@ ResetCancelConn(void)
  * AcceptResult
  *
  * Checks whether a result is valid, giving an error message if necessary;
- * resets cancelConn as needed, and ensures that the connection to the backend
- * is still up.
+ * and ensures that the connection to the backend is still up.
  *
  * Returns true for valid result, false for error state.
  */
 static bool
-AcceptResult(const PGresult *result, const char *query)
+AcceptResult(const PGresult *result)
 {
    bool        OK = true;
 
-   ResetCancelConn();
-
    if (!result)
        OK = false;
    else
@@ -560,7 +560,9 @@ PSQLexec(const char *query, bool start_xact)
 
    res = PQexec(pset.db, query);
 
-   if (!AcceptResult(res, query) && res)
+   ResetCancelConn();
+
+   if (!AcceptResult(res))
    {
        PQclear(res);
        res = NULL;
@@ -602,6 +604,7 @@ PrintQueryTuples(const PGresult *results)
    /* write output to \g argument, if any */
    if (pset.gfname)
    {
+       /* keep this code in sync with ExecQueryUsingCursor */
        FILE       *queryFout_copy = pset.queryFout;
        bool        queryFoutPipe_copy = pset.queryFoutPipe;
 
@@ -782,11 +785,10 @@ bool
 SendQuery(const char *query)
 {
    PGresult   *results;
-   TimevalStruct before,
-               after;
+   PGTransactionStatusType transaction_status;
+   double      elapsed_msec = 0;
    bool        OK,
                on_error_rollback_savepoint = false;
-   PGTransactionStatusType transaction_status;
    static bool on_error_rollback_warning = false;
 
    if (!pset.db)
@@ -869,20 +871,38 @@ SendQuery(const char *query)
        }
    }
 
-   if (pset.timing)
-       GETTIMEOFDAY(&before);
+   if (pset.fetch_count <= 0 || !is_select_command(query))
+   {
+       /* Default fetch-it-all-and-print mode */
+       TimevalStruct before,
+               after;
 
-   results = PQexec(pset.db, query);
+       if (pset.timing)
+           GETTIMEOFDAY(&before);
 
-   /* these operations are included in the timing result: */
-   OK = (AcceptResult(results, query) && ProcessCopyResult(results));
+       results = PQexec(pset.db, query);
 
-   if (pset.timing)
-       GETTIMEOFDAY(&after);
+       /* these operations are included in the timing result: */
+       ResetCancelConn();
+       OK = (AcceptResult(results) && ProcessCopyResult(results));
 
-   /* but printing results isn't: */
-   if (OK)
-       OK = PrintQueryResults(results);
+       if (pset.timing)
+       {
+           GETTIMEOFDAY(&after);
+           elapsed_msec = DIFF_MSEC(&after, &before);
+       }
+
+       /* but printing results isn't: */
+       if (OK)
+           OK = PrintQueryResults(results);
+   }
+   else
+   {
+       /* Fetch-in-segments mode */
+       OK = ExecQueryUsingCursor(query, &elapsed_msec);
+       ResetCancelConn();
+       results = NULL;         /* PQclear(NULL) does nothing */
+   }
 
    /* If we made a temporary savepoint, possibly release/rollback */
    if (on_error_rollback_savepoint)
@@ -904,9 +924,10 @@ SendQuery(const char *query)
             * the user did RELEASE or ROLLBACK, our savepoint is gone. If
             * they issued a SAVEPOINT, releasing ours would remove theirs.
             */
-           if (strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 ||
-               strcmp(PQcmdStatus(results), "RELEASE") == 0 ||
-               strcmp(PQcmdStatus(results), "ROLLBACK") == 0)
+           if (results &&
+               (strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 ||
+                strcmp(PQcmdStatus(results), "RELEASE") == 0 ||
+                strcmp(PQcmdStatus(results), "ROLLBACK") == 0))
                svptres = NULL;
            else
                svptres = PQexec(pset.db, "RELEASE pg_psql_temporary_savepoint");
@@ -927,7 +948,7 @@ SendQuery(const char *query)
 
    /* Possible microtiming output */
    if (OK && pset.timing)
-       printf(_("Time: %.3f ms\n"), DIFF_MSEC(&after, &before));
+       printf(_("Time: %.3f ms\n"), elapsed_msec);
 
    /* check for events that may occur during query execution */
 
@@ -947,6 +968,198 @@ SendQuery(const char *query)
 }
 
 
+/*
+ * ExecQueryUsingCursor: run a SELECT-like query using a cursor
+ *
+ * This feature allows result sets larger than RAM to be dealt with.
+ *
+ * Returns true if the query executed successfully, false otherwise.
+ *
+ * If pset.timing is on, total query time (exclusive of result-printing) is
+ * stored into *elapsed_msec.
+ */
+static bool
+ExecQueryUsingCursor(const char *query, double *elapsed_msec)
+{
+   bool        OK = true;
+   PGresult        *results;
+   PQExpBufferData buf;
+   printQueryOpt my_popt = pset.popt;
+   FILE       *queryFout_copy = pset.queryFout;
+   bool        queryFoutPipe_copy = pset.queryFoutPipe;
+   bool            started_txn = false;
+   bool            did_pager = false;
+   int             ntuples;
+   char            fetch_cmd[64];
+   TimevalStruct before,
+                   after;
+
+   *elapsed_msec = 0;
+
+   /* initialize print options for partial table output */
+   my_popt.topt.start_table = true;
+   my_popt.topt.stop_table = false;
+   my_popt.topt.prior_records = 0;
+
+   if (pset.timing)
+       GETTIMEOFDAY(&before);
+
+   /* if we're not in a transaction, start one */
+   if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
+   {
+       results = PQexec(pset.db, "BEGIN");
+       OK = AcceptResult(results) &&
+           (PQresultStatus(results) == PGRES_COMMAND_OK);
+       PQclear(results);
+       if (!OK)
+           return false;
+       started_txn = true;
+   }
+
+   /* Send DECLARE CURSOR */
+   initPQExpBuffer(&buf);
+   appendPQExpBuffer(&buf, "DECLARE _psql_cursor NO SCROLL CURSOR FOR\n%s",
+                     query);
+
+   results = PQexec(pset.db, buf.data);
+   OK = AcceptResult(results) &&
+       (PQresultStatus(results) == PGRES_COMMAND_OK);
+   PQclear(results);
+   termPQExpBuffer(&buf);
+   if (!OK)
+       goto cleanup;
+
+   if (pset.timing)
+   {
+       GETTIMEOFDAY(&after);
+       *elapsed_msec += DIFF_MSEC(&after, &before);
+   }
+
+   snprintf(fetch_cmd, sizeof(fetch_cmd),
+            "FETCH FORWARD %d FROM _psql_cursor",
+            pset.fetch_count);
+
+   /* prepare to write output to \g argument, if any */
+   if (pset.gfname)
+   {
+       /* keep this code in sync with PrintQueryTuples */
+       pset.queryFout = stdout;    /* so it doesn't get closed */
+
+       /* open file/pipe */
+       if (!setQFout(pset.gfname))
+       {
+           pset.queryFout = queryFout_copy;
+           pset.queryFoutPipe = queryFoutPipe_copy;
+           OK = false;
+           goto cleanup;
+       }
+   }
+
+   for (;;)
+   {
+       if (pset.timing)
+           GETTIMEOFDAY(&before);
+
+       /* get FETCH_COUNT tuples at a time */
+       results = PQexec(pset.db, fetch_cmd);
+       OK = AcceptResult(results) &&
+           (PQresultStatus(results) == PGRES_TUPLES_OK);
+
+       if (pset.timing)
+       {
+           GETTIMEOFDAY(&after);
+           *elapsed_msec += DIFF_MSEC(&after, &before);
+       }
+
+       if (!OK)
+       {
+           PQclear(results);
+           break;
+       }
+
+       ntuples = PQntuples(results);
+
+       if (ntuples < pset.fetch_count)
+       {
+           /* this is the last result set, so allow footer decoration */
+           my_popt.topt.stop_table = true;
+       }
+       else if (pset.queryFout == stdout && !did_pager)
+       {
+           /*
+            * If query requires multiple result sets, hack to ensure that
+            * only one pager instance is used for the whole mess
+            */
+           pset.queryFout = PageOutput(100000, my_popt.topt.pager);
+           did_pager = true;
+       }
+
+       printQuery(results, &my_popt, pset.queryFout, pset.logfile);
+
+       /* after the first result set, disallow header decoration */
+       my_popt.topt.start_table = false;
+       my_popt.topt.prior_records += ntuples;
+
+       PQclear(results);
+
+       if (ntuples < pset.fetch_count || cancel_pressed)
+           break;
+   }
+
+   /* close \g argument file/pipe, restore old setting */
+   if (pset.gfname)
+   {
+       /* keep this code in sync with PrintQueryTuples */
+       setQFout(NULL);
+
+       pset.queryFout = queryFout_copy;
+       pset.queryFoutPipe = queryFoutPipe_copy;
+
+       free(pset.gfname);
+       pset.gfname = NULL;
+   }
+   else if (did_pager)
+   {
+       ClosePager(pset.queryFout);
+       pset.queryFout = queryFout_copy;
+       pset.queryFoutPipe = queryFoutPipe_copy;
+   }
+
+cleanup:
+   if (pset.timing)
+       GETTIMEOFDAY(&before);
+
+   /*
+    * We try to close the cursor on either success or failure, but on
+    * failure ignore the result (it's probably just a bleat about
+    * being in an aborted transaction)
+    */
+   results = PQexec(pset.db, "CLOSE _psql_cursor");
+   if (OK)
+   {
+       OK = AcceptResult(results) &&
+           (PQresultStatus(results) == PGRES_COMMAND_OK);
+   }
+   PQclear(results);
+
+   if (started_txn)
+   {
+       results = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
+       OK &= AcceptResult(results) &&
+           (PQresultStatus(results) == PGRES_COMMAND_OK);
+       PQclear(results);
+   }
+
+   if (pset.timing)
+   {
+       GETTIMEOFDAY(&after);
+       *elapsed_msec += DIFF_MSEC(&after, &before);
+   }
+
+   return OK;
+}
+
+
 /*
  * Advance the given char pointer over white space and SQL comments.
  */
@@ -1158,6 +1371,43 @@ command_no_begin(const char *query)
 }
 
 
+/*
+ * Check whether the specified command is a SELECT (or VALUES).
+ */
+static bool
+is_select_command(const char *query)
+{
+   int         wordlen;
+
+   /*
+    * First advance over any whitespace, comments and left parentheses.
+    */
+   for (;;)
+   {
+       query = skip_white_space(query);
+       if (query[0] == '(')
+           query++;
+       else
+           break;
+   }
+
+   /*
+    * Check word length (since "selectx" is not "select").
+    */
+   wordlen = 0;
+   while (isalpha((unsigned char) query[wordlen]))
+       wordlen += PQmblen(&query[wordlen], pset.encoding);
+
+   if (wordlen == 6 && pg_strncasecmp(query, "select", 6) == 0)
+       return true;
+   
+   if (wordlen == 6 && pg_strncasecmp(query, "values", 6) == 0)
+       return true;
+
+   return false;
+}
+
+
 /*
  * Test if the current user is a database superuser.
  *
index 9674acf8b0077bac36053c89163f687772f2846b..01f47e43b4fea0875bb4134e67e805ff13a21e36 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2006, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/print.c,v 1.88 2006/07/14 14:52:26 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/print.c,v 1.89 2006/08/29 22:25:07 tgl Exp $
  *
  * Note: we include postgres.h not postgres_fe.h so that we can include
  * catalog/pg_type.h, and thereby have access to INT4OID and similar macros.
@@ -175,10 +175,13 @@ format_numeric_locale(const char *my_str)
 static void
 print_unaligned_text(const char *title, const char *const * headers,
                     const char *const * cells, const char *const * footers,
-                    const char *opt_align, const char *opt_fieldsep,
-                    const char *opt_recordsep, bool opt_tuples_only,
-                    bool opt_numeric_locale, FILE *fout)
+                    const char *opt_align, const printTableOpt *opt,
+                    FILE *fout)
 {
+   const char *opt_fieldsep = opt->fieldSep;
+   const char *opt_recordsep = opt->recordSep;
+   bool opt_tuples_only = opt->tuples_only;
+   bool opt_numeric_locale = opt->numericLocale;
    unsigned int col_count = 0;
    unsigned int i;
    const char *const * ptr;
@@ -192,27 +195,33 @@ print_unaligned_text(const char *title, const char *const * headers,
    if (!opt_recordsep)
        opt_recordsep = "";
 
-   /* print title */
-   if (!opt_tuples_only && title)
-       fprintf(fout, "%s%s", title, opt_recordsep);
-
-   /* print headers and count columns */
+   /* count columns */
    for (ptr = headers; *ptr; ptr++)
-   {
        col_count++;
+
+   if (opt->start_table)
+   {
+       /* print title */
+       if (!opt_tuples_only && title)
+           fprintf(fout, "%s%s", title, opt_recordsep);
+
+       /* print headers */
        if (!opt_tuples_only)
        {
-           if (col_count > 1)
-               fputs(opt_fieldsep, fout);
-           fputs(*ptr, fout);
+           for (ptr = headers; *ptr; ptr++)
+           {
+               if (ptr != headers)
+                   fputs(opt_fieldsep, fout);
+               fputs(*ptr, fout);
+           }
+           need_recordsep = true;
        }
    }
-   if (!opt_tuples_only)
+   else                        /* assume continuing printout */
        need_recordsep = true;
 
    /* print cells */
-   i = 0;
-   for (ptr = cells; *ptr; ptr++)
+   for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
        if (need_recordsep)
        {
@@ -235,40 +244,44 @@ print_unaligned_text(const char *title, const char *const * headers,
            fputs(opt_fieldsep, fout);
        else
            need_recordsep = true;
-       i++;
    }
 
    /* print footers */
-
-   if (!opt_tuples_only && footers && !cancel_pressed)
-       for (ptr = footers; *ptr; ptr++)
-       {
-           if (need_recordsep)
+   if (opt->stop_table)
+   {
+       if (!opt_tuples_only && footers && !cancel_pressed)
+           for (ptr = footers; *ptr; ptr++)
            {
-               fputs(opt_recordsep, fout);
-               need_recordsep = false;
+               if (need_recordsep)
+               {
+                   fputs(opt_recordsep, fout);
+                   need_recordsep = false;
+               }
+               fputs(*ptr, fout);
+               need_recordsep = true;
            }
-           fputs(*ptr, fout);
-           need_recordsep = true;
-       }
 
-   /* the last record needs to be concluded with a newline */
-   if (need_recordsep)
-       fputc('\n', fout);
+       /* the last record needs to be concluded with a newline */
+       if (need_recordsep)
+           fputc('\n', fout);
+   }
 }
 
 
-
 static void
 print_unaligned_vertical(const char *title, const char *const * headers,
                         const char *const * cells,
                         const char *const * footers, const char *opt_align,
-                        const char *opt_fieldsep, const char *opt_recordsep,
-                  bool opt_tuples_only, bool opt_numeric_locale, FILE *fout)
+                        const printTableOpt *opt, FILE *fout)
 {
+   const char *opt_fieldsep = opt->fieldSep;
+   const char *opt_recordsep = opt->recordSep;
+   bool opt_tuples_only = opt->tuples_only;
+   bool opt_numeric_locale = opt->numericLocale;
    unsigned int col_count = 0;
    unsigned int i;
    const char *const * ptr;
+   bool        need_recordsep = false;
 
    if (cancel_pressed)
        return;
@@ -278,22 +291,31 @@ print_unaligned_vertical(const char *title, const char *const * headers,
    if (!opt_recordsep)
        opt_recordsep = "";
 
-   /* print title */
-   if (!opt_tuples_only && title)
-       fputs(title, fout);
-
    /* count columns */
    for (ptr = headers; *ptr; ptr++)
        col_count++;
 
+   if (opt->start_table)
+   {
+       /* print title */
+       if (!opt_tuples_only && title)
+       {
+           fputs(title, fout);
+           need_recordsep = true;
+       }
+   }
+   else                        /* assume continuing printout */
+       need_recordsep = true;
+
    /* print records */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
-       if (i != 0 || (!opt_tuples_only && title))
+       if (need_recordsep)
        {
+           /* record separator is 2 occurrences of recordsep in this mode */
+           fputs(opt_recordsep, fout);
            fputs(opt_recordsep, fout);
-           if (i % col_count == 0)
-               fputs(opt_recordsep, fout);     /* another one */
+           need_recordsep = false;
            if (cancel_pressed)
                break;
        }
@@ -309,24 +331,31 @@ print_unaligned_vertical(const char *title, const char *const * headers,
        }
        else
            fputs(*ptr, fout);
+
+       if ((i + 1) % col_count)
+           fputs(opt_recordsep, fout);
+       else
+           need_recordsep = true;
    }
 
-   /* print footers */
-   if (!opt_tuples_only && footers && *footers && !cancel_pressed)
+   if (opt->stop_table)
    {
-       fputs(opt_recordsep, fout);
-       for (ptr = footers; *ptr; ptr++)
+       /* print footers */
+       if (!opt_tuples_only && footers && *footers && !cancel_pressed)
        {
            fputs(opt_recordsep, fout);
-           fputs(*ptr, fout);
+           for (ptr = footers; *ptr; ptr++)
+           {
+               fputs(opt_recordsep, fout);
+               fputs(*ptr, fout);
+           }
        }
-   }
 
-   fputc('\n', fout);
+       fputc('\n', fout);
+   }
 }
 
 
-
 /********************/
 /* Aligned text        */
 /********************/
@@ -367,14 +396,16 @@ _print_horizontal_line(const unsigned int col_count, const unsigned int *widths,
 }
 
 
-
 static void
 print_aligned_text(const char *title, const char *const * headers,
                   const char *const * cells, const char *const * footers,
-       const char *opt_align, bool opt_tuples_only, bool opt_numeric_locale,
-                  unsigned short int opt_border, int encoding,
+                  const char *opt_align, const printTableOpt *opt,
                   FILE *fout)
 {
+   bool opt_tuples_only = opt->tuples_only;
+   bool opt_numeric_locale = opt->numericLocale;
+   unsigned short int opt_border = opt->border;
+   int encoding = opt->encoding;
    unsigned int col_count = 0;
    unsigned int cell_count = 0;
    unsigned int i;
@@ -395,6 +426,9 @@ print_aligned_text(const char *title, const char *const * headers,
    if (cancel_pressed)
        return;
 
+   if (opt_border > 2)
+       opt_border = 2;
+
    /* count columns */
    for (ptr = headers; *ptr; ptr++)
        col_count++;
@@ -407,7 +441,6 @@ print_aligned_text(const char *title, const char *const * headers,
        format_space = pg_local_calloc(col_count, sizeof(*format_space));
        format_buf = pg_local_calloc(col_count, sizeof(*format_buf));
        complete = pg_local_calloc(col_count, sizeof(*complete));
-   
    }
    else
    {
@@ -496,80 +529,83 @@ print_aligned_text(const char *title, const char *const * headers,
    }
    else
        lineptr_list = NULL;
-           
-   /* print title */
-   if (title && !opt_tuples_only)
-   {
-       /* Get width & height */
-       int height;
-       pg_wcssize((unsigned char *)title, strlen(title), encoding, &tmp, &height, NULL);
-       if (tmp >= total_w)
-           fprintf(fout, "%s\n", title);
-       else
-           fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title);
-   }
 
-   /* print headers */
-   if (!opt_tuples_only)
+   if (opt->start_table)
    {
-       int cols_todo;
-       int line_count;
-       
-       if (opt_border == 2)
-           _print_horizontal_line(col_count, widths, opt_border, fout);
+       /* print title */
+       if (title && !opt_tuples_only)
+       {
+           /* Get width & height */
+           int height;
+           pg_wcssize((unsigned char *)title, strlen(title), encoding, &tmp, &height, NULL);
+           if (tmp >= total_w)
+               fprintf(fout, "%s\n", title);
+           else
+               fprintf(fout, "%-*s%s\n", (total_w - tmp) / 2, "", title);
+       }
 
-       for (i = 0; i < col_count; i++)
-           pg_wcsformat((unsigned char *)headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]);
-   
-       cols_todo = col_count;
-       line_count = 0;
-       memset(complete, 0, col_count*sizeof(int));
-       while (cols_todo)
+       /* print headers */
+       if (!opt_tuples_only)
        {
+           int cols_todo;
+           int line_count;
+       
            if (opt_border == 2)
-               fprintf(fout, "|%c", line_count ? '+' : ' ');
-           else if (opt_border == 1)
-               fputc(line_count ? '+' : ' ', fout);
+               _print_horizontal_line(col_count, widths, opt_border, fout);
 
            for (i = 0; i < col_count; i++)
+               pg_wcsformat((unsigned char *)headers[i], strlen(headers[i]), encoding, col_lineptrs[i], heights[i]);
+   
+           cols_todo = col_count;
+           line_count = 0;
+           memset(complete, 0, col_count*sizeof(int));
+           while (cols_todo)
            {
-               unsigned int nbspace;
+               if (opt_border == 2)
+                   fprintf(fout, "|%c", line_count ? '+' : ' ');
+               else if (opt_border == 1)
+                   fputc(line_count ? '+' : ' ', fout);
 
-               struct lineptr *this_line = col_lineptrs[i] + line_count;
-               if (!complete[i])
+               for (i = 0; i < col_count; i++)
                {
-                   nbspace = widths[i] - this_line->width;
-
-                   /* centered */
-                   fprintf(fout, "%-*s%s%-*s",
-                           nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, "");
+                   unsigned int nbspace;
 
-                   if (line_count == (heights[i]-1) || !(this_line+1)->ptr)
+                   struct lineptr *this_line = col_lineptrs[i] + line_count;
+                   if (!complete[i])
                    {
-                       cols_todo--;
-                       complete[i] = 1;
+                       nbspace = widths[i] - this_line->width;
+
+                       /* centered */
+                       fprintf(fout, "%-*s%s%-*s",
+                               nbspace / 2, "", this_line->ptr, (nbspace + 1) / 2, "");
+
+                       if (line_count == (heights[i]-1) || !(this_line+1)->ptr)
+                       {
+                           cols_todo--;
+                           complete[i] = 1;
+                       }
                    }
-               }
-               else
-                   fprintf(fout, "%*s", widths[i], "");
-               if (i < col_count - 1)
-               {
-                   if (opt_border == 0)
-                       fputc(line_count ? '+' : ' ', fout);
                    else
-                       fprintf(fout, " |%c", line_count ? '+' : ' ');
+                       fprintf(fout, "%*s", widths[i], "");
+                   if (i < col_count - 1)
+                   {
+                       if (opt_border == 0)
+                           fputc(line_count ? '+' : ' ', fout);
+                       else
+                           fprintf(fout, " |%c", line_count ? '+' : ' ');
+                   }
                }
+               line_count++;
+
+               if (opt_border == 2)
+                   fputs(" |", fout);
+               else if (opt_border == 1)
+                   fputc(' ', fout);;
+               fputc('\n', fout);
            }
-           line_count++;
 
-           if (opt_border == 2)
-               fputs(" |", fout);
-           else if (opt_border == 1)
-               fputc(' ', fout);;
-           fputc('\n', fout);
+           _print_horizontal_line(col_count, widths, opt_border, fout);
        }
-
-       _print_horizontal_line(col_count, widths, opt_border, fout);
    }
 
    /* print cells */
@@ -658,21 +694,24 @@ print_aligned_text(const char *title, const char *const * headers,
        }
    }
 
-   if (opt_border == 2 && !cancel_pressed)
-       _print_horizontal_line(col_count, widths, opt_border, fout);
+   if (opt->stop_table)
+   {
+       if (opt_border == 2 && !cancel_pressed)
+           _print_horizontal_line(col_count, widths, opt_border, fout);
 
-   /* print footers */
-   if (footers && !opt_tuples_only && !cancel_pressed)
-       for (ptr = footers; *ptr; ptr++)
-           fprintf(fout, "%s\n", *ptr);
+       /* print footers */
+       if (footers && !opt_tuples_only && !cancel_pressed)
+           for (ptr = footers; *ptr; ptr++)
+               fprintf(fout, "%s\n", *ptr);
 
+       /*
+        * for some reason MinGW (and MSVC) outputs an extra newline,
+        * so this suppresses it
+        */
 #ifndef WIN32
-
-   /*
-    * for some reason MinGW (and MSVC) outputs an extra newline, so this supresses it
-    */
-   fputc('\n', fout);
+       fputc('\n', fout);
 #endif
+   }
 
    /* clean up */
    free(widths);
@@ -687,16 +726,18 @@ print_aligned_text(const char *title, const char *const * headers,
 }
 
 
-
 static void
 print_aligned_vertical(const char *title, const char *const * headers,
                       const char *const * cells, const char *const * footers,
-                      const char *opt_align, bool opt_tuples_only,
-                      bool opt_numeric_locale, unsigned short int opt_border,
-                      int encoding, FILE *fout)
+                      const char *opt_align, const printTableOpt *opt,
+                      FILE *fout)
 {
+   bool opt_tuples_only = opt->tuples_only;
+   bool opt_numeric_locale = opt->numericLocale;
+   unsigned short int opt_border = opt->border;
+   int encoding = opt->encoding;
    unsigned int col_count = 0;
-   unsigned int record = 1;
+   unsigned long record = opt->prior_records + 1;
    const char *const * ptr;
    unsigned int i,
                hwidth = 0,
@@ -712,14 +753,17 @@ print_aligned_vertical(const char *title, const char *const * headers,
 
    if (cancel_pressed)
        return;
+
+   if (opt_border > 2)
+       opt_border = 2;
    
-   if (cells[0] == NULL)
+   if (cells[0] == NULL && opt->start_table && opt->stop_table)
    {
        fprintf(fout, _("(No rows)\n"));
        return;
    }
 
-   /* count headers and find longest one */
+   /* count columns */
    for (ptr = headers; *ptr; ptr++)
        col_count++;
 
@@ -767,10 +811,6 @@ print_aligned_vertical(const char *title, const char *const * headers,
    
    dlineptr->ptr = pg_local_malloc(dformatsize);
    hlineptr->ptr = pg_local_malloc(hformatsize);
-   
-   /* print title */
-   if (!opt_tuples_only && title)
-       fprintf(fout, "%s\n", title);
 
    /* make horizontal border */
    divider = pg_local_malloc(hwidth + dwidth + 10);
@@ -788,6 +828,13 @@ print_aligned_vertical(const char *title, const char *const * headers,
    if (opt_border == 2)
        strcat(divider, "-+");
 
+   if (opt->start_table)
+   {
+       /* print title */
+       if (!opt_tuples_only && title)
+           fprintf(fout, "%s\n", title);
+   }
+
    /* print records */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
@@ -799,13 +846,13 @@ print_aligned_vertical(const char *title, const char *const * headers,
                break;
            if (!opt_tuples_only)
            {
-               char       *record_str = pg_local_malloc(32);
+               char        record_str[64];
                size_t      record_str_len;
 
                if (opt_border == 0)
-                   snprintf(record_str, 32, "* Record %d", record++);
+                   snprintf(record_str, 64, "* Record %lu", record++);
                else
-                   snprintf(record_str, 32, "[ RECORD %d ]", record++);
+                   snprintf(record_str, 64, "[ RECORD %lu ]", record++);
                record_str_len = strlen(record_str);
 
                if (record_str_len + opt_border > strlen(divider))
@@ -824,9 +871,8 @@ print_aligned_vertical(const char *title, const char *const * headers,
                    fprintf(fout, "%s\n", div_copy);
                    free(div_copy);
                }
-               free(record_str);
            }
-           else if (i != 0 || opt_border == 2)
+           else if (i != 0 || !opt->start_table || opt_border == 2)
                fprintf(fout, "%s\n", divider);
        }
 
@@ -893,20 +939,23 @@ print_aligned_vertical(const char *title, const char *const * headers,
        }
    }
 
-   if (opt_border == 2 && !cancel_pressed)
-       fprintf(fout, "%s\n", divider);
+   if (opt->stop_table)
+   {
+       if (opt_border == 2 && !cancel_pressed)
+           fprintf(fout, "%s\n", divider);
 
-   /* print footers */
+       /* print footers */
+       if (!opt_tuples_only && footers && *footers && !cancel_pressed)
+       {
+           if (opt_border < 2)
+               fputc('\n', fout);
+           for (ptr = footers; *ptr; ptr++)
+               fprintf(fout, "%s\n", *ptr);
+       }
 
-   if (!opt_tuples_only && footers && *footers && !cancel_pressed)
-   {
-       if (opt_border < 2)
-           fputc('\n', fout);
-       for (ptr = footers; *ptr; ptr++)
-           fprintf(fout, "%s\n", *ptr);
+       fputc('\n', fout);
    }
 
-   fputc('\n', fout);
    free(divider);
    free(hlineptr->ptr);
    free(dlineptr->ptr);
@@ -915,9 +964,6 @@ print_aligned_vertical(const char *title, const char *const * headers,
 }
 
 
-
-
-
 /**********************/
 /* HTML printing ******/
 /**********************/
@@ -964,14 +1010,16 @@ html_escaped_print(const char *in, FILE *fout)
 }
 
 
-
 static void
 print_html_text(const char *title, const char *const * headers,
                const char *const * cells, const char *const * footers,
-               const char *opt_align, bool opt_tuples_only,
-               bool opt_numeric_locale, unsigned short int opt_border,
-               const char *opt_table_attr, FILE *fout)
+               const char *opt_align, const printTableOpt *opt,
+               FILE *fout)
 {
+   bool opt_tuples_only = opt->tuples_only;
+   bool opt_numeric_locale = opt->numericLocale;
+   unsigned short int opt_border = opt->border;
+   const char *opt_table_attr = opt->tableAttr;
    unsigned int col_count = 0;
    unsigned int i;
    const char *const * ptr;
@@ -979,34 +1027,38 @@ print_html_text(const char *title, const char *const * headers,
    if (cancel_pressed)
        return;
 
-   fprintf(fout, "
-   if (opt_table_attr)
-       fprintf(fout, " %s", opt_table_attr);
-   fputs(">\n", fout);
+   /* count columns */
+   for (ptr = headers; *ptr; ptr++)
+       col_count++;
 
-   /* print title */
-   if (!opt_tuples_only && title)
+   if (opt->start_table)
    {
-       fputs("  ", fout);
-       html_escaped_print(title, fout);
-       fputs("\n", fout);
-   }
+       fprintf(fout, ");
+       if (opt_table_attr)
+           fprintf(fout, " %s", opt_table_attr);
+       fputs(">\n", fout);
 
-   /* print headers and count columns */
-   if (!opt_tuples_only)
-       fputs("  \n", fout);
-   for (i = 0, ptr = headers; *ptr; i++, ptr++)
-   {
-       col_count++;
+       /* print title */
+       if (!opt_tuples_only && title)
+       {
+           fputs("  ", fout);
+           html_escaped_print(title, fout);
+           fputs("\n", fout);
+       }
+
+       /* print headers */
        if (!opt_tuples_only)
        {
-           fputs("    ", fout);
-           html_escaped_print(*ptr, fout);
-           fputs("\n", fout);
+           fputs("  \n", fout);
+           for (ptr = headers; *ptr; ptr++)
+           {
+               fputs("    ", fout);
+               html_escaped_print(*ptr, fout);
+               fputs("\n", fout);
+           }
+           fputs("  \n", fout);
        }
    }
-   if (!opt_tuples_only)
-       fputs("  \n", fout);
 
    /* print cells */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
@@ -1038,57 +1090,65 @@ print_html_text(const char *title, const char *const * headers,
            fputs("  \n", fout);
    }
 
-   fputs("\n", fout);
-
-   /* print footers */
-
-   if (!opt_tuples_only && footers && *footers && !cancel_pressed)
+   if (opt->stop_table)
    {
-       fputs("

", fout);

-       for (ptr = footers; *ptr; ptr++)
+       fputs("\n", fout);
+
+       /* print footers */
+       if (!opt_tuples_only && footers && *footers && !cancel_pressed)
        {
-           html_escaped_print(*ptr, fout);
-           fputs("\n", fout);
+           fputs("

", fout);

+           for (ptr = footers; *ptr; ptr++)
+           {
+               html_escaped_print(*ptr, fout);
+               fputs("\n", fout);
+           }
+           fputs("

", fout);
        }
-       fputs("

", fout);
+
+       fputc('\n', fout);
    }
-   fputc('\n', fout);
 }
 
 
-
 static void
 print_html_vertical(const char *title, const char *const * headers,
                    const char *const * cells, const char *const * footers,
-                   const char *opt_align, bool opt_tuples_only,
-                   bool opt_numeric_locale, unsigned short int opt_border,
-                   const char *opt_table_attr, FILE *fout)
+                   const char *opt_align, const printTableOpt *opt,
+                   FILE *fout)
 {
+   bool opt_tuples_only = opt->tuples_only;
+   bool opt_numeric_locale = opt->numericLocale;
+   unsigned short int opt_border = opt->border;
+   const char *opt_table_attr = opt->tableAttr;
    unsigned int col_count = 0;
+   unsigned long record = opt->prior_records + 1;
    unsigned int i;
-   unsigned int record = 1;
    const char *const * ptr;
 
    if (cancel_pressed)
        return;
 
-   fprintf(fout, "
-   if (opt_table_attr)
-       fprintf(fout, " %s", opt_table_attr);
-   fputs(">\n", fout);
-
-   /* print title */
-   if (!opt_tuples_only && title)
-   {
-       fputs("  ", fout);
-       html_escaped_print(title, fout);
-       fputs("\n", fout);
-   }
-
    /* count columns */
    for (ptr = headers; *ptr; ptr++)
        col_count++;
 
+   if (opt->start_table)
+   {
+       fprintf(fout, "
+       if (opt_table_attr)
+           fprintf(fout, " %s", opt_table_attr);
+       fputs(">\n", fout);
+
+       /* print title */
+       if (!opt_tuples_only && title)
+       {
+           fputs("  ", fout);
+           html_escaped_print(title, fout);
+           fputs("\n", fout);
+       }
+   }
+
    /* print records */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
@@ -1097,7 +1157,9 @@ print_html_vertical(const char *title, const char *const * headers,
            if (cancel_pressed)
                break;
            if (!opt_tuples_only)
-               fprintf(fout, "\n  Record %d\n", record++);
+               fprintf(fout,
+                       "\n  Record %lu\n",
+                       record++);
            else
                fputs("\n   \n", fout);
        }
@@ -1123,26 +1185,29 @@ print_html_vertical(const char *title, const char *const * headers,
        fputs("\n  \n", fout);
    }
 
-   fputs("\n", fout);
-
-   /* print footers */
-   if (!opt_tuples_only && footers && *footers && !cancel_pressed)
+   if (opt->stop_table)
    {
-       fputs("

", fout);

-       for (ptr = footers; *ptr; ptr++)
+       fputs("\n", fout);
+
+       /* print footers */
+       if (!opt_tuples_only && footers && *footers && !cancel_pressed)
        {
-           html_escaped_print(*ptr, fout);
-           fputs("\n", fout);
+           fputs("

", fout);

+           for (ptr = footers; *ptr; ptr++)
+           {
+               html_escaped_print(*ptr, fout);
+               fputs("\n", fout);
+           }
+           fputs("

", fout);
        }
-       fputs("

", fout);
+
+       fputc('\n', fout);
    }
-   fputc('\n', fout);
 }
 
 
-
 /*************************/
-/* LaTeX        */
+/* LaTeX                */
 /*************************/
 
 
@@ -1184,14 +1249,15 @@ latex_escaped_print(const char *in, FILE *fout)
 }
 
 
-
 static void
 print_latex_text(const char *title, const char *const * headers,
                 const char *const * cells, const char *const * footers,
-                const char *opt_align, bool opt_tuples_only,
-                bool opt_numeric_locale, unsigned short int opt_border,
+                const char *opt_align, const printTableOpt *opt,
                 FILE *fout)
 {
+   bool opt_tuples_only = opt->tuples_only;
+   bool opt_numeric_locale = opt->numericLocale;
+   unsigned short int opt_border = opt->border;
    unsigned int col_count = 0;
    unsigned int i;
    const char *const * ptr;
@@ -1199,56 +1265,58 @@ print_latex_text(const char *title, const char *const * headers,
    if (cancel_pressed)
        return;
 
-   /* print title */
-   if (!opt_tuples_only && title)
-   {
-       fputs("\\begin{center}\n", fout);
-       latex_escaped_print(title, fout);
-       fputs("\n\\end{center}\n\n", fout);
-   }
+   if (opt_border > 2)
+       opt_border = 2;
 
    /* count columns */
    for (ptr = headers; *ptr; ptr++)
        col_count++;
 
-   /* begin environment and set alignments and borders */
-   fputs("\\begin{tabular}{", fout);
-
-   if (opt_border == 2)
-       fputs("| ", fout);
-   for (i = 0; i < col_count; i++)
+   if (opt->start_table)
    {
-       fputc(*(opt_align + i), fout);
-       if (opt_border != 0 && i < col_count - 1)
-           fputs(" | ", fout);
-   }
-   if (opt_border == 2)
-       fputs(" |", fout);
+       /* print title */
+       if (!opt_tuples_only && title)
+       {
+           fputs("\\begin{center}\n", fout);
+           latex_escaped_print(title, fout);
+           fputs("\n\\end{center}\n\n", fout);
+       }
 
-   fputs("}\n", fout);
+       /* begin environment and set alignments and borders */
+       fputs("\\begin{tabular}{", fout);
 
-   if (!opt_tuples_only && opt_border == 2)
-       fputs("\\hline\n", fout);
+       if (opt_border == 2)
+           fputs("| ", fout);
+       for (i = 0; i < col_count; i++)
+       {
+           fputc(*(opt_align + i), fout);
+           if (opt_border != 0 && i < col_count - 1)
+               fputs(" | ", fout);
+       }
+       if (opt_border == 2)
+           fputs(" |", fout);
 
-   /* print headers and count columns */
-   for (i = 0, ptr = headers; i < col_count; i++, ptr++)
-   {
+       fputs("}\n", fout);
+
+       if (!opt_tuples_only && opt_border == 2)
+           fputs("\\hline\n", fout);
+
+       /* print headers */
        if (!opt_tuples_only)
        {
-           if (i != 0)
-               fputs(" & ", fout);
-           fputs("\\textit{", fout);
-           latex_escaped_print(*ptr, fout);
-           fputc('}', fout);
+           for (i = 0, ptr = headers; i < col_count; i++, ptr++)
+           {
+               if (i != 0)
+                   fputs(" & ", fout);
+               fputs("\\textit{", fout);
+               latex_escaped_print(*ptr, fout);
+               fputc('}', fout);
+           }
+           fputs(" \\\\\n", fout);
+           fputs("\\hline\n", fout);
        }
    }
 
-   if (!opt_tuples_only)
-   {
-       fputs(" \\\\\n", fout);
-       fputs("\\hline\n", fout);
-   }
-
    /* print cells */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
@@ -1272,66 +1340,74 @@ print_latex_text(const char *title, const char *const * headers,
            fputs(" & ", fout);
    }
 
-   if (opt_border == 2)
-       fputs("\\hline\n", fout);
-
-   fputs("\\end{tabular}\n\n\\noindent ", fout);
-
+   if (opt->stop_table)
+   {
+       if (opt_border == 2)
+           fputs("\\hline\n", fout);
 
-   /* print footers */
+       fputs("\\end{tabular}\n\n\\noindent ", fout);
 
-   if (footers && !opt_tuples_only && !cancel_pressed)
-       for (ptr = footers; *ptr; ptr++)
+       /* print footers */
+       if (footers && !opt_tuples_only && !cancel_pressed)
        {
-           latex_escaped_print(*ptr, fout);
-           fputs(" \\\\\n", fout);
+           for (ptr = footers; *ptr; ptr++)
+           {
+               latex_escaped_print(*ptr, fout);
+               fputs(" \\\\\n", fout);
+           }
        }
 
-   fputc('\n', fout);
+       fputc('\n', fout);
+   }
 }
 
 
-
 static void
 print_latex_vertical(const char *title, const char *const * headers,
                     const char *const * cells, const char *const * footers,
-                    const char *opt_align, bool opt_tuples_only,
-                    bool opt_numeric_locale, unsigned short int opt_border,
+                    const char *opt_align, const printTableOpt *opt,
                     FILE *fout)
 {
+   bool opt_tuples_only = opt->tuples_only;
+   bool opt_numeric_locale = opt->numericLocale;
+   unsigned short int opt_border = opt->border;
    unsigned int col_count = 0;
+   unsigned long record = opt->prior_records + 1;
    unsigned int i;
    const char *const * ptr;
-   unsigned int record = 1;
 
    (void) opt_align;           /* currently unused parameter */
 
    if (cancel_pressed)
        return;
 
-   /* print title */
-   if (!opt_tuples_only && title)
-   {
-       fputs("\\begin{center}\n", fout);
-       latex_escaped_print(title, fout);
-       fputs("\n\\end{center}\n\n", fout);
-   }
-
-   /* begin environment and set alignments and borders */
-   fputs("\\begin{tabular}{", fout);
-   if (opt_border == 0)
-       fputs("cl", fout);
-   else if (opt_border == 1)
-       fputs("c|l", fout);
-   else if (opt_border == 2)
-       fputs("|c|l|", fout);
-   fputs("}\n", fout);
-
+   if (opt_border > 2)
+       opt_border = 2;
 
    /* count columns */
    for (ptr = headers; *ptr; ptr++)
        col_count++;
 
+   if (opt->start_table)
+   {
+       /* print title */
+       if (!opt_tuples_only && title)
+       {
+           fputs("\\begin{center}\n", fout);
+           latex_escaped_print(title, fout);
+           fputs("\n\\end{center}\n\n", fout);
+       }
+
+       /* begin environment and set alignments and borders */
+       fputs("\\begin{tabular}{", fout);
+       if (opt_border == 0)
+           fputs("cl", fout);
+       else if (opt_border == 1)
+           fputs("c|l", fout);
+       else if (opt_border == 2)
+           fputs("|c|l|", fout);
+       fputs("}\n", fout);
+   }
 
    /* print records */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
@@ -1346,10 +1422,10 @@ print_latex_vertical(const char *title, const char *const * headers,
                if (opt_border == 2)
                {
                    fputs("\\hline\n", fout);
-                   fprintf(fout, "\\multicolumn{2}{|c|}{\\textit{Record %d}} \\\\\n", record++);
+                   fprintf(fout, "\\multicolumn{2}{|c|}{\\textit{Record %lu}} \\\\\n", record++);
                }
                else
-                   fprintf(fout, "\\multicolumn{2}{c}{\\textit{Record %d}} \\\\\n", record++);
+                   fprintf(fout, "\\multicolumn{2}{c}{\\textit{Record %lu}} \\\\\n", record++);
            }
            if (opt_border >= 1)
                fputs("\\hline\n", fout);
@@ -1361,34 +1437,36 @@ print_latex_vertical(const char *title, const char *const * headers,
        fputs(" \\\\\n", fout);
    }
 
-   if (opt_border == 2)
-       fputs("\\hline\n", fout);
-
-   fputs("\\end{tabular}\n\n\\noindent ", fout);
-
+   if (opt->stop_table)
+   {
+       if (opt_border == 2)
+           fputs("\\hline\n", fout);
 
-   /* print footers */
+       fputs("\\end{tabular}\n\n\\noindent ", fout);
 
-   if (footers && !opt_tuples_only && !cancel_pressed)
-       for (ptr = footers; *ptr; ptr++)
+       /* print footers */
+       if (footers && !opt_tuples_only && !cancel_pressed)
        {
-           if (opt_numeric_locale)
+           for (ptr = footers; *ptr; ptr++)
            {
-               char       *my_cell = format_numeric_locale(*ptr);
+               if (opt_numeric_locale)
+               {
+                   char       *my_cell = format_numeric_locale(*ptr);
 
-               latex_escaped_print(my_cell, fout);
-               free(my_cell);
+                   latex_escaped_print(my_cell, fout);
+                   free(my_cell);
+               }
+               else
+                   latex_escaped_print(*ptr, fout);
+               fputs(" \\\\\n", fout);
            }
-           else
-               latex_escaped_print(*ptr, fout);
-           fputs(" \\\\\n", fout);
        }
 
-   fputc('\n', fout);
+       fputc('\n', fout);
+   }
 }
 
 
-
 /*************************/
 /* Troff -ms        */
 /*************************/
@@ -1411,14 +1489,15 @@ troff_ms_escaped_print(const char *in, FILE *fout)
 }
 
 
-
 static void
 print_troff_ms_text(const char *title, const char *const * headers,
                    const char *const * cells, const char *const * footers,
-                   const char *opt_align, bool opt_tuples_only,
-                   bool opt_numeric_locale, unsigned short int opt_border,
+                   const char *opt_align, const printTableOpt *opt,
                    FILE *fout)
 {
+   bool opt_tuples_only = opt->tuples_only;
+   bool opt_numeric_locale = opt->numericLocale;
+   unsigned short int opt_border = opt->border;
    unsigned int col_count = 0;
    unsigned int i;
    const char *const * ptr;
@@ -1426,49 +1505,53 @@ print_troff_ms_text(const char *title, const char *const * headers,
    if (cancel_pressed)
        return;
 
-   /* print title */
-   if (!opt_tuples_only && title)
-   {
-       fputs(".LP\n.DS C\n", fout);
-       troff_ms_escaped_print(title, fout);
-       fputs("\n.DE\n", fout);
-   }
+   if (opt_border > 2)
+       opt_border = 2;
 
    /* count columns */
    for (ptr = headers; *ptr; ptr++)
        col_count++;
 
-   /* begin environment and set alignments and borders */
-   fputs(".LP\n.TS\n", fout);
-   if (opt_border == 2)
-       fputs("center box;\n", fout);
-   else
-       fputs("center;\n", fout);
-
-   for (i = 0; i < col_count; i++)
+   if (opt->start_table)
    {
-       fputc(*(opt_align + i), fout);
-       if (opt_border > 0 && i < col_count - 1)
-           fputs(" | ", fout);
-   }
-   fputs(".\n", fout);
+       /* print title */
+       if (!opt_tuples_only && title)
+       {
+           fputs(".LP\n.DS C\n", fout);
+           troff_ms_escaped_print(title, fout);
+           fputs("\n.DE\n", fout);
+       }
 
-   /* print headers and count columns */
-   for (i = 0, ptr = headers; i < col_count; i++, ptr++)
-   {
+       /* begin environment and set alignments and borders */
+       fputs(".LP\n.TS\n", fout);
+       if (opt_border == 2)
+           fputs("center box;\n", fout);
+       else
+           fputs("center;\n", fout);
+
+       for (i = 0; i < col_count; i++)
+       {
+           fputc(*(opt_align + i), fout);
+           if (opt_border > 0 && i < col_count - 1)
+               fputs(" | ", fout);
+       }
+       fputs(".\n", fout);
+
+       /* print headers */
        if (!opt_tuples_only)
        {
-           if (i != 0)
-               fputc('\t', fout);
-           fputs("\\fI", fout);
-           troff_ms_escaped_print(*ptr, fout);
-           fputs("\\fP", fout);
+           for (i = 0, ptr = headers; i < col_count; i++, ptr++)
+           {
+               if (i != 0)
+                   fputc('\t', fout);
+               fputs("\\fI", fout);
+               troff_ms_escaped_print(*ptr, fout);
+               fputs("\\fP", fout);
+           }
+           fputs("\n_\n", fout);
        }
    }
 
-   if (!opt_tuples_only)
-       fputs("\n_\n", fout);
-
    /* print cells */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
@@ -1492,34 +1575,36 @@ print_troff_ms_text(const char *title, const char *const * headers,
            fputc('\t', fout);
    }
 
-   fputs(".TE\n.DS L\n", fout);
-
-
-   /* print footers */
+   if (opt->stop_table)
+   {
+       fputs(".TE\n.DS L\n", fout);
 
-   if (footers && !opt_tuples_only && !cancel_pressed)
-       for (ptr = footers; *ptr; ptr++)
-       {
-           troff_ms_escaped_print(*ptr, fout);
-           fputc('\n', fout);
-       }
+       /* print footers */
+       if (footers && !opt_tuples_only && !cancel_pressed)
+           for (ptr = footers; *ptr; ptr++)
+           {
+               troff_ms_escaped_print(*ptr, fout);
+               fputc('\n', fout);
+           }
 
-   fputs(".DE\n", fout);
+       fputs(".DE\n", fout);
+   }
 }
 
 
-
 static void
 print_troff_ms_vertical(const char *title, const char *const * headers,
-                     const char *const * cells, const char *const * footers,
-                       const char *opt_align, bool opt_tuples_only,
-                     bool opt_numeric_locale, unsigned short int opt_border,
+                       const char *const * cells, const char *const * footers,
+                       const char *opt_align, const printTableOpt *opt,
                        FILE *fout)
 {
+   bool opt_tuples_only = opt->tuples_only;
+   bool opt_numeric_locale = opt->numericLocale;
+   unsigned short int opt_border = opt->border;
    unsigned int col_count = 0;
+   unsigned long record = opt->prior_records + 1;
    unsigned int i;
    const char *const * ptr;
-   unsigned int record = 1;
    unsigned short current_format = 0;  /* 0=none, 1=header, 2=body */
 
    (void) opt_align;           /* currently unused parameter */
@@ -1527,29 +1612,37 @@ print_troff_ms_vertical(const char *title, const char *const * headers,
    if (cancel_pressed)
        return;
 
-   /* print title */
-   if (!opt_tuples_only && title)
-   {
-       fputs(".LP\n.DS C\n", fout);
-       troff_ms_escaped_print(title, fout);
-       fputs("\n.DE\n", fout);
-   }
-
-   /* begin environment and set alignments and borders */
-   fputs(".LP\n.TS\n", fout);
-   if (opt_border == 2)
-       fputs("center box;\n", fout);
-   else
-       fputs("center;\n", fout);
-
-   /* basic format */
-   if (opt_tuples_only)
-       fputs("c l;\n", fout);
+   if (opt_border > 2)
+       opt_border = 2;
 
    /* count columns */
    for (ptr = headers; *ptr; ptr++)
        col_count++;
 
+   if (opt->start_table)
+   {
+       /* print title */
+       if (!opt_tuples_only && title)
+       {
+           fputs(".LP\n.DS C\n", fout);
+           troff_ms_escaped_print(title, fout);
+           fputs("\n.DE\n", fout);
+       }
+
+       /* begin environment and set alignments and borders */
+       fputs(".LP\n.TS\n", fout);
+       if (opt_border == 2)
+           fputs("center box;\n", fout);
+       else
+           fputs("center;\n", fout);
+
+       /* basic format */
+       if (opt_tuples_only)
+           fputs("c l;\n", fout);
+   }
+   else
+       current_format = 2;     /* assume tuples printed already */
+
    /* print records */
    for (i = 0, ptr = cells; *ptr; i++, ptr++)
    {
@@ -1562,14 +1655,14 @@ print_troff_ms_vertical(const char *title, const char *const * headers,
            {
                if (current_format != 1)
                {
-                   if (opt_border == 2 && i > 0)
+                   if (opt_border == 2 && record > 1)
                        fputs("_\n", fout);
                    if (current_format != 0)
                        fputs(".T&\n", fout);
                    fputs("c s.\n", fout);
                    current_format = 1;
                }
-               fprintf(fout, "\\fIRecord %d\\fP\n", record++);
+               fprintf(fout, "\\fIRecord %lu\\fP\n", record++);
            }
            if (opt_border >= 1)
                fputs("_\n", fout);
@@ -1604,23 +1697,23 @@ print_troff_ms_vertical(const char *title, const char *const * headers,
        fputc('\n', fout);
    }
 
-   fputs(".TE\n.DS L\n", fout);
-
-
-   /* print footers */
+   if (opt->stop_table)
+   {
+       fputs(".TE\n.DS L\n", fout);
 
-   if (footers && !opt_tuples_only && !cancel_pressed)
-       for (ptr = footers; *ptr; ptr++)
-       {
-           troff_ms_escaped_print(*ptr, fout);
-           fputc('\n', fout);
-       }
+       /* print footers */
+       if (footers && !opt_tuples_only && !cancel_pressed)
+           for (ptr = footers; *ptr; ptr++)
+           {
+               troff_ms_escaped_print(*ptr, fout);
+               fputc('\n', fout);
+           }
 
-   fputs(".DE\n", fout);
+       fputs(".DE\n", fout);
+   }
 }
 
 
-
 /********************************/
 /* Public functions        */
 /********************************/
@@ -1644,6 +1737,7 @@ PageOutput(int lines, unsigned short int pager)
        )
    {
        const char *pagerprog;
+       FILE *pagerpipe;
 
 #ifdef TIOCGWINSZ
        int         result;
@@ -1661,7 +1755,9 @@ PageOutput(int lines, unsigned short int pager)
 #ifndef WIN32
            pqsignal(SIGPIPE, SIG_IGN);
 #endif
-           return popen(pagerprog, "w");
+           pagerpipe = popen(pagerprog, "w");
+           if (pagerpipe)
+               return pagerpipe;
 #ifdef TIOCGWINSZ
        }
 #endif
@@ -1670,6 +1766,33 @@ PageOutput(int lines, unsigned short int pager)
    return stdout;
 }
 
+/*
+ * ClosePager
+ *
+ * Close previously opened pager pipe, if any
+ */
+void
+ClosePager(FILE *pagerpipe)
+{
+   if (pagerpipe && pagerpipe != stdout)
+   {
+       /*
+        * If printing was canceled midstream, warn about it.
+        *
+        * Some pagers like less use Ctrl-C as part of their command
+        * set. Even so, we abort our processing and warn the user
+        * what we did.  If the pager quit as a result of the
+        * SIGINT, this message won't go anywhere ...
+        */
+       if (cancel_pressed)
+           fprintf(pagerpipe, _("Interrupted\n"));
+
+       pclose(pagerpipe);
+#ifndef WIN32
+       pqsignal(SIGPIPE, SIG_DFL);
+#endif
+   }
+}
 
 
 void
@@ -1680,10 +1803,8 @@ printTable(const char *title,
           const char *align,
           const printTableOpt *opt, FILE *fout, FILE *flog)
 {
-   const char *default_footer[] = {NULL};
-   unsigned short int border = opt->border;
+   static const char *default_footer[] = {NULL};
    FILE       *output;
-   bool        use_expanded;
 
    if (cancel_pressed)
        return;
@@ -1694,19 +1815,6 @@ printTable(const char *title,
    if (!footers)
        footers = default_footer;
 
-   if (opt->format != PRINT_HTML && border > 2)
-       border = 2;
-
-   /*
-    * We only want to display the results in "expanded" format if this is a
-    * normal (user-submitted) query, not a table we're printing for a slash
-    * command.
-    */
-   if (opt->expanded)
-       use_expanded = true;
-   else
-       use_expanded = false;
-
    if (fout == stdout)
    {
        int         col_count = 0,
@@ -1739,59 +1847,50 @@ printTable(const char *title,
    /* print the stuff */
 
    if (flog)
-       print_aligned_text(title, headers, cells, footers, align, opt->tuples_only, opt->numericLocale, border, opt->encoding, flog);
+       print_aligned_text(title, headers, cells, footers, align,
+                          opt, flog);
 
    switch (opt->format)
    {
        case PRINT_UNALIGNED:
-           if (use_expanded)
+           if (opt->expanded)
                print_unaligned_vertical(title, headers, cells, footers, align,
-                                        opt->fieldSep, opt->recordSep,
-                              opt->tuples_only, opt->numericLocale, output);
+                                        opt, output);
            else
                print_unaligned_text(title, headers, cells, footers, align,
-                                    opt->fieldSep, opt->recordSep,
-                              opt->tuples_only, opt->numericLocale, output);
+                                    opt, output);
            break;
        case PRINT_ALIGNED:
-           if (use_expanded)
+           if (opt->expanded)
                print_aligned_vertical(title, headers, cells, footers, align,
-                               opt->tuples_only, opt->numericLocale, border,
-                                      opt->encoding, output);
+                                      opt, output);
            else
                print_aligned_text(title, headers, cells, footers, align,
-                                  opt->tuples_only, opt->numericLocale,
-                                  border, opt->encoding, output);
+                                  opt, output);
            break;
        case PRINT_HTML:
-           if (use_expanded)
+           if (opt->expanded)
                print_html_vertical(title, headers, cells, footers, align,
-                                   opt->tuples_only, opt->numericLocale,
-                                   border, opt->tableAttr, output);
+                                   opt, output);
            else
-               print_html_text(title, headers, cells, footers,
-                        align, opt->tuples_only, opt->numericLocale, border,
-                               opt->tableAttr, output);
+               print_html_text(title, headers, cells, footers, align,
+                               opt, output);
            break;
        case PRINT_LATEX:
-           if (use_expanded)
+           if (opt->expanded)
                print_latex_vertical(title, headers, cells, footers, align,
-                                    opt->tuples_only, opt->numericLocale,
-                                    border, output);
+                                    opt, output);
            else
                print_latex_text(title, headers, cells, footers, align,
-                                opt->tuples_only, opt->numericLocale,
-                                border, output);
+                                opt, output);
            break;
        case PRINT_TROFF_MS:
-           if (use_expanded)
+           if (opt->expanded)
                print_troff_ms_vertical(title, headers, cells, footers, align,
-                                       opt->tuples_only, opt->numericLocale,
-                                       border, output);
+                                       opt, output);
            else
                print_troff_ms_text(title, headers, cells, footers, align,
-                                   opt->tuples_only, opt->numericLocale,
-                                   border, output);
+                                   opt, output);
            break;
        default:
            fprintf(stderr, _("invalid output format (internal error): %d"), opt->format);
@@ -1799,28 +1898,11 @@ printTable(const char *title,
    }
 
    /* Only close if we used the pager */
-   if (fout == stdout && output != stdout)
-   {
-       /*
-        * If printing was canceled midstream, warn about it.
-        *
-        * Some pagers like less use Ctrl-C as part of their command
-        * set. Even so, we abort our processing and warn the user
-        * what we did.  If the pager quit as a result of the
-        * SIGINT, this message won't go anywhere ...
-        */
-       if (cancel_pressed)
-           fprintf(output, _("Interrupted\n"));
-
-       pclose(output);
-#ifndef WIN32
-       pqsignal(SIGPIPE, SIG_DFL);
-#endif
-   }
+   if (output != fout)
+       ClosePager(output);
 }
 
 
-
 void
 printQuery(const PGresult *result, const printQueryOpt *opt, FILE *fout, FILE *flog)
 {
@@ -1864,13 +1946,15 @@ printQuery(const PGresult *result, const printQueryOpt *opt, FILE *fout, FILE *f
        footers = opt->footers;
    else if (!opt->topt.expanded && opt->default_footer)
    {
-       footers = pg_local_calloc(2, sizeof(*footers));
+       unsigned long total_records;
 
+       footers = pg_local_calloc(2, sizeof(*footers));
        footers[0] = pg_local_malloc(100);
-       if (PQntuples(result) == 1)
+       total_records = opt->topt.prior_records + PQntuples(result);
+       if (total_records == 1)
            snprintf(footers[0], 100, _("(1 row)"));
        else
-           snprintf(footers[0], 100, _("(%d rows)"), PQntuples(result));
+           snprintf(footers[0], 100, _("(%lu rows)"), total_records);
    }
    else
        footers = NULL;
index a112998b4842d94f8ade716f9b0f28bc5d2ced6f..0fc25dc846c3a751408c981d2fef5ce07793570a 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2006, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/print.h,v 1.31 2006/03/05 15:58:51 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/print.h,v 1.32 2006/08/29 22:25:07 tgl Exp $
  */
 #ifndef PRINT_H
 #define PRINT_H
@@ -12,6 +12,7 @@
 
 
 extern FILE *PageOutput(int lines, unsigned short int pager);
+extern void ClosePager(FILE *pagerpipe);
 
 extern void html_escaped_print(const char *in, FILE *fout);
 
@@ -32,11 +33,14 @@ typedef struct _printTableOpt
    enum printFormat format;    /* one of the above */
    bool        expanded;       /* expanded/vertical output (if supported by
                                 * output format) */
+   unsigned short int border;  /* Print a border around the table. 0=none,
+                                * 1=dividing lines, 2=full */
    unsigned short int pager;   /* use pager for output (if to stdout and
                                 * stdout is a tty) 0=off 1=on 2=always */
    bool        tuples_only;    /* don't output headers, row counts, etc. */
-   unsigned short int border;  /* Print a border around the table. 0=none,
-                                * 1=dividing lines, 2=full */
+   bool        start_table;    /* print start decoration, eg  */
+   bool        stop_table;     /* print stop decoration, eg 
 */
+   unsigned long prior_records;    /* start offset for record counters */
    char       *fieldSep;       /* field separator for unaligned text mode */
    char       *recordSep;      /* record separator for unaligned text mode */
    bool        numericLocale;  /* locale-aware numeric units separator and
index 9dc41e3bdfb5b206c086bb0d3d7cedcd6a5875c7..3e34f93b95575df4cc5efe267bc2520f53589687 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2006, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/settings.h,v 1.29 2006/08/29 15:19:51 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/settings.h,v 1.30 2006/08/29 22:25:07 tgl Exp $
  */
 #ifndef SETTINGS_H
 #define SETTINGS_H
@@ -96,6 +96,7 @@ typedef struct _psqlSettings
    bool        quiet;
    bool        singleline;
    bool        singlestep;
+   int         fetch_count;
    PSQL_ECHO   echo;
    PSQL_ECHO_HIDDEN echo_hidden;
    PSQL_ERROR_ROLLBACK on_error_rollback;
index 8d3409bd19441ffa33d30656bf5cf3a1f74e3aee..6cbd95da9ed69863f85303081ee9ebab814fab34 100644 (file)
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2006, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/startup.c,v 1.136 2006/08/29 15:19:51 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/startup.c,v 1.137 2006/08/29 22:25:08 tgl Exp $
  */
 #include "postgres_fe.h"
 
@@ -145,6 +145,8 @@ main(int argc, char *argv[])
    pset.popt.topt.format = PRINT_ALIGNED;
    pset.popt.topt.border = 1;
    pset.popt.topt.pager = 1;
+   pset.popt.topt.start_table = true;
+   pset.popt.topt.stop_table = true;
    pset.popt.default_footer = true;
 
    pset.notty = (!isatty(fileno(stdin)) || !isatty(fileno(stdout)));
@@ -798,6 +800,12 @@ singlestep_hook(const char *newval)
    pset.singlestep = ParseVariableBool(newval);
 }
 
+static void
+fetch_count_hook(const char *newval)
+{
+   pset.fetch_count = ParseVariableNum(newval, -1, -1, false);
+}
+
 static void
 echo_hook(const char *newval)
 {
@@ -899,6 +907,7 @@ EstablishVariableSpace(void)
    SetVariableAssignHook(pset.vars, "QUIET", quiet_hook);
    SetVariableAssignHook(pset.vars, "SINGLELINE", singleline_hook);
    SetVariableAssignHook(pset.vars, "SINGLESTEP", singlestep_hook);
+   SetVariableAssignHook(pset.vars, "FETCH_COUNT", fetch_count_hook);
    SetVariableAssignHook(pset.vars, "ECHO", echo_hook);
    SetVariableAssignHook(pset.vars, "ECHO_HIDDEN", echo_hidden_hook);
    SetVariableAssignHook(pset.vars, "ON_ERROR_ROLLBACK", on_error_rollback_hook);