diff --git a/doc/src/sgml/ref/copy.sgml b/doc/src/sgml/ref/copy.sgml
new file mode 100644
index 1ecc939..0038d03
*** a/doc/src/sgml/ref/copy.sgml
--- b/doc/src/sgml/ref/copy.sgml
*************** COPY { <replaceable class="parameter">ta
*** 42,47 ****
--- 42,48 ----
      ESCAPE '<replaceable class="parameter">escape_character</replaceable>'
      FORCE_QUOTE { ( <replaceable class="parameter">column_name</replaceable> [, ...] ) | * }
      FORCE_NOT_NULL ( <replaceable class="parameter">column_name</replaceable> [, ...] )
+     FORCE_NULL ( <replaceable class="parameter">column_name</replaceable> [, ...] )
      ENCODING '<replaceable class="parameter">encoding_name</replaceable>'
  </synopsis>
   </refsynopsisdiv>
*************** COPY { <replaceable class="parameter">ta
*** 329,334 ****
--- 330,347 ----
     </varlistentry>
  
     <varlistentry>
+     <term><literal>FORCE_NULL</></term>
+     <listitem>
+      <para>
+       Force the specified columns' values to be converted to <literal>NULL</> 
+       if the value contains an empty string. 
+       This option is allowed only in <command>COPY FROM</>, and only when
+       using <literal>CSV</> format.
+      </para>
+     </listitem>
+    </varlistentry>
+ 
+    <varlistentry>
      <term><literal>ENCODING</></term>
      <listitem>
       <para>
*************** COPY <replaceable class="parameter">coun
*** 637,643 ****
      string, while an empty string data value 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>
  
     <para>
--- 650,658 ----
      string, while an empty string data value 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. Alternatively you can use 
!     <literal>FORCE_NULL</> to convert empty string data values to 
!     <literal>NULL</>.
     </para>
  
     <para>
diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c
new file mode 100644
index 6b20144..40a37a8
*** a/src/backend/commands/copy.c
--- b/src/backend/commands/copy.c
*************** typedef struct CopyStateData
*** 125,130 ****
--- 125,132 ----
  	bool	   *force_quote_flags;		/* per-column CSV FQ flags */
  	List	   *force_notnull;	/* list of column names */
  	bool	   *force_notnull_flags;	/* per-column CSV FNN flags */
+ 	List	   *force_null;	/* list of column names */
+ 	bool	   *force_null_flags;	/* per-column CSV FN flags */
  	bool		convert_selectively;	/* do selective binary conversion? */
  	List	   *convert_select; /* list of column names (can be NIL) */
  	bool	   *convert_select_flags;	/* per-column CSV/TEXT CS flags */
*************** ProcessCopyOptions(CopyState cstate,
*** 1019,1024 ****
--- 1021,1040 ----
  						 errmsg("argument to option \"%s\" must be a list of column names",
  								defel->defname)));
  		}
+ 		else if (strcmp(defel->defname, "force_null") == 0)
+ 		{
+ 			if (cstate->force_null)
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_SYNTAX_ERROR),
+ 						 errmsg("conflicting or redundant options")));
+ 			if (defel->arg && IsA(defel->arg, List))
+ 				cstate->force_null = (List *) defel->arg;
+ 			else
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ 						 errmsg("argument to option \"%s\" must be a list of column names",
+ 								defel->defname)));
+ 		}
  		else if (strcmp(defel->defname, "convert_selectively") == 0)
  		{
  			/*
*************** ProcessCopyOptions(CopyState cstate,
*** 1178,1183 ****
--- 1194,1210 ----
  				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
  			  errmsg("COPY force not null only available using COPY FROM")));
  
+ 	/* Check force_null */
+ 	if (!cstate->csv_mode && cstate->force_null != NIL)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 				 errmsg("COPY force null available only in CSV mode")));
+ 
+ 	if (cstate->force_notnull != NIL && !is_from)
+ 		ereport(ERROR,
+ 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ 			  errmsg("COPY force null only available using COPY FROM")));
+ 
  	/* Don't allow the delimiter to appear in the null string. */
  	if (strchr(cstate->null_print, cstate->delim[0]) != NULL)
  		ereport(ERROR,
*************** BeginCopy(bool is_from,
*** 1385,1390 ****
--- 1412,1439 ----
  		}
  	}
  
+ 	/* Convert FORCE NULL name list to per-column flags, check validity */
+ 	cstate->force_null_flags = (bool *) palloc0(num_phys_attrs * sizeof(bool));
+ 	if (cstate->force_null)
+ 	{
+ 		List	   *attnums;
+ 		ListCell   *cur;
+ 
+ 		attnums = CopyGetAttnums(tupDesc, cstate->rel, cstate->force_null);
+ 
+ 		foreach(cur, attnums)
+ 		{
+ 			int			attnum = lfirst_int(cur);
+ 
+ 			if (!list_member_int(cstate->attnumlist, attnum))
+ 				ereport(ERROR,
+ 						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+ 				errmsg("FORCE NULL column \"%s\" not referenced by COPY",
+ 					   NameStr(tupDesc->attrs[attnum - 1]->attname))));
+ 			cstate->force_null_flags[attnum - 1] = true;
+ 		}
+ 	}
+ 
  	/* Convert convert_selectively name list to per-column flags */
  	if (cstate->convert_selectively)
  	{
*************** NextCopyFrom(CopyState cstate, ExprConte
*** 2795,2805 ****
  				continue;
  			}
  
! 			if (cstate->csv_mode && string == NULL &&
! 				cstate->force_notnull_flags[m])
  			{
! 				/* Go ahead and read the NULL string */
! 				string = cstate->null_print;
  			}
  
  			cstate->cur_attname = NameStr(attr[m]->attname);
--- 2844,2867 ----
  				continue;
  			}
  
