diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c
index 5b91df26b407d9a0cca27cf00fe40ca2085d16c1..ae389cfdae08927d48d4b72c75628cb71307e15e 100644
--- a/src/bin/psql/copy.c
+++ b/src/bin/psql/copy.c
@@ -3,7 +3,7 @@
  *
  * Copyright (c) 2000-2009, PostgreSQL Global Development Group
  *
- * $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.82 2009/08/07 20:16:11 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/psql/copy.c,v 1.83 2009/09/19 21:51:21 tgl Exp $
  */
 #include "postgres_fe.h"
 #include "copy.h"
@@ -32,43 +32,28 @@
  * -- parses \copy command line
  *
  * The documented syntax is:
- *	\copy tablename [(columnlist)] from|to filename
- *	  [ with ] [ binary ] [ oids ] [ delimiter [as] char ] [ null [as] string ]
- *	  [ csv  [ header ] [ quote [ AS ] string ]  escape [as] string
- *		[ force not null column [, ...] | force quote column [, ...] | * ] ]
+ *	\copy tablename [(columnlist)] from|to filename [options]
+ *	\copy ( select stmt ) to filename [options]
  *
- *	\copy ( select stmt ) to filename
- *	  [ with ] [ binary ] [ delimiter [as] char ] [ null [as] string ]
- *	  [ csv  [ header ] [ quote [ AS ] string ]  escape [as] string
- *		[ force quote column [, ...] | * ] ]
- *
- * Force quote only applies for copy to; force not null only applies for
- * copy from.
+ * An undocumented fact is that you can still write BINARY before the
+ * tablename; this is a hangover from the pre-7.3 syntax.  The options
+ * syntax varies across backend versions, but we avoid all that mess
+ * by just transmitting the stuff after the filename literally.
  *
  * table name can be double-quoted and can have a schema part.
  * column names can be double-quoted.
- * filename, char, and string can be single-quoted like SQL literals.
+ * filename can be single-quoted like SQL literals.
  *
  * returns a malloc'ed structure with the options, or NULL on parsing error
  */
 
 struct copy_options
 {
-	char	   *table;
-	char	   *column_list;
+	char	   *before_tofrom;	/* COPY string before TO/FROM */
+	char	   *after_tofrom;	/* COPY string after TO/FROM filename */
 	char	   *file;			/* NULL = stdin/stdout */
 	bool		psql_inout;		/* true = use psql stdin/stdout */
-	bool		from;
-	bool		binary;
-	bool		oids;
-	bool		csv_mode;
-	bool		header;
-	char	   *delim;
-	char	   *null;
-	char	   *quote;
-	char	   *escape;
-	char	   *force_quote_list;
-	char	   *force_notnull_list;
+	bool		from;			/* true = FROM, false = TO */
 };
 
 
@@ -77,15 +62,9 @@ free_copy_options(struct copy_options * ptr)
 {
 	if (!ptr)
 		return;
-	free(ptr->table);
-	free(ptr->column_list);
+	free(ptr->before_tofrom);
+	free(ptr->after_tofrom);
 	free(ptr->file);
-	free(ptr->delim);
-	free(ptr->null);
-	free(ptr->quote);
-	free(ptr->escape);
-	free(ptr->force_quote_list);
-	free(ptr->force_notnull_list);
 	free(ptr);
 }
 
