*** a/src/backend/access/common/heaptuple.c
--- b/src/backend/access/common/heaptuple.c
***************
*** 127,154 **** heap_compute_data_size(TupleDesc tupleDesc,
   * We also fill the null bitmap (if any) and set the infomask bits
   * that reflect the tuple's data contents.
   *
   * NOTE: it is now REQUIRED that the caller have pre-zeroed the data area.
   */
  void
  heap_fill_tuple(TupleDesc tupleDesc,
  				Datum *values, bool *isnull,
  				char *data, Size data_size,
! 				uint16 *infomask, bits8 *bit)
  {
  	bits8	   *bitP;
  	int			bitmask;
  	int			i;
- 	int			numberOfAttributes = tupleDesc->natts;
  	Form_pg_attribute *att = tupleDesc->attrs;
  
  #ifdef USE_ASSERT_CHECKING
  	char	   *start = data;
  #endif
  
  	if (bit != NULL)
  	{
  		bitP = &bit[-1];
  		bitmask = HIGHBIT;
  	}
  	else
  	{
--- 127,160 ----
   * We also fill the null bitmap (if any) and set the infomask bits
   * that reflect the tuple's data contents.
   *
+  * For truncating trailing nulls in the tuple pass the last non-null column
+  * value as numberOfAttributes. Also make sure that same number of columns
+  * are set in the tuple header formation.
+  *
   * NOTE: it is now REQUIRED that the caller have pre-zeroed the data area.
   */
  void
  heap_fill_tuple(TupleDesc tupleDesc,
  				Datum *values, bool *isnull,
  				char *data, Size data_size,
! 				uint16 *infomask, bits8 *bit,
! 				int numberOfAttributes)
  {
  	bits8	   *bitP;
  	int			bitmask;
  	int			i;
  	Form_pg_attribute *att = tupleDesc->attrs;
  
  #ifdef USE_ASSERT_CHECKING
  	char	   *start = data;
  #endif
  
+ 	*infomask &= ~(HEAP_HASNULL | HEAP_HASVARWIDTH | HEAP_HASEXTERNAL);
  	if (bit != NULL)
  	{
  		bitP = &bit[-1];
  		bitmask = HIGHBIT;
+ 		*infomask |= HEAP_HASNULL;
  	}
  	else
  	{
***************
*** 157,163 **** heap_fill_tuple(TupleDesc tupleDesc,
  		bitmask = 0;
  	}
  
- 	*infomask &= ~(HEAP_HASNULL | HEAP_HASVARWIDTH | HEAP_HASEXTERNAL);
  
  	for (i = 0; i < numberOfAttributes; i++)
  	{
--- 163,168 ----
***************
*** 176,182 **** heap_fill_tuple(TupleDesc tupleDesc,
  
  			if (isnull[i])
  			{
- 				*infomask |= HEAP_HASNULL;
  				continue;
  			}
  
--- 181,186 ----
***************
*** 636,649 **** heap_form_tuple(TupleDesc tupleDescriptor,
  	int			hoff;
  	bool		hasnull = false;
  	Form_pg_attribute *att = tupleDescriptor->attrs;
! 	int			numberOfAttributes = tupleDescriptor->natts;
  	int			i;
  
! 	if (numberOfAttributes > MaxTupleAttributeNumber)
  		ereport(ERROR,
  				(errcode(ERRCODE_TOO_MANY_COLUMNS),
  				 errmsg("number of columns (%d) exceeds limit (%d)",
! 						numberOfAttributes, MaxTupleAttributeNumber)));
  
  	/*
  	 * Check for nulls and embedded tuples; expand any toasted attributes in
--- 640,659 ----
  	int			hoff;
  	bool		hasnull = false;
  	Form_pg_attribute *att = tupleDescriptor->attrs;
! 	int			maxNumberOfAttributes = tupleDescriptor->natts;
  	int			i;
+ 	int			lastNonNullAttribute = -1;
+ 	int			numberOfAttributes;		/* The actual number of attributes for
+ 										 * this particular row. trailing nulls
+ 										 * aren't stored so this may be
+ 										 * smaller than maxNumberOfAttributes */
  
! 
! 	if (maxNumberOfAttributes > MaxTupleAttributeNumber)
  		ereport(ERROR,
  				(errcode(ERRCODE_TOO_MANY_COLUMNS),
  				 errmsg("number of columns (%d) exceeds limit (%d)",
! 						maxNumberOfAttributes, MaxTupleAttributeNumber)));
  
  	/*
  	 * Check for nulls and embedded tuples; expand any toasted attributes in
***************
*** 656,675 **** heap_form_tuple(TupleDesc tupleDescriptor,
  	 * if an attribute is already toasted, it must have been sent to disk
  	 * already and so cannot contain toasted attributes.
  	 */
! 	for (i = 0; i < numberOfAttributes; i++)
  	{
  		if (isnull[i])
  			hasnull = true;
! 		else if (att[i]->attlen == -1 &&
  				 att[i]->attalign == 'd' &&
  				 att[i]->attndims == 0 &&
  				 !VARATT_IS_EXTENDED(DatumGetPointer(values[i])))
! 		{
! 			values[i] = toast_flatten_tuple_attribute(values[i],
  													  att[i]->atttypid,
  													  att[i]->atttypmod);
  		}
  	}
  
  	/*
  	 * Determine total space needed
--- 666,691 ----
  	 * if an attribute is already toasted, it must have been sent to disk
  	 * already and so cannot contain toasted attributes.
  	 */
! 	for (i = 0; i < maxNumberOfAttributes; i++)
  	{
  		if (isnull[i])
  			hasnull = true;
! 		else
! 		{
! 			lastNonNullAttribute = i;
! 
! 			if (att[i]->attlen == -1 &&
  				 att[i]->attalign == 'd' &&
  				 att[i]->attndims == 0 &&
  				 !VARATT_IS_EXTENDED(DatumGetPointer(values[i])))
! 			{
! 				values[i] = toast_flatten_tuple_attribute(values[i],
  													  att[i]->atttypid,
  													  att[i]->atttypmod);
+ 			}
  		}
  	}
+ 	numberOfAttributes = lastNonNullAttribute + 1;
  
  	/*
  	 * Determine total space needed
***************
*** 719,725 **** heap_form_tuple(TupleDesc tupleDescriptor,
  					(char *) td + hoff,
  					data_len,
  					&td->t_infomask,
! 					(hasnull ? td->t_bits : NULL));
  
  	return tuple;
  }
--- 735,742 ----
  					(char *) td + hoff,
  					data_len,
  					&td->t_infomask,
! 					(hasnull ? td->t_bits : NULL),
! 					numberOfAttributes);
  
  	return tuple;
  }
***************
*** 1390,1403 **** heap_form_minimal_tuple(TupleDesc tupleDescriptor,
  	int			hoff;
  	bool		hasnull = false;
  	Form_pg_attribute *att = tupleDescriptor->attrs;
! 	int			numberOfAttributes = tupleDescriptor->natts;
  	int			i;
  
! 	if (numberOfAttributes > MaxTupleAttributeNumber)
  		ereport(ERROR,
  				(errcode(ERRCODE_TOO_MANY_COLUMNS),
  				 errmsg("number of columns (%d) exceeds limit (%d)",
! 						numberOfAttributes, MaxTupleAttributeNumber)));
  
  	/*
  	 * Check for nulls and embedded tuples; expand any toasted attributes in
--- 1407,1425 ----
  	int			hoff;
  	bool		hasnull = false;
  	Form_pg_attribute *att = tupleDescriptor->attrs;
! 	int			maxNumberOfAttributes = tupleDescriptor->natts;
  	int			i;
+ 	int			lastNonNullAttribute = -1;
+ 	int			numberOfAttributes;		/* The actual number of attributes for
+ 										 * this particular row. trailing nulls
+ 										 * aren't stored so this may be
+ 										 * smaller than maxNumberOfAttributes */
  
! 	if (maxNumberOfAttributes > MaxTupleAttributeNumber)
  		ereport(ERROR,
  				(errcode(ERRCODE_TOO_MANY_COLUMNS),
  				 errmsg("number of columns (%d) exceeds limit (%d)",
! 						maxNumberOfAttributes, MaxTupleAttributeNumber)));
  
  	/*
  	 * Check for nulls and embedded tuples; expand any toasted attributes in
***************
*** 1410,1429 **** heap_form_minimal_tuple(TupleDesc tupleDescriptor,
  	 * if an attribute is already toasted, it must have been sent to disk
  	 * already and so cannot contain toasted attributes.
  	 */
! 	for (i = 0; i < numberOfAttributes; i++)
  	{
  		if (isnull[i])
  			hasnull = true;
! 		else if (att[i]->attlen == -1 &&
  				 att[i]->attalign == 'd' &&
  				 att[i]->attndims == 0 &&
  				 !VARATT_IS_EXTENDED(values[i]))
! 		{
! 			values[i] = toast_flatten_tuple_attribute(values[i],
  													  att[i]->atttypid,
  													  att[i]->atttypmod);
  		}
  	}
  
  	/*
  	 * Determine total space needed
--- 1432,1456 ----
  	 * if an attribute is already toasted, it must have been sent to disk
  	 * already and so cannot contain toasted attributes.
  	 */
! 	for (i = 0; i < maxNumberOfAttributes; i++)
  	{
  		if (isnull[i])
  			hasnull = true;
! 		else
! 		{
! 			lastNonNullAttribute = i;
! 			if (att[i]->attlen == -1 &&
  				 att[i]->attalign == 'd' &&
  				 att[i]->attndims == 0 &&
  				 !VARATT_IS_EXTENDED(values[i]))
! 			{
! 				values[i] = toast_flatten_tuple_attribute(values[i],
  													  att[i]->atttypid,
  													  att[i]->atttypmod);
+ 			}
  		}
  	}
+ 	numberOfAttributes = lastNonNullAttribute + 1;
  
  	/*
  	 * Determine total space needed
***************
*** 1463,1469 **** heap_form_minimal_tuple(TupleDesc tupleDescriptor,
  					(char *) tuple + hoff,
  					data_len,
  					&tuple->t_infomask,
! 					(hasnull ? tuple->t_bits : NULL));
  
  	return tuple;
  }
--- 1490,1497 ----
  					(char *) tuple + hoff,
  					data_len,
  					&tuple->t_infomask,
! 					(hasnull ? tuple->t_bits : NULL),
! 					numberOfAttributes);
  
  	return tuple;
  }
*** a/src/backend/access/common/indextuple.c
--- b/src/backend/access/common/indextuple.c
***************
*** 29,34 ****
--- 29,41 ----
  /* ----------------
   *		index_form_tuple
   * ----------------
+  *
+  * Note that as opposed to heap rows we don't bother truncating off trailing
+  * null attributes. This is because the maximum number of attributes in an
+  * index (32) is well bounded and there isn't much to be gained by trying to
+  * reduce the bitmap that tracks null attributes since we don't expect index
+  * keys have a lot of trailing nulls. Also, the logic for tracking the null
+  * bitmap is hard-wired at 4 bytes (see IndexAttributeBitMapData).
   */
  IndexTuple
  index_form_tuple(TupleDesc tupleDescriptor,
***************
*** 139,145 **** index_form_tuple(TupleDesc tupleDescriptor,
  					(char *) tp + hoff,
  					data_size,
  					&tupmask,
! 					(hasnull ? (bits8 *) tp + sizeof(IndexTupleData) : NULL));
  
  #ifdef TOAST_INDEX_HACK
  	for (i = 0; i < numberOfAttributes; i++)
--- 146,153 ----
  					(char *) tp + hoff,
  					data_size,
  					&tupmask,
! 					(hasnull ? (bits8 *) tp + sizeof(IndexTupleData) : NULL),
! 					numberOfAttributes);
  
  #ifdef TOAST_INDEX_HACK
  	for (i = 0; i < numberOfAttributes; i++)
*** a/src/backend/access/heap/tuptoaster.c
--- b/src/backend/access/heap/tuptoaster.c
***************
*** 425,430 **** toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
--- 425,431 ----
  	bool		need_free = false;
  	bool		need_delold = false;
  	bool		has_nulls = false;
+ 	int			lastNonNullValOffset = -1;
  
  	Size		maxDataLen;
  	Size		hoff;
***************
*** 534,539 **** toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
--- 535,544 ----
  			has_nulls = true;
  			continue;
  		}
+ 		else
+ 		{
+ 			lastNonNullValOffset = i;
+ 		}
  
  		/*
  		 * Now look at varlena attributes
***************
*** 593,600 **** toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
  
  	/* compute header overhead --- this should match heap_form_tuple() */
  	hoff = offsetof(HeapTupleHeaderData, t_bits);
! 	if (has_nulls)
! 		hoff += BITMAPLEN(numAttrs);
  	if (newtup->t_data->t_infomask & HEAP_HASOID)
  		hoff += sizeof(Oid);
  	hoff = MAXALIGN(hoff);
--- 598,605 ----
  
  	/* compute header overhead --- this should match heap_form_tuple() */
  	hoff = offsetof(HeapTupleHeaderData, t_bits);
! 	if (has_nulls && lastNonNullValOffset > -1)
! 		hoff += BITMAPLEN(lastNonNullValOffset + 1);
  	if (newtup->t_data->t_infomask & HEAP_HASOID)
  		hoff += sizeof(Oid);
  	hoff = MAXALIGN(hoff);
***************
*** 869,874 **** toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
--- 874,886 ----
  		int32		new_tuple_len;
  
  		/*
+ 		 * Only store up to the last non-null attribute in side tuple i.e
+ 		 * truncate the trailing null columns. Once the tuple is formed reset
+ 		 * numAttrs to previous value
+ 		 */
+ 		numAttrs = lastNonNullValOffset + 1;
+ 
+ 		/*
  		 * Calculate the new size of the tuple.
  		 *
  		 * Note: we used to assume here that the old tuple's t_hoff must equal
***************
*** 879,885 **** toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
  		 * whether there needs to be one at all.
  		 */
  		new_header_len = offsetof(HeapTupleHeaderData, t_bits);
! 		if (has_nulls)
  			new_header_len += BITMAPLEN(numAttrs);
  		if (olddata->t_infomask & HEAP_HASOID)
  			new_header_len += sizeof(Oid);
--- 891,897 ----
  		 * whether there needs to be one at all.
  		 */
  		new_header_len = offsetof(HeapTupleHeaderData, t_bits);
! 		if (has_nulls && numAttrs > 0)
  			new_header_len += BITMAPLEN(numAttrs);
  		if (olddata->t_infomask & HEAP_HASOID)
  			new_header_len += sizeof(Oid);
***************
*** 914,920 **** toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup,
  						(char *) new_data + new_header_len,
  						new_data_len,
  						&(new_data->t_infomask),
! 						has_nulls ? new_data->t_bits : NULL);
  	}
  	else
  		result_tuple = newtup;
--- 926,935 ----
  						(char *) new_data + new_header_len,
  						new_data_len,
  						&(new_data->t_infomask),
! 						has_nulls ? new_data->t_bits : NULL,
! 						numAttrs);
! 
! 		numAttrs = tupleDesc->natts;
  	}
  	else
  		result_tuple = newtup;
***************
*** 1147,1153 **** toast_flatten_tuple_attribute(Datum value,
  					(char *) new_data + new_header_len,
  					new_data_len,
  					&(new_data->t_infomask),
! 					has_nulls ? new_data->t_bits : NULL);
  
  	/*
  	 * Free allocated temp values
--- 1162,1169 ----
  					(char *) new_data + new_header_len,
  					new_data_len,
  					&(new_data->t_infomask),
! 					has_nulls ? new_data->t_bits : NULL,
! 					numAttrs);
  
  	/*
  	 * Free allocated temp values
*** a/src/include/access/htup_details.h
--- b/src/include/access/htup_details.h
***************
*** 618,624 **** extern Size heap_compute_data_size(TupleDesc tupleDesc,
  extern void heap_fill_tuple(TupleDesc tupleDesc,
  				Datum *values, bool *isnull,
  				char *data, Size data_size,
! 				uint16 *infomask, bits8 *bit);
  extern bool heap_attisnull(HeapTuple tup, int attnum);
  extern Datum nocachegetattr(HeapTuple tup, int attnum,
  			   TupleDesc att);
--- 618,624 ----
  extern void heap_fill_tuple(TupleDesc tupleDesc,
  				Datum *values, bool *isnull,
  				char *data, Size data_size,
! 				uint16 *infomask, bits8 *bit, int lastNonNullAttributeOffset);
  extern bool heap_attisnull(HeapTuple tup, int attnum);
  extern Datum nocachegetattr(HeapTuple tup, int attnum,
  			   TupleDesc att);
*** /dev/null
--- b/src/test/regress/expected/trailing_nulls.out
***************
*** 0 ****
--- 1,210 ----
+ --
+ -- The row bitmap and row length don't track trailing nulls. Verify that this works correctly.
+ --
+ -- simple case
+ CREATE TABLE trailing_null (
+ 	c1 varchar(10), 
+ 	c2 varchar(10), 
+ 	c3 varchar(10), 
+ 	c4 varchar(10), 
+ 	c5 varchar(10) 
+ 	);
+ 	
+ INSERT INTO trailing_null VALUES (null, null, null, null, null);
+ INSERT INTO trailing_null VALUES (null, null, null, null, '5');
+ INSERT INTO trailing_null VALUES (null, null, null, '4', null);
+ INSERT INTO trailing_null VALUES (null, null, '3', null, null);
+ INSERT INTO trailing_null VALUES (null, '2', null, null, null);
+ INSERT INTO trailing_null VALUES ('1', null, null, null, null);
+ SELECT * FROM trailing_null ORDER BY c1, c2, c3, c4, c5;
+  c1 | c2 | c3 | c4 | c5 
+ ----+----+----+----+----
+  1  |    |    |    | 
+     | 2  |    |    | 
+     |    | 3  |    | 
+     |    |    | 4  | 
+     |    |    |    | 5
+     |    |    |    | 
+ (6 rows)
+ 
+ CREATE INDEX trailing_nulli ON trailing_null (c1, c2, c3, c4, c5);
+ INSERT INTO trailing_null VALUES ('1', null, null, null, null);
+ SELECT * FROM trailing_null ORDER BY c1, c2, c3, c4, c5;
+  c1 | c2 | c3 | c4 | c5 
+ ----+----+----+----+----
+  1  |    |    |    | 
+  1  |    |    |    | 
+     | 2  |    |    | 
+     |    | 3  |    | 
+     |    |    | 4  | 
+     |    |    |    | 5
+     |    |    |    | 
+ (7 rows)
+ 
+ DROP TABLE trailing_null;
+ -- corner case, no columns
+ CREATE TABLE trailing_null ();
+ DROP TABLE trailing_null;
+ -- toastable
+ CREATE TABLE trailing_null (
+ 	c1 varchar(500), 
+ 	c2 varchar(500), 
+ 	c3 varchar(500), 
+ 	c4 varchar(500), 
+ 	c5 varchar(500), 
+ 	c6 varchar(500), 
+ 	c7 varchar(500), 
+ 	c8 varchar(500), 
+ 	c9 varchar(500), 
+ 	c10 varchar(500), 
+ 	c11 varchar(500), 
+ 	c12 varchar(500), 
+ 	c13 varchar(500), 
+ 	c14 varchar(500), 
+ 	c15 varchar(500), 
+ 	c16 varchar(500), 
+ 	c17 varchar(500)
+ 	);
+ -- toast: easily compressed
+ INSERT INTO trailing_null VALUES (
+ 	rpad('c1', 450, 'x'),
+ 	rpad('c2', 450, 'x'),
+ 	rpad('c3', 450, 'x'),
+ 	rpad('c4', 450, 'x'),
+ 	rpad('c5', 450, 'x'),
+ 	rpad('c6', 450, 'x'),
+ 	rpad('c7', 450, 'x'),
+ 	rpad('c8', 450, 'x'),
+ 	rpad('c9', 450, 'x'),
+ 	rpad('c10', 450, 'x'),
+ 	rpad('c11', 450, 'x'),
+ 	rpad('c12', 450, 'x'),
+ 	rpad('c13', 450, 'x'),
+ 	rpad('c14', 450, 'x'),
+ 	rpad('c15', 450, 'x'),
+ 	rpad('c16', 450, 'x'),
+ 	rpad('c17', 450, 'x')
+ 	);
+ -- toast: off row storage
+ INSERT INTO trailing_null VALUES (
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3)
+ );
+ -- verify that we have rows in our toast storage
+ VACUUM FULL trailing_null;
+ VACUUM ANALYZE trailing_null;
+ SELECT CAST(reltuples AS INTEGER) FROM pg_class
+ 	WHERE oid = (SELECT reltoastrelid FROM pg_class where relname = 'trailing_null');
+  reltuples 
+ -----------
+          4
+ (1 row)
+ 
+ 	
+ -- non-toast, separate codepath
+ UPDATE trailing_null SET c17 = null;
+ SELECT length(c1), length(c16), c17 FROM trailing_null;
+  length | length | c17 
+ --------+--------+-----
+     450 |    450 | 
+     336 |    336 | 
+ (2 rows)
+ 
+ VACUUM FULL trailing_null;
+ VACUUM ANALYZE trailing_null;
+ SELECT CAST(reltuples AS INTEGER) FROM pg_class
+ 	WHERE oid = (SELECT reltoastrelid FROM pg_class where relname = 'trailing_null');
+  reltuples 
+ -----------
+          4
+ (1 row)
+ 
+ UPDATE trailing_null SET c16 = null;
+ SELECT length(c1), c16, c17 FROM trailing_null;
+  length | c16 | c17 
+ --------+-----+-----
+     450 |     | 
+     336 |     | 
+ (2 rows)
+ 
+ VACUUM FULL trailing_null;
+ VACUUM ANALYZE trailing_null;
+ SELECT CAST(reltuples AS INTEGER) FROM pg_class
+ 	WHERE oid = (SELECT reltoastrelid FROM pg_class where relname = 'trailing_null');
+  reltuples 
+ -----------
+          4
+ (1 row)
+ 
+ UPDATE trailing_null SET
+ 	c1 = null,
+ 	c2 = null,
+ 	c3 = null,
+ 	c4 = null,
+ 	c5 = null,
+ 	c6 = null,
+ 	c7 = null,
+ 	c8 = null,
+ 	c9 = null,
+ 	c10 = null,
+ 	c11 = null,
+ 	c12 = null,
+ 	c13 = null,
+ 	c14 = null,
+ 	c15 = null,
+ 	c16 = null,
+ 	c17 = null;
+ -- verify that we got rid of our toasted data
+ VACUUM FULL trailing_null;
+ VACUUM ANALYZE trailing_null;
+ SELECT CAST(reltuples AS INTEGER) FROM pg_class
+ 	WHERE oid = (SELECT reltoastrelid FROM pg_class where relname = 'trailing_null');
+  reltuples 
+ -----------
+          4
+ (1 row)
+ 
+ 	
+ DROP TABLE trailing_null;
+ -- Let's hit the detoasting logic related to composite objects. Borrowed
+ -- from rowtypes.sql
+ -- The object here is to ensure that toasted references inside
+ -- composite values don't cause problems.  The large f1 value will
+ -- be toasted inside pp, it must still work after being copied to people.
+ CREATE TYPE fullname as (first text, last text, extra text);
+ CREATE temp TABLE pp (f1 text, c1 varchar(10) null);
+ INSERT INTO pp VALUES (repeat('abcdefghijkl', 100000), null);
+ CREATE temp TABLE people (fn fullname, c1 varchar(10), c2 varchar(10));
+ -- trailing null in our nested object 
+ INSERT into people SELECT('Jim', f1, null)::fullname, c1, null from pp;
+ SELECT (fn).first, length((fn).last), c1, c2 from people;
+  first | length  | c1 | c2 
+ -------+---------+----+----
+  Jim   | 1200000 |    | 
+ (1 row)
+ 
+ ALTER TABLE people ADD COLUMN c3 varchar(10);
+ SELECT (fn).first, length((fn).last), c1, c2, c3 from people;
+  first | length  | c1 | c2 | c3 
+ -------+---------+----+----+----
+  Jim   | 1200000 |    |    | 
+ (1 row)
+ 
+ DROP TABLE pp;
+ DROP TABLE people;
+ DROP TYPE fullname;
*** a/src/test/regress/parallel_schedule
--- b/src/test/regress/parallel_schedule
***************
*** 98,104 **** test: event_trigger
  # ----------
  # Another group of parallel tests
  # ----------
! test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json
  
  # ----------
  # Another group of parallel tests
--- 98,104 ----
  # ----------
  # Another group of parallel tests
  # ----------
! test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combocid tsearch tsdicts foreign_data window xmlmap functional_deps advisory_lock json trailing_nulls 
  
  # ----------
  # Another group of parallel tests
*** /dev/null
--- b/src/test/regress/sql/trailing_nulls.sql
***************
*** 0 ****
--- 1,166 ----
+ --
+ -- The row bitmap and row length don't track trailing nulls. Verify that this works correctly.
+ --
+ 
+ -- simple case
+ CREATE TABLE trailing_null (
+ 	c1 varchar(10), 
+ 	c2 varchar(10), 
+ 	c3 varchar(10), 
+ 	c4 varchar(10), 
+ 	c5 varchar(10) 
+ 	);
+ 	
+ INSERT INTO trailing_null VALUES (null, null, null, null, null);
+ INSERT INTO trailing_null VALUES (null, null, null, null, '5');
+ INSERT INTO trailing_null VALUES (null, null, null, '4', null);
+ INSERT INTO trailing_null VALUES (null, null, '3', null, null);
+ INSERT INTO trailing_null VALUES (null, '2', null, null, null);
+ INSERT INTO trailing_null VALUES ('1', null, null, null, null);
+ 
+ SELECT * FROM trailing_null ORDER BY c1, c2, c3, c4, c5;
+ 
+ CREATE INDEX trailing_nulli ON trailing_null (c1, c2, c3, c4, c5);
+ 
+ INSERT INTO trailing_null VALUES ('1', null, null, null, null);
+ 
+ SELECT * FROM trailing_null ORDER BY c1, c2, c3, c4, c5;
+ 
+ DROP TABLE trailing_null;
+ 
+ -- corner case, no columns
+ CREATE TABLE trailing_null ();
+ DROP TABLE trailing_null;
+ 
+ 
+ -- toastable
+ CREATE TABLE trailing_null (
+ 	c1 varchar(500), 
+ 	c2 varchar(500), 
+ 	c3 varchar(500), 
+ 	c4 varchar(500), 
+ 	c5 varchar(500), 
+ 	c6 varchar(500), 
+ 	c7 varchar(500), 
+ 	c8 varchar(500), 
+ 	c9 varchar(500), 
+ 	c10 varchar(500), 
+ 	c11 varchar(500), 
+ 	c12 varchar(500), 
+ 	c13 varchar(500), 
+ 	c14 varchar(500), 
+ 	c15 varchar(500), 
+ 	c16 varchar(500), 
+ 	c17 varchar(500)
+ 	);
+ 
+ -- toast: easily compressed
+ INSERT INTO trailing_null VALUES (
+ 	rpad('c1', 450, 'x'),
+ 	rpad('c2', 450, 'x'),
+ 	rpad('c3', 450, 'x'),
+ 	rpad('c4', 450, 'x'),
+ 	rpad('c5', 450, 'x'),
+ 	rpad('c6', 450, 'x'),
+ 	rpad('c7', 450, 'x'),
+ 	rpad('c8', 450, 'x'),
+ 	rpad('c9', 450, 'x'),
+ 	rpad('c10', 450, 'x'),
+ 	rpad('c11', 450, 'x'),
+ 	rpad('c12', 450, 'x'),
+ 	rpad('c13', 450, 'x'),
+ 	rpad('c14', 450, 'x'),
+ 	rpad('c15', 450, 'x'),
+ 	rpad('c16', 450, 'x'),
+ 	rpad('c17', 450, 'x')
+ 	);
+ 
+ -- toast: off row storage
+ INSERT INTO trailing_null VALUES (
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3),
+  repeat('1234567890abcdefghijklmnopqrstuvwzyzABCDEFGHIJKLMNOPQRSTUVWZYZ0987654321zywvutsrqponmlkjihgfedcba1b2c3e5f6g7h9i0', 3)
+ );
+ 
+ -- verify that we have rows in our toast storage
+ VACUUM FULL trailing_null;
+ VACUUM ANALYZE trailing_null;
+ SELECT CAST(reltuples AS INTEGER) FROM pg_class
+ 	WHERE oid = (SELECT reltoastrelid FROM pg_class where relname = 'trailing_null');
+ 	
+ -- non-toast, separate codepath
+ UPDATE trailing_null SET c17 = null;
+ SELECT length(c1), length(c16), c17 FROM trailing_null;
+ VACUUM FULL trailing_null;
+ VACUUM ANALYZE trailing_null;
+ SELECT CAST(reltuples AS INTEGER) FROM pg_class
+ 	WHERE oid = (SELECT reltoastrelid FROM pg_class where relname = 'trailing_null');
+ 
+ UPDATE trailing_null SET c16 = null;
+ SELECT length(c1), c16, c17 FROM trailing_null;
+ VACUUM FULL trailing_null;
+ VACUUM ANALYZE trailing_null;
+ SELECT CAST(reltuples AS INTEGER) FROM pg_class
+ 	WHERE oid = (SELECT reltoastrelid FROM pg_class where relname = 'trailing_null');
+ 
+ UPDATE trailing_null SET
+ 	c1 = null,
+ 	c2 = null,
+ 	c3 = null,
+ 	c4 = null,
+ 	c5 = null,
+ 	c6 = null,
+ 	c7 = null,
+ 	c8 = null,
+ 	c9 = null,
+ 	c10 = null,
+ 	c11 = null,
+ 	c12 = null,
+ 	c13 = null,
+ 	c14 = null,
+ 	c15 = null,
+ 	c16 = null,
+ 	c17 = null;
+ 
+ -- verify that we got rid of our toasted data
+ VACUUM FULL trailing_null;
+ VACUUM ANALYZE trailing_null;
+ SELECT CAST(reltuples AS INTEGER) FROM pg_class
+ 	WHERE oid = (SELECT reltoastrelid FROM pg_class where relname = 'trailing_null');
+ 	
+ DROP TABLE trailing_null;
+ 
+ -- Let's hit the detoasting logic related to composite objects. Borrowed
+ -- from rowtypes.sql
+ 
+ -- The object here is to ensure that toasted references inside
+ -- composite values don't cause problems.  The large f1 value will
+ -- be toasted inside pp, it must still work after being copied to people.
+ CREATE TYPE fullname as (first text, last text, extra text);
+ CREATE temp TABLE pp (f1 text, c1 varchar(10) null);
+ INSERT INTO pp VALUES (repeat('abcdefghijkl', 100000), null);
+ CREATE temp TABLE people (fn fullname, c1 varchar(10), c2 varchar(10));
+ 
+ -- trailing null in our nested object 
+ INSERT into people SELECT('Jim', f1, null)::fullname, c1, null from pp;
+ SELECT (fn).first, length((fn).last), c1, c2 from people;
+ ALTER TABLE people ADD COLUMN c3 varchar(10);
+ SELECT (fn).first, length((fn).last), c1, c2, c3 from people;
+ 
+ DROP TABLE pp;
+ DROP TABLE people;
+ DROP TYPE fullname;
