Hi

2016-04-05 10:45 GMT+02:00 Pavel Stehule <pavel.steh...@gmail.com>:

> Hi
>
> here is cleaned/finished previous implementation of RAW_TEXT/RAW_BINARY
> formats for COPY statements.
>
> The RAW with text formats means unescaped data, but with correct encoding
> - input/output is realised with input/output function. RAW binary means
> content produced/received by sending/received functions.
>
> Now both directions (input/output) working well
>
> Some examples of expected usage:
>
> copy (select xmlelement(name foo, 'hello')) to stdout (format raw_binary,
> encoding 'latin2');
>
> create table avatars(id serial, picture bytea);
> \copy avatars(picture) from ~/images/foo.jpg (format raw_binary);
> select lastval();
>
> create table doc(id serial, txt text);
> \copy doc(txt) from ~/files/aaa.txt (format raw_text, encoding 'latin2');
> select lastval();
>
> Regards
>
> Pavel
>
>
I am sending fresh version of COPY RAW patch.

There is new regress client test requested by Tom.

Note: I though about another solution based on binary parameters and binary
result support in psql. Somelike:

INSERT INTO foo(a) VALUES($1)
\gpush filename

SELECT a FROM foo
\gpop filename

but, it is less intuitive, and doesn't work with stdin/stdout - so it is
significant week against COPY based solution for scripting from shell. More
\g***** solution is still possible if will be requested in future.

Regards

Pavel

[pavel@nemesis ~]$ cat avatar.gif | psql -Xq -At -c "copy xx(b) from stdin
(format raw_text)" -c "select lastval()" postgres
313
commit d62c1ff8dee2324ce1fe7765c2d015e68f5f923a
Author: Pavel Stehule <pavel.steh...@gooddata.com>
Date:   Sat Jul 16 10:35:25 2016 +0200

    with regress tests

diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 6285dd0..4c6cacb 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -3226,8 +3226,9 @@ int PQfformat(const PGresult *res,
 
       <para>
        Format code zero indicates textual data representation, while format
-       code one indicates binary representation.  (Other codes are reserved
-       for future definition.)
+       code one indicates binary representation. Format code two indicates
+       raw_text representation and format code three indicates raw_binary
+       representation (Other codes are reserved for future definition.)
       </para>
      </listitem>
     </varlistentry>
@@ -3557,6 +3558,26 @@ typedef struct
    </para>
 
    <variablelist>
+    <varlistentry id="libpq-pqcopyformat">
+     <term>
+      <function>PQcopyFormat</function>
+      <indexterm>
+       <primary>PQcopyFormat</primary>
+      </indexterm>
+     </term>
+
+     <listitem>
+      <para>
+       Format code zero indicates textual data representation, format one
+       indicates binary representation, format two indicates raw
+       representation.
+<synopsis>
+int PQcopyFormat(PGresult *res);
+</synopsis>
+      </para>
+     </listitem>
+    </varlistentry>
+
     <varlistentry id="libpq-pqcmdstatus">
      <term>
       <function>PQcmdStatus</function>
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 9c96d8f..adcff46 100644
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -3239,6 +3239,7 @@ CopyInResponse (B)
                 characters, etc).
                 1 indicates the overall copy format is binary (similar
                 to DataRow format).
+                2 indicates the overall copy format is raw.
                 See <xref linkend="sql-copy">
                 for more information.
 </para>
@@ -3262,8 +3263,9 @@ CopyInResponse (B)
 <listitem>
 <para>
                 The format codes to be used for each column.
-                Each must presently be zero (text) or one (binary).
-                All must be zero if the overall copy format is textual.
+                Each must be zero (text), one (binary), two (raw_text)
+                or three (raw_binary). All must be zero if the overall
+                copy format is textual.
 </para>
 </listitem>
 </varlistentry>
@@ -3313,7 +3315,8 @@ CopyOutResponse (B)
                 is textual (rows separated by newlines, columns
                 separated by separator characters, etc). 1 indicates
                 the overall copy format is binary (similar to DataRow
-                format). See <xref linkend="sql-copy"> for more information.
+                format). 2 indicates raw_text or raw_binary format.
+                See <xref linkend="sql-copy"> for more information.
 </para>
 </listitem>
 </varlistentry>
@@ -3335,8 +3338,9 @@ CopyOutResponse (B)
 <listitem>
 <para>
                 The format codes to be used for each column.
-                Each must presently be zero (text) or one (binary).
-                All must be zero if the overall copy format is textual.
+                Each must be zero (text), one (binary), two (raw_text)
+                or three (raw_binary). All must be zero if the overall
+                copy format is textual.
 </para>
 </listitem>
 </varlistentry>
diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
index 07e2f45..4e339e4 100644
--- a/doc/src/sgml/ref/copy.sgml
+++ b/doc/src/sgml/ref/copy.sgml
@@ -197,7 +197,9 @@ COPY { <replaceable class="parameter">table_name</replaceable> [ ( <replaceable
       Selects the data format to be read or written:
       <literal>text</>,
       <literal>csv</> (Comma Separated Values),
-      or <literal>binary</>.
+      <literal>binary</>,
+      <literal>raw_text</>
+      or <literal>raw_binary</>.
       The default is <literal>text</>.
      </para>
     </listitem>
@@ -888,6 +890,44 @@ OIDs to be shown as null if that ever proves desirable.
     </para>
    </refsect3>
   </refsect2>
+
+  <refsect2>
+     <title>Raw_text/raw_binary Format</title>
+
+   <para>
+    The <literal>raw_text</literal> format option causes all data to be
+    stored/read as one text value. This format doesn't use any metadata
+    - only raw data are exported or imported.
+   </para>
+
+   <para>
+    The <literal>raw_binary</literal> format option causes all data to be
+    stored/read as binary format rather than as text. It shares format
+    for data with <literal>binary</literal> format. This format doesn't
+    use any metadata - only row data in network byte order are exported
+    or imported.
+   </para>
+
+   <para>
+    Because this format doesn't support any delimiter, only one value
+    can be exported or imported. NULL values are not allowed.
+   </para>
+   <para>
+    The <literal>raw_binary</literal> format can be used for export or import
+    bytea values.
+<programlisting>
+COPY images(data) FROM '/usr1/proj/img/01.jpg' (FORMAT raw_binary);
+</programlisting>
+    It can be used successfully for export XML in different encoding
+    or import valid XML document with any supported encoding:
+<screen><![CDATA[
+SET client_encoding TO latin2;
+
+COPY (SELECT xmlelement(NAME data, 'Hello')) TO stdout (FORMAT raw_binary);
+<?xml version="1.0" encoding="LATIN2"?><data>Hello</data>
+]]></screen>
+   </para>
+  </refsect2>
  </refsect1>
 
  <refsect1>
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
index f45b330..c63d052 100644
--- a/src/backend/commands/copy.c
+++ b/src/backend/commands/copy.c
@@ -110,6 +110,7 @@ typedef struct CopyStateData
 	char	   *filename;		/* filename, or NULL for STDIN/STDOUT */
 	bool		is_program;		/* is 'filename' a program to popen? */
 	bool		binary;			/* binary format? */
+	bool		raw;			/* raw mode? */
 	bool		oids;			/* include OIDs? */
 	bool		freeze;			/* freeze rows on loading? */
 	bool		csv_mode;		/* Comma Separated Value format? */
@@ -342,12 +343,27 @@ SendCopyBegin(CopyState cstate)
 		/* new way */
 		StringInfoData buf;
 		int			natts = list_length(cstate->attnumlist);
-		int16		format = (cstate->binary ? 1 : 0);
+		int16		format;
+		int			mode;
 		int			i;
 
 		pq_beginmessage(&buf, 'H');
-		pq_sendbyte(&buf, format);		/* overall format */
+
+		if (cstate->raw)
+			mode = 2;
+		else if (cstate->binary)
+			mode = 1;
+		else
+			mode = 0;
+
+		pq_sendbyte(&buf, mode);		/* overall mode */
 		pq_sendint(&buf, natts, 2);
+
+		if (!cstate->raw)
+			format = cstate->binary ? 1 : 0;
+		else
+			format = cstate->binary ? 3 : 2;
+
 		for (i = 0; i < natts; i++)
 			pq_sendint(&buf, format, 2);		/* per-column formats */
 		pq_endmessage(&buf);
@@ -356,10 +372,10 @@ SendCopyBegin(CopyState cstate)
 	else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
 	{
 		/* old way */
-		if (cstate->binary)
+		if (cstate->binary || cstate->raw)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			errmsg("COPY BINARY is not supported to stdout or from stdin")));
+			errmsg("COPY BINARY or COPY RAW_TEXT/RAW_BINARY is not supported to stdout or from stdin")));
 		pq_putemptymessage('H');
 		/* grottiness needed for old COPY OUT protocol */
 		pq_startcopyout();