! 			if (cstate->csv_mode) 
  			{
! 				if(string == NULL &&
! 				   cstate->force_notnull_flags[m])
! 				{
! 					/* FORCE_NOT_NULL option is set and column is NULL -
! 					   convert it to an empty string 
! 					*/
! 					string = cstate->null_print;
! 				}
! 				else if(string != NULL && strlen(string) == 0 &&
! 						cstate->force_null_flags[m])
! 				{
! 					/* FORCE_NULL option is set and column is an empty string -
! 					   convert it to NULL
! 					*/
! 					string = NULL;
! 				}
  			}
  
  			cstate->cur_attname = NameStr(attr[m]->attname);
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
new file mode 100644
index a9812af..ca57ab7
*** a/src/backend/parser/gram.y
--- b/src/backend/parser/gram.y
*************** copy_opt_item:
*** 2493,2498 ****
--- 2493,2502 ----
  				{
  					$$ = makeDefElem("force_not_null", (Node *)$4);
  				}
+ 			| FORCE NULL_P columnList
+ 				{
+ 					$$ = makeDefElem("force_null", (Node *)$3);
+ 				}
  			| ENCODING Sconst
  				{
  					$$ = makeDefElem("encoding", (Node *)makeString($2));
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
new file mode 100644
index 34fa131..de99108
*** a/src/test/regress/expected/copy2.out
--- b/src/test/regress/expected/copy2.out
*************** SELECT * FROM vistest;
*** 382,387 ****
--- 382,435 ----
   e
  (2 rows)
  
+ -- Test FORCE_NOT_NULL and FORCE_NULL options
+ -- should succeed with "b" set to an empty string and "c" set to NULL
+ CREATE TEMP TABLE forcetest (
+     a INT NOT NULL,
+     b TEXT NOT NULL,
+     c TEXT,
+     d TEXT,
+     e TEXT
+ );
+ \pset null NULL
+ BEGIN;
+ COPY forcetest (a, b, c) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(b), FORCE_NULL(c));
+ COMMIT;
+ SELECT b, c FROM forcetest WHERE a = 1;
+  b |  c   
+ ---+------
+    | NULL
+ (1 row)
+ 
+ -- should succeed with no effect ("b" remains an empty string, "c" remains NULL)
+ BEGIN;
+ COPY forcetest (a, b, c) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(b), FORCE_NULL(c));
+ COMMIT;
+ SELECT b, c FROM forcetest WHERE a = 2;
+  b |  c   
+ ---+------
+    | NULL
+ (1 row)
+ 
+ -- should fail with not-null constraint violiaton
+ BEGIN;
+ COPY forcetest (a, b, c) FROM STDIN WITH (FORMAT csv, FORCE_NULL(b), FORCE_NOT_NULL(c));
+ ERROR:  null value in column "b" violates not-null constraint
+ DETAIL:  Failing row contains (3, null, , null, null).
+ CONTEXT:  COPY forcetest, line 1: "3,,"""
+ ROLLBACK;
+ -- should fail with "not referenced by COPY" error
+ BEGIN;
+ COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(b));
+ ERROR:  FORCE NOT NULL column "b" not referenced by COPY
+ ROLLBACK;
+ -- should fail with "not referenced by COPY" error
+ BEGIN;
+ COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NULL(b));
+ ERROR:  FORCE NULL column "b" not referenced by COPY
+ ROLLBACK;
+ \pset null ''
+ DROP TABLE forcetest;
  DROP TABLE vistest;
  DROP FUNCTION truncate_in_subxact();
  DROP TABLE x, y;
diff --git a/src/test/regress/sql/copy2.sql b/src/test/regress/sql/copy2.sql
new file mode 100644
index c46128b..b417cf7
*** a/src/test/regress/sql/copy2.sql
--- b/src/test/regress/sql/copy2.sql
*************** e
*** 270,275 ****
--- 270,314 ----
  SELECT * FROM vistest;
  COMMIT;
  SELECT * FROM vistest;
+ -- Test FORCE_NOT_NULL and FORCE_NULL options
+ -- should succeed with "b" set to an empty string and "c" set to NULL
+ CREATE TEMP TABLE forcetest (
+     a INT NOT NULL,
+     b TEXT NOT NULL,
+     c TEXT,
+     d TEXT,
+     e TEXT
+ );
+ \pset null NULL
+ BEGIN;
+ COPY forcetest (a, b, c) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(b), FORCE_NULL(c));
+ 1,,""
+ \.
+ COMMIT;
+ SELECT b, c FROM forcetest WHERE a = 1;
+ -- should succeed with no effect ("b" remains an empty string, "c" remains NULL)
+ BEGIN;
+ COPY forcetest (a, b, c) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(b), FORCE_NULL(c));
+ 2,,""
+ \.
+ COMMIT;
+ SELECT b, c FROM forcetest WHERE a = 2;
+ -- should fail with not-null constraint violiaton
+ BEGIN;
+ COPY forcetest (a, b, c) FROM STDIN WITH (FORMAT csv, FORCE_NULL(b), FORCE_NOT_NULL(c));
+ 3,,""
+ \.
+ ROLLBACK;
+ -- should fail with "not referenced by COPY" error
+ BEGIN;
+ COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NOT_NULL(b));
+ ROLLBACK;
+ -- should fail with "not referenced by COPY" error
+ BEGIN;
+ COPY forcetest (d, e) FROM STDIN WITH (FORMAT csv, FORCE_NULL(b));
+ ROLLBACK;
+ \pset null ''
+ DROP TABLE forcetest;
  DROP TABLE vistest;
  DROP FUNCTION truncate_in_subxact();
  DROP TABLE x, y;
