Index: doc/src/sgml/ref/copy.sgml
===================================================================
RCS file: /cvsroot/pgsql-server/doc/src/sgml/ref/copy.sgml,v
retrieving revision 1.56
diff -c -c -r1.56 copy.sgml
*** doc/src/sgml/ref/copy.sgml	19 Apr 2004 17:22:30 -0000	1.56
--- doc/src/sgml/ref/copy.sgml	21 Apr 2004 00:17:51 -0000
***************
*** 29,35 ****
            [ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
            [ CSV [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ] 
                  [ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
!                 [ LITERAL <replaceable class="parameter">column</replaceable> [, ...] ]
  
  COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable class="parameter">column</replaceable> [, ...] ) ]
      TO { '<replaceable class="parameter">filename</replaceable>' | STDOUT }
--- 29,35 ----
            [ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
            [ CSV [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ] 
                  [ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
!                 [ FORCE NOT NULL <replaceable class="parameter">column</replaceable> [, ...] ]
  
  COPY <replaceable class="parameter">tablename</replaceable> [ ( <replaceable class="parameter">column</replaceable> [, ...] ) ]
      TO { '<replaceable class="parameter">filename</replaceable>' | STDOUT }
***************
*** 40,46 ****
            [ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
            [ CSV [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ] 
                  [ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
!                 [ FORCE <replaceable class="parameter">column</replaceable> [, ...] ]
  </synopsis>
   </refsynopsisdiv>
   
--- 40,46 ----
            [ NULL [ AS ] '<replaceable class="parameter">null string</replaceable>' ]
            [ CSV [ QUOTE [ AS ] '<replaceable class="parameter">quote</replaceable>' ] 
                  [ ESCAPE [ AS ] '<replaceable class="parameter">escape</replaceable>' ]
!                 [ FORCE QUOTE <replaceable class="parameter">column</replaceable> [, ...] ]
  </synopsis>
   </refsynopsisdiv>
   
***************
*** 185,194 ****
      <term><literal>CSV</literal></term>
      <listitem>
       <para>
!       Enables Comma Separated Variable (<literal>CSV</>) mode.  (Also called
!       Comma Separated Value).  It sets the default <literal>DELIMITER</> to 
!       comma, and <literal>QUOTE</> and <literal>ESCAPE</> values to 
!       double-quote.
       </para>
      </listitem>
     </varlistentry>
--- 185,194 ----
      <term><literal>CSV</literal></term>
      <listitem>
       <para>
!       Enables Comma Separated Variable (<literal>CSV</>) mode. (Also
!       called Comma Separated Value). It sets the default
!       <literal>DELIMITER</> to comma, and <literal>QUOTE</> and
!       <literal>ESCAPE</> values to double-quote.
       </para>
      </listitem>
     </varlistentry>
***************
*** 207,244 ****
      <term><replaceable class="parameter">escape</replaceable></term>
      <listitem>
       <para>
!       Specifies the character that should appear before a <literal>QUOTE</>
!       data character value in <literal>CSV</> mode.  The default is the
!       <literal>QUOTE</> value (usually double-quote).
       </para>
      </listitem>
     </varlistentry>
  
     <varlistentry>
!     <term><literal>FORCE</></term>
      <listitem>
       <para>
!       In <literal>CSV</> <command>COPY TO</> mode, forces quoting
!       to be used for all non-<literal>NULL</> values in each specified 
!       column.  <literal>NULL</> output is never quoted.
       </para>
      </listitem>
     </varlistentry>
  
     <varlistentry>
!     <term><literal>LITERAL</></term>
      <listitem>
       <para>
!       In <literal>CSV</> <command>COPY FROM</> mode, for each column specified,
!       do not do a <literal>null string</> comparison;  instead load the value 
!       literally.  <literal>QUOTE</> and <literal>ESCAPE</> processing are still
!       performed.
!      </para>
!      <para>
!       If the <literal>null string</> is <literal>''</> (the default 
!       in <literal>CSV</> mode), a missing input value (<literal>delimiter, 
!       delimiter</>), will load as a zero-length string.  <literal>Delimiter, quote, 
!       quote, delimiter</> is always treated as a zero-length string on input.
       </para>
      </listitem>
     </varlistentry>
--- 207,239 ----
      <term><replaceable class="parameter">escape</replaceable></term>
      <listitem>
       <para>
!       Specifies the character that should appear before a
!       <literal>QUOTE</> data character value in <literal>CSV</> mode.
!       The default is the <literal>QUOTE</> value (usually double-quote).
       </para>
      </listitem>
     </varlistentry>
  
     <varlistentry>
!     <term><literal>FORCE QUOTE</></term>
      <listitem>
       <para>
!       In <literal>CSV</> <command>COPY TO</> mode, forces quoting to be
!       used for all non-<literal>NULL</> values in each specified column.
!       <literal>NULL</> output is never quoted.
       </para>
      </listitem>
     </varlistentry>
  
     <varlistentry>
!     <term><literal>FORCE NOT NULL</></term>
      <listitem>
       <para>
!       In <literal>CSV</> <command>COPY FROM</> mode, process each
!       specified column as though it was quoted and not a
!       <literal>NULL</> value. For the default <literal>null string</> in
!       <literal>CSV</> mode (<literal>''</>), this causes a missing
!       values to be input as a zero-length strings.
       </para>
      </listitem>
     </varlistentry>
***************
*** 483,489 ****
      suffixed by the <literal>QUOTE</> character, and any occurrence
      within the value of a <literal>QUOTE</> character or the
      <literal>ESCAPE</> character is preceded by the escape character.
!     You can also use <literal>FORCE</> to force quotes when outputting
      non-<literal>NULL</> values in specific columns.
     </para>
  
--- 478,484 ----
      suffixed by the <literal>QUOTE</> character, and any occurrence
      within the value of a <literal>QUOTE</> character or the
      <literal>ESCAPE</> character is preceded by the escape character.
!     You can also use <literal>FORCE QUOTE</> to force quotes when outputting
      non-<literal>NULL</> values in specific columns.
     </para>
  
***************
*** 496,502 ****
      is quoted. Therefore, using the default settings, a <literal>NULL</> is
      written as an unquoted empty string, while an empty string is
      written with double quotes (<literal>""</>). Reading values follows 
!     similar rules. You can use <literal>LITERAL</> to prevent <literal>NULL</>
      input comparisons for specific columns.
     </para>
  
--- 491,497 ----
      is quoted. Therefore, using the default settings, a <literal>NULL</> is
      written as an unquoted empty string, while an empty string is
      written with double quotes (<literal>""</>). Reading values follows 
!     similar rules. You can use <literal>FORCE NOT NULL</> to prevent <literal>NULL</>
      input comparisons for specific columns.
     </para>
  
Index: doc/src/sgml/ref/psql-ref.sgml
===================================================================
RCS file: /cvsroot/pgsql-server/doc/src/sgml/ref/psql-ref.sgml,v
retrieving revision 1.111
diff -c -c -r1.111 psql-ref.sgml
*** doc/src/sgml/ref/psql-ref.sgml	19 Apr 2004 17:22:30 -0000	1.111
--- doc/src/sgml/ref/psql-ref.sgml	21 Apr 2004 00:17:53 -0000
***************
*** 713,720 ****
              [ <literal>null [as] </literal> '<replaceable class="parameter">string</replaceable>' ]</literal>
              [ <literal>csv [ quote [as] </literal> '<replaceable class="parameter">character</replaceable>' ]
                             [ <literal>escape [as] </literal> '<replaceable class="parameter">character</replaceable>' ]
!                            [ <literal>force</> <replaceable class="parameter">column_list</replaceable> ]
!                            [ <literal>literal</> <replaceable class="parameter">column_list</replaceable> ] ]
          </term>
  
          <listitem>
--- 713,720 ----
              [ <literal>null [as] </literal> '<replaceable class="parameter">string</replaceable>' ]</literal>
              [ <literal>csv [ quote [as] </literal> '<replaceable class="parameter">character</replaceable>' ]
                             [ <literal>escape [as] </literal> '<replaceable class="parameter">character</replaceable>' ]
!                            [ <literal>force quote</> <replaceable class="parameter">column_list</replaceable> ]
!                            [ <literal>force not null</> <replaceable class="parameter">column_list</replaceable> ] ]
          </term>
  
          <listitem>
Index: src/backend/commands/copy.c
===================================================================
RCS file: /cvsroot/pgsql-server/src/backend/commands/copy.c,v
retrieving revision 1.222
diff -c -c -r1.222 copy.c
*** src/backend/commands/copy.c	19 Apr 2004 21:58:02 -0000	1.222
--- src/backend/commands/copy.c	21 Apr 2004 00:17:55 -0000
***************
*** 132,141 ****
  /* non-export function prototypes */
  static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
  	   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
! 	   List *force_atts);
  static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
  		 char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
! 		 List *literal_atts);
  static bool CopyReadLine(void);
  static char *CopyReadAttribute(const char *delim, const char *null_print,
  							   CopyReadResult *result, bool *isnull);
--- 132,141 ----
  /* non-export function prototypes */
  static void CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
  	   char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
! 	   List *force_quote_atts);
  static void CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
  		 char *delim, char *null_print, bool csv_mode, char *quote, char *escape,
! 		 List *force_notnull_atts);
  static bool CopyReadLine(void);
  static char *CopyReadAttribute(const char *delim, const char *null_print,
  							   CopyReadResult *result, bool *isnull);
***************
*** 695,704 ****
  	char	   *quote = NULL;
  	char	   *escape = NULL;
  	char	   *null_print = NULL;
! 	List	   *force = NIL;
! 	List	   *literal = NIL;
! 	List	   *force_atts = NIL;
! 	List	   *literal_atts = NIL;
  	Relation	rel;
  	AclMode		required_access = (is_from ? ACL_INSERT : ACL_SELECT);
  	AclResult	aclresult;
--- 695,704 ----
  	char	   *quote = NULL;
  	char	   *escape = NULL;
  	char	   *null_print = NULL;
! 	List	   *force_quote = NIL;
! 	List	   *force_notnull = NIL;
! 	List	   *force_quote_atts = NIL;
! 	List	   *force_notnull_atts = NIL;
  	Relation	rel;
  	AclMode		required_access = (is_from ? ACL_INSERT : ACL_SELECT);
  	AclResult	aclresult;
***************
*** 764,784 ****
  						 errmsg("conflicting or redundant options")));
  			escape = strVal(defel->arg);
  		}
! 		else if (strcmp(defel->defname, "force") == 0)
  		{
! 			if (force)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			force = (List *)defel->arg;
  		}
! 		else if (strcmp(defel->defname, "literal") == 0)
  		{
! 			if (literal)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			literal = (List *)defel->arg;
  		}
  		else
  			elog(ERROR, "option \"%s\" not recognized",
--- 764,784 ----
  						 errmsg("conflicting or redundant options")));
  			escape = strVal(defel->arg);
  		}
