> > Patch with fix against current CVS is attached.

Forgot the attachment... soory.


-- 

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-17 16:50:58.000000000 +0200
--- pgsql/doc/src/sgml/ref/psql-ref.sgml	2006-08-17 18:02:29.000000000 +0200
***************
*** 1201,1206 ****
--- 1201,1231 ----
          </listitem>
        </varlistentry>
  
+ 
+       <varlistentry>
+         <term><literal>\u</literal> [ { <replaceable class="parameter">filename</replaceable> | <literal>|</literal><replaceable class="parameter">command</replaceable> } ]</term>
+ 
+         <listitem>
+         <para>
+         Sends the current query input buffer to the server and
+         optionally stores the query's output in <replaceable
+         class="parameter">filename</replaceable> or pipes the output
+         into a separate Unix shell executing <replaceable
+         class="parameter">command</replaceable>.
+         Unlike <literal>\g</literal>, <literal>\u</literal> works only
+         for select statements and uses a cursor to retrieve the result set.
+         Therefore <literal>\u</literal> uses only a limited amount of memory,
+         regardless the size of the result set. It can be used whenever a result
+         set needs to be retrieved that exceeds the client's memory resources.
+         <literal>\u</literal> always gives unaligned output. It does, however
+         use the current field separator (see <command>\pset</command>).
+         <literal>\u</literal> gives an error when trying to execute something
+         that is not a SELECT statement.
+         </para>
+         </listitem>
+       </varlistentry>
+ 
+ 
        <varlistentry>
          <term><literal>\help</literal> (or <literal>\h</literal>) <literal>[ <replaceable class="parameter">command</replaceable> ]</literal></term>
          <listitem>
diff -rc pgsql.original/src/bin/psql/command.c pgsql/src/bin/psql/command.c
*** pgsql.original/src/bin/psql/command.c	2006-08-17 16:51:04.000000000 +0200
--- pgsql/src/bin/psql/command.c	2006-08-17 18:02:49.000000000 +0200
***************
*** 830,835 ****
--- 830,866 ----
  		}
  	}
  