@@ -368,10 +384,10 @@ SendCopyBegin(CopyState cstate)
 	else
 	{
 		/* very old way */
-		if (cstate->binary)
+		if (cstate->binary || cstate->raw)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			errmsg("COPY BINARY is not supported to stdout or from stdin")));
+			errmsg("COPY BINARY or COPY RAW_TEXT/RAW_BINARY is not supported to stdout or from stdin")));
 		pq_putemptymessage('B');
 		/* grottiness needed for old COPY OUT protocol */
 		pq_startcopyout();
@@ -387,12 +403,27 @@ ReceiveCopyBegin(CopyState cstate)
 		/* new way */
 		StringInfoData buf;
 		int			natts = list_length(cstate->attnumlist);
-		int16		format = (cstate->binary ? 1 : 0);
+		int16		format;
+		int			mode;
 		int			i;
 
 		pq_beginmessage(&buf, 'G');
-		pq_sendbyte(&buf, format);		/* overall format */
+
+		if (cstate->raw)
+			mode = 2;
+		else if (cstate->binary)
+			mode = 1;
+		else
+			mode = 0;
+
+		pq_sendbyte(&buf, mode);		/* overall format */
 		pq_sendint(&buf, natts, 2);
+
+		if (!cstate->raw)
+			format = cstate->binary ? 1 : 0;
+		else
+			format = cstate->binary ? 3 : 2;
+
 		for (i = 0; i < natts; i++)
 			pq_sendint(&buf, format, 2);		/* per-column formats */
 		pq_endmessage(&buf);
@@ -402,10 +433,10 @@ ReceiveCopyBegin(CopyState cstate)
 	else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2)
 	{
 		/* old way */
-		if (cstate->binary)
+		if (cstate->binary || cstate->raw)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			errmsg("COPY BINARY is not supported to stdout or from stdin")));
+			errmsg("COPY BINARY or COPY RAW_TEXT/RAW_BINARY is not supported to stdout or from stdin")));
 		pq_putemptymessage('G');
 		/* any error in old protocol will make us lose sync */
 		pq_startmsgread();
@@ -414,10 +445,10 @@ ReceiveCopyBegin(CopyState cstate)
 	else
 	{
 		/* very old way */
-		if (cstate->binary)
+		if (cstate->binary || cstate->raw)
 			ereport(ERROR,
 					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-			errmsg("COPY BINARY is not supported to stdout or from stdin")));
+			errmsg("COPY BINARY or COPY RAW_TEXT/RAW_BINARY is not supported to stdout or from stdin")));
 		pq_putemptymessage('D');
 		/* any error in old protocol will make us lose sync */
 		pq_startmsgread();
@@ -482,7 +513,7 @@ CopySendEndOfRow(CopyState cstate)
 	switch (cstate->copy_dest)
 	{
 		case COPY_FILE:
-			if (!cstate->binary)
+			if (!cstate->binary && !cstate->raw)
 			{
 				/* Default line termination depends on platform */
 #ifndef WIN32
@@ -526,6 +557,9 @@ CopySendEndOfRow(CopyState cstate)
 			}
 			break;
 		case COPY_OLD_FE:
+			/* This old protocol doesn't allow RAW_TEXT/RAW_BINARY */
+			Assert(!cstate->raw);
+
 			/* The FE/BE protocol uses \n as newline for all platforms */
 			if (!cstate->binary)
 				CopySendChar(cstate, '\n');
@@ -540,7 +574,7 @@ CopySendEndOfRow(CopyState cstate)
 			break;
 		case COPY_NEW_FE:
 			/* The FE/BE protocol uses \n as newline for all platforms */
-			if (!cstate->binary)
+			if (!cstate->binary && !cstate->raw)
 				CopySendChar(cstate, '\n');
 
 			/* Dump the accumulated row as one CopyData message */
@@ -766,6 +800,38 @@ CopyLoadRawBuf(CopyState cstate)
 	return (inbytes > 0);
 }
 
+/*
+ * CopyLoadallRawBuf loads all content into raw_buf.
+ *
+ * This routine is used in raw_text/raw_binary mode. If original RAW_BUF_SIZE is not
+ * enough, then the buffer is enlarged.
+ */
+static void
+CopyLoadallRawBuf(CopyState cstate)
+{
+	int			nbytes = 0;
+	int			inbytes;
+	Size		raw_buf_size = RAW_BUF_SIZE;
+
+	do
+	{
+		/* hold enough space for one data packet */
+		if ((raw_buf_size - nbytes - 1) < 8 * 1024)
+		{
+			raw_buf_size += RAW_BUF_SIZE;
+			cstate->raw_buf = repalloc(cstate->raw_buf, raw_buf_size);
+		}
+
+		inbytes = CopyGetData(cstate, cstate->raw_buf + nbytes, 1, raw_buf_size - nbytes - 1);
+		nbytes += inbytes;
+	}
+	while (inbytes > 0);
+
+	cstate->raw_buf[nbytes] = '\0';
+	cstate->raw_buf_index = 0;
+	cstate->raw_buf_len = nbytes;
+}
+
 
 /*
  *	 DoCopy executes the SQL COPY statement
@@ -1013,6 +1079,13 @@ ProcessCopyOptions(CopyState cstate,
 				cstate->csv_mode = true;
 			else if (strcmp(fmt, "binary") == 0)
 				cstate->binary = true;
+			else if (strcmp(fmt, "raw_text") == 0)
+				cstate->raw = true;
+			else if (strcmp(fmt, "raw_binary") == 0)
+			{
+				cstate->binary = true;
+				cstate->raw = true;
+			}
 			else
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
@@ -1162,16 +1235,21 @@ ProcessCopyOptions(CopyState cstate,
 	 * Check for incompatible options (must do these two before inserting
 	 * defaults)
 	 */
-	if (cstate->binary && cstate->delim)
+	if ((cstate->binary || cstate->raw)  && cstate->delim)
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("cannot specify DELIMITER in BINARY mode")));
 
-	if (cstate->binary && cstate->null_print)
+	if ((cstate->binary || cstate->raw) && cstate->null_print)
 		ereport(ERROR,
 				(errcode(ERRCODE_SYNTAX_ERROR),
 				 errmsg("cannot specify NULL in BINARY mode")));
 
+	if (cstate->raw && cstate->oids)
+		ereport(ERROR,
+				(errcode(ERRCODE_SYNTAX_ERROR),
+				 errmsg("cannot specify OIDS in RAW_TEXT/RAW_BINARY mode")));
+
 	/* Set defaults for omitted options */
 	if (!cstate->delim)
 		cstate->delim = cstate->csv_mode ? "," : "\t";
@@ -1608,6 +1686,12 @@ BeginCopy(bool is_from,
 		}
 	}
 
+	/* No more columns are allowed in RAW mode */
+	if (cstate->raw && list_length(cstate->attnumlist) > 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+		 errmsg("Single column result/target is required in RAW_TEXT/RAW_BINARY mode")));
+
 	/* Use client encoding when ENCODING option is not specified. */
 	if (cstate->file_encoding < 0)
 		cstate->file_encoding = pg_get_client_encoding();
@@ -1899,7 +1983,7 @@ CopyTo(CopyState cstate)
 											   ALLOCSET_DEFAULT_INITSIZE,
 											   ALLOCSET_DEFAULT_MAXSIZE);
 