! 		else if (strcmp(defel->defname, "force_quote") == 0)
  		{
! 			if (force_quote)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			force_quote = (List *)defel->arg;
  		}
! 		else if (strcmp(defel->defname, "force_notnull") == 0)
  		{
! 			if (force_notnull)
  				ereport(ERROR,
  						(errcode(ERRCODE_SYNTAX_ERROR),
  						 errmsg("conflicting or redundant options")));
! 			force_notnull = (List *)defel->arg;
  		}
  		else
  			elog(ERROR, "option \"%s\" not recognized",
***************
*** 850,877 ****
  				 errmsg("COPY escape must be a single character")));
  
  	/*
! 	 * Check force
  	 */
! 	if (!csv_mode && force != NIL)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("COPY force available only in CSV mode")));
! 	if (force != NIL && is_from)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("COPY force only available using COPY TO")));
  
  	/*
! 	 * Check literal
  	 */
! 	if (!csv_mode && literal != NIL)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("COPY literal available only in CSV mode")));
! 	if (literal != NIL && !is_from)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("COPY literal only available using COPY FROM")));
  
  	/*
  	 * Don't allow the delimiter to appear in the null string.
--- 850,877 ----
  				 errmsg("COPY escape must be a single character")));
  
  	/*
! 	 * Check force_quote
  	 */