@@ -108,14 +87,11 @@ static struct copy_options *
 parse_slash_copy(const char *args)
 {
 	struct copy_options *result;
-	char	   *line;
 	char	   *token;
 	const char *whitespace = " \t\n\r";
 	char		nonstd_backslash = standard_strings() ? 0 : '\\';
 
-	if (args)
-		line = pg_strdup(args);
-	else
+	if (!args)
 	{
 		psql_error("\\copy: arguments required\n");
 		return NULL;
@@ -123,22 +99,23 @@ parse_slash_copy(const char *args)
 
 	result = pg_calloc(1, sizeof(struct copy_options));
 
-	token = strtokx(line, whitespace, ".,()", "\"",
+	result->before_tofrom = pg_strdup("");		/* initialize for appending */
+
+	token = strtokx(args, whitespace, ".,()", "\"",
 					0, false, false, pset.encoding);
 	if (!token)
 		goto error;
 
+	/* The following can be removed when we drop 7.3 syntax support */
 	if (pg_strcasecmp(token, "binary") == 0)
 	{
-		result->binary = true;
+		xstrcat(&result->before_tofrom, token);
 		token = strtokx(NULL, whitespace, ".,()", "\"",
 						0, false, false, pset.encoding);
 		if (!token)
 			goto error;
 	}
 
-	result->table = pg_strdup(token);
-
 	/* Handle COPY (SELECT) case */
 	if (token[0] == '(')
 	{
@@ -146,7 +123,9 @@ parse_slash_copy(const char *args)
 
 		while (parens > 0)
 		{
-			token = strtokx(NULL, whitespace, ".,()", "\"'",
+			xstrcat(&result->before_tofrom, " ");
+			xstrcat(&result->before_tofrom, token);
+			token = strtokx(NULL, whitespace, "()", "\"'",
 							nonstd_backslash, true, false, pset.encoding);
 			if (!token)
 				goto error;
@@ -154,11 +133,11 @@ parse_slash_copy(const char *args)
 				parens++;
 			else if (token[0] == ')')
 				parens--;
-			xstrcat(&result->table, " ");
-			xstrcat(&result->table, token);
 		}
 	}
 
+	xstrcat(&result->before_tofrom, " ");
+	xstrcat(&result->before_tofrom, token);
 	token = strtokx(NULL, whitespace, ".,()", "\"",
 					0, false, false, pset.encoding);
 	if (!token)
@@ -171,12 +150,12 @@ parse_slash_copy(const char *args)
 	if (token[0] == '.')
 	{
 		/* handle schema . table */
-		xstrcat(&result->table, token);
+		xstrcat(&result->before_tofrom, token);
 		token = strtokx(NULL, whitespace, ".,()", "\"",
 						0, false, false, pset.encoding);
 		if (!token)
 			goto error;
-		xstrcat(&result->table, token);
+		xstrcat(&result->before_tofrom, token);
 		token = strtokx(NULL, whitespace, ".,()", "\"",
 						0, false, false, pset.encoding);
 		if (!token)
@@ -186,24 +165,19 @@ parse_slash_copy(const char *args)
 	if (token[0] == '(')
 	{
 		/* handle parenthesized column list */
-		result->column_list = pg_strdup(token);
 		for (;;)
 		{
-			token = strtokx(NULL, whitespace, ".,()", "\"",
-							0, false, false, pset.encoding);
-			if (!token || strchr(".,()", token[0]))
-				goto error;
-			xstrcat(&result->column_list, token);
-			token = strtokx(NULL, whitespace, ".,()", "\"",
+			xstrcat(&result->before_tofrom, " ");
+			xstrcat(&result->before_tofrom, token);
+			token = strtokx(NULL, whitespace, "()", "\"",
 							0, false, false, pset.encoding);
 			if (!token)
 				goto error;
-			xstrcat(&result->column_list, token);
 			if (token[0] == ')')
 				break;
-			if (token[0] != ',')
-				goto error;
 		}
+		xstrcat(&result->before_tofrom, " ");
+		xstrcat(&result->before_tofrom, token);
 		token = strtokx(NULL, whitespace, ".,()", "\"",
 						0, false, false, pset.encoding);
 		if (!token)
@@ -241,156 +215,11 @@ parse_slash_copy(const char *args)
 		expand_tilde(&result->file);
 	}
 
-	token = strtokx(NULL, whitespace, NULL, NULL,
+	/* Collect the rest of the line (COPY options) */
+	token = strtokx(NULL, "", NULL, NULL,
 					0, false, false, pset.encoding);
-
 	if (token)
-	{
-		/*
-		 * WITH is optional.  Also, the backend will allow WITH followed by
-		 * nothing, so we do too.
-		 */
-		if (pg_strcasecmp(token, "with") == 0)
-			token = strtokx(NULL, whitespace, NULL, NULL,
-							0, false, false, pset.encoding);
-
-		while (token)
-		{
-			bool		fetch_next;
-
-			fetch_next = true;
-
-			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)
-				result->header = true;
-			else if (pg_strcasecmp(token, "delimiter") == 0)
-			{
-				if (result->delim)
-					goto error;
-				token = strtokx(NULL, whitespace, NULL, "'",
-								nonstd_backslash, true, false, pset.encoding);
-				if (token && pg_strcasecmp(token, "as") == 0)
-					token = strtokx(NULL, whitespace, NULL, "'",
-							   nonstd_backslash, true, false, pset.encoding);
-				if (token)
-					result->delim = pg_strdup(token);
-				else
-					goto error;
-			}
-			else if (pg_strcasecmp(token, "null") == 0)
-			{
-				if (result->null)
-					goto error;
-				token = strtokx(NULL, whitespace, NULL, "'",
-								nonstd_backslash, true, false, pset.encoding);
-				if (token && pg_strcasecmp(token, "as") == 0)
-					token = strtokx(NULL, whitespace, NULL, "'",
-							   nonstd_backslash, true, false, pset.encoding);
-				if (token)
-					result->null = pg_strdup(token);
-				else
-					goto error;
-			}
-			else if (pg_strcasecmp(token, "quote") == 0)
-			{
-				if (result->quote)
-					goto error;
-				token = strtokx(NULL, whitespace, NULL, "'",
-								nonstd_backslash, true, false, pset.encoding);
-				if (token && pg_strcasecmp(token, "as") == 0)
-					token = strtokx(NULL, whitespace, NULL, "'",
-							   nonstd_backslash, true, false, pset.encoding);
-				if (token)
-					result->quote = pg_strdup(token);
-				else
-					goto error;
-			}
-			else if (pg_strcasecmp(token, "escape") == 0)
-			{
-				if (result->escape)
-					goto error;
-				token = strtokx(NULL, whitespace, NULL, "'",
-								nonstd_backslash, true, false, pset.encoding);
-				if (token && pg_strcasecmp(token, "as") == 0)
-					token = strtokx(NULL, whitespace, NULL, "'",
-							   nonstd_backslash, true, false, pset.encoding);
-				if (token)
-					result->escape = pg_strdup(token);
-				else
-					goto error;
-			}
-			else if (pg_strcasecmp(token, "force") == 0)
-			{
-				token = strtokx(NULL, whitespace, ",", "\"",
-								0, false, false, pset.encoding);
-				if (pg_strcasecmp(token, "quote") == 0)
-				{
-					if (result->force_quote_list)
-						goto error;
-					/* handle column list */
-					fetch_next = false;
-					for (;;)
-					{
-						token = strtokx(NULL, whitespace, ",", "\"",
-										0, false, false, pset.encoding);
-						if (!token || strchr(",", token[0]))
-							goto error;
-						if (!result->force_quote_list)
-							result->force_quote_list = pg_strdup(token);
-						else
-							xstrcat(&result->force_quote_list, token);
-						token = strtokx(NULL, whitespace, ",", "\"",
-										0, false, false, pset.encoding);
-						if (!token || token[0] != ',')
-							break;
-						xstrcat(&result->force_quote_list, token);
-					}
-				}
-				else if (pg_strcasecmp(token, "not") == 0)
-				{
-					if (result->force_notnull_list)
-						goto error;
-					token = strtokx(NULL, whitespace, ",", "\"",
-									0, false, false, pset.encoding);
-					if (pg_strcasecmp(token, "null") != 0)
-						goto error;
-					/* handle column list */
-					fetch_next = false;
-					for (;;)
-					{
-						token = strtokx(NULL, whitespace, ",", "\"",
-										0, false, false, pset.encoding);
-						if (!token || strchr(",", token[0]))
-							goto error;
-						if (!result->force_notnull_list)
-							result->force_notnull_list = pg_strdup(token);
-						else
-							xstrcat(&result->force_notnull_list, token);
-						token = strtokx(NULL, whitespace, ",", "\"",
-										0, false, false, pset.encoding);
-						if (!token || token[0] != ',')
-							break;
-						xstrcat(&result->force_notnull_list, token);
-					}
-				}
-				else
-					goto error;
-			}
-			else
-				goto error;
-
-			if (fetch_next)
-				token = strtokx(NULL, whitespace, NULL, NULL,
-								0, false, false, pset.encoding);
-		}
-	}
-
-	free(line);
+		result->after_tofrom = pg_strdup(token);
 
 	return result;
 
@@ -400,29 +229,11 @@ error:
 	else
 		psql_error("\\copy: parse error at end of line\n");
 	free_copy_options(result);
-	free(line);
 
 	return NULL;
 }
 
 
-/*
- * Handle one of the "string" options of COPY.	If the user gave a quoted
- * string, pass it to the backend as-is; if it wasn't quoted then quote
- * and escape it.
- */
-static void
-emit_copy_option(PQExpBuffer query, const char *keyword, const char *option)
-{
-	appendPQExpBufferStr(query, keyword);
-	if (option[0] == '\'' ||
-		((option[0] == 'E' || option[0] == 'e') && option[1] == '\''))
-		appendPQExpBufferStr(query, option);
-	else
-		appendStringLiteralConn(query, option, pset.db);
-}
-
-
 /*
  * Execute a \copy command (frontend copy). We have to open a file, then
  * submit a COPY query to the backend and either feed it data from the
@@ -444,51 +255,7 @@ do_copy(const char *args)
 	if (!options)
 		return false;
 
-	initPQExpBuffer(&query);
-
-	printfPQExpBuffer(&query, "COPY ");
-
-	appendPQExpBuffer(&query, "%s ", options->table);
-
-	if (options->column_list)
-		appendPQExpBuffer(&query, "%s ", options->column_list);
-
-	if (options->from)
-		appendPQExpBuffer(&query, "FROM STDIN");
-	else
-		appendPQExpBuffer(&query, "TO STDOUT");
-
-
-	if (options->binary)
-		appendPQExpBuffer(&query, " BINARY ");
-
-	if (options->oids)
-		appendPQExpBuffer(&query, " OIDS ");
-
-	if (options->delim)
-		emit_copy_option(&query, " DELIMITER ", options->delim);
-
-	if (options->null)
-		emit_copy_option(&query, " NULL AS ", options->null);
-
-	if (options->csv_mode)
-		appendPQExpBuffer(&query, " CSV");
-
-	if (options->header)
-		appendPQExpBuffer(&query, " HEADER");
-
-	if (options->quote)
-		emit_copy_option(&query, " QUOTE AS ", options->quote);
-
-	if (options->escape)
-		emit_copy_option(&query, " ESCAPE AS ", options->escape);
-
-	if (options->force_quote_list)
-		appendPQExpBuffer(&query, " FORCE QUOTE %s", options->force_quote_list);
-
-	if (options->force_notnull_list)
-		appendPQExpBuffer(&query, " FORCE NOT NULL %s", options->force_notnull_list);
-
+	/* prepare to read or write the target file */
 	if (options->file)
 		canonicalize_path(options->file);
 
@@ -504,8 +271,7 @@ do_copy(const char *args)
 	else
 	{
 		if (options->file)
-			copystream = fopen(options->file,
-							   options->binary ? PG_BINARY_W : "w");
+			copystream = fopen(options->file, PG_BINARY_W);
 		else if (!options->psql_inout)
 			copystream = pset.queryFout;
 		else
@@ -531,6 +297,17 @@ do_copy(const char *args)
 		return false;
 	}
 
+	/* build the command we will send to the backend */
+	initPQExpBuffer(&query);
+	printfPQExpBuffer(&query, "COPY ");
+	appendPQExpBufferStr(&query, options->before_tofrom);
+	if (options->from)
+		appendPQExpBuffer(&query, " FROM STDIN ");
+	else
+		appendPQExpBuffer(&query, " TO STDOUT ");
+	if (options->after_tofrom)
+		appendPQExpBufferStr(&query, options->after_tofrom);
+
 	result = PSQLexec(query.data, true);
 	termPQExpBuffer(&query);