Hi there,

here comes the latest version (version 7) of the patch to handle large
result sets with psql.  As previously discussed, a cursor is used
for SELECT queries when \set FETCH_COUNT some_value > 0
(defaults to 100 if FETCH_COUNT is set with no value).

Comparing to the previous version, the patch actually got smaller and is
less invasive, because I doesn't have to deal with a new command and
can share some more code with SendQuery() in common.c.

Bye,
Chris.


--
Chris Mair
http://www.1006.org


diff -rc pgsql.original/doc/src/sgml/ref/psql-ref.sgml pgsql/doc/src/sgml/ref/psql-ref.sgml
*** pgsql.original/doc/src/sgml/ref/psql-ref.sgml	2006-08-28 16:01:15.000000000 +0200
--- pgsql/doc/src/sgml/ref/psql-ref.sgml	2006-08-28 16:04:46.000000000 +0200
***************
*** 2008,2013 ****
--- 2008,2030 ----
        </varlistentry>
  
        <varlistentry>
+         <term><varname>FETCH_COUNT</varname></term>
+         <listitem>
+         <para>
+         If this variable is set to an integer value &gt; 0,
+ 		all <command>SELECT</command> or <command>VALUES</command>
+ 		queries are performed using a cursor. Therefore only a
+ 		limited amount of memory is used, regardless the size of
+ 		the result set. The integer value defines the fetch count.
+ 		It defaults to <literal>100</literal>. This variable can be
+ 		used whenever a result set needs to be retrieved that exceeds
+ 	 	the client's memory resources. Output is always unaligned
+ 		and uses the current field separator (see <command>\pset</command>).
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+       <varlistentry>
          <term><varname>HISTCONTROL</varname></term>
          <listitem>
          <para>
diff -rc pgsql.original/src/bin/psql/common.c pgsql/src/bin/psql/common.c
*** pgsql.original/src/bin/psql/common.c	2006-08-28 16:01:18.000000000 +0200
--- pgsql/src/bin/psql/common.c	2006-08-28 16:04:07.000000000 +0200
***************
*** 28,33 ****
--- 28,34 ----
  #include "command.h"
  #include "copy.h"
  #include "mb/pg_wchar.h"
+ #include "mbprint.h"
  
  
  /* Workarounds for Windows */
***************
*** 52,59 ****
  	 ((T)->millitm - (U)->millitm))
  #endif
  