! 	if (!csv_mode && force_quote != NIL)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("COPY force quote available only in CSV mode")));
! 	if (force_quote != NIL && is_from)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("COPY force quote only available using COPY TO")));
  
  	/*
! 	 * Check force_notnull
  	 */
! 	if (!csv_mode && force_notnull != NIL)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("COPY force not null available only in CSV mode")));
! 	if (force_notnull != NIL && !is_from)
  		ereport(ERROR,
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
! 				 errmsg("COPY force not null only available using COPY FROM")));
  
  	/*
  	 * Don't allow the delimiter to appear in the null string.
***************
*** 928,974 ****
  	attnumlist = CopyGetAttnums(rel, attnamelist);
  
  	/*
! 	 * Check that FORCE references valid COPY columns
  	 */
! 	if (force)
  	{
  		TupleDesc	tupDesc = RelationGetDescr(rel);
  		Form_pg_attribute *attr = tupDesc->attrs;
  		List	   *cur;
  
! 		force_atts = CopyGetAttnums(rel, force);
  
! 		foreach(cur, force_atts)
  		{
  			int			attnum = lfirsti(cur);
  
  			if (!intMember(attnum, attnumlist))
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
! 						 errmsg("FORCE column \"%s\" not referenced by COPY",
  								NameStr(attr[attnum - 1]->attname))));
  		}
  	}
  	
  	/*
! 	 * Check that LITERAL references valid COPY columns
  	 */
