diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index d04d1a8..2bb97c7 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -2073,6 +2073,9 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 	MemoryContext oldcontext;
 	bool		direct_function_call;
 	bool		first_time = true;
+	Datum	   *nulldatums = NULL;
+	bool	   *nullflags = NULL;
+	int64		initial_nulls = 0;
 
 	callerContext = CurrentMemoryContext;
 
@@ -2228,21 +2231,35 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 			if (rsinfo.isDone == ExprEndResult)
 				break;
 
-			/*
-			 * Can't do anything very useful with NULL rowtype values. For a
-			 * function returning set, we consider this a protocol violation
-			 * (but another alternative would be to just ignore the result and
-			 * "continue" to get another row).  For a function not returning
-			 * set, we fall out of the loop; we'll cons up an all-nulls result
-			 * row below.
-			 */
-			if (returnsTuple && fcinfo.isnull)
+			if (fcinfo.isnull)
 			{
-				if (!returnsSet)
+				/*
+				 * We used to error out on NULL rowtype values, but this is
+				 * quite awkward for unnest().  Instead, we substitute an
+				 * all-nulls row of the expected rowtype (see below).  For a
+				 * function not returning set, we fall out of the loop; we'll
+				 * cons up an all-nulls result row below.
+				 */
+				if (returnsTuple && !returnsSet)
 					break;
-				ereport(ERROR,
-						(errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
-						 errmsg("function returning set of rows cannot return null value")));
+
+				/*
+				 * but since we don't build the tuplestore for tuple-returning
+				 * funcs until we see a non-null, just count the number of
+				 * initial nulls
+				 */
+				if (!tupdesc && returnsTuple)
+				{
+					++initial_nulls;
+
+					/*
+					 * Are we done?
+					 */
+					if (rsinfo.isDone != ExprMultipleResult)
+						break;
+
+					continue;
+				}
 			}
 
 			/*
@@ -2280,12 +2297,24 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 				MemoryContextSwitchTo(oldcontext);
 				rsinfo.setResult = tupstore;
 				rsinfo.setDesc = tupdesc;
+
+				if (initial_nulls > 0)
+				{
+					int			natts = tupdesc->natts;
+
+					nulldatums = (Datum *) palloc0(natts * sizeof(Datum));
+					nullflags = (bool *) palloc(natts * sizeof(bool));
+					memset(nullflags, true, natts * sizeof(bool));
+
+					while (initial_nulls--)
+						tuplestore_putvalues(tupstore, tupdesc, nulldatums, nullflags);
+				}
 			}
 
 			/*
 			 * Store current resultset item.
 			 */
-			if (returnsTuple)
+			if (returnsTuple && !fcinfo.isnull)
 			{
 				HeapTupleHeader td;
 
@@ -2310,6 +2339,18 @@ ExecMakeTableFunctionResult(ExprState *funcexpr,
 
 				tuplestore_puttuple(tupstore, &tmptup);
 			}
+			else if (returnsTuple)
+			{
+				if (!nulldatums)
+				{
+					int			natts = tupdesc->natts;
+
+					nulldatums = (Datum *) palloc0(natts * sizeof(Datum));
+					nullflags = (bool *) palloc(natts * sizeof(bool));
+					memset(nullflags, true, natts * sizeof(bool));
+				}
+				tuplestore_putvalues(tupstore, tupdesc, nulldatums, nullflags);
+			}
 			else
 				tuplestore_putvalues(tupstore, tupdesc, &result, &fcinfo.isnull);
 
@@ -2350,18 +2391,20 @@ no_function_result:
 		MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
 		tupstore = tuplestore_begin_heap(randomAccess, false, work_mem);
 		rsinfo.setResult = tupstore;
-		if (!returnsSet)
+		if (!returnsSet || initial_nulls > 0)
 		{
 			int			natts = expectedDesc->natts;
-			Datum	   *nulldatums;
-			bool	   *nullflags;
 
 			MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
 			nulldatums = (Datum *) palloc0(natts * sizeof(Datum));
 			nullflags = (bool *) palloc(natts * sizeof(bool));
 			memset(nullflags, true, natts * sizeof(bool));
 			MemoryContextSwitchTo(econtext->ecxt_per_query_memory);
-			tuplestore_putvalues(tupstore, expectedDesc, nulldatums, nullflags);
+
+			do
+			{
+				tuplestore_putvalues(tupstore, expectedDesc, nulldatums, nullflags);
+			} while (--initial_nulls > 0);
 		}
 	}
 
diff --git a/src/test/regress/expected/rangefuncs.out b/src/test/regress/expected/rangefuncs.out
index 00ef421..4d50db0 100644
--- a/src/test/regress/expected/rangefuncs.out
+++ b/src/test/regress/expected/rangefuncs.out
@@ -2076,3 +2076,28 @@ select x from int8_tbl, extractq2_2_opt(int8_tbl) f(x);
  -4567890123456789
 (5 rows)
 
+-- handling of nulls in SRF results (bug #7808)
+create type foo2 as (a integer, b text);
+select * from unnest(array[(1,'foo')::foo2, null::foo2]) u;
+ a |  b  
+---+-----
+ 1 | foo
+   | 
+(2 rows)
+
+select * from unnest(array[null::foo2, null::foo2]) u;
+ a | b 
+---+---
+   | 
+   | 
+(2 rows)
+
+select * from unnest(array[null::foo2, (1,'foo')::foo2, null::foo2]) u;
+ a |  b  
+---+-----
+   | 
+ 1 | foo
+   | 
+(3 rows)
+
+drop type foo2;
diff --git a/src/test/regress/sql/rangefuncs.sql b/src/test/regress/sql/rangefuncs.sql
index 9484023..fd76932 100644
--- a/src/test/regress/sql/rangefuncs.sql
+++ b/src/test/regress/sql/rangefuncs.sql
@@ -639,3 +639,13 @@ explain (verbose, costs off)
 select x from int8_tbl, extractq2_2_opt(int8_tbl) f(x);
 
 select x from int8_tbl, extractq2_2_opt(int8_tbl) f(x);
+
+-- handling of nulls in SRF results (bug #7808)
+
+create type foo2 as (a integer, b text);
+
+select * from unnest(array[(1,'foo')::foo2, null::foo2]) u;
+select * from unnest(array[null::foo2, null::foo2]) u;
+select * from unnest(array[null::foo2, (1,'foo')::foo2, null::foo2]) u;
+
+drop type foo2;