-	if (cstate->binary)
+	if (cstate->binary && !cstate->raw)
 	{
 		/* Generate header for a binary copy */
 		int32		tmp;
@@ -1931,6 +2015,9 @@ CopyTo(CopyState cstate)
 		{
 			bool		hdr_delim = false;
 
+			/* raw_text/raw_binary mode is not allowed here */
+			Assert(!cstate->raw);
+
 			foreach(cur, cstate->attnumlist)
 			{
 				int			attnum = lfirst_int(cur);
@@ -1967,6 +2054,10 @@ CopyTo(CopyState cstate)
 		{
 			CHECK_FOR_INTERRUPTS();
 
+			/* stop quickly in raw_text/raw_binary when more rows is detected */
+			if (cstate->raw && processed > 0)
+				break;
+
 			/* Deconstruct the tuple ... faster than repeated heap_getattr */
 			heap_deform_tuple(tuple, tupDesc, values, nulls);
 
@@ -1983,11 +2074,25 @@ CopyTo(CopyState cstate)
 	else
 	{
 		/* run the plan --- the dest receiver will send tuples */
-		ExecutorRun(cstate->queryDesc, ForwardScanDirection, 0L);
+		ExecutorRun(cstate->queryDesc, ForwardScanDirection, cstate->raw ? 2L : 0L);
 		processed = ((DR_copy *) cstate->queryDesc->dest)->processed;
 	}
 
-	if (cstate->binary)
+	/* raw_text/raw_binary requires exactly one row */
+	if (cstate->raw)
+	{
+		if (processed > 1)
+			ereport(ERROR,
+					(errcode(ERRCODE_TOO_MANY_ROWS),
+					 errmsg("single row result is required by RAW_TEXT/RAW_BINARY mode")));
+
+		if (processed == 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_NO_DATA_FOUND),
+					 errmsg("single row result is required by RAW_TEXT/RAW_BINARY mode")));
+	}
+
+	if (cstate->binary && !cstate->raw)
 	{
 		/* Generate trailer for a binary copy */
 		CopySendInt16(cstate, -1);
@@ -2015,28 +2120,31 @@ CopyOneRowTo(CopyState cstate, Oid tupleOid, Datum *values, bool *nulls)
 	MemoryContextReset(cstate->rowcontext);
 	oldcontext = MemoryContextSwitchTo(cstate->rowcontext);
 
-	if (cstate->binary)
+	if (!cstate->raw)
 	{
-		/* Binary per-tuple header */
-		CopySendInt16(cstate, list_length(cstate->attnumlist));
-		/* Send OID if wanted --- note attnumlist doesn't include it */
-		if (cstate->oids)
+		if (cstate->binary)
 		{
-			/* Hack --- assume Oid is same size as int32 */
-			CopySendInt32(cstate, sizeof(int32));
-			CopySendInt32(cstate, tupleOid);
+			/* Binary per-tuple header */
+			CopySendInt16(cstate, list_length(cstate->attnumlist));
+			/* Send OID if wanted --- note attnumlist doesn't include it */
+			if (cstate->oids)
+			{
+				/* Hack --- assume Oid is same size as int32 */
+				CopySendInt32(cstate, sizeof(int32));
+				CopySendInt32(cstate, tupleOid);
+			}
 		}
-	}
-	else
-	{
-		/* Text format has no per-tuple header, but send OID if wanted */
-		/* Assume digits don't need any quoting or encoding conversion */
-		if (cstate->oids)
+		else
 		{
-			string = DatumGetCString(DirectFunctionCall1(oidout,
-												ObjectIdGetDatum(tupleOid)));
-			CopySendString(cstate, string);
-			need_delim = true;
+			/* Text format has no per-tuple header, but send OID if wanted */
+			/* Assume digits don't need any quoting or encoding conversion */
+			if (cstate->oids)
+			{
+				string = DatumGetCString(DirectFunctionCall1(oidout,
+													ObjectIdGetDatum(tupleOid)));
+				CopySendString(cstate, string);
+				need_delim = true;
+			}
 		}
 	}
 
@@ -2055,6 +2163,11 @@ CopyOneRowTo(CopyState cstate, Oid tupleOid, Datum *values, bool *nulls)
 
 		if (isnull)
 		{
+			if (cstate->raw)
+				ereport(ERROR,
+						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
+						 errmsg("NULL value is not allowed in RAW_TEXT/RAW_BINARY mode")));
+
 			if (!cstate->binary)
 				CopySendString(cstate, cstate->null_print_client);
 			else
@@ -2062,7 +2175,72 @@ CopyOneRowTo(CopyState cstate, Oid tupleOid, Datum *values, bool *nulls)
 		}
 		else
 		{
-			if (!cstate->binary)
+			if (cstate->raw)
+			{
+				const void *content;
+				int size;
+			
+				if (!cstate->binary)
+				{
+					string = OutputFunctionCall(&out_functions[attnum - 1],
+												value);
+
+					/* We would to transcode, but without escaping */
+					if (cstate->need_transcoding)
+						content = pg_server_to_any(string, strlen(string), cstate->file_encoding);
+					else
+						content = string;
+
+					size = strlen((const char *) content);
+				}
+				else
+				{
+					bytea *outputbytes;
+
+					/*
+					 * Some binary output functions depends can depends on client encoding.
+					 * The binary output of xml is good example. Set client_encoding
+					 * temporaly before out function execution.
+					 */
+					if (cstate->need_transcoding)
+					{
+						int		old_server_encoding = pg_get_client_encoding();
+						volatile bool reset_encoding = false;
+
+						PG_TRY();
+						{
+							/* We don't expect an error, because encoding was checked before */
+							if (PrepareClientEncoding(cstate->file_encoding) < 0)
+								elog(ERROR, "PrepareClientEncoding(%d) failed", cstate->file_encoding);
+
+							SetClientEncoding(cstate->file_encoding);
+							reset_encoding = true;
+
+							outputbytes = SendFunctionCall(&out_functions[attnum - 1],
+													   value);
+							SetClientEncoding(old_server_encoding);
+						}
+						PG_CATCH();
+						{
+							if (reset_encoding)
+								SetClientEncoding(old_server_encoding);
+							PG_RE_THROW();
+						}
+						PG_END_TRY();
+					}
+					else
+					{
+						outputbytes = SendFunctionCall(&out_functions[attnum - 1],
+												   value);
+					}
+					content = VARDATA(outputbytes);
+					size = VARSIZE(outputbytes) - VARHDRSZ;
+				}
+
+				/* Send only content in RAW_TEXT/RAW_BINARY mode */
+				CopySendData(cstate, content, size);
+			}
+			else if (!cstate->binary)
 			{
 				string = OutputFunctionCall(&out_functions[attnum - 1],
 											value);
@@ -2811,65 +2989,69 @@ BeginCopyFrom(Relation rel,
 		}
 	}
 
-	if (!cstate->binary)
+	/* The raw mode hasn't any header information */
+	if (!cstate->raw)
 	{
-		/* must rely on user to tell us... */
-		cstate->file_has_oids = cstate->oids;
-	}
-	else
-	{
-		/* Read and verify binary header */
-		char		readSig[11];
-		int32		tmp;
-
-		/* Signature */
-		if (CopyGetData(cstate, readSig, 11, 11) != 11 ||
-			memcmp(readSig, BinarySignature, 11) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
-					 errmsg("COPY file signature not recognized")));
-		/* Flags field */
-		if (!CopyGetInt32(cstate, &tmp))
-			ereport(ERROR,
-					(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
-					 errmsg("invalid COPY file header (missing flags)")));
-		cstate->file_has_oids = (tmp & (1 << 16)) != 0;
-		tmp &= ~(1 << 16);
-		if ((tmp >> 16) != 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
-				 errmsg("unrecognized critical flags in COPY file header")));
-		/* Header extension length */
-		if (!CopyGetInt32(cstate, &tmp) ||
-			tmp < 0)
-			ereport(ERROR,
-					(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
-					 errmsg("invalid COPY file header (missing length)")));
-		/* Skip extension header, if present */
-		while (tmp-- > 0)
+		if (!cstate->binary)
+		{
+			/* must rely on user to tell us... */
+			cstate->file_has_oids = cstate->oids;
+		}
+		else
 		{
-			if (CopyGetData(cstate, readSig, 1, 1) != 1)
+			/* Read and verify binary header */
+			char		readSig[11];
+			int32		tmp;
+
+			/* Signature */
+			if (CopyGetData(cstate, readSig, 11, 11) != 11 ||
+				memcmp(readSig, BinarySignature, 11) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
+						 errmsg("COPY file signature not recognized")));
+			/* Flags field */
+			if (!CopyGetInt32(cstate, &tmp))
 				ereport(ERROR,
 						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
-						 errmsg("invalid COPY file header (wrong length)")));
+						 errmsg("invalid COPY file header (missing flags)")));
+			cstate->file_has_oids = (tmp & (1 << 16)) != 0;
+			tmp &= ~(1 << 16);
+			if ((tmp >> 16) != 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
+					 errmsg("unrecognized critical flags in COPY file header")));
+			/* Header extension length */
+			if (!CopyGetInt32(cstate, &tmp) ||
+				tmp < 0)
+				ereport(ERROR,
+						(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
+						 errmsg("invalid COPY file header (missing length)")));
+			/* Skip extension header, if present */
+			while (tmp-- > 0)
+			{
+				if (CopyGetData(cstate, readSig, 1, 1) != 1)
+					ereport(ERROR,
+							(errcode(ERRCODE_BAD_COPY_FILE_FORMAT),
+							 errmsg("invalid COPY file header (wrong length)")));
+			}
 		}