! 	if (literal)
  	{
  		List	   *cur;
  		TupleDesc	tupDesc = RelationGetDescr(rel);
  		Form_pg_attribute *attr = tupDesc->attrs;
  
! 		literal_atts = CopyGetAttnums(rel, literal);
  
! 		foreach(cur, literal_atts)
  		{
  			int			attnum = lfirsti(cur);
  
  			if (!intMember(attnum, attnumlist))
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
! 						 errmsg("LITERAL column \"%s\" not referenced by COPY",
  								NameStr(attr[attnum - 1]->attname))));
  		}
  	}
--- 928,974 ----
  	attnumlist = CopyGetAttnums(rel, attnamelist);
  
  	/*
! 	 * Check that FORCE QUOTE references valid COPY columns
  	 */
! 	if (force_quote)
  	{
  		TupleDesc	tupDesc = RelationGetDescr(rel);
  		Form_pg_attribute *attr = tupDesc->attrs;
  		List	   *cur;
  
! 		force_quote_atts = CopyGetAttnums(rel, force_quote);
  
! 		foreach(cur, force_quote_atts)
  		{
  			int			attnum = lfirsti(cur);
  
  			if (!intMember(attnum, attnumlist))
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
! 						 errmsg("FORCE QUOTE column \"%s\" not referenced by COPY",
  								NameStr(attr[attnum - 1]->attname))));
  		}
  	}
  	
  	/*
! 	 * Check that FORCE NOT NULL references valid COPY columns
  	 */
! 	if (force_notnull)
  	{
  		List	   *cur;
  		TupleDesc	tupDesc = RelationGetDescr(rel);
  		Form_pg_attribute *attr = tupDesc->attrs;
  
! 		force_notnull_atts = CopyGetAttnums(rel, force_notnull);
  
! 		foreach(cur, force_notnull_atts)
  		{
  			int			attnum = lfirsti(cur);
  
  			if (!intMember(attnum, attnumlist))
  				ereport(ERROR,
  						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
! 						 errmsg("FORCE NOT NULL column \"%s\" not referenced by COPY",
  								NameStr(attr[attnum - 1]->attname))));
  		}
  	}
