]> granicus.if.org Git - postgresql/commitdiff
Create a FETCH_COUNT parameter that causes psql to execute SELECT-like
authorTom Lane <tgl@sss.pgh.pa.us>
Tue, 29 Aug 2006 22:25:08 +0000 (22:25 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
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 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.166 2006/07/27 19:52:04 tgl Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.167 2006/08/29 22:25:04 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -2007,6 +2007,33 @@ bar
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>FETCH_COUNT</varname></term>
+        <listitem>
+        <para>
+        If this variable is set to an integer value &gt; 0,
+        the results of <command>SELECT</command> 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.
+        </para>
+        <tip>
+        <para>
+        Although you can use any output format with this feature,
+        the default <literal>aligned</> format tends to look bad
+        because each group of <varname>FETCH_COUNT</varname> rows
+        will be formatted separately, leading to varying column
+        widths across the row groups.  The other output formats work better.
+        </para>
+        </tip>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>HISTCONTROL</varname></term>
         <listitem>
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, "<table border=\"%d\"", opt_border);
-       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("  <caption>", fout);
-               html_escaped_print(title, fout);
-               fputs("</caption>\n", fout);
-       }
+               fprintf(fout, "<table border=\"%d\"", opt_border);
+               if (opt_table_attr)
+                       fprintf(fout, " %s", opt_table_attr);
+               fputs(">\n", fout);
 
-       /* print headers and count columns */
-       if (!opt_tuples_only)
-               fputs("  <tr>\n", fout);
-       for (i = 0, ptr = headers; *ptr; i++, ptr++)
-       {
-               col_count++;
+               /* print title */
+               if (!opt_tuples_only && title)
+               {
+                       fputs("  <caption>", fout);
+                       html_escaped_print(title, fout);
+                       fputs("</caption>\n", fout);
+               }
+
+               /* print headers */
                if (!opt_tuples_only)
                {
-                       fputs("    <th align=\"center\">", fout);
-                       html_escaped_print(*ptr, fout);
-                       fputs("</th>\n", fout);
+                       fputs("  <tr>\n", fout);
+                       for (ptr = headers; *ptr; ptr++)
+                       {
+                               fputs("    <th align=\"center\">", fout);
+                               html_escaped_print(*ptr, fout);
+                               fputs("</th>\n", fout);
+                       }
+                       fputs("  </tr>\n", fout);
                }
        }
-       if (!opt_tuples_only)
-               fputs("  </tr>\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("  </tr>\n", fout);
        }
 
-       fputs("</table>\n", fout);
-
-       /* print footers */
-
-       if (!opt_tuples_only && footers && *footers && !cancel_pressed)
+       if (opt->stop_table)
        {
-               fputs("<p>", fout);
-               for (ptr = footers; *ptr; ptr++)
+               fputs("</table>\n", fout);
+
+               /* print footers */
+               if (!opt_tuples_only && footers && *footers && !cancel_pressed)
                {
-                       html_escaped_print(*ptr, fout);
-                       fputs("<br />\n", fout);
+                       fputs("<p>", fout);
+                       for (ptr = footers; *ptr; ptr++)
+                       {
+                               html_escaped_print(*ptr, fout);
+                               fputs("<br />\n", fout);
+                       }
+                       fputs("</p>", fout);
                }
-               fputs("</p>", 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, "<table border=\"%d\"", opt_border);
-       if (opt_table_attr)
-               fprintf(fout, " %s", opt_table_attr);
-       fputs(">\n", fout);
-
-       /* print title */
-       if (!opt_tuples_only && title)
-       {
-               fputs("  <caption>", fout);
-               html_escaped_print(title, fout);
-               fputs("</caption>\n", fout);
-       }
-
        /* count columns */
        for (ptr = headers; *ptr; ptr++)
                col_count++;
 
+       if (opt->start_table)
+       {
+               fprintf(fout, "<table border=\"%d\"", opt_border);
+               if (opt_table_attr)
+                       fprintf(fout, " %s", opt_table_attr);
+               fputs(">\n", fout);
+
+               /* print title */
+               if (!opt_tuples_only && title)
+               {
+                       fputs("  <caption>", fout);
+                       html_escaped_print(title, fout);
+                       fputs("</caption>\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  <tr><td colspan=\"2\" align=\"center\">Record %d</td></tr>\n", record++);
+                               fprintf(fout,
+                                               "\n  <tr><td colspan=\"2\" align=\"center\">Record %lu</td></tr>\n",
+                                               record++);
                        else
                                fputs("\n  <tr><td colspan=\"2\">&nbsp;</td></tr>\n", fout);
                }
@@ -1123,26 +1185,29 @@ print_html_vertical(const char *title, const char *const * headers,
                fputs("</td>\n  </tr>\n", fout);
        }
 
-       fputs("</table>\n", fout);
-
-       /* print footers */
-       if (!opt_tuples_only && footers && *footers && !cancel_pressed)
+       if (opt->stop_table)
        {
-               fputs("<p>", fout);
-               for (ptr = footers; *ptr; ptr++)
+               fputs("</table>\n", fout);
+
+               /* print footers */
+               if (!opt_tuples_only && footers && *footers && !cancel_pressed)
                {
-                       html_escaped_print(*ptr, fout);
-                       fputs("<br />\n", fout);
+                       fputs("<p>", fout);
+                       for (ptr = footers; *ptr; ptr++)
+                       {
+                               html_escaped_print(*ptr, fout);
+                               fputs("<br />\n", fout);
+                       }
+                       fputs("</p>", fout);
                }
-               fputs("</p>", 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 <table> */
+       bool            stop_table;             /* print stop decoration, eg </table> */
+       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);