-	}
 
-	if (cstate->file_has_oids && cstate->binary)
-	{
-		getTypeBinaryInputInfo(OIDOID,
-							   &in_func_oid, &cstate->oid_typioparam);
-		fmgr_info(in_func_oid, &cstate->oid_in_function);
-	}
+		if (cstate->file_has_oids && cstate->binary)
+		{
+			getTypeBinaryInputInfo(OIDOID,
+								   &in_func_oid, &cstate->oid_typioparam);
+			fmgr_info(in_func_oid, &cstate->oid_in_function);
+		}
 
-	/* create workspace for CopyReadAttributes results */
-	if (!cstate->binary)
-	{
-		AttrNumber	attr_count = list_length(cstate->attnumlist);
-		int			nfields = cstate->file_has_oids ? (attr_count + 1) : attr_count;
+		/* create workspace for CopyReadAttributes results */
+		if (!cstate->binary)
+		{
+			AttrNumber	attr_count = list_length(cstate->attnumlist);
+			int			nfields = cstate->file_has_oids ? (attr_count + 1) : attr_count;
 
-		cstate->max_fields = nfields;
-		cstate->raw_fields = (char **) palloc(nfields * sizeof(char *));
+			cstate->max_fields = nfields;
+			cstate->raw_fields = (char **) palloc(nfields * sizeof(char *));
+		}
 	}
 
 	MemoryContextSwitchTo(oldcontext);
@@ -2968,7 +3150,54 @@ NextCopyFrom(CopyState cstate, ExprContext *econtext,
 	MemSet(values, 0, num_phys_attrs * sizeof(Datum));
 	MemSet(nulls, true, num_phys_attrs * sizeof(bool));
 
-	if (!cstate->binary)
+	if (cstate->raw)
+	{
+		int		m = linitial_int(cstate->attnumlist) - 1;
+
+		/* All content was read in first cycle */
+		if (++cstate->cur_lineno > 1)
+			return false;
+
+		CopyLoadallRawBuf(cstate);
+
+		cstate->cur_attname = NameStr(attr[m]->attname);
+
+		if (!cstate->binary)
+		{
+			char	   *cvt;
+
+			cvt = pg_any_to_server(cstate->raw_buf,
+								   cstate->raw_buf_len,
+								   cstate->file_encoding);
+
+			values[m] = InputFunctionCall(&in_functions[m],
+										  cvt,
+										  typioparams[m],
+										  attr[m]->atttypmod);
+		}
+		else
+		{
+			cstate->attribute_buf.data = cstate->raw_buf;
+			cstate->attribute_buf.len = cstate->raw_buf_len;
+			cstate->attribute_buf.cursor = 0;
+			cstate->raw_buf = NULL;
+
+			/* Call the column type's binary input converter */
+			values[m] = ReceiveFunctionCall(&in_functions[m], &cstate->attribute_buf,
+									 typioparams[m], attr[m]->atttypmod);
+
+			/* Trouble if it didn't eat the whole buffer */
+			if (cstate->attribute_buf.cursor != cstate->attribute_buf.len)
+				ereport(ERROR,
+						(errcode(ERRCODE_INVALID_BINARY_REPRESENTATION),
+						 errmsg("incorrect binary data format")));
+		}
+
+		nulls[m] = false;
+
+		cstate->cur_attname = NULL;
+	}
+	else if (!cstate->binary)
 	{
 		char	  **field_strings;
 		ListCell   *cur;
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 8469d9f..2c8d6f5 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1974,8 +1974,8 @@ psql_completion(const char *text, int start, int end)
 	/* Handle COPY [BINARY] <sth> FROM|TO filename */
 	else if (Matches4("COPY|\\copy", MatchAny, "FROM|TO", MatchAny) ||
 			 Matches5("COPY", "BINARY", MatchAny, "FROM|TO", MatchAny))
-		COMPLETE_WITH_LIST6("BINARY", "OIDS", "DELIMITER", "NULL", "CSV",
-							"ENCODING");
+		COMPLETE_WITH_LIST8("BINARY", "RAW_TEXT", "RAW_BINARY", "OIDS",
+							"DELIMITER", "NULL", "CSV", "ENCODING");
 
 	/* Handle COPY [BINARY] <sth> FROM|TO filename CSV */
 	else if (Matches5("COPY|\\copy", MatchAny, "FROM|TO", MatchAny, "CSV") ||
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index 21dd772..a2754f1 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -171,3 +171,4 @@ PQsslAttributeNames       168
 PQsslAttribute            169
 PQsetErrorContextVisibility 170
 PQresultVerboseErrorMessage 171
+PQcopyFormat              172
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index 2621767..09967e9 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -155,6 +155,7 @@ PQmakeEmptyPGresult(PGconn *conn, ExecStatusType status)
 	result->resultStatus = status;
 	result->cmdStatus[0] = '\0';
 	result->binary = 0;
+	result->raw = 0;
 	result->events = NULL;
 	result->nEvents = 0;
 	result->errMsg = NULL;
@@ -256,8 +257,10 @@ PQsetResultAttrs(PGresult *res, int numAttributes, PGresAttDesc *attDescs)
 		if (!res->attDescs[i].name)
 			return FALSE;
 
-		if (res->attDescs[i].format == 0)
+		if (res->attDescs[i].format == 0 || res->attDescs[i].format == 2)
 			res->binary = 0;
+		if (res->attDescs[i].format == 2 || res->attDescs[i].format == 3)
+			res->raw = 1;
 	}
 
 	return TRUE;
@@ -2932,6 +2935,21 @@ PQcmdStatus(PGresult *res)
 }
 
 /*
+ * PQcopyFormat
+ *
+ * Returns a info about copy mode:
+ * -1 signalize a error, 0 = text mode, 1 = binary mode, 2 = raw mode
+ */
+int
+PQcopyFormat(const PGresult *res)
+{
+	if (res->raw)
+		return 2;
+	else
+		return res->binary;
+}
+
+/*
  * PQoidStatus -
  *	if the last command was an INSERT, return the oid string
  *	if not, return ""
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 0b8c62f..1783844 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -1486,6 +1486,10 @@ getCopyStart(PGconn *conn, ExecStatusType copytype)
 		 */
 		format = (int) ((int16) format);
 		result->attDescs[i].format = format;
+
+		/* when any field uses raw format, then COPY RAW_* was used */
+		if (format == 2 || format == 3)
+			result->raw = true;
 	}
 
 	/* Success! */
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index 9ca0756..7984666 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -479,6 +479,7 @@ extern Oid	PQftype(const PGresult *res, int field_num);
 extern int	PQfsize(const PGresult *res, int field_num);
 extern int	PQfmod(const PGresult *res, int field_num);
 extern char *PQcmdStatus(PGresult *res);
+extern int	PQcopyFormat(const PGresult *res);
 extern char *PQoidStatus(const PGresult *res);	/* old and ugly */
 extern Oid	PQoidValue(const PGresult *res);	/* new and improved */
 extern char *PQcmdTuples(PGresult *res);
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 1183323..8fc4b04 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -180,6 +180,7 @@ struct pg_result
 	char		cmdStatus[CMDSTATUS_LEN];		/* cmd status from the query */
 	int			binary;			/* binary tuple values if binary == 1,
 								 * otherwise text */