***************
*** 1037,1043 ****
  			}
  		}
  		CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
! 				 quote, escape, literal_atts);
  	}
  	else
  	{							/* copy from database to file */
--- 1037,1043 ----
  			}
  		}
  		CopyFrom(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
! 				 quote, escape, force_notnull_atts);
  	}
  	else
  	{							/* copy from database to file */
***************
*** 1100,1106 ****
  			}
  		}
  		CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
! 				quote, escape, force_atts);
  	}
  
  	if (!pipe)
--- 1100,1106 ----
  			}
  		}
  		CopyTo(rel, attnumlist, binary, oids, delim, null_print, csv_mode,
! 				quote, escape, force_quote_atts);
  	}
  
  	if (!pipe)
***************
*** 1133,1139 ****
  static void
  CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
  	   char *delim, char *null_print, bool csv_mode, char *quote,
! 	   char *escape, List *force_atts)
  {
  	HeapTuple	tuple;
  	TupleDesc	tupDesc;
--- 1133,1139 ----
  static void
  CopyTo(Relation rel, List *attnumlist, bool binary, bool oids,
  	   char *delim, char *null_print, bool csv_mode, char *quote,
! 	   char *escape, List *force_quote_atts)
  {
  	HeapTuple	tuple;
  	TupleDesc	tupDesc;
***************
*** 1180,1186 ****
  							  &isvarlena[attnum - 1]);
  		fmgr_info(out_func_oid, &out_functions[attnum - 1]);
  
! 		if (intMember(attnum, force_atts))
  			force_quote[attnum - 1] = true;
  		else
  			force_quote[attnum - 1] = false;
--- 1180,1186 ----
  							  &isvarlena[attnum - 1]);
  		fmgr_info(out_func_oid, &out_functions[attnum - 1]);
  
! 		if (intMember(attnum, force_quote_atts))
  			force_quote[attnum - 1] = true;
  		else
  			force_quote[attnum - 1] = false;
***************
*** 1434,1440 ****
  static void
  CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
  		 char *delim, char *null_print, bool csv_mode, char *quote,
