From 223ae6957f76f356bda883d427812d9214394f84 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Fri, 26 May 2006 19:51:29 +0000
Subject: [PATCH] Support binary COPY through psql.  Also improve detection of
 write errors during COPY OUT.  Andreas Pflug, some editorialization by moi.

---
 doc/src/sgml/ref/psql-ref.sgml |  13 ++--
 src/bin/psql/common.c          |   5 +-
 src/bin/psql/copy.c            | 135 +++++++++++++++++++++------------
 src/bin/psql/copy.h            |   4 +-
 4 files changed, 100 insertions(+), 57 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 19d88c9843f..f00b4c09f6f 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -1,5 +1,5 @@
 <!--
-$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.161 2006/04/02 20:08:20 neilc Exp $
+$PostgreSQL: pgsql/doc/src/sgml/ref/psql-ref.sgml,v 1.162 2006/05/26 19:51:29 tgl Exp $
 PostgreSQL documentation
 -->
 
@@ -744,13 +744,16 @@ testdb=&gt;
         { <literal>from</literal> | <literal>to</literal> }
         { <replaceable class="parameter">filename</replaceable> | stdin | stdout | pstdin | pstdout }
         [ with ]
+            [ binary ]
             [ oids ]
             [ delimiter [ as ] '<replaceable class="parameter">character</replaceable>' ]
             [ null [ as ] '<replaceable class="parameter">string</replaceable>' ]
-            [ csv [ quote [ as ] '<replaceable class="parameter">character</replaceable>' ]
-            [ escape [ as ] '<replaceable class="parameter">character</replaceable>' ]
-            [ force quote <replaceable class="parameter">column_list</replaceable> ]
-            [ force not null <replaceable class="parameter">column_list</replaceable> ] ]</literal>
+            [ csv
+              [ header ]
+              [ quote [ as ] '<replaceable class="parameter">character</replaceable>' ]
+              [ escape [ as ] '<replaceable class="parameter">character</replaceable>' ]
+              [ force quote <replaceable class="parameter">column_list</replaceable> ]
+              [ force not null <replaceable class="parameter">column_list</replaceable> ] ]</literal>
         </term>
 
         <listitem>
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index ba8e403dd2a..6b7f683b055 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2006, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.117 2006/05/11 19:15:35 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/common.c,v 1.118 2006/05/26 19:51:29 tgl Exp $
  */
 #include "postgres_fe.h"
 #include "common.h"
@@ -652,7 +652,8 @@ ProcessCopyResult(PGresult *results)
 			break;
 
 		case PGRES_COPY_IN:
-			success = handleCopyIn(pset.db, pset.cur_cmd_source);
+			success = handleCopyIn(pset.db, pset.cur_cmd_source,
+								   PQbinaryTuples(results));
 			break;
 
 		default:
diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 53162b69cc9..1c3d812abdf 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -3,12 +3,11 @@
  *
  * Copyright (c) 2000-2006, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.60 2006/03/05 15:58:51 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.61 2006/05/26 19:51:29 tgl Exp $
  */
 #include "postgres_fe.h"
 #include "copy.h"
 
-#include <errno.h>
 #include <signal.h>
 #include <sys/stat.h>
 #ifndef WIN32
@@ -37,11 +36,10 @@
  *
  * The documented preferred syntax is:
  *	\copy tablename [(columnlist)] from|to filename
- *		[ with ] [ oids ] [ delimiter [as] char ] [ null [as] string ]
- * (binary is not here yet)
+ *	  [ with ] [ binary ] [ oids ] [ delimiter [as] char ] [ null [as] string ]
  *
  * The pre-7.3 syntax was:
- *	\copy tablename [(columnlist)] [with oids] from|to filename
+ *	\copy [ binary ] tablename [(columnlist)] [with oids] from|to filename
  *		[ [using] delimiters char ] [ with null as string ]
  *
  * The actual accepted syntax is a rather unholy combination of these,
@@ -131,8 +129,6 @@ parse_slash_copy(const char *args)
 	if (!token)
 		goto error;
 
-#ifdef NOT_USED
-	/* this is not implemented yet */
 	if (pg_strcasecmp(token, "binary") == 0)
 	{
 		result->binary = true;
@@ -141,7 +137,6 @@ parse_slash_copy(const char *args)
 		if (!token)
 			goto error;
 	}
-#endif
 
 	result->table = pg_strdup(token);
 
@@ -284,9 +279,10 @@ parse_slash_copy(const char *args)
 
 			fetch_next = true;
 
-			/* someday allow BINARY here */
 			if (pg_strcasecmp(token, "oids") == 0)
 				result->oids = true;
+			else if (pg_strcasecmp(token, "binary") == 0)
+				result->binary = true;
 			else if (pg_strcasecmp(token, "csv") == 0)
 				result->csv_mode = true;
 			else if (pg_strcasecmp(token, "header") == 0)
@@ -442,6 +438,8 @@ do_copy(const char *args)
 	initPQExpBuffer(&query);
 
 	printfPQExpBuffer(&query, "COPY ");
+
+	/* Uses old COPY syntax for backward compatibility 2002-06-19 */
 	if (options->binary)
 		appendPQExpBuffer(&query, "BINARY ");
 