+	int			raw;			/* raw mode for COPY */
 
 	/*
 	 * These fields are copied from the originating PGconn, so that operations
diff --git a/src/interfaces/libpq/test/Makefile b/src/interfaces/libpq/test/Makefile
index ab41dc3..f5f1511 100644
--- a/src/interfaces/libpq/test/Makefile
+++ b/src/interfaces/libpq/test/Makefile
@@ -9,14 +9,18 @@ endif
 override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 override LDLIBS := $(libpq_pgport) $(LDLIBS)
 
-PROGS = uri-regress
+PROGS = uri-regress copy-raw-regress
 
 all: $(PROGS)
 
 installcheck: all
 	SRCDIR='$(top_srcdir)' SUBDIR='$(subdir)' \
 		   $(PERL) $(top_srcdir)/$(subdir)/regress.pl
+	SRCDIR='$(top_srcdir)' SUBDIR='$(subdir)' \
+		   $(PERL) $(top_srcdir)/$(subdir)/copy-raw-regress.pl
 
 clean distclean maintainer-clean:
 	rm -f $(PROGS)
 	rm -f regress.out regress.diff
+	rm -f copy-raw-regress.out
+	rm -f uri-regress.o copy-raw-regress.o
\ No newline at end of file
diff --git a/src/interfaces/libpq/test/copy-raw-expected.out b/src/interfaces/libpq/test/copy-raw-expected.out
new file mode 100644
index 0000000..6bbef51
--- /dev/null
+++ b/src/interfaces/libpq/test/copy-raw-expected.out
@@ -0,0 +1,33 @@
+trying
+copy-raw-regress: buffer is empty
+copy-raw-regress: the test COPY TO was successful (a, raw_text, 0)
+copy-raw-regress: buffer is empty
+copy-raw-regress: the test COPY TO was successful (b, raw_binary, 0)
+copy-raw-regress: the test COPY TO was successful (a, raw_text, 1)
+copy-raw-regress: the test COPY TO was successful (b, raw_binary, 1)
+copy-raw-regress: the test COPY TO was successful (a, raw_text, 10)
+copy-raw-regress: the test COPY TO was successful (b, raw_binary, 10)
+copy-raw-regress: the test COPY TO was successful (a, raw_text, 1000)
+copy-raw-regress: the test COPY TO was successful (b, raw_binary, 1000)
+copy-raw-regress: the test COPY TO was successful (a, raw_text, 10000)
+copy-raw-regress: the test COPY TO was successful (b, raw_binary, 10000)
+copy-raw-regress: the test COPY TO was successful (a, raw_text, 100000)
+copy-raw-regress: the test COPY TO was successful (b, raw_binary, 100000)
+copy-raw-regress: the test COPY TO was successful (a, raw_text, 1000000)
+copy-raw-regress: the test COPY TO was successful (b, raw_binary, 1000000)
+copy-raw-regress: the test COPY FROM was successful (a, raw_text, 0)
+copy-raw-regress: the test COPY FROM was successful (b, raw_binary, 0)
+copy-raw-regress: the test COPY FROM was successful (a, raw_text, 1)
+copy-raw-regress: the test COPY FROM was successful (b, raw_binary, 1)
+copy-raw-regress: the test COPY FROM was successful (a, raw_text, 10)
+copy-raw-regress: the test COPY FROM was successful (b, raw_binary, 10)
+copy-raw-regress: the test COPY FROM was successful (a, raw_text, 1000)
+copy-raw-regress: the test COPY FROM was successful (b, raw_binary, 1000)
+copy-raw-regress: the test COPY FROM was successful (a, raw_text, 10000)
+copy-raw-regress: the test COPY FROM was successful (b, raw_binary, 10000)
+copy-raw-regress: the test COPY FROM was successful (a, raw_text, 100000)
+copy-raw-regress: the test COPY FROM was successful (b, raw_binary, 100000)
+copy-raw-regress: the test COPY FROM was successful (a, raw_text, 1000000)
+copy-raw-regress: the test COPY FROM was successful (b, raw_binary, 1000000)
+COPY RAW tests done
+
diff --git a/src/interfaces/libpq/test/copy-raw-regress.c b/src/interfaces/libpq/test/copy-raw-regress.c
new file mode 100644
index 0000000..2cdb331
--- /dev/null
+++ b/src/interfaces/libpq/test/copy-raw-regress.c
@@ -0,0 +1,597 @@
+/*
+ * copy-raw-regress.c
+ *		A test program for COPY API
+ *
+ * Portions Copyright (c) 2012-2016, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ *		src/interfaces/libpq/test/copy-raw-regress.c
+ */
+
+#include "postgres_fe.h"
+
+#include "libpq-fe.h"
+#include "pqexpbuffer.h"
+#include "catalog/pg_type.h"
+
+char *text_message = "The PostgreSQL Database, ";
+char *binary_message = "\360\237\230\203\040\360\237\230\202\040";
+
+const char *prgname = "copy-raw-regress";
+
+/*
+ * Hide possible notice from DROP TABLE IF EXIT statement
+ *
+ */
+static void
+terseNoticeProcessor(void *arg, const char *message)
+{
+	/* do nothing */
+}
+
+/*
+ * Prepare textual and binary data with requested size.
+ *
+ */
+static void
+PrepareTestValues(char *values[], int size)
+{
+	char	*tbuffer;
+	char	*bbuffer;
+	char		*p;
+	int			 nchars;
+	int			 l;
+
+	tbuffer = pg_malloc(size + 1);
+	bbuffer = pg_malloc(size + 1);
+
+	p = tbuffer;
+	nchars = size;
+	l = strlen(text_message);
+	while (nchars > 0)
+	{
+		if (l < nchars)
+		{
+			strcpy(p, text_message);
+			p += l;
+			nchars -= l;
+		}
+		else
+		{
+			strncpy(p, text_message, nchars);
+			nchars = 0;
+		}
+	}
+
+	tbuffer[size] = '\0';
+	values[0] = tbuffer;
+
+	p = bbuffer;
+	nchars = size;
+	l = 10;
+	while (nchars > 0)
+	{
+		if (l < nchars)
+		{
+			memcpy(p, binary_message, l);
+			p += l;
+			nchars -= l;
+		}
+		else
+		{
+			memset(p, 0, nchars);
+			nchars = 0;
+		}
+	}
+
+	bbuffer[size] = '\0';
+	values[1] = bbuffer;
+}
+
+/*
+ * Store textual and binary data in table copy_raw. Clean this table before.
+ *
+ */
+static bool
+InsertData(PGconn *conn, char *values[], int size)
+{
+	PGresult	*res;
+	Oid		 types[] = {TEXTOID, BYTEAOID};
+	int		 formats[] = {0, 1};
+	int		 lens[2];
+
+	lens[0] = 0;						/* text format is used, value is ignored */
+	lens[1] = size;						/* binary mode, value should be correct */
+
+	res = PQexec(conn, "TRUNCATE TABLE copy_raw");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "%s: could not to truncate table \"copy_raw\": %s",
+				  prgname, PQerrorMessage(conn));
+		PQclear(res);
+		return false;
+	}
+	PQclear(res);
+
+	res = PQexecParams(conn, "INSERT INTO copy_raw(id, a,b) VALUES(1, $1, $2)",
+							 2,
+							 types,
+							 (const char * const*) values,
+							 lens,
+							 formats,
+							 0);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "%s: could not to insert to table \"copy_raw\": %s\n",
+				  prgname, PQerrorMessage(conn));
+		PQclear(res);
+		return false;
+	}
+	PQclear(res);
+	return true;
+}
+
+/*
+ * COPY TO test - insert data to server, get it with COPY API and compare result
+ *
+ */
+static bool
+CopyToTest(PGconn *conn, int colnum, const char *method, int size)
+{
+	char 	*colnames[] = {"a", "b"};
+	char	*values[2];
+	PGresult	*res;
+	PQExpBufferData		 query;
+	int					 len;
+	char				*buffer;
+
+	PrepareTestValues(values, size);
+	if (!InsertData(conn, values, size))
+		return false;
+
+	initPQExpBuffer(&query);
+	appendPQExpBuffer(&query,
+					  "COPY (SELECT %s FROM copy_raw WHERE id = 1) TO stdout (FORMAT %s)",
+					  colnames[colnum],
+					  method);
+
+	res = PQexec(conn, query.data);
+	if (PQresultStatus(res) != PGRES_COPY_OUT)
+	{
+		fprintf(stderr, "%s: could not to execute \"%s\": %s\n",
+				  prgname,
+				  query.data,
+				  PQerrorMessage(conn));
+		PQclear(res);
+		return false;
+	}
+
+	PQclear(res);
+
+	len = PQgetCopyData(conn, &buffer, false);
+	if (len == -2)
+	{
+		fprintf(stderr, "%s: cannot to get data: %s\n",
+				  prgname, PQerrorMessage(conn));
+		return false;
+	}
+
+	if (len == -1)
+	{
+		fprintf(stderr, "%s: buffer is empty\n",
+				  prgname);
+
+		res = PQgetResult(conn);
+		if (PQresultStatus(res) != PGRES_COMMAND_OK)
+		{
+			fprintf(stderr, "%s: could not to execute \"%s\": %s\n",
+					  prgname,
+					  query.data,
+					  PQerrorMessage(conn));
+			PQclear(res);
+			return false;
+		}
+		PQclear(res);
+
+		len = 0;
+	}
+
+	if (len != size)
+	{
+		fprintf(stderr, "%s: the length of data (%d) is different than expected (%d)\n",
+				prgname, len, size);
+		return false;
+	}
+
+	if (len > 0 && memcmp(values[colnum], buffer, len) != 0)
+	{
+		fprintf(stderr, "%s: the content of loaded data is different than expected\n",
+				  prgname);
+		return false;
+	}
+
+	fprintf(stderr, "%s: the test COPY TO was successful (%s, %s, %d)\n",
+				prgname,
+				colnames[colnum],
+				method,
+				size);
+
+	PQfreemem(buffer);
+	termPQExpBuffer(&query);
+
+	pg_free(values[0]);
+	pg_free(values[1]);
+
+	return true;
+}
+
+static PGconn *
+PrepareConnection(char *connectionstr)
+{
+	PQconninfoOption *opts;
+	PQconninfoOption *opt;
+	char	   *errmsg = NULL;
+	const char **keywords = NULL;
+	const char **values = NULL;
+	int		nopts = 0;
+	int		i = 0;
+	PGconn		*conn;
+
+	opts = PQconninfoParse(connectionstr, &errmsg);
+	if (opts == NULL)
+	{
+		fprintf(stderr, "%s: %s\n", prgname, errmsg);
+		return NULL;
+	}
+
+	for (opt = opts; opt->keyword != NULL; opt++)
+	{
+		if (opt->val != NULL && opt->val[0] != '\0')
+			nopts++;
+	}
+
+	keywords = pg_malloc0((nopts + 1) * sizeof(*keywords));
+	values = pg_malloc0((nopts + 1) * sizeof(*values));
+
+	for (opt = opts; opt->keyword != NULL; opt++)
+	{
+		if (opt->val != NULL && opt->val[0] != '\0')
+		{
+			keywords[i] = opt->keyword;
+			values[i] = opt->val;
+			i++;
+		}
+	}
+
+	conn = PQconnectdbParams(keywords, values, false);
+	if (!conn)
+	{
+		fprintf(stderr, "%s: could not connect to server\n",
+				  prgname);
+		return NULL;
+	}
+
+	if (PQstatus(conn) != CONNECTION_OK)
+	{
+		fprintf(stderr, "%s: could not connect to server: %s",
+					prgname, PQerrorMessage(conn));
+		PQfinish(conn);
+		return NULL;
+	}
+
+	pg_free(values);
+	pg_free(keywords);
+	PQconninfoFree(opts);
+
+	return conn;
+}
+
+static bool
+CreateTable(PGconn *conn)
+{
+	PGresult *res;
+	PQnoticeProcessor prev_notproc;
+
+	prev_notproc = PQsetNoticeProcessor(conn, terseNoticeProcessor, NULL);
+
+	res = PQexec(conn, "DROP TABLE IF EXISTS copy_raw");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "%s: could not drop table \"copy_raw\": %s",
+				  prgname, PQerrorMessage(conn));
+		PQclear(res);
+		return false;
+	}
+	PQclear(res);
+
+	PQsetNoticeProcessor(conn, prev_notproc, NULL);
+
+	res = PQexec(conn, "CREATE TABLE copy_raw(id serial, a text, b bytea)");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "%s: could not create table table \"copy_raw\": %s",
+				  prgname, PQerrorMessage(conn));
+		PQclear(res);
+		return false;
+	}
+	PQclear(res);
+
+	return true;
+}
+
+static bool
+DropTable(PGconn *conn)
+{
+	PGresult *res;
+
+	res = PQexec(conn, "DROP TABLE copy_raw");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "%s: could not drop table \"copy_raw\": %s",
+				  prgname, PQerrorMessage(conn));
+		PQclear(res);
+		return false;
+	}
+	PQclear(res);
+
+	return true;
+}
+
+/*
+ * returns result of lastval()
+ *
+ *  returns false on error
+ */
+static bool
+get_lastval(PGconn *conn, int *lastval)
+{
+	PGresult *res;
+	char		*val;
+
+	res = PQexec(conn, "SELECT lastval()");
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		fprintf(stderr, "%s: could not to call lastval function: %s",
+				  prgname, PQerrorMessage(conn));
+		PQclear(res);
+		return false;
+	}
+
+	val = PQgetvalue(res, 0, 0);
+	Assert(val != NULL);
+
+	*lastval = atoi(val);
+
+	PQclear(res);
+
+	return true;
+}
+
+
+/*
+ * does COPY FROM RAW
+ *
+ */
+static bool
+DoCopyFromBuffer(PGconn *conn, int colnum, const char *method, const char *buffer, int size, int *id)
+{
+	PQExpBufferData		 query;
+	PGresult			*res;
+	char 	*colnames[] = {"a", "b"};
+	int		copy_data_res;
+	const char		*errormsg = NULL;
+
+	res = PQexec(conn, "TRUNCATE TABLE copy_raw");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "%s: could not to truncate table \"copy_raw\": %s",
+				  prgname, PQerrorMessage(conn));
+		PQclear(res);
+		return false;
+	}
+	PQclear(res);
+
+	initPQExpBuffer(&query);
+	appendPQExpBuffer(&query,
+					  "COPY copy_raw(%s) FROM stdin (FORMAT %s)",
+					  colnames[colnum],
+					  method);
+
+	res = PQexec(conn, query.data);
+
+
+	if (PQresultStatus(res) != PGRES_COPY_IN)
+	{
+		fprintf(stderr, "%s: could not to execute \"%s\": %s\n",
+				  prgname,
+				  query.data,
+				  PQerrorMessage(conn));
+		PQclear(res);
+		return false;
+	}
+
+	PQclear(res);
+
+	copy_data_res = PQputCopyData(conn, buffer, size);
+	if (copy_data_res == -1)
+	{
+		fprintf(stderr, "%s: could not to send data: %s\n",
+				  prgname, PQerrorMessage(conn));
+		return false;
+	}
+
+	copy_data_res = PQputCopyEnd(conn, errormsg);
+	if (errormsg != NULL)
+	{
+		fprintf(stderr, "%s: could not to send data: %s\n",
+				  prgname, errormsg);
+		return false;
+	}
+	if (copy_data_res == -1)
+	{
+		fprintf(stderr, "%s: could not to send data: %s\n",
+				  prgname, PQerrorMessage(conn));
+		return false;
+	}
+
+	res = PQgetResult(conn);
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "%s: could not to execute \"%s\": %s\n",
+				  prgname,
+				  query.data,
+				  PQerrorMessage(conn));
+		PQclear(res);
+		return false;
+	}
+
+	PQclear(res);
+	termPQExpBuffer(&query);
+
+	if (!get_lastval(conn, id))
+		return false;
+
+	return true;
+}
+
+/*
+ * Send data to server with COPY TO RAW, then get data back with SELECT BINARY
+ * and compare data.
+ */
+static bool
+CopyFromTest(PGconn *conn, int colnum, const char *method, int size)
+{
+	char 	*colnames[] = {"a", "b"};
+	char	*values[2];
+	PGresult	*res;
+	int			 id;
+	PQExpBufferData		 query;
+	char				*buffer;
+	int					len;
+
+	PrepareTestValues(values, size);
+
+	if (!DoCopyFromBuffer(conn, colnum, method, (const char *) values[colnum], size, &id))
+		return false;
+
+	initPQExpBuffer(&query);
+	appendPQExpBuffer(&query,
+					  "SELECT %s FROM copy_raw WHERE id = %d",
+					  colnames[colnum],
+					  id);
+
+	res = PQexecParams(conn, query.data,
+							 0,
+							 NULL,
+							 NULL,
+							 NULL,
+							 NULL,
+							 1);
+
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		fprintf(stderr, "%s: could not to execute \"%s\": %s\n",
+				  prgname,
+				  query.data,
+				  PQerrorMessage(conn));
+		PQclear(res);
+		return false;
+	}
+
+	termPQExpBuffer(&query);
+
+	buffer = PQgetvalue(res, 0, 0);
+	len = PQgetlength(res, 0, 0);
+
+	if (len != size)
+	{
+		fprintf(stderr, "%s: the length of data (%d) is different than expected (%d)\n",
+				prgname, len, size);
+		PQclear(res);
+		return false;
+	}
+
+	if (len > 0 && memcmp(values[colnum], buffer, len) != 0)
+	{
+		fprintf(stderr, "%s: the content of stored data is different than expected\n",
+				  prgname);
+		PQclear(res);
+		return false;
+	}
+
+	PQclear(res);
+
+	pg_free(values[0]);
+	pg_free(values[1]);
+
+	fprintf(stderr, "%s: the test COPY FROM was successful (%s, %s, %d)\n",
+				prgname,
+				colnames[colnum],
+				method,
+				size);
+
+	return true;
+}
+
+int
+main(int argc, char *argv[])
+{
+	PGconn *conn;
+	int		sizes[] = {0, 1, 10, 1000, 10000, 100000, 1000000};
+	const char *method[] = {"raw_text", "raw_binary"};
+	int		i;
+	int			j;
+
+	if (argc != 2)
+	{
+		fprintf(stderr, "%s: usage %s connection string\n",
+			  prgname, prgname);
+		exit(1);
+	}
+
+	conn = PrepareConnection(argv[1]);
+	if (conn == NULL)
+		exit(1);
+
+	if (!CreateTable(conn))
+	{
+		PQfinish(conn);
+		exit(1);
+	}
+
+	for (i = 0; i < 7; i++)
+	{
+		for (j = 0; j < 2; j++)
+		{
+			if (!CopyToTest(conn, j, method[j], sizes[i]))
+			{
+				PQfinish(conn);
+				exit(1);
+			}
+		}
+	}
+
+	for (i = 0; i < 7; i++)
+	{
+		for (j = 0; j < 2; j++)
+		{
+			if (!CopyFromTest(conn, j, method[j], sizes[i]))
+			{
+				PQfinish(conn);
+				exit(1);
+			}
+		}
+	}
+
+	if (!DropTable(conn))
+	{
+		PQfinish(conn);
+		exit(1);
+	}
+
+	PQfinish(conn);
+
+	fprintf(stderr, "COPY RAW tests done\n");
+
+	exit(0);
+}
diff --git a/src/interfaces/libpq/test/copy-raw-regress.pl b/src/interfaces/libpq/test/copy-raw-regress.pl
new file mode 100644
index 0000000..7b1714a
--- /dev/null
+++ b/src/interfaces/libpq/test/copy-raw-regress.pl
@@ -0,0 +1,48 @@
+#!/usr/bin/perl -w
+
+use strict;
+
+# use of SRCDIR/SUBDIR is required for supporting VPath builds
+my $srcdir = $ENV{'SRCDIR'} or die 'SRCDIR environment variable is not set';
+my $subdir = $ENV{'SUBDIR'} or die 'SUBDIR environment variable is not set';
+
+my $expected_out = "$srcdir/$subdir/copy-raw-expected.out";
+
+# the output file should land in the build_dir of VPath, or just in
+# the current dir, if VPath isn't used
+my $regress_out = "copy-raw-regress.out";
+
+# save STDOUT/ERR and redirect both to regress.out
+open(OLDOUT, ">&", \*STDOUT) or die "can't dup STDOUT: $!";
+open(OLDERR, ">&", \*STDERR) or die "can't dup STDERR: $!";
+
+open(STDOUT, ">", $regress_out)
+  or die "can't open $regress_out for writing: $!";
+open(STDERR, ">&", \*STDOUT) or die "can't dup STDOUT: $!";
+
+print "trying\n";
+system("./copy-raw-regress \"postgresql://\"");
+print "\n";
+
+# restore STDOUT/ERR so we can print the outcome to the user
+open(STDERR, ">&", \*OLDERR) or die; # can't complain as STDERR is still duped
+open(STDOUT, ">&", \*OLDOUT) or die "can't restore STDOUT: $!";
+
+my $diff_status = system(
+	"diff -c \"$srcdir/$subdir/$regress_out\" $expected_out >regress.diff");
+
+print "=" x 70, "\n";
+if ($diff_status == 0)
+{
+	print "All tests passed\n";
+	exit 0;
+}
+else
+{
+	print <<EOF;
+FAILED: the test result differs from the expected output
+
+Review the difference in "$subdir/regress.diff"
+EOF
+	exit 1;
+}
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index 5f6260a..3d36dd3 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -466,3 +466,17 @@ DROP FUNCTION truncate_in_subxact();
 DROP TABLE x, y;
 DROP FUNCTION fn_x_before();
 DROP FUNCTION fn_x_after();