! 		 char *escape, List *literal_atts)
  {
  	HeapTuple	tuple;
  	TupleDesc	tupDesc;
--- 1434,1440 ----
  static void
  CopyFrom(Relation rel, List *attnumlist, bool binary, bool oids,
  		 char *delim, char *null_print, bool csv_mode, char *quote,
! 		 char *escape, List *force_notnull_atts)
  {
  	HeapTuple	tuple;
  	TupleDesc	tupDesc;
***************
*** 1447,1453 ****
  	Oid		   *elements;
  	Oid			oid_in_element;
  	ExprState **constraintexprs;
! 	bool	   *literal_nullstr;
  	bool		hasConstraints = false;
  	int			attnum;
  	int			i;
--- 1447,1453 ----
  	Oid		   *elements;
  	Oid			oid_in_element;
  	ExprState **constraintexprs;
! 	bool	   *force_notnull;
  	bool		hasConstraints = false;
  	int			attnum;
  	int			i;
***************
*** 1509,1515 ****
  	defmap = (int *) palloc((num_phys_attrs + 1) * sizeof(int));
  	defexprs = (ExprState **) palloc((num_phys_attrs + 1) * sizeof(ExprState *));
  	constraintexprs = (ExprState **) palloc0((num_phys_attrs + 1) * sizeof(ExprState *));
! 	literal_nullstr = (bool *) palloc((num_phys_attrs + 1) * sizeof(bool));
  
  	for (attnum = 1; attnum <= num_phys_attrs; attnum++)
  	{
--- 1509,1515 ----
  	defmap = (int *) palloc((num_phys_attrs + 1) * sizeof(int));
  	defexprs = (ExprState **) palloc((num_phys_attrs + 1) * sizeof(ExprState *));
  	constraintexprs = (ExprState **) palloc0((num_phys_attrs + 1) * sizeof(ExprState *));
! 	force_notnull = (bool *) palloc((num_phys_attrs + 1) * sizeof(bool));
  
  	for (attnum = 1; attnum <= num_phys_attrs; attnum++)
  	{
***************
*** 1526,1535 ****
  							 &in_func_oid, &elements[attnum - 1]);
  		fmgr_info(in_func_oid, &in_functions[attnum - 1]);
  
! 		if (intMember(attnum, literal_atts))
! 			literal_nullstr[attnum - 1] = true;
  		else
! 			literal_nullstr[attnum - 1] = false;
  		
  		/* Get default info if needed */
  		if (!intMember(attnum, attnumlist))
--- 1526,1535 ----
  							 &in_func_oid, &elements[attnum - 1]);
  		fmgr_info(in_func_oid, &in_functions[attnum - 1]);
  
! 		if (intMember(attnum, force_notnull_atts))
! 			force_notnull[attnum - 1] = true;
  		else
! 			force_notnull[attnum - 1] = false;
  		
  		/* Get default info if needed */
  		if (!intMember(attnum, attnumlist))
***************
*** 1748,1754 ****
  					string = CopyReadAttribute(delim, null_print, 
  											   &result, &isnull);
  
! 				if (csv_mode && isnull && literal_nullstr[m])
  				{
  					string = null_print;	/* set to NULL string */
  					isnull = false;
--- 1748,1754 ----
  					string = CopyReadAttribute(delim, null_print, 
  											   &result, &isnull);
  
! 				if (csv_mode && isnull && force_notnull[m])
  				{
  					string = null_print;	/* set to NULL string */
  					isnull = false;
***************
*** 1947,1953 ****
  	pfree(defmap);
  	pfree(defexprs);
  	pfree(constraintexprs);
! 	pfree(literal_nullstr);
  
  	ExecDropTupleTable(tupleTable, true);
  
--- 1947,1953 ----
  	pfree(defmap);
  	pfree(defexprs);
  	pfree(constraintexprs);
! 	pfree(force_notnull);
  
  	ExecDropTupleTable(tupleTable, true);
  
***************
*** 2558,2571 ****
   */
  static void
  CopyAttributeOutCSV(char *server_string, char *delim, char *quote,
! 					char *escape, bool force_quote)
  {
  	char	   *string;
  	char		c;
  	char		delimc = delim[0];
  	char        quotec = quote[0];
   	char        escapec = escape[0];
- 	bool        need_quote = force_quote;
  	char        *test_string;
  	bool		same_encoding;
  	int			mblen;
--- 2558,2570 ----
   */
  static void
  CopyAttributeOutCSV(char *server_string, char *delim, char *quote,
! 					char *escape, bool use_quote)
  {
  	char	   *string;
  	char		c;
  	char		delimc = delim[0];
  	char        quotec = quote[0];
   	char        escapec = escape[0];
  	char        *test_string;
  	bool		same_encoding;
  	int			mblen;
***************
*** 2583,2605 ****
  	 */
  
  	for(test_string = string; 
! 		!need_quote && (c = *test_string) != '\0'; 
  		test_string += mblen)
  	{
  		if (c == delimc || c == quotec || c == '\n' || c == '\r')
! 			need_quote = true;
  		if (!same_encoding)
  			mblen = pg_encoding_mblen(client_encoding, test_string);
  		else
  			mblen = 1;
  	}
  
! 	if (need_quote)
  		CopySendChar(quotec);
  
  	for (; (c = *string) != '\0'; string += mblen)
  	{
! 		if (need_quote && (c == quotec || c == escapec))
  			CopySendChar(escapec);
  
  		CopySendChar(c);
--- 2582,2604 ----
  	 */
  
  	for(test_string = string; 
! 		!use_quote && (c = *test_string) != '\0'; 
  		test_string += mblen)
  	{
  		if (c == delimc || c == quotec || c == '\n' || c == '\r')
! 			use_quote = true;
  		if (!same_encoding)
  			mblen = pg_encoding_mblen(client_encoding, test_string);
  		else
  			mblen = 1;
  	}
  
! 	if (use_quote)
  		CopySendChar(quotec);
  
  	for (; (c = *string) != '\0'; string += mblen)
  	{
! 		if (use_quote && (c == quotec || c == escapec))
  			CopySendChar(escapec);
  
  		CopySendChar(c);
***************
*** 2615,2621 ****
  			mblen = 1;
  	}
  
! 	if (need_quote)
  		CopySendChar(quotec);
  }
  
--- 2614,2620 ----
  			mblen = 1;
  	}
  
! 	if (use_quote)
  		CopySendChar(quotec);
  }
  
Index: src/backend/parser/gram.y
===================================================================
RCS file: /cvsroot/pgsql-server/src/backend/parser/gram.y,v
retrieving revision 2.451
diff -c -c -r2.451 gram.y
*** src/backend/parser/gram.y	19 Apr 2004 17:22:30 -0000	2.451
--- src/backend/parser/gram.y	21 Apr 2004 00:18:00 -0000
***************
*** 370,376 ****
  	KEY
  
  	LANCOMPILER LANGUAGE LARGE_P LAST_P LEADING LEFT LEVEL LIKE LIMIT
! 	LISTEN LITERAL LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
  	LOCK_P
  
  	MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
--- 370,376 ----
  	KEY
  
  	LANCOMPILER LANGUAGE LARGE_P LAST_P LEADING LEFT LEVEL LIKE LIMIT
! 	LISTEN LOAD LOCAL LOCALTIME LOCALTIMESTAMP LOCATION
  	LOCK_P
  
  	MATCH MAXVALUE MINUTE_P MINVALUE MODE MONTH_P MOVE
***************
*** 1374,1386 ****
  				{
  					$$ = makeDefElem("escape", (Node *)makeString($3));
  				}
! 			| FORCE columnList
  				{
! 					$$ = makeDefElem("force", (Node *)$2);
  				}
! 			| LITERAL columnList
  				{
! 					$$ = makeDefElem("literal", (Node *)$2);
  				}
  		;
  
--- 1374,1386 ----
  				{
  					$$ = makeDefElem("escape", (Node *)makeString($3));
  				}
! 			| FORCE QUOTE columnList
  				{
! 					$$ = makeDefElem("force_quote", (Node *)$3);
  				}
! 			| FORCE NOT NULL_P columnList
  				{
! 					$$ = makeDefElem("force_notnull", (Node *)$4);
  				}
  		;
  
***************
*** 7496,7502 ****
  			| LAST_P
  			| LEVEL
  			| LISTEN
- 			| LITERAL
  			| LOAD
  			| LOCAL
  			| LOCATION
--- 7496,7501 ----
Index: src/backend/parser/keywords.c
===================================================================
RCS file: /cvsroot/pgsql-server/src/backend/parser/keywords.c,v
retrieving revision 1.148
diff -c -c -r1.148 keywords.c
*** src/backend/parser/keywords.c	19 Apr 2004 17:22:31 -0000	1.148
--- src/backend/parser/keywords.c	21 Apr 2004 00:18:01 -0000
***************
*** 187,193 ****
  	{"like", LIKE},
  	{"limit", LIMIT},
  	{"listen", LISTEN},
- 	{"literal", LITERAL},
  	{"load", LOAD},
  	{"local", LOCAL},
  	{"localtime", LOCALTIME},
--- 187,192 ----
Index: src/bin/psql/copy.c
===================================================================
RCS file: /cvsroot/pgsql-server/src/bin/psql/copy.c,v
retrieving revision 1.45
diff -c -c -r1.45 copy.c
*** src/bin/psql/copy.c	19 Apr 2004 17:42:58 -0000	1.45
--- src/bin/psql/copy.c	21 Apr 2004 00:18:03 -0000
***************
*** 71,78 ****
  	char	   *null;
  	char	   *quote;
  	char	   *escape;
! 	char 	   *force_list;
! 	char 	   *literal_list;
  };
  
  
--- 71,78 ----
  	char	   *null;
  	char	   *quote;
  	char	   *escape;
! 	char 	   *force_quote_list;
! 	char 	   *force_notnull_list;
  };
  
  
***************
*** 88,95 ****
  	free(ptr->null);
  	free(ptr->quote);
  	free(ptr->escape);
! 	free(ptr->force_list);
! 	free(ptr->literal_list);
  	free(ptr);
  }
  
--- 88,95 ----
  	free(ptr->null);
  	free(ptr->quote);
  	free(ptr->escape);
! 	free(ptr->force_quote_list);
! 	free(ptr->force_notnull_list);
  	free(ptr);
  }
  