+ 	/*
+ 	 *  \u executes the current query buffer using a cursor
+ 	 */
+ 	else if (strcmp(cmd, "u") == 0)
+ 	{
+ 		char 	   *fname = psql_scan_slash_option(scan_state,
+ 								OT_FILEPIPE, NULL, false);
+ 
+ 		if (!fname)
+ 			pset.gfname = NULL;
+ 		else
+ 		{
+ 			expand_tilde(&fname);
+ 			pset.gfname = pg_strdup(fname);
+ 		}
+ 		free(fname);
+ 
+ 		if (query_buf && query_buf->len == 0)
+ 		{
+ 			if (!quiet)
+ 			{
+ 				puts(_("Query buffer is empty."));
+ 				fflush(stdout);
+ 			}
+ 		}
+ 		else
+ 		{
+ 			status = PSQL_CMD_SEND_USING_CURSOR;
+ 		}
+ 	}
+ 
  	/* \unset */
  	else if (strcmp(cmd, "unset") == 0)
  	{
diff -rc pgsql.original/src/bin/psql/command.h pgsql/src/bin/psql/command.h
*** pgsql.original/src/bin/psql/command.h	2006-08-17 16:51:04.000000000 +0200
--- pgsql/src/bin/psql/command.h	2006-08-17 16:55:25.000000000 +0200
***************
*** 16,21 ****
--- 16,22 ----
  {
  	PSQL_CMD_UNKNOWN = 0,			/* not done parsing yet (internal only) */
  	PSQL_CMD_SEND,					/* query complete; send off */
+ 	PSQL_CMD_SEND_USING_CURSOR,		/* query complete; send off using cursor */
  	PSQL_CMD_SKIP_LINE,				/* keep building query */
  	PSQL_CMD_TERMINATE,				/* quit program */
  	PSQL_CMD_NEWEDIT,				/* query buffer was changed (e.g., via \e) */
diff -rc pgsql.original/src/bin/psql/common.c pgsql/src/bin/psql/common.c
*** pgsql.original/src/bin/psql/common.c	2006-08-17 16:51:04.000000000 +0200
--- pgsql/src/bin/psql/common.c	2006-08-17 18:40:51.000000000 +0200
***************
*** 28,33 ****
--- 28,34 ----
  #include "command.h"
  #include "copy.h"
  #include "mb/pg_wchar.h"
+ #include "mbprint.h"
  
  
  /* Workarounds for Windows */
***************
*** 52,58 ****
  	 ((T)->millitm - (U)->millitm))
  #endif
  
! 
  static bool command_no_begin(const char *query);
  
  /*
--- 53,59 ----
  	 ((T)->millitm - (U)->millitm))
  #endif
  
! static bool is_select_command(const char *query);
  static bool command_no_begin(const char *query);
  
  /*
***************
*** 952,957 ****
--- 953,1146 ----
  
  
  /*
+  * SendQueryUsingCursor: send the (SELECT) query string to the backend
+  * using a cursor and print out results.
+  *
+  * Unlike with SendQuery(), single step mode, ON_ERROR_ROLLBACK mode,
+  * timing and format settings (except delimiters) are NOT honoured.
+  *
+  * Returns true if the query executed successfully, false otherwise.
+  */
+ bool
+ SendQueryUsingCursor(const char *query)
+ {
+ 	PGresult		*results;
+ 	bool			started_txn			= false;
+ 	PQExpBufferData	buf;
+ 	FILE			*queryFout_copy 	= NULL;
+ 	bool			queryFoutPipe_copy	= false;
+ 	int				ntuples, nfields = -1;
+ 	int				i, j;
+ 
+ 	if (!pset.db)
+ 	{
+ 		psql_error("You are currently not connected to a database.\n");
+ 		return false;
+ 	}
+ 
+ 	if (!is_select_command(query))
+ 	{
+ 		psql_error("Need a SELECT command to perform cursor fetch.\n");
+ 		return false;
+ 	}
+ 
+ 	if (VariableEquals(pset.vars, "ECHO", "queries"))
+ 	{
+ 		puts(query);
+ 		fflush(stdout);
+ 	}
+ 
+ 	if (pset.logfile)
+ 	{
+ 		fprintf(pset.logfile,
+ 				_("********* QUERY **********\n"
+ 				  "%s\n"
+ 				  "**************************\n\n"), query);
+ 		fflush(pset.logfile);
+ 	}
+ 
+ 	SetCancelConn();
+ 
+ 	/* prepare to write output to \u 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);
+ 
+ 	for (;;)
+ 	{
+ 		/* space-time trade-off: get 100 tuples at a time */
+ 		results = PQexec(pset.db, "FETCH FORWARD 100 FROM _psql_cursor");
+ 		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 \u 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 ****
--- 1201,1244 ----
  
  
  /*
+  * 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/common.h pgsql/src/bin/psql/common.h
*** pgsql.original/src/bin/psql/common.h	2006-08-17 16:51:04.000000000 +0200
--- pgsql/src/bin/psql/common.h	2006-08-17 16:55:25.000000000 +0200
***************
*** 55,60 ****
--- 55,61 ----
  extern PGresult *PSQLexec(const char *query, bool start_xact);
  
  extern bool SendQuery(const char *query);
+ extern bool SendQueryUsingCursor(const char *query);
  
  extern bool is_superuser(void);
  extern bool standard_strings(void);
diff -rc pgsql.original/src/bin/psql/help.c pgsql/src/bin/psql/help.c
*** pgsql.original/src/bin/psql/help.c	2006-08-17 16:51:04.000000000 +0200
--- pgsql/src/bin/psql/help.c	2006-08-17 16:55:25.000000000 +0200
***************
*** 190,195 ****
--- 190,196 ----
  	fprintf(output, _("Query Buffer\n"));
  	fprintf(output, _("  \\e [FILE]      edit the query buffer (or file) with external editor\n"));
  	fprintf(output, _("  \\g [FILE]      send query buffer to server (and results to file or |pipe)\n"));
+ 	fprintf(output, _("  \\u [FILE]      like \\g, but use cursor for SELECT (for large result sets)\n"));
  	fprintf(output, _("  \\p             show the contents of the query buffer\n"));
  	fprintf(output, _("  \\r             reset (clear) the query buffer\n"));
  #ifdef USE_READLINE
diff -rc pgsql.original/src/bin/psql/mainloop.c pgsql/src/bin/psql/mainloop.c
*** pgsql.original/src/bin/psql/mainloop.c	2006-08-17 16:51:04.000000000 +0200
--- pgsql/src/bin/psql/mainloop.c	2006-08-17 16:55:25.000000000 +0200
***************
*** 264,270 ****
  
  				success = slashCmdStatus != PSQL_CMD_ERROR;
  
! 				if ((slashCmdStatus == PSQL_CMD_SEND || slashCmdStatus == PSQL_CMD_NEWEDIT) &&
  					query_buf->len == 0)
  				{
  					/* copy previous buffer to current for handling */
--- 264,270 ----
  
  				success = slashCmdStatus != PSQL_CMD_ERROR;
  
! 				if ((slashCmdStatus == PSQL_CMD_SEND || slashCmdStatus == PSQL_CMD_NEWEDIT || slashCmdStatus == PSQL_CMD_SEND_USING_CURSOR) &&
  					query_buf->len == 0)
  				{
  					/* copy previous buffer to current for handling */
***************
*** 282,287 ****
--- 282,298 ----
  					/* flush any paren nesting info after forced send */
  					psql_scan_reset(scan_state);
  				}
+ 				else if (slashCmdStatus == PSQL_CMD_SEND_USING_CURSOR)
+ 				{
+ 					success = SendQueryUsingCursor(query_buf->data);
+ 
+ 					resetPQExpBuffer(previous_buf);
+ 					appendPQExpBufferStr(previous_buf, query_buf->data);
+ 					resetPQExpBuffer(query_buf);
+ 
+ 					/* flush any paren nesting info after forced send */
+ 					psql_scan_reset(scan_state);
+ 				}
  				else if (slashCmdStatus == PSQL_CMD_NEWEDIT)
  				{
  					/* rescan query_buf as new input */
diff -rc pgsql.original/src/bin/psql/settings.h pgsql/src/bin/psql/settings.h
*** pgsql.original/src/bin/psql/settings.h	2006-08-17 16:51:04.000000000 +0200
--- pgsql/src/bin/psql/settings.h	2006-08-17 16:55:25.000000000 +0200
***************
*** 37,43 ****
  	printQueryOpt popt;
  	VariableSpace vars;			/* "shell variable" repository */
  
! 	char	   *gfname;			/* one-shot file output argument for \g */
  
  	bool		notty;			/* stdin or stdout is not a tty (as determined
  								 * on startup) */
--- 37,43 ----
  	printQueryOpt popt;
  	VariableSpace vars;			/* "shell variable" repository */
  
! 	char	   *gfname;			/* one-shot file output argument for \g and \u */
  
  	bool		notty;			/* stdin or stdout is not a tty (as determined
  								 * on startup) */
---------------------------(end of broadcast)---------------------------
TIP 6: explain analyze is your friend

Reply via email to