! 
  static bool command_no_begin(const char *query);
  
  /*
   * "Safe" wrapper around strdup()
--- 53,61 ----
  	 ((T)->millitm - (U)->millitm))
  #endif
  
! static bool is_select_command(const char *query);
  static bool command_no_begin(const char *query);
+ static bool SendQueryUsingCursor(const char *query);
  
  /*
   * "Safe" wrapper around strdup()
***************
*** 827,832 ****
--- 829,841 ----
  
  	SetCancelConn();
  
+ 	/* if FETCH_COUNT is set > 0 and this is a select query, use
+ 	 * alternative query/output code for large result sets
+ 	 */
+ 	if (GetVariableNum(pset.vars, "FETCH_COUNT", -1, DEFAULT_FETCH_COUNT, false) > 0 && is_select_command(query)) {
+ 		return SendQueryUsingCursor(query);
+ 	}
+ 
  	transaction_status = PQtransactionStatus(pset.db);
  
  	if (transaction_status == PQTRANS_IDLE &&
***************
*** 952,957 ****
--- 961,1131 ----
  
  
  /*
+  * SendQueryUsingCursor:
+  * This is called by SendQuery for SELECT queries when FETCH_COUNT is set > 0.
+  * The query is performed using a cursor, so that large result sets exceeding
+  * the client's RAM size can be dealt with.
+  *
+  * Unlike with SendQuery(), timing and format settings (except delimiters)
+  * are NOT honoured.
+  *
+  * Returns true if the query executed successfully, false otherwise.
+  */
+ static bool
+ SendQueryUsingCursor(const char *query)
+ {
+ 	PGresult		*results;
+ 	PQExpBufferData	buf;
+ 	FILE            *queryFout_copy = NULL;
+ 	bool            queryFoutPipe_copy = false;
+ 	bool			started_txn = false;
+ 	int				ntuples, nfields = -1;
+ 	int				i, j;
+     char            fetch_str[64];
+ 
+ 	/* prepare to write output to \g argument, if any */
+ 	if (pset.gfname)
+ 	{
+ 		queryFout_copy = pset.queryFout;
+ 		queryFoutPipe_copy = pset.queryFoutPipe;
+ 
+ 		pset.queryFout = stdout;    /* so it doesn't get closed */
+ 
+ 		/* open file/pipe */
+ 		if (!setQFout(pset.gfname))
+ 		{
+ 			pset.queryFout = queryFout_copy;
+ 			pset.queryFoutPipe = queryFoutPipe_copy;
+ 			ResetCancelConn();
+ 			return false;
+ 		}
+ 	}
+ 
+ 	/* if we're not in a transaction, start one */
+ 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
+ 	{
+ 		results = PQexec(pset.db, "BEGIN");
+ 		if (PQresultStatus(results) != PGRES_COMMAND_OK)
+ 			goto error;
+ 
+ 		PQclear(results);
+ 		started_txn = true;
+ 	}
+ 
+ 	initPQExpBuffer(&buf);
+ 	appendPQExpBuffer(&buf,
+ 					  "DECLARE _psql_cursor NO SCROLL CURSOR FOR %s",
+ 					  query);
+ 
+ 	results = PQexec(pset.db, buf.data);
+ 	if (PQresultStatus(results) != PGRES_COMMAND_OK)
+ 		goto error;
+ 
+ 	PQclear(results);
+ 	termPQExpBuffer(&buf);
+ 
+ 	snprintf(fetch_str, sizeof(fetch_str), "FETCH FORWARD %d FROM _psql_cursor",
+ 			 GetVariableNum(pset.vars, "FETCH_COUNT", -1, DEFAULT_FETCH_COUNT, false));
+ 
+ 	for (;;)
+ 	{
+ 		/* get FETCH_COUNT tuples at a time */
+ 		results = PQexec(pset.db, fetch_str);
+ 		if (PQresultStatus(results) != PGRES_TUPLES_OK)
+ 			goto error;
+ 
+ 		ntuples = PQntuples(results);
+ 		if (ntuples == 0)
+ 		{
+ 			PQclear(results);
+ 			break;
+ 		}
+ 
+ 		if (nfields == -1)
+ 			nfields = PQnfields(results);
+ 
+ 		for (j = 0; j < ntuples; j++)
+ 		{
+ 			for (i = 0; i < nfields; i++)
+ 			{ 
+ 				fprintf(
+ 					pset.queryFout, "%s", 
+ 					(char*) mbvalidate((unsigned char*) PQgetvalue(results, j, i),
+ 					pset.popt.topt.encoding)
+ 				);
+ 				if (i != nfields - 1)
+ 				{
+ 					fprintf(pset.queryFout, "%s", pset.popt.topt.fieldSep);
+ 				}
+ 			}
+ 			fprintf(pset.queryFout, "\n");
+ 		}
+ 
+ 		PQclear(results);
+ 
+ 		if (cancel_pressed)
+ 			break;
+ 	}
+ 
+ 	/* close \g argument file/pipe, restore old setting */
+ 	if (pset.gfname)
+ 	{
+ 		setQFout(NULL);
+ 
+ 		pset.queryFout = queryFout_copy;
+ 		pset.queryFoutPipe = queryFoutPipe_copy;
+ 
+ 		free(pset.gfname);
+ 		pset.gfname = NULL;
+ 	}
+ 
+ 	results = PQexec(pset.db, "CLOSE _psql_cursor");
+ 	if (PQresultStatus(results) != PGRES_COMMAND_OK)
+ 		goto error;
+ 	PQclear(results);
+ 
+ 	if (started_txn)
+ 	{
+ 		results = PQexec(pset.db, "COMMIT");
+ 		started_txn = false;
+ 		if (PQresultStatus(results) != PGRES_COMMAND_OK)
+ 			goto error;
+ 		PQclear(results);
+ 	}
+ 
+ 	/* check for events that may occur during query execution */
+ 
+ 	if (pset.encoding != PQclientEncoding(pset.db) &&
+ 		PQclientEncoding(pset.db) >= 0)
+ 	{
+ 		/* track effects of SET CLIENT_ENCODING */
+ 		pset.encoding = PQclientEncoding(pset.db);
+ 		pset.popt.topt.encoding = pset.encoding;
+ 		SetVariable(pset.vars, "ENCODING",
+ 					pg_encoding_to_char(pset.encoding));
+ 	}
+ 
+ 	PrintNotifications();
+ 
+ 	return true;
+ 
+ error:
+ 	psql_error("%s", PQerrorMessage(pset.db));
+ 	if (results)
+ 		PQclear(results);
+ 	if (started_txn)
+ 	{
+ 		results = PQexec(pset.db, "ROLLBACK");
+ 		if (PQresultStatus(results) != PGRES_COMMAND_OK)
+ 			goto error;
+ 		PQclear(results);
+ 	}
+ 	ResetCancelConn();
+ 	return false;
+ }
+ 
+ 
+ /*
   * Advance the given char pointer over white space and SQL comments.
   */
  static const char *
***************
*** 1012,1017 ****
--- 1186,1229 ----
  
  
  /*
+  * Check whether the specified command is a SELECT (or VALUES).
+  */
+ static bool
+ is_select_command(const char *query)
+ {
+ 	int wordlen;
+ 	const char *pos = 0;
+ 
+ 	/*
+ 	 * First we must advance over any whitespace, comments and left parentheses.
+ 	 */
+ 	while (pos != query)
+ 	{
+ 		pos = query;
+ 		query = skip_white_space(query);
+ 		if (query[0] == '(') {
+ 			query++;
+ 		}
+ 	}
+ 
+ 	/*
+ 	 * 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;
+ }
+ 
+ 
+ /*
   * Check whether a command is one of those for which we should NOT start
   * a new transaction block (ie, send a preceding BEGIN).
   *
diff -rc pgsql.original/src/bin/psql/settings.h pgsql/src/bin/psql/settings.h
*** pgsql.original/src/bin/psql/settings.h	2006-08-28 16:01:18.000000000 +0200
--- pgsql/src/bin/psql/settings.h	2006-08-28 16:04:07.000000000 +0200
***************
*** 16,21 ****
--- 16,23 ----
  #define DEFAULT_FIELD_SEP "|"
  #define DEFAULT_RECORD_SEP "\n"
  
+ #define DEFAULT_FETCH_COUNT 100
+ 
  #if defined(WIN32) || defined(__CYGWIN__)
  #define DEFAULT_EDITOR	"notepad.exe"
  #else
---------------------------(end of broadcast)---------------------------
TIP 6: explain analyze is your friend

Reply via email to