***************
*** 344,388 ****
  			}
  			else if (strcasecmp(token, "force") == 0)
  			{
! 				/* handle column list */
! 				fetch_next = false;
! 				for (;;)
  				{
! 					token = strtokx(NULL, whitespace, ",", "\"",
! 									0, false, pset.encoding);
! 					if (!token || strchr(",", token[0]))
! 						goto error;
! 					if (!result->force_list)
! 						result->force_list = pg_strdup(token);
! 					else
! 						xstrcat(&result->force_list, token);
! 					token = strtokx(NULL, whitespace, ",", "\"",
! 									0, false, pset.encoding);
! 					if (!token || token[0] != ',')
! 						break;
! 					xstrcat(&result->force_list, token);
  				}
! 			}
! 			else if (strcasecmp(token, "literal") == 0)
! 			{
! 				/* handle column list */
! 				fetch_next = false;
! 				for (;;)
  				{
  					token = strtokx(NULL, whitespace, ",", "\"",
  									0, false, pset.encoding);
! 					if (!token || strchr(",", token[0]))
  						goto error;
! 					if (!result->literal_list)
! 						result->literal_list = pg_strdup(token);
! 					else
! 						xstrcat(&result->literal_list, token);
! 					token = strtokx(NULL, whitespace, ",", "\"",
! 									0, false, pset.encoding);
! 					if (!token || token[0] != ',')
! 						break;
! 					xstrcat(&result->literal_list, token);
  				}
  			}
  			else
  				goto error;