+CREATE TABLE x(a bytea);
+INSERT INTO x VALUES('\x41484f4a0a');
+INSERT INTO x VALUES('\x41484f4a0a');
+-- should to fail
+COPY (SELECT a,a FROM x LIMIT 1) TO STDOUT (FORMAT raw_binary);
+ERROR:  Single column result/target is required in RAW_TEXT/RAW_BINARY mode
+COPY (SELECT a FROM x) TO STDOUT (FORMAT raw_binary);
+AHOJ
+AHOJ
+ERROR:  single row result is required by RAW_TEXT/RAW_BINARY mode
+-- should be ok
+COPY (SELECT a FROM x LIMIT 1) TO STDOUT (FORMAT raw_binary);
+AHOJ
+DROP TABLE x;
diff --git a/src/test/regress/input/copy.source b/src/test/regress/input/copy.source
index cb13606..085ae36 100644
--- a/src/test/regress/input/copy.source
+++ b/src/test/regress/input/copy.source
@@ -133,3 +133,82 @@ this is just a line full of junk that would error out if parsed
 \.
 
 copy copytest3 to stdout csv header;
+
+-- copy raw
+CREATE TABLE x(a bytea);
+INSERT INTO x VALUES('\x41484f4a0a');
+SELECT length(a) FROM x;
+
+INSERT INTO x VALUES('\x41484f4a0a');
+
+-- should to fail
+COPY (SELECT a,a FROM x LIMIT 1) TO '@abs_builddir@/results/raw.data' (FORMAT raw_binary);
+COPY (SELECT a FROM x) TO '@abs_builddir@/results/raw.data' (FORMAT raw_binary);
+
+-- should be ok
+COPY (SELECT a FROM x LIMIT 1) TO '@abs_builddir@/results/raw.data' (FORMAT raw_binary);
+TRUNCATE x;
+COPY x FROM '@abs_builddir@/results/raw.data' (FORMAT raw_binary);
+SELECT length(a) FROM x;
+COPY x TO stdout (FORMAT raw_binary);
+
+TRUNCATE x;
+
+\COPY x FROM '@abs_builddir@/results/raw.data' (FORMAT raw_binary)
+SELECT length(a) FROM x;
+COPY x TO stdout (FORMAT raw_binary);
+
+\COPY x TO '@abs_builddir@/results/raw2.data' (FORMAT raw_binary)
+TRUNCATE x;
+
+\COPY x FROM '@abs_builddir@/results/raw2.data' (FORMAT raw_binary)
+SELECT length(a) FROM x;
+COPY x TO stdout (FORMAT raw_binary);
+
+-- test big file
+TRUNCATE x;
+-- use different mechanism for load to bytea
+\lo_import @abs_builddir@/data/hash.data
+\set lo_oid :LASTOID
+INSERT INTO x VALUES(lo_get(:lo_oid));
+\lo_unlink :lo_oid
+
+COPY x FROM '@abs_builddir@/data/hash.data' (FORMAT raw_binary);
+\COPY x FROM '@abs_builddir@/data/hash.data' (FORMAT raw_binary)
+
+SELECT md5(a), length(a) FROM x;
+
+TRUNCATE x;
+COPY x FROM '@abs_builddir@/data/hash.data' (FORMAT raw_binary);
+COPY x TO '@abs_builddir@/results/hash2.data' (FORMAT raw_binary);
+\COPY x TO '@abs_builddir@/results/hash3.data' (FORMAT raw_binary)
+
+-- read again
+COPY x FROM '@abs_builddir@/results/hash2.data' (FORMAT raw_binary);
+\COPY x FROM '@abs_builddir@/results/hash3.data' (FORMAT raw_binary)
+-- cross
+COPY x FROM '@abs_builddir@/results/hash3.data' (FORMAT raw_binary);
+\COPY x FROM '@abs_builddir@/results/hash2.data' (FORMAT raw_binary)
+
+SELECT md5(a), length(a) FROM x;
+
+DROP TABLE x;
+
+-- insert into multicolumn table
+CREATE TABLE x(id serial, a bytea, b bytea);
+
+-- should fail, too much columns
+COPY x FROM '@abs_builddir@/results/hash2.data' (FORMAT raw_binary);
+
+-- should work
+COPY x(a) FROM '@abs_builddir@/results/hash2.data' (FORMAT raw_binary);
+COPY x(b) FROM '@abs_builddir@/results/hash2.data' (FORMAT raw_binary);
+SELECT id, md5(a), md5(b) FROM x;
+
+-- test raw_text
+COPY (SELECT a FROM x WHERE id = 1) TO '@abs_builddir@/results/hash4.data' (FORMAT raw_text);
+COPY x(a) FROM '@abs_builddir@/results/hash4.data' (FORMAT raw_text);
+SELECT id, md5(a) FROM x WHERE id = lastval();
+
+DROP TABLE x;
+
diff --git a/src/test/regress/output/copy.source b/src/test/regress/output/copy.source
index b7e372d..e34bbab 100644
--- a/src/test/regress/output/copy.source
+++ b/src/test/regress/output/copy.source
@@ -95,3 +95,114 @@ copy copytest3 to stdout csv header;
 c1,"col with , comma","col with "" quote"
 1,a,1
 2,b,2