@@ -523,7 +521,8 @@ do_copy(const char *args)
 	else
 	{
 		if (options->file)
-			copystream = fopen(options->file, "w");
+			copystream = fopen(options->file,
+							   options->binary ? PG_BINARY_W : "w");
 		else if (!options->psql_inout)
 			copystream = pset.queryFout;
 		else
@@ -558,7 +557,8 @@ do_copy(const char *args)
 			success = handleCopyOut(pset.db, copystream);
 			break;
 		case PGRES_COPY_IN:
-			success = handleCopyIn(pset.db, copystream);
+			success = handleCopyIn(pset.db, copystream,
+								   PQbinaryTuples(result));
 			break;
 		case PGRES_NONFATAL_ERROR:
 		case PGRES_FATAL_ERROR:
@@ -622,12 +622,23 @@ handleCopyOut(PGconn *conn, FILE *copystream)
 
 		if (buf)
 		{
-			fputs(buf, copystream);
+			if (fwrite(buf, 1, ret, copystream) != ret)
+			{
+				if (OK)			/* complain only once, keep reading data */
+					psql_error("could not write COPY data: %s\n",
+							   strerror(errno));
+				OK = false;
+			}
 			PQfreemem(buf);
 		}
 	}
 
-	fflush(copystream);
+	if (OK && fflush(copystream))
+	{
+		psql_error("could not write COPY data: %s\n",
+				   strerror(errno));
+		OK = false;
+	}
 
 	if (ret == -2)
 	{
@@ -657,6 +668,7 @@ handleCopyOut(PGconn *conn, FILE *copystream)
  * conn should be a database connection that you just issued COPY FROM on
  * and got back a PGRES_COPY_IN result.
  * copystream is the file stream to read the data from.
+ * isbinary can be set from PQbinaryTuples().
  *
  * result is true if successful, false if not.
  */
@@ -665,13 +677,10 @@ handleCopyOut(PGconn *conn, FILE *copystream)
 #define COPYBUFSIZ 8192
 
 bool
-handleCopyIn(PGconn *conn, FILE *copystream)
+handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary)
 {
 	bool		OK = true;
 	const char *prompt;
-	bool		copydone = false;
-	bool		firstload;
-	bool		linedone;
 	char		buf[COPYBUFSIZ];
 	PGresult *res;
 
@@ -686,59 +695,89 @@ handleCopyIn(PGconn *conn, FILE *copystream)
 	else
 		prompt = NULL;
 
-	while (!copydone)
-	{							/* for each input line ... */
+	if (isbinary)
+	{
+	    int buflen;
+
+		/* interactive input probably silly, but give one prompt anyway */
 		if (prompt)
 		{
 			fputs(prompt, stdout);
 			fflush(stdout);
 		}
-		
-		firstload = true;
-		linedone = false;
 
-		while (!linedone)
-		{						/* for each bufferload in line ... */
-			int		linelen;
-
-			if (!fgets(buf, COPYBUFSIZ, copystream))
+		while ((buflen = fread(buf, 1, COPYBUFSIZ, copystream)) > 0)
+		{
+			if (PQputCopyData(conn, buf, buflen) <= 0)
 			{
-				if (ferror(copystream))
-					OK = false;
-				copydone = true;
+				OK = false;
 				break;
 			}
+		}
+	}
+	else
+	{
+		bool		copydone = false;
 
-			linelen = strlen(buf);
-
-			/* current line is done? */
-			if (linelen > 0 && buf[linelen-1] == '\n')
-				linedone = true;
+		while (!copydone)
+		{							/* for each input line ... */
+			bool		firstload;
+			bool		linedone;
 
-			/* check for EOF marker, but not on a partial line */
-			if (firstload)
+			if (prompt)
 			{
-				if (strcmp(buf, "\\.\n") == 0 ||
-					strcmp(buf, "\\.\r\n") == 0)
+				fputs(prompt, stdout);
+				fflush(stdout);
+			}
+		
+			firstload = true;
+			linedone = false;
+
+			while (!linedone)
+			{						/* for each bufferload in line ... */
+				int		linelen;
+
+				if (!fgets(buf, COPYBUFSIZ, copystream))
 				{
 					copydone = true;
 					break;
 				}
+
+				linelen = strlen(buf);
+
+				/* current line is done? */
+				if (linelen > 0 && buf[linelen-1] == '\n')
+					linedone = true;
+
+				/* check for EOF marker, but not on a partial line */
+				if (firstload)
+				{
+					if (strcmp(buf, "\\.\n") == 0 ||
+						strcmp(buf, "\\.\r\n") == 0)
+					{
+						copydone = true;
+						break;
+					}
 				
-				firstload = false;
-			}
+					firstload = false;
+				}
 			
-			if (PQputCopyData(conn, buf, linelen) <= 0)
-			{
-				OK = false;
-				copydone = true;
-				break;
+				if (PQputCopyData(conn, buf, linelen) <= 0)
+				{
+					OK = false;
+					copydone = true;
+					break;
+				}
 			}
-		}
 		
-		pset.lineno++;
+			pset.lineno++;
+		}
 	}
 
+	/* Check for read error */
+	if (ferror(copystream))
+		OK = false;
+
 	/* Terminate data transfer */
 	if (PQputCopyEnd(conn,
 					 OK ? NULL : _("aborted due to read failure")) <= 0)
diff --git a/src/bin/psql/copy.h b/src/bin/psql/copy.h
index 689cd8f49c3..7e0c5e0d838 100644
--- a/src/bin/psql/copy.h
+++ b/src/bin/psql/copy.h
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2006, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/copy.h,v 1.18 2006/03/05 15:58:51 momjian Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/copy.h,v 1.19 2006/05/26 19:51:29 tgl Exp $
  */
 #ifndef COPY_H
 #define COPY_H
@@ -17,6 +17,6 @@ bool		do_copy(const char *args);
 /* lower level processors for copy in/out streams */
 
 bool		handleCopyOut(PGconn *conn, FILE *copystream);
-bool		handleCopyIn(PGconn *conn, FILE *copystream);
+bool		handleCopyIn(PGconn *conn, FILE *copystream, bool isbinary);
 
 #endif
-- 
GitLab