--- 344,399 ----
  			}
  			else if (strcasecmp(token, "force") == 0)
  			{
! 				token = strtokx(NULL, whitespace, ",", "\"",
! 								0, false, pset.encoding);
! 				if (strcasecmp(token, "quote") == 0)
  				{
! 					/* handle column list */
! 					fetch_next = false;
! 					for (;;)
! 					{
! 						token = strtokx(NULL, whitespace, ",", "\"",
! 										0, 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, pset.encoding);
! 						if (!token || token[0] != ',')
! 							break;
! 						xstrcat(&result->force_quote_list, token);
! 					}
  				}
! 				else if (strcasecmp(token, "not") == 0)
  				{
  					token = strtokx(NULL, whitespace, ",", "\"",
  									0, false, pset.encoding);
! 					if (strcasecmp(token, "null") != 0)
  						goto error;
! 					/* handle column list */
! 					fetch_next = false;
! 					for (;;)
! 					{
! 						token = strtokx(NULL, whitespace, ",", "\"",
! 										0, 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, pset.encoding);
! 						if (!token || token[0] != ',')
! 							break;
! 						xstrcat(&result->force_notnull_list, token);
! 					}
  				}
+ 				else
+ 					goto error;
  			}
  			else
  				goto error;
***************
*** 493,506 ****
  			appendPQExpBuffer(&query, " ESCAPE AS '%s'", options->escape);
  	}
  
! 	if (options->force_list)
  	{
! 		appendPQExpBuffer(&query, " FORCE %s", options->force_list);
  	}
  
! 	if (options->literal_list)
  	{
! 		appendPQExpBuffer(&query, " LITERAL %s", options->literal_list);
  	}
  
  	if (options->from)
--- 504,517 ----
  			appendPQExpBuffer(&query, " ESCAPE AS '%s'", 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);
  	}
  
  	if (options->from)