+-- copy raw
+CREATE TABLE x(a bytea);
+INSERT INTO x VALUES('\x41484f4a0a');
+SELECT length(a) FROM x;
+ length 
+--------
+      5
+(1 row)
+
+INSERT INTO x VALUES('\x41484f4a0a');
+-- should to fail
+COPY (SELECT a,a FROM x LIMIT 1) TO '@abs_builddir@/results/raw.data' (FORMAT raw_binary);
+ERROR:  Single column result/target is required in RAW_TEXT/RAW_BINARY mode
+COPY (SELECT a FROM x) TO '@abs_builddir@/results/raw.data' (FORMAT raw_binary);
+ERROR:  single row result is required by RAW_TEXT/RAW_BINARY mode
+-- should be ok
+COPY (SELECT a FROM x LIMIT 1) TO '@abs_builddir@/results/raw.data' (FORMAT raw_binary);
+TRUNCATE x;
+COPY x FROM '@abs_builddir@/results/raw.data' (FORMAT raw_binary);
+SELECT length(a) FROM x;
+ length 
+--------
+      5
+(1 row)
+
+COPY x TO stdout (FORMAT raw_binary);
+AHOJ
+TRUNCATE x;
+\COPY x FROM '@abs_builddir@/results/raw.data' (FORMAT raw_binary)
+SELECT length(a) FROM x;
+ length 
+--------
+      5
+(1 row)
+
+COPY x TO stdout (FORMAT raw_binary);
+AHOJ
+\COPY x TO '@abs_builddir@/results/raw2.data' (FORMAT raw_binary)
+TRUNCATE x;
+\COPY x FROM '@abs_builddir@/results/raw2.data' (FORMAT raw_binary)
+SELECT length(a) FROM x;
+ length 
+--------
+      5
+(1 row)
+
+COPY x TO stdout (FORMAT raw_binary);
+AHOJ
+-- test big file
+TRUNCATE x;
+-- use different mechanism for load to bytea
+\lo_import @abs_builddir@/data/hash.data
+\set lo_oid :LASTOID
+INSERT INTO x VALUES(lo_get(:lo_oid));
+\lo_unlink :lo_oid
+COPY x FROM '@abs_builddir@/data/hash.data' (FORMAT raw_binary);
+\COPY x FROM '@abs_builddir@/data/hash.data' (FORMAT raw_binary)
+SELECT md5(a), length(a) FROM x;
+               md5                | length 
+----------------------------------+--------
+ e446fe6ea5a347e69670633412c7f8cb | 153749
+ e446fe6ea5a347e69670633412c7f8cb | 153749
+ e446fe6ea5a347e69670633412c7f8cb | 153749
+(3 rows)
+
+TRUNCATE x;
+COPY x FROM '@abs_builddir@/data/hash.data' (FORMAT raw_binary);
+COPY x TO '@abs_builddir@/results/hash2.data' (FORMAT raw_binary);
+\COPY x TO '@abs_builddir@/results/hash3.data' (FORMAT raw_binary)
+-- read again
+COPY x FROM '@abs_builddir@/results/hash2.data' (FORMAT raw_binary);
+\COPY x FROM '@abs_builddir@/results/hash3.data' (FORMAT raw_binary)
+-- cross
+COPY x FROM '@abs_builddir@/results/hash3.data' (FORMAT raw_binary);
+\COPY x FROM '@abs_builddir@/results/hash2.data' (FORMAT raw_binary)
+SELECT md5(a), length(a) FROM x;
+               md5                | length 
+----------------------------------+--------
+ e446fe6ea5a347e69670633412c7f8cb | 153749
+ e446fe6ea5a347e69670633412c7f8cb | 153749
+ e446fe6ea5a347e69670633412c7f8cb | 153749
+ e446fe6ea5a347e69670633412c7f8cb | 153749
+ e446fe6ea5a347e69670633412c7f8cb | 153749
+(5 rows)
+
+DROP TABLE x;
+-- insert into multicolumn table
+CREATE TABLE x(id serial, a bytea, b bytea);
+-- should fail, too much columns
+COPY x FROM '@abs_builddir@/results/hash2.data' (FORMAT raw_binary);
+ERROR:  Single column result/target is required in RAW_TEXT/RAW_BINARY mode
+-- should work
+COPY x(a) FROM '@abs_builddir@/results/hash2.data' (FORMAT raw_binary);
+COPY x(b) FROM '@abs_builddir@/results/hash2.data' (FORMAT raw_binary);
+SELECT id, md5(a), md5(b) FROM x;
+ id |               md5                |               md5                
+----+----------------------------------+----------------------------------
+  1 | e446fe6ea5a347e69670633412c7f8cb | 
+  2 |                                  | e446fe6ea5a347e69670633412c7f8cb
+(2 rows)
+
+-- test raw_text
+COPY (SELECT a FROM x WHERE id = 1) TO '@abs_builddir@/results/hash4.data' (FORMAT raw_text);
+COPY x(a) FROM '@abs_builddir@/results/hash4.data' (FORMAT raw_text);
+SELECT id, md5(a) FROM x WHERE id = lastval();
+ id |               md5                
+----+----------------------------------
+  3 | e446fe6ea5a347e69670633412c7f8cb
+(1 row)
+
+DROP TABLE x;
diff --git a/src/test/regress/sql/copy2.sql b/src/test/regress/sql/copy2.sql
index 39a9deb..7e22ee4 100644
--- a/src/test/regress/sql/copy2.sql
+++ b/src/test/regress/sql/copy2.sql
@@ -333,3 +333,16 @@ DROP FUNCTION truncate_in_subxact();
 DROP TABLE x, y;
 DROP FUNCTION fn_x_before();
 DROP FUNCTION fn_x_after();
+
+CREATE TABLE x(a bytea);
+INSERT INTO x VALUES('\x41484f4a0a');
+INSERT INTO x VALUES('\x41484f4a0a');
+
+-- should to fail
+COPY (SELECT a,a FROM x LIMIT 1) TO STDOUT (FORMAT raw_binary);
+COPY (SELECT a FROM x) TO STDOUT (FORMAT raw_binary);
+
+-- should be ok
+COPY (SELECT a FROM x LIMIT 1) TO STDOUT (FORMAT raw_binary);
+
+DROP TABLE x;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to