Fast Default WIP patch for discussion

Started by Serge Rielauabout 9 years ago12 messages
#1Serge Rielau
serge@rielau.com
1 attachment(s)

As promised and requested find attached a work in progress patch for fast defaults.
This is my first patch, I hope I used the right format…..

The premise of the feature is to avoid a table rewrite when adding a column with a default.

This is done by remembering the default value in pg_attribute instead of updating all the rows.
When a tuple is read from disk which is shorter than the tuple descriptor mandates the default is “plugged in”
either when filling in the datum/null array of a virtual tuple, or by “expanding” the tuple to a complete heap or minimal tuple
with all attributes.

Example:
CREATE TABLE t (pk INT NOT NULL PRIMARY KEY);
INSERT INTO t VALUES (1);
=> t: (1)

ALTER TABLE t ADD COLUMN c1 INT DEFAULT 5;
=> 1. Stores the default expression in pg_attrdef.adbin (no news)
2. a. Stores the default values (computed at time of ALTER TABLE) in pg_attribute.att_existdef
b. Sets pg_attribute.att_hasexistdef to true

SELECT * FROM t;
=> Build a virtual tuple using getAttr() API (no news)
but since tuple has fewer mats than table signature fill in extra attributes from pg_attribute.att_existdef
if att_hasexistdef is true
=> (1) becomes (1, 5)

INSERT INTO t VALUES (2);
=> Fill in DEFAULT for t.c1 from pg_attrdef.adbin as usual: (2, 5)
=> t: (1), (2, 5)

ALTER TABLE t ALTER COLUMN c1 SET DEFAULT -1;
=> 1. Drop row from pg_attrdef for c1 (no news)
2. Add row to pg_attrdef for c1 DEFAULT -1 (no news)
3. Leave pg_atribute.att_existdef alone!!

INSERT INTO t VALUES (3);
=> Fill in DEFAULT for t.c1 from pg_attrdef.adbin as usual: (3, -1)
=> t: (1), (2, 5), (3, -1)

SELECT * FROM t;
=> Build a virtual tuple using get*Attr() API (no news)
but since tuple has fewer mats than table signature fill in extra attributes from pg_attribute.att_existdef
if att_hasexistdef is true
=> (1) becomes (1, 5)
(2, 5)
(3, -1)

ALTER TABLE t ALTER COLUMN c1 DROP DEFAULT;
=> 1. Drop row from pg_attrdef for c1 (no news)
2. Leave pg_atribute.att_existdef alone!!

INSERT INTO t VALUES (4);
=> Fill in default DEFAULT (4, NULL)
=> t: (1), (2, 5), (3, -1), (4, NULL)

SELECT * FROM t;
=> Build a virtual tuple using get*Attr() API (no news)
but since tuple has fewer mats than table signature fill in extra attributes from pg_attribute.att_existdef
if att_hasexistdef is true
=> (1) becomes (1, 5)
(2, 5)
(3, -1)
(4, NULL)

You can find a new (incomplete) test file fast_default.sql in regress/sql

Some key design points requiring discussion:
1. Storage of the “exist” (working name) default
Right now the patch stores the default value in its binary form as it would be in the tuple into a BYTEA.
It would be feasible to store the pretty printed literal, however this requires calling the io functions when a
tuple descriptor is built.
2. The exist default is cached alongside the “current” default in the tuple descriptor’s constraint structure.
Seems most natural too me, but debatable.
3. Delayed vs. early expansion of the tuples.
To avoid having to decide when to copy tuple descriptors with or without constraints and defaults
I have opted to expand the tuple at the core entry points.
How do I know I have them all? An omission means wrong results!
4. attisnull()
This routine is used in many places, but to give correct result sit must now be accompanied
by the tuple descriptor. This becomes moderately messy and it’s not always clear where to get that.
Interestingly most usages are related to catalog lookups.
Assuming we have no intention to support fast default for catalog tables we could keep using the
existing attisnull() api for catalog lookups and use a new version (name tbd) for user tables.
5. My head hurts looking at the PK/FK code - it’s not always clear which tuple descriptor belongs
to which tuple
6. Performance of the expansion code.
The current code needs to walk all defaults and then start padding by filling in values.
But the outcome is always the same. We will produce the same payload and the name null map.
It would be feasible to cache an “all defaults tuple”, remember the offsets (and VARWIDTH, HASNULL)
for each attribute and then simply splice the short and default tuples together.
This ought to be faster, but the meta data to cache is not insignificant and the expansion code is messy enough
without this already.
7. Grooming
Obviously we can remove all exist defaults for a table from pg_attribute whenever the table is rewrittem.
That’s easy.
But could we/should we keep track of the short tuples and either eliminate them or drop exist defaults once they
become obsolete because there is no tuple short enough for them to matter.
8. Do we need to worry about toasted defaults?

Cheers
Serge Rielau
Salesforce.com <http://salesforce.com/&gt;

Attachments:

fast_default.patchapplication/octet-stream; name=fast_default.patchDownload
commit 863379cc8478422aaeb55a73536ab286d9bbfa29
Author: Serge Rielau <srielau@salesforce.com>
Date:   Wed Oct 19 17:06:12 2016 -0700

    fast_defaults

diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index 6d0f3f3..8880ed3 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -76,6 +76,95 @@
  * ----------------------------------------------------------------
  */

+/*
+ * Pad an attribute beyond the size of the tuple
+ * with NULL or the exist default
+ */
+static Datum getmissingattr(TupleDesc tupleDesc,
+                            int attnum, bool *isnull)
+{
+	int         defnum;
+	Form_pg_attribute att;
+	AttrDefault *attrdef;
+
+	Assert(attnum <= tupleDesc->natts);
+	Assert(attnum > 0);
+
+	att = tupleDesc->attrs[attnum - 1];
+
+	if(att->atthasexistdef)
+	{
+		Assert(tupleDesc->constr);
+		Assert(tupleDesc->constr->num_defval > 0);
+		Assert(tupleDesc->constr->has_existdefval);
+
+		attrdef = tupleDesc->constr->defval;
+		for (defnum = tupleDesc->constr->num_defval - 1;
+			 defnum >= 0; defnum--)
+		{
+			if (attrdef[defnum].adnum == attnum)
+			{
+				if (attrdef[defnum].adexistNull)
+				{
+					*isnull = true;
+					return (Datum) 0;
+				}
+				else
+				{
+					*isnull = false;
+					return attrdef[defnum].adexist;
+				}
+			}
+		}
+		Assert(defnum >= 0);
+	}
+
+	*isnull = true;
+	return PointerGetDatum(NULL);
+}
+
+static void slot_getdefaultattrs(TupleTableSlot *slot, int startAttNum)
+{
+	int         tdesc_natts = slot->tts_tupleDescriptor->natts;
+	int         defattnum;
+	int         defnum;
+	AttrDefault *attrdef;
+
+	if (slot->tts_tupleDescriptor->constr
+		&& slot->tts_tupleDescriptor->constr->has_existdefval)
+	{
+		defnum = slot->tts_tupleDescriptor->constr->num_defval - 1;
+		attrdef = slot->tts_tupleDescriptor->constr->defval;
+	}
+	else
+	{
+		defnum = -1;
+		attrdef = NULL;
+	}
+
+	for (defattnum = tdesc_natts - 1; defattnum >= startAttNum; defattnum--)
+	{
+		if (defnum >= 0 && attrdef[defnum].adnum - 1 == defattnum)
+		{
+			if (attrdef[defnum].adexistNull)
+			{
+				slot->tts_values[defattnum] = PointerGetDatum(NULL);
+				slot->tts_isnull[defattnum] = true;
+			}
+			else
+			{
+				slot->tts_values[defattnum] = attrdef[defnum].adexist;
+				slot->tts_isnull[defattnum] = false;
+			}
+			defnum--;
+		}
+		else
+		{
+			slot->tts_values[defattnum] = PointerGetDatum(NULL);
+			slot->tts_isnull[defattnum] = true;
+		}
+	}
+}

 /*
  * heap_compute_data_size
@@ -133,6 +222,127 @@ heap_compute_data_size(TupleDesc tupleDesc,
 	return data_length;
 }

+static inline void
+fill_val(Form_pg_attribute  att,
+		 bits8            **bit,
+		 int               *bitmask,
+		 char             **dataP,
+		 uint16            *infomask,
+		 Datum              datum,
+		 bool               isnull)
+{
+	Size data_length;
+	char *data = *dataP;
+	/*
+	 * If we're building a null bitmap, set the appropriate bit for the
+	 * current column value here.
+	 */
+	if (bit != NULL)
+	{
+		if (*bitmask != HIGHBIT)
+			*bitmask <<= 1;
+		else
+		{
+			*bit += 1;
+			**bit = 0x0;
+			*bitmask = 1;
+		}
+
+		if (isnull)
+		{
+			*infomask |= HEAP_HASNULL;
+			return;
+		}
+
+		**bit |= *bitmask;
+	}
+
+	Assert(att);
+
+	/*
+	 * XXX we use the att_align macros on the pointer value itself, not on
+	 * an offset.  This is a bit of a hack.
+	 */
+	if (att->attbyval)
+	{
+		/* pass-by-value */
+		data = (char *) att_align_nominal(data, att->attalign);
+		store_att_byval(data, datum, att->attlen);
+		data_length = att->attlen;
+	}
+	else if (att->attlen == -1)
+	{
+		/* varlena */
+		Pointer		val = DatumGetPointer(datum);
+
+		*infomask |= HEAP_HASVARWIDTH;
+		if (VARATT_IS_EXTERNAL(val))
+		{
+			if (VARATT_IS_EXTERNAL_EXPANDED(val))
+			{
+				/*
+				 * we want to flatten the expanded value so that the
+				 * constructed tuple doesn't depend on it
+				 */
+				ExpandedObjectHeader *eoh = DatumGetEOHP(datum);
+
+				data = (char *) att_align_nominal(data,
+												  att->attalign);
+				data_length = EOH_get_flat_size(eoh);
+				EOH_flatten_into(eoh, data, data_length);
+			}
+			else
+			{
+				*infomask |= HEAP_HASEXTERNAL;
+				/* no alignment, since it's short by definition */
+				data_length = VARSIZE_EXTERNAL(val);
+				memcpy(data, val, data_length);
+			}
+		}
+		else if (VARATT_IS_SHORT(val))
+		{
+			/* no alignment for short varlenas */
+			data_length = VARSIZE_SHORT(val);
+			memcpy(data, val, data_length);
+		}
+		else if (VARLENA_ATT_IS_PACKABLE(att) &&
+				 VARATT_CAN_MAKE_SHORT(val))
+		{
+			/* convert to short varlena -- no alignment */
+			data_length = VARATT_CONVERTED_SHORT_SIZE(val);
+			SET_VARSIZE_SHORT(data, data_length);
+			memcpy(data + 1, VARDATA(val), data_length - 1);
+		}
+		else
+		{
+			/* full 4-byte header varlena */
+			data = (char *) att_align_nominal(data,
+											  att->attalign);
+			data_length = VARSIZE(val);
+			memcpy(data, val, data_length);
+		}
+	}
+	else if (att->attlen == -2)
+	{
+		/* cstring ... never needs alignment */
+		*infomask |= HEAP_HASVARWIDTH;
+		Assert(att->attalign == 'c');
+		data_length = strlen(DatumGetCString(datum)) + 1;
+		memcpy(data, DatumGetPointer(datum), data_length);
+	}
+	else
+	{
+		/* fixed-length pass-by-reference */
+		data = (char *) att_align_nominal(data, att->attalign);
+		Assert(att->attlen > 0);
+		data_length = att->attlen;
+		memcpy(data, DatumGetPointer(datum), data_length);
+	}
+
+	data += data_length;
+	*dataP = data;
+}
+
 /*
  * heap_fill_tuple
  *		Load data portion of a tuple from values/isnull arrays
@@ -174,110 +384,13 @@ heap_fill_tuple(TupleDesc tupleDesc,

 	for (i = 0; i < numberOfAttributes; i++)
 	{
-		Size		data_length;
-
-		if (bit != NULL)
-		{
-			if (bitmask != HIGHBIT)
-				bitmask <<= 1;
-			else
-			{
-				bitP += 1;
-				*bitP = 0x0;
-				bitmask = 1;
-			}
-
-			if (isnull[i])
-			{
-				*infomask |= HEAP_HASNULL;
-				continue;
-			}
-
-			*bitP |= bitmask;
-		}
-
-		/*
-		 * XXX we use the att_align macros on the pointer value itself, not on
-		 * an offset.  This is a bit of a hack.
-		 */
-
-		if (att[i]->attbyval)
-		{
-			/* pass-by-value */
-			data = (char *) att_align_nominal(data, att[i]->attalign);
-			store_att_byval(data, values[i], att[i]->attlen);
-			data_length = att[i]->attlen;
-		}
-		else if (att[i]->attlen == -1)
-		{
-			/* varlena */
-			Pointer		val = DatumGetPointer(values[i]);
-
-			*infomask |= HEAP_HASVARWIDTH;
-			if (VARATT_IS_EXTERNAL(val))
-			{
-				if (VARATT_IS_EXTERNAL_EXPANDED(val))
-				{
-					/*
-					 * we want to flatten the expanded value so that the
-					 * constructed tuple doesn't depend on it
-					 */
-					ExpandedObjectHeader *eoh = DatumGetEOHP(values[i]);
-
-					data = (char *) att_align_nominal(data,
-													  att[i]->attalign);
-					data_length = EOH_get_flat_size(eoh);
-					EOH_flatten_into(eoh, data, data_length);
-				}
-				else
-				{
-					*infomask |= HEAP_HASEXTERNAL;
-					/* no alignment, since it's short by definition */
-					data_length = VARSIZE_EXTERNAL(val);
-					memcpy(data, val, data_length);
-				}
-			}
-			else if (VARATT_IS_SHORT(val))
-			{
-				/* no alignment for short varlenas */
-				data_length = VARSIZE_SHORT(val);
-				memcpy(data, val, data_length);
-			}
-			else if (VARLENA_ATT_IS_PACKABLE(att[i]) &&
-					 VARATT_CAN_MAKE_SHORT(val))
-			{
-				/* convert to short varlena -- no alignment */
-				data_length = VARATT_CONVERTED_SHORT_SIZE(val);
-				SET_VARSIZE_SHORT(data, data_length);
-				memcpy(data + 1, VARDATA(val), data_length - 1);
-			}
-			else
-			{
-				/* full 4-byte header varlena */
-				data = (char *) att_align_nominal(data,
-												  att[i]->attalign);
-				data_length = VARSIZE(val);
-				memcpy(data, val, data_length);
-			}
-		}
-		else if (att[i]->attlen == -2)
-		{
-			/* cstring ... never needs alignment */
-			*infomask |= HEAP_HASVARWIDTH;
-			Assert(att[i]->attalign == 'c');
-			data_length = strlen(DatumGetCString(values[i])) + 1;
-			memcpy(data, DatumGetPointer(values[i]), data_length);
-		}
-		else
-		{
-			/* fixed-length pass-by-reference */
-			data = (char *) att_align_nominal(data, att[i]->attalign);
-			Assert(att[i]->attlen > 0);
-			data_length = att[i]->attlen;
-			memcpy(data, DatumGetPointer(values[i]), data_length);
-		}
-
-		data += data_length;
+		fill_val(att[i],
+				 bitP ? &bitP : NULL,
+				 &bitmask,
+				 &data,
+				 infomask,
+				 values ? values[i] : PointerGetDatum(NULL),
+				 isnull ? isnull[i] : true);
 	}

 	Assert((data - start) == data_size);
@@ -294,10 +407,24 @@ heap_fill_tuple(TupleDesc tupleDesc,
  * ----------------
  */
 bool
-heap_attisnull(HeapTuple tup, int attnum)
+heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc)
 {
+	/* We allow a NULL tupledesc for catalog relations */
+	Assert(tupleDesc || attnum <= tupleDesc->natts);
+
 	if (attnum > (int) HeapTupleHeaderGetNatts(tup->t_data))
-		return true;
+	{
+		if (tupleDesc &&
+			attnum <= tupleDesc->natts &&
+			tupleDesc->attrs[attnum - 1]->atthasexistdef)
+		{
+			return false;
+		}
+		else
+		{
+			return true;
+		}
+    }

 	if (attnum > 0)
 	{
@@ -646,6 +773,253 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest)
 	memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len);
 }

+static void
+expand_tuple(HeapTuple    *targetHeapTuple,
+				  MinimalTuple *targetMinimalTuple,
+				  HeapTuple     sourceTuple,
+				  TupleDesc     tupleDesc)
+{
+	Form_pg_attribute *att     = NULL;
+	AttrDefault       *attrdef = NULL;
+	int                attnum;
+	int                defnum;
+	int                firstdefnum = 0;
+	int                numdef = 0;
+	bool               hasNulls    = HeapTupleHasNulls(sourceTuple);
+	bool               hasVarWidth = HeapTupleHasVarWidth(sourceTuple);
+	HeapTupleHeader    targetTHeader;
+	HeapTupleHeader    sourceTHeader = sourceTuple->t_data;
+	int                sourceNatts = HeapTupleHeaderGetNatts(sourceTHeader);
+	int                natts       = tupleDesc->natts;
+	int                sourceIndicatorLen;
+	int                targetIndicatorLen;
+	Size               sourceDataLen = sourceTuple->t_len - sourceTHeader->t_hoff;
+	Size               targetDataLen;
+	Size               len;
+	int                hoff;
+	bits8             *nullBits = NULL;
+	int                bitMask;
+	char              *targetData;
+	uint16            *infoMask;
+
+	Assert(targetHeapTuple && !targetMinimalTuple
+		   || !targetHeapTuple && targetMinimalTuple);
+
+	Assert(sourceNatts < natts);
+
+	sourceIndicatorLen = (hasNulls ? BITMAPLEN(sourceNatts) : 0);
+
+	targetDataLen = sourceDataLen;
+
+	if (tupleDesc->constr &&
+		tupleDesc->constr->has_existdefval)
+	{
+		/* If so we want to ingest the exist defaults into the tuple
+		 * Compute the extra length for the values array and the variable length data
+		 */
+		numdef  = tupleDesc->constr->num_defval;
+		att     = tupleDesc->attrs;
+		attrdef = tupleDesc->constr->defval;
+
+		/*
+		 * Unfortunately the data length computation is
+		 * directional we have to walk the attributes incrementally
+		 */
+
+		for (firstdefnum = 0;
+			 firstdefnum < numdef
+				 && attrdef[firstdefnum].adnum <= sourceNatts;
+			 firstdefnum++)
+		{ /* empty */ }
+
+		for (defnum = firstdefnum, attnum = sourceNatts + 1; attnum <= natts; attnum++)
+		{
+			if (defnum >= numdef)
+			{
+				hasNulls = true;
+				break;
+			}
+
+			if (attrdef[defnum].adnum == attnum)
+			{
+				if (attrdef[defnum].adexistNull)
+					hasNulls = true;
+				else
+				{
+					targetDataLen = att_align_datum(targetDataLen,
+													att[attnum - 1]->attalign,
+													att[attnum - 1]->attlen,
+													attrdef[defnum].adexist);
+
+					targetDataLen = att_addlength_pointer(targetDataLen,
+														  att[attnum - 1]->attlen,
+														  attrdef[defnum].adexist);
+					if (att[attnum - 1]->attlen == -1 ||
+						att[attnum - 1]->attlen == -2)
+						hasVarWidth = true;
+				}
+				defnum++;
+			}
+			else
+				hasNulls = true;
+		}
+	} /* end if Have exist defaults */
+	else
+	{
+		hasNulls = true;
+	}
+
+	len = 0;
+
+	if (hasNulls)
+	{
+		targetIndicatorLen = BITMAPLEN(natts);
+		len += targetIndicatorLen;
+	}
+	else
+		targetIndicatorLen = 0;
+
+	if (tupleDesc->tdhasoid)
+		len += sizeof(Oid);
+
+	/*
+	 * Allocate and zero the space needed.  Note that the tuple body and
+	 * HeapTupleData management structure are allocated in one chunk.
+	 */
+	if (targetHeapTuple)
+	{
+		len += offsetof(HeapTupleHeaderData, t_bits);
+		hoff = len = MAXALIGN(len); /* align user data safely */
+		len += targetDataLen;
+
+		*targetHeapTuple = (HeapTuple) palloc0(HEAPTUPLESIZE + len);
+		(*targetHeapTuple)->t_data
+			= targetTHeader
+			= (HeapTupleHeader) ((char *) *targetHeapTuple + HEAPTUPLESIZE);
+		(*targetHeapTuple)->t_len = len;
+		(*targetHeapTuple)->t_tableOid = sourceTuple->t_tableOid;
+		ItemPointerSetInvalid(&((*targetHeapTuple)->t_self));
+
+		targetTHeader->t_infomask = sourceTHeader->t_infomask;
+		targetTHeader->t_hoff = hoff;
+		HeapTupleHeaderSetNatts(targetTHeader, natts);
+		HeapTupleHeaderSetDatumLength(targetTHeader, len);
+		HeapTupleHeaderSetTypeId(targetTHeader, tupleDesc->tdtypeid);
+		HeapTupleHeaderSetTypMod(targetTHeader, tupleDesc->tdtypmod);
+		/* We also make sure that t_ctid is invalid unless explicitly set */
+		ItemPointerSetInvalid(&(targetTHeader->t_ctid));
+		if (targetIndicatorLen > 0)
+			nullBits = (bits8 *) ((char *) (*targetHeapTuple)->t_data
+								  + offsetof(HeapTupleHeaderData, t_bits));
+		targetData = (char *) (*targetHeapTuple)->t_data + hoff;
+		infoMask = &(targetTHeader->t_infomask);
+	}
+	else
+	{
+		len += SizeofMinimalTupleHeader;
+		hoff = len = MAXALIGN(len); /* align user data safely */
+		len += targetDataLen;
+
+		*targetMinimalTuple = (MinimalTuple) palloc0(len);
+		(*targetMinimalTuple)->t_len = len;
+		(*targetMinimalTuple)->t_hoff = hoff + MINIMAL_TUPLE_OFFSET;
+		(*targetMinimalTuple)->t_infomask = sourceTHeader->t_infomask;
+		/* Same macro works for MinimalTuples */
+		HeapTupleHeaderSetNatts(*targetMinimalTuple, natts);
+		if (targetIndicatorLen > 0)
+			nullBits = (bits8 *) ((char *) *targetMinimalTuple
+								  + offsetof(MinimalTupleData, t_bits));
+		targetData = (char *) *targetMinimalTuple + hoff;
+		infoMask = &((*targetMinimalTuple)->t_infomask);
+	}
+
+	if (targetIndicatorLen > 0)
+	{
+		if (sourceIndicatorLen > 0)
+		{
+			/* if bitmap pre-existed copy in - all is set */
+			memcpy(nullBits,
+				   ((char *) sourceTHeader)
+				   +  offsetof(HeapTupleHeaderData, t_bits),
+				   sourceIndicatorLen);
+			nullBits += sourceIndicatorLen - 1;
+		}
+		else
+		{
+			sourceIndicatorLen = BITMAPLEN(sourceNatts);
+			/* Set NOT NULL for all existing attributes */
+			memset(nullBits, 0xff, sourceIndicatorLen);
+
+			nullBits += sourceIndicatorLen - 1;
+
+			if (sourceNatts & 0x07)
+			{
+				/* build the mask (inverted!) */
+				bitMask = 0xff << (sourceNatts & 0x07);
+				/* Voila */
+				*nullBits = ~bitMask;
+			}
+		}
+
+		bitMask = (1 << ((sourceNatts - 1) & 0x07));
+	} /* End if have null bitmap */
+
+	memcpy(targetData,
+		   ((char *) sourceTuple->t_data) + sourceTHeader->t_hoff,
+		   sourceDataLen);
+
+	/* if there are no exist defaults there is nothing more to do */
+	if (firstdefnum < numdef)
+	{
+		targetData += sourceDataLen;
+		defnum = firstdefnum;
+
+		/* Now fill in the defaults */
+		for (attnum = sourceNatts + 1;
+			 attnum <= natts && defnum < tupleDesc->constr->num_defval;
+			 attnum++)
+		{
+			if (attrdef[defnum].adnum == attnum)
+			{
+				fill_val(att[attnum -1],
+						 nullBits ? &nullBits : NULL,
+						 &bitMask,
+						 &targetData,
+						 infoMask,
+						 attrdef[defnum].adexist,
+						 attrdef[defnum].adexistNull);
+				defnum++;
+			}
+			else
+			{
+				fill_val(att[attnum -1],
+						 &nullBits,
+						 &bitMask,
+						 &targetData,
+						 infoMask,
+						 (Datum) 0,
+						 true);
+			}
+		} /* end loop over defaulted attributes */
+	} /* end if there is no exist default */
+}
+
+MinimalTuple
+minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc)
+{
+	MinimalTuple minimalTuple;
+	expand_tuple(NULL, &minimalTuple, sourceTuple, tupleDesc);
+	return minimalTuple;
+}
+
+HeapTuple
+heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc)
+{
+	HeapTuple heapTuple;
+	expand_tuple(&heapTuple, NULL, sourceTuple, tupleDesc);
+	return heapTuple;
+}
+
 /* ----------------
  *		heap_copy_tuple_as_datum
  *
@@ -1125,8 +1499,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
 	tup = tuple->t_data;
 	if (attnum > HeapTupleHeaderGetNatts(tup))
 	{
-		*isnull = true;
-		return (Datum) 0;
+		return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull);
 	}

 	/*
@@ -1196,13 +1569,13 @@ slot_getallattrs(TupleTableSlot *slot)

 	/*
 	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
+	 * rest as defaults
 	 */
-	for (; attnum < tdesc_natts; attnum++)
+	if (attnum < tdesc_natts)
 	{
-		slot->tts_values[attnum] = (Datum) 0;
-		slot->tts_isnull[attnum] = true;
+		slot_getdefaultattrs(slot, attnum);
 	}
+
 	slot->tts_nvalid = tdesc_natts;
 }

@@ -1243,12 +1616,11 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum)

 	/*
 	 * If tuple doesn't have all the atts indicated by tupleDesc, read the
-	 * rest as null
+	 * rest as defaults
 	 */
-	for (; attno < attnum; attno++)
+	if (attno < attnum)
 	{
-		slot->tts_values[attno] = (Datum) 0;
-		slot->tts_isnull[attno] = true;
+		slot_getdefaultattrs(slot, attno);
 	}
 	slot->tts_nvalid = attnum;
 }
@@ -1273,7 +1645,7 @@ slot_attisnull(TupleTableSlot *slot, int attnum)
 			elog(ERROR, "cannot extract system attribute from virtual tuple");
 		if (tuple == &(slot->tts_minhdr))		/* internal error */
 			elog(ERROR, "cannot extract system attribute from minimal tuple");
-		return heap_attisnull(tuple, attnum);
+		return heap_attisnull(tuple, attnum, tupleDesc);
 	}

 	/*
@@ -1296,7 +1668,7 @@ slot_attisnull(TupleTableSlot *slot, int attnum)
 		elog(ERROR, "cannot extract attribute from empty tuple slot");

 	/* and let the tuple tell it */
-	return heap_attisnull(tuple, attnum);
+	return heap_attisnull(tuple, attnum, tupleDesc);
 }

 /*
diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c
index b56d0e3..52b5f79 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -25,6 +25,7 @@
 #include "parser/parse_type.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/resowner_private.h"
 #include "utils/syscache.h"

@@ -190,6 +191,13 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
 			{
 				if (constr->defval[i].adbin)
 					cpy->defval[i].adbin = pstrdup(constr->defval[i].adbin);
+				if (constr->defval[i].adexist)
+				{
+					int attnum = constr->defval[i].adnum - 1;
+					cpy->defval[i].adexist = datumCopy(constr->defval[i].adexist,
+													   tupdesc->attrs[attnum]->attbyval,
+													   tupdesc->attrs[attnum]->attlen);
+				}
 			}
 		}

@@ -282,6 +290,9 @@ FreeTupleDesc(TupleDesc tupdesc)
 			{
 				if (attrdef[i].adbin)
 					pfree(attrdef[i].adbin);
+				if (attrdef[i].adexist
+					&& !tupdesc->attrs[attrdef[i].adnum -1]->attbyval)
+					pfree(DatumGetPointer(attrdef[i].adexist));
 			}
 			pfree(attrdef);
 		}
@@ -350,9 +361,10 @@ DecrTupleDescRefCount(TupleDesc tupdesc)
 bool
 equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 {
-	int			i,
-				j,
-				n;
+	int	i,
+		j,
+		n1,
+		n2;

 	if (tupdesc1->natts != tupdesc2->natts)
 		return false;
@@ -420,10 +432,9 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			return false;
 		if (constr1->has_not_null != constr2->has_not_null)
 			return false;
-		n = constr1->num_defval;
-		if (n != (int) constr2->num_defval)
-			return false;
-		for (i = 0; i < n; i++)
+		n1 = constr1->num_defval;
+		n2 = constr2->num_defval;
+		for (i = 0; i < n1; i++)
 		{
 			AttrDefault *defval1 = constr1->defval + i;
 			AttrDefault *defval2 = constr2->defval;
@@ -433,20 +444,24 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			 * catalogs in the same order; so use the adnum field to identify
 			 * the matching item to compare.
 			 */
-			for (j = 0; j < n; defval2++, j++)
+			for (j = 0; j < n2; defval2++, j++)
 			{
 				if (defval1->adnum == defval2->adnum)
 					break;
 			}
-			if (j >= n)
+			if (j >= n2)
+				return false;
+			if ((!defval1->adbin && defval2->adbin) ||
+                (defval1->adbin && !defval2->adbin))
 				return false;
-			if (strcmp(defval1->adbin, defval2->adbin) != 0)
+			if (defval1->adbin && defval2->adbin &&
+				strcmp(defval1->adbin, defval2->adbin) != 0)
 				return false;
 		}
-		n = constr1->num_check;
-		if (n != (int) constr2->num_check)
+		n1 = constr1->num_check;
+		if (n1 != (int) constr2->num_check)
 			return false;
-		for (i = 0; i < n; i++)
+		for (i = 0; i < n1; i++)
 		{
 			ConstrCheck *check1 = constr1->check + i;
 			ConstrCheck *check2 = constr2->check;
@@ -456,7 +471,7 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 			 * same order; match them up by name and contents. (The name
 			 * *should* be unique, but...)
 			 */
-			for (j = 0; j < n; check2++, j++)
+			for (j = 0; j < n2; check2++, j++)
 			{
 				if (strcmp(check1->ccname, check2->ccname) == 0 &&
 					strcmp(check1->ccbin, check2->ccbin) == 0 &&
@@ -464,7 +479,7 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
 					check1->ccnoinherit == check2->ccnoinherit)
 					break;
 			}
-			if (j >= n)
+			if (j >= n1)
 				return false;
 		}
 	}
@@ -533,6 +548,7 @@ TupleDescInitEntry(TupleDesc desc,

 	att->attnotnull = false;
 	att->atthasdef = false;
+	att->atthasexistdef = false;
 	att->attisdropped = false;
 	att->attislocal = true;
 	att->attinhcount = 0;
@@ -657,6 +673,7 @@ BuildDescForRelation(List *schema)
 		constr->has_not_null = true;
 		constr->defval = NULL;
 		constr->num_defval = 0;
+		constr->has_existdefval = false;
 		constr->check = NULL;
 		constr->num_check = 0;
 		desc->constr = constr;
diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c
index c0df671..037a0ce 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -4377,7 +4377,7 @@ pg_attribute_aclcheck_all(Oid table_oid, Oid roleid, AclMode mode,
 		 * grants no privileges, so that we can fall out quickly in the very
 		 * common case where attacl is null.
 		 */
-		if (heap_attisnull(attTuple, Anum_pg_attribute_attacl))
+		if (heap_attisnull(attTuple, Anum_pg_attribute_attacl, NULL))
 			attmask = 0;
 		else
 			attmask = pg_attribute_aclmask(table_oid, curr_att, roleid,
diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl
index 26d1652..e76c10e 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -409,12 +409,14 @@ sub emit_pgattr_row
 		attcacheoff   => '-1',
 		atttypmod     => '-1',
 		atthasdef     => 'f',
+		atthasexistdef=> 'f',
 		attisdropped  => 'f',
 		attislocal    => 't',
 		attinhcount   => '0',
 		attacl        => '_null_',
 		attoptions    => '_null_',
-		attfdwoptions => '_null_');
+		attfdwoptions => '_null_',
+		attexistdefault => '_null_');
 	return { %PGATTR_DEFAULTS, %row };
 }

@@ -445,6 +447,7 @@ sub emit_schemapg_row
 	delete $row->{attacl};
 	delete $row->{attoptions};
 	delete $row->{attfdwoptions};
+	delete $row->{attexistdefault};

 	# Expand booleans from 'f'/'t' to 'false'/'true'.
 	# Some values might be other macros (eg FLOAT4PASSBYVAL), don't change.
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 0cf7b9e..233c20f 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -56,9 +56,12 @@
 #include "catalog/storage_xlog.h"
 #include "commands/tablecmds.h"
 #include "commands/typecmds.h"
+#include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "optimizer/clauses.h"
 #include "optimizer/var.h"
+#include "optimizer/planner.h"
 #include "parser/parse_coerce.h"
 #include "parser/parse_collate.h"
 #include "parser/parse_expr.h"
@@ -67,6 +70,7 @@
 #include "storage/smgr.h"
 #include "utils/acl.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
@@ -139,37 +143,37 @@ static List *insert_ordered_unique_oid(List *list, Oid datum);
 static FormData_pg_attribute a1 = {
 	0, {"ctid"}, TIDOID, 0, sizeof(ItemPointerData),
 	SelfItemPointerAttributeNumber, 0, -1, -1,
-	false, 'p', 's', true, false, false, true, 0
+	false, 'p', 's', true, false, false, false, true, 0
 };

 static FormData_pg_attribute a2 = {
 	0, {"oid"}, OIDOID, 0, sizeof(Oid),
 	ObjectIdAttributeNumber, 0, -1, -1,
-	true, 'p', 'i', true, false, false, true, 0
+	true, 'p', 'i', true, false, false, false, true, 0
 };

 static FormData_pg_attribute a3 = {
 	0, {"xmin"}, XIDOID, 0, sizeof(TransactionId),
 	MinTransactionIdAttributeNumber, 0, -1, -1,
-	true, 'p', 'i', true, false, false, true, 0
+	true, 'p', 'i', true, false, false, false, true, 0
 };

 static FormData_pg_attribute a4 = {
 	0, {"cmin"}, CIDOID, 0, sizeof(CommandId),
 	MinCommandIdAttributeNumber, 0, -1, -1,
-	true, 'p', 'i', true, false, false, true, 0
+	true, 'p', 'i', true, false, false, false, true, 0
 };

 static FormData_pg_attribute a5 = {
 	0, {"xmax"}, XIDOID, 0, sizeof(TransactionId),
 	MaxTransactionIdAttributeNumber, 0, -1, -1,
-	true, 'p', 'i', true, false, false, true, 0
+	true, 'p', 'i', true, false, false, false, true, 0
 };

 static FormData_pg_attribute a6 = {
 	0, {"cmax"}, CIDOID, 0, sizeof(CommandId),
 	MaxCommandIdAttributeNumber, 0, -1, -1,
-	true, 'p', 'i', true, false, false, true, 0
+	true, 'p', 'i', true, false, false, false, true, 0
 };

 /*
@@ -181,7 +185,7 @@ static FormData_pg_attribute a6 = {
 static FormData_pg_attribute a7 = {
 	0, {"tableoid"}, OIDOID, 0, sizeof(Oid),
 	TableOidAttributeNumber, 0, -1, -1,
-	true, 'p', 'i', true, false, false, true, 0
+	true, 'p', 'i', true, false, false, false, true, 0
 };

 static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7};
@@ -625,6 +629,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel,
 	values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign);
 	values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
 	values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
+	values[Anum_pg_attribute_atthasexistdef - 1] = BoolGetDatum(new_attribute->atthasexistdef);
 	values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
 	values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
 	values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount);
@@ -634,6 +639,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel,
 	nulls[Anum_pg_attribute_attacl - 1] = true;
 	nulls[Anum_pg_attribute_attoptions - 1] = true;
 	nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
+	nulls[Anum_pg_attribute_attexistdefault - 1] = true;

 	tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);

@@ -1860,7 +1866,7 @@ heap_drop_with_catalog(Oid relid)
  */
 Oid
 StoreAttrDefault(Relation rel, AttrNumber attnum,
-				 Node *expr, bool is_internal)
+				 Node *expr, bool is_internal, bool add_column_mode)
 {
 	char	   *adbin;
 	char	   *adsrc;
@@ -1871,9 +1877,20 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	Relation	attrrel;
 	HeapTuple	atttup;
 	Form_pg_attribute attStruct;
+	Form_pg_attribute defAttStruct;
 	Oid			attrdefOid;
 	ObjectAddress colobject,
 				defobject;
+	ExprState  *exprState;
+	Expr       *expr2 = (Expr *) expr;
+	EState     *estate = NULL;
+	ExprContext *econtext;
+	char        *existBuf = NULL;
+	Datum       valuesAtt[Natts_pg_attribute];
+	bool        nullsAtt[Natts_pg_attribute];
+	bool        replacesAtt[Natts_pg_attribute];
+	Datum       existDefault = (Datum) 0;
+	bool        existDefIsNull = true;

 	/*
 	 * Flatten expression to string form for storage.
@@ -1887,6 +1904,44 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 							deparse_context_for(RelationGetRelationName(rel),
 												RelationGetRelid(rel)),
 							   false, false);
+    /*
+	 * Compute the exist default
+	 */
+	expr2 = expression_planner(expr2);
+
+	exprState = ExecInitExpr(expr2, NULL);
+	estate   = CreateExecutorState();
+	econtext = GetPerTupleExprContext(estate);
+
+	if (add_column_mode)
+	{
+		existDefault = ExecEvalExpr(exprState, econtext,
+									&existDefIsNull,
+									NULL);
+
+		defAttStruct = rel->rd_att->attrs[attnum - 1];
+
+		if (existDefIsNull)
+		{
+			existDefault = PointerGetDatum(NULL);
+		}
+		else if (defAttStruct->attbyval)
+		{
+			existBuf = palloc(VARHDRSZ + sizeof(Datum));
+			memcpy(VARDATA(existBuf), &existDefault, sizeof(Datum));
+			SET_VARSIZE(existBuf, VARHDRSZ + sizeof(Datum));
+			existDefault = PointerGetDatum(existBuf);
+		}
+		else if (defAttStruct->attlen >= 0)
+		{
+			existBuf = palloc(VARHDRSZ + defAttStruct->attlen);
+			memcpy(VARDATA(existBuf), DatumGetPointer(existDefault),
+				   defAttStruct->attlen);
+			SET_VARSIZE(existBuf,
+						VARHDRSZ + defAttStruct->attlen);
+			existDefault = PointerGetDatum(existBuf);
+		}
+	}

 	/*
 	 * Make the pg_attrdef entry.
@@ -1930,7 +1985,23 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
 	if (!attStruct->atthasdef)
 	{
-		attStruct->atthasdef = true;
+		MemSet(valuesAtt, 0, sizeof(valuesAtt));
+        MemSet(nullsAtt, false, sizeof(nullsAtt));
+        MemSet(replacesAtt, false, sizeof(replacesAtt));
+        valuesAtt[Anum_pg_attribute_atthasdef - 1] = true;
+        replacesAtt[Anum_pg_attribute_atthasdef - 1] = true;
+        if (add_column_mode)
+        {
+            valuesAtt[Anum_pg_attribute_atthasexistdef - 1] = true;
+            replacesAtt[Anum_pg_attribute_atthasexistdef - 1] = true;
+            valuesAtt[Anum_pg_attribute_atthasexistdef - 1] = true;
+            replacesAtt[Anum_pg_attribute_atthasexistdef - 1] = true;
+            valuesAtt[Anum_pg_attribute_attexistdefault -1] = existDefault;
+            replacesAtt[Anum_pg_attribute_attexistdefault -1] = true;
+            nullsAtt[Anum_pg_attribute_attexistdefault -1] = existDefIsNull;
+        }
+        atttup = heap_modify_tuple(atttup, RelationGetDescr(attrrel),
+                                   valuesAtt, nullsAtt, replacesAtt);
 		simple_heap_update(attrrel, &atttup->t_self, atttup);
 		/* keep catalog indexes current */
 		CatalogUpdateIndexes(attrrel, atttup);
@@ -1964,6 +2035,17 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
 	InvokeObjectPostCreateHookArg(AttrDefaultRelationId,
 								  RelationGetRelid(rel), attnum, is_internal);

+	if (estate)
+	{
+		FreeExecutorState(estate);
+	}
+
+	if (existBuf)
+	{
+		pfree(existBuf);
+		existBuf = NULL;
+	}
+
 	return attrdefOid;
 }

@@ -2105,7 +2187,7 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal)
 		{
 			case CONSTR_DEFAULT:
 				con->conoid = StoreAttrDefault(rel, con->attnum, con->expr,
-											   is_internal);
+											   is_internal, false);
 				break;
 			case CONSTR_CHECK:
 				con->conoid =
@@ -2221,7 +2303,14 @@ AddRelationNewConstraints(Relation rel,
 			(IsA(expr, Const) &&((Const *) expr)->constisnull))
 			continue;

-		defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal);
+		/* If the default is volatile we cannot use an exist default */
+        if (contain_volatile_functions((Node *) expr))
+		{
+			colDef->existDefMode = false;
+		}
+
+		defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal,
+								  colDef->existDefMode);

 		cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
 		cooked->contype = CONSTR_DEFAULT;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 08b646d..8188e2f 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -353,6 +353,7 @@ ConstructTupleDescriptor(Relation heapRelation,
 			to->attcacheoff = -1;
 			to->attnotnull = false;
 			to->atthasdef = false;
+			to->atthasexistdef = false;
 			to->attislocal = true;
 			to->attinhcount = 0;
 			to->attcollation = collationObjectId[i];
@@ -1575,7 +1576,8 @@ index_drop(Oid indexId, bool concurrent)
 	if (!HeapTupleIsValid(tuple))
 		elog(ERROR, "cache lookup failed for index %u", indexId);

-	hasexprs = !heap_attisnull(tuple, Anum_pg_index_indexprs);
+	hasexprs = !heap_attisnull(tuple, Anum_pg_index_indexprs,
+							   RelationGetDescr(indexRelation));

 	simple_heap_delete(indexRelation, &tuple->t_self);

diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index dc1f79f..6956e99 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -445,7 +445,8 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck, LOCKMOD
 	 * seqscan pass over the table to copy the missing rows, but that seems
 	 * expensive and tedious.
 	 */
-	if (!heap_attisnull(OldIndex->rd_indextuple, Anum_pg_index_indpred))
+	if (!heap_attisnull(OldIndex->rd_indextuple, Anum_pg_index_indpred,
+						RelationGetDescr(OldIndex)))
 		ereport(ERROR,
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("cannot cluster on partial index \"%s\"",
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 85817c6..1cf750a 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -206,8 +206,8 @@ CheckIndexCompatible(Oid oldId,
 	 * We don't assess expressions or predicates; assume incompatibility.
 	 * Also, if the index is invalid for any reason, treat it as incompatible.
 	 */
-	if (!(heap_attisnull(tuple, Anum_pg_index_indpred) &&
-		  heap_attisnull(tuple, Anum_pg_index_indexprs) &&
+	if (!(heap_attisnull(tuple, Anum_pg_index_indpred, NULL) &&
+		  heap_attisnull(tuple, Anum_pg_index_indexprs, NULL) &&
 		  IndexIsValid(indexForm)))
 	{
 		ReleaseSysCache(tuple);
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index f822ed9..9461368 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -630,6 +630,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
 			rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
 			rawEnt->attnum = attnum;
 			rawEnt->raw_default = colDef->raw_default;
+			rawEnt->existDefMode = false;
 			rawDefaults = lappend(rawDefaults, rawEnt);
 			descriptor->attrs[attnum - 1]->atthasdef = true;
 		}
@@ -4180,7 +4181,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
 			{
 				int			attn = lfirst_int(l);

-				if (heap_attisnull(tuple, attn + 1))
+				if (heap_attisnull(tuple, attn + 1, newTupDesc))
 					ereport(ERROR,
 							(errcode(ERRCODE_NOT_NULL_VIOLATION),
 							 errmsg("column \"%s\" contains null values",
@@ -4267,7 +4268,7 @@ ATGetQueueEntry(List **wqueue, Relation rel)
 	tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo));
 	tab->relid = relid;
 	tab->relkind = rel->rd_rel->relkind;
-	tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel));
+	tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel));
 	tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
 	tab->chgPersistence = false;

@@ -4850,6 +4851,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 	attribute.attalign = tform->typalign;
 	attribute.attnotnull = colDef->is_not_null;
 	attribute.atthasdef = false;
+	attribute.atthasexistdef = false;
 	attribute.attisdropped = false;
 	attribute.attislocal = colDef->is_local;
 	attribute.attinhcount = colDef->inhcount;
@@ -4895,6 +4897,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 		rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
 		rawEnt->attnum = attribute.attnum;
 		rawEnt->raw_default = copyObject(colDef->raw_default);
+		rawEnt->existDefMode = true;

 		/*
 		 * This function is intended for CREATE TABLE, so it processes a
@@ -4905,6 +4908,15 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,

 		/* Make the additional catalog changes visible */
 		CommandCounterIncrement();
+
+		/*
+		 * Did the request for an exist default work?
+		 * If not we'll have to do a rewrite
+		 */
+		if (!rawEnt->existDefMode)
+		{
+			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
+		}
 	}

 	/*
@@ -4971,16 +4983,26 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
 			newval->expr = expression_planner(defval);

 			tab->newvals = lappend(tab->newvals, newval);
-			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
 		}

+		if (DomainHasConstraints(typeOid))
+			tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
+
 		/*
-		 * If the new column is NOT NULL, tell Phase 3 it needs to test that.
-		 * (Note we don't do this for an OID column.  OID will be marked not
-		 * null, but since it's filled specially, there's no need to test
-		 * anything.)
+		 * If we add a column that is not null and there is no exist default
+		 * I.e. the exist default is NULL then this ADD COLUMN is doomed.
+		 * Unless the table is empty...
 		 */
-		tab->new_notnull |= colDef->is_not_null;
+		if (!rel->rd_att->attrs[attribute.attnum - 1]->atthasexistdef)
+		{
+			/*
+			 * If the new column is NOT NULL, tell Phase 3 it needs to test that.
+			 * (Note we don't do this for an OID column.  OID will be marked not
+			 * null, but since it's filled specially, there's no need to test
+			 * anything.)
+			 */
+			tab->new_notnull |= colDef->is_not_null;
+		}
 	}

 	/*
@@ -5390,6 +5412,7 @@ ATExecColumnDefault(Relation rel, const char *colName,
 		rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
 		rawEnt->attnum = attnum;
 		rawEnt->raw_default = newDefault;
+		rawEnt->existDefMode = false;

 		/*
 		 * This function is intended for CREATE TABLE, so it processes a
@@ -7165,8 +7188,8 @@ transformFkeyCheckAttrs(Relation pkrel,
 		if (indexStruct->indnatts == numattrs &&
 			indexStruct->indisunique &&
 			IndexIsValid(indexStruct) &&
-			heap_attisnull(indexTuple, Anum_pg_index_indpred) &&
-			heap_attisnull(indexTuple, Anum_pg_index_indexprs))
+			heap_attisnull(indexTuple, Anum_pg_index_indpred, RelationGetDescr(pkrel)) &&
+			heap_attisnull(indexTuple, Anum_pg_index_indexprs, RelationGetDescr(pkrel)))
 		{
 			Datum		indclassDatum;
 			bool		isnull;
@@ -8438,7 +8461,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
 		RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true,
 						  true);

-		StoreAttrDefault(rel, attnum, defaultexpr, true);
+		StoreAttrDefault(rel, attnum, defaultexpr, true, false);
 	}

 	ObjectAddressSubSet(address, RelationRelationId,
diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c
index 056933a..559a741 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2324,7 +2324,7 @@ AlterDomainNotNull(List *names, bool notNull)
 				{
 					int			attnum = rtc->atts[i];

-					if (heap_attisnull(tuple, attnum))
+					if (heap_attisnull(tuple, attnum, tupdesc))
 					{
 						/*
 						 * In principle the auxiliary information for this
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 32bb3f9..58156b7 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2633,8 +2633,16 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate)
 								false, NULL))
 					elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck");

-				/* successful, copy tuple */
-				copyTuple = heap_copytuple(&tuple);
+				if (HeapTupleHeaderGetNatts(tuple.t_data) < RelationGetDescr(erm->relation)->natts)
+				{
+					copyTuple = heap_expand_tuple(&tuple,
+												  RelationGetDescr(erm->relation));
+				}
+				else
+				{
+					/* successful, copy tuple */
+					copyTuple = heap_copytuple(&tuple);
+				}
 				ReleaseBuffer(buffer);
 			}

diff --git a/src/backend/executor/execQual.c b/src/backend/executor/execQual.c
index 743e7d6..ed3396b 100644
--- a/src/backend/executor/execQual.c
+++ b/src/backend/executor/execQual.c
@@ -1064,7 +1064,7 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext,

 		if (!vattr->attisdropped)
 			continue;			/* already checked non-dropped cols */
-		if (heap_attisnull(tuple, i + 1))
+		if (heap_attisnull(tuple, i + 1, tupleDesc))
 			continue;			/* null is always okay */
 		if (vattr->attlen != sattr->attlen ||
 			vattr->attalign != sattr->attalign)
@@ -3948,7 +3948,7 @@ ExecEvalNullTest(NullTestState *nstate,
 			/* ignore dropped columns */
 			if (tupDesc->attrs[att - 1]->attisdropped)
 				continue;
-			if (heap_attisnull(&tmptup, att))
+			if (heap_attisnull(&tmptup, att, tupDesc))
 			{
 				/* null field disproves IS NOT NULL */
 				if (ntest->nulltesttype == IS_NOT_NULL)
diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c
index 533050d..6116056 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -592,8 +592,15 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot)
 	if (slot->tts_mintuple)
 		return heap_copy_minimal_tuple(slot->tts_mintuple);
 	if (slot->tts_tuple)
-		return minimal_tuple_from_heap_tuple(slot->tts_tuple);
-
+	{
+		if (TTS_HAS_PHYSICAL_TUPLE(slot) &&
+			HeapTupleHeaderGetNatts(slot->tts_tuple->t_data)
+			< slot->tts_tupleDescriptor->natts)
+			return minimal_expand_tuple(slot->tts_tuple,
+										slot->tts_tupleDescriptor);
+		else
+			return minimal_tuple_from_heap_tuple(slot->tts_tuple);
+	}
 	/*
 	 * Otherwise we need to build a tuple from the Datum array.
 	 */
@@ -630,8 +637,21 @@ ExecFetchSlotTuple(TupleTableSlot *slot)
 	 * If we have a regular physical tuple then just return it.
 	 */
 	if (TTS_HAS_PHYSICAL_TUPLE(slot))
-		return slot->tts_tuple;
-
+	{
+		if (HeapTupleHeaderGetNatts(slot->tts_tuple->t_data) < slot->tts_tupleDescriptor->natts)
+		{
+			MemoryContext oldContext = MemoryContextSwitchTo(slot->tts_mcxt);
+			slot->tts_tuple = heap_expand_tuple(slot->tts_tuple,
+												slot->tts_tupleDescriptor);
+			slot->tts_shouldFree = true;
+			MemoryContextSwitchTo(oldContext);
+			return slot->tts_tuple;
+		}
+		else
+		{
+			return slot->tts_tuple;
+		}
+	}
 	/*
 	 * Otherwise materialize the slot...
 	 */
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 663ffe0..7f02a3f 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4356,7 +4356,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
 		funcform->prosecdef ||
 		funcform->proretset ||
 		funcform->prorettype == RECORDOID ||
-		!heap_attisnull(func_tuple, Anum_pg_proc_proconfig) ||
+		!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL) ||
 		funcform->pronargs != list_length(args))
 		return NULL;

@@ -4879,7 +4879,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
 		funcform->provolatile == PROVOLATILE_VOLATILE ||
 		funcform->prosecdef ||
 		!funcform->proretset ||
-		!heap_attisnull(func_tuple, Anum_pg_proc_proconfig))
+		!heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL))
 	{
 		ReleaseSysCache(func_tuple);
 		return NULL;
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b828e3c..e352adb 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1034,7 +1034,7 @@ build_column_default(Relation rel, int attrno)
 	/*
 	 * Scan to see if relation has a default for this column.
 	 */
-	if (rd_att->constr && rd_att->constr->num_defval > 0)
+	if (att_tup->atthasdef && rd_att->constr && rd_att->constr->num_defval > 0)
 	{
 		AttrDefault *defval = rd_att->constr->defval;
 		int			ndef = rd_att->constr->num_defval;
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index b476500..614aadb 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -209,7 +209,7 @@ static void ri_GenerateQual(StringInfo buf,
 				const char *rightop, Oid rightoptype);
 static void ri_add_cast_to(StringInfo buf, Oid typid);
 static void ri_GenerateQualCollation(StringInfo buf, Oid collation);
-static int ri_NullCheck(HeapTuple tup,
+static int ri_NullCheck(TupleDesc tupdesc, HeapTuple tup,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk);
 static void ri_BuildQueryKey(RI_QueryKey *key,
 				 const RI_ConstraintInfo *riinfo,
@@ -315,7 +315,7 @@ RI_FKey_check(TriggerData *trigdata)
 				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
 				 errmsg("MATCH PARTIAL not yet implemented")));

-	switch (ri_NullCheck(new_row, riinfo, false))
+	switch (ri_NullCheck(RelationGetDescr(pk_rel), new_row, riinfo, false))
 	{
 		case RI_KEYS_ALL_NULL:

@@ -522,7 +522,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel,
 	bool		result;

 	/* Only called for non-null rows */
-	Assert(ri_NullCheck(old_row, riinfo, true) == RI_KEYS_NONE_NULL);
+	Assert(ri_NullCheck(RelationGetDescr(fk_rel), old_row, riinfo, true) == RI_KEYS_NONE_NULL);

 	if (SPI_connect() != SPI_OK_CONNECT)
 		elog(ERROR, "SPI_connect failed");
@@ -682,7 +682,7 @@ ri_restrict_del(TriggerData *trigdata, bool is_no_action)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -897,7 +897,7 @@ ri_restrict_upd(TriggerData *trigdata, bool is_no_action)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -1076,7 +1076,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -1236,7 +1236,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -1413,7 +1413,7 @@ RI_FKey_setnull_del(PG_FUNCTION_ARGS)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -1580,7 +1580,7 @@ RI_FKey_setnull_upd(PG_FUNCTION_ARGS)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -1753,7 +1753,7 @@ RI_FKey_setdefault_del(PG_FUNCTION_ARGS)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -1935,7 +1935,7 @@ RI_FKey_setdefault_upd(PG_FUNCTION_ARGS)
 			 */
 		case FKCONSTR_MATCH_SIMPLE:
 		case FKCONSTR_MATCH_FULL:
-			switch (ri_NullCheck(old_row, riinfo, true))
+			switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true))
 			{
 				case RI_KEYS_ALL_NULL:
 				case RI_KEYS_SOME_NULL:
@@ -2105,7 +2105,7 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel,
 			 * If any old key value is NULL, the row could not have been
 			 * referenced by an FK row, so no check is needed.
 			 */
-			if (ri_NullCheck(old_row, riinfo, true) != RI_KEYS_NONE_NULL)
+			if (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true) != RI_KEYS_NONE_NULL)
 				return false;

 			/* If all old and new key values are equal, no check is needed */
@@ -2161,7 +2161,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 			 * If any new key value is NULL, the row must satisfy the
 			 * constraint, so no check is needed.
 			 */
-			if (ri_NullCheck(new_row, riinfo, false) != RI_KEYS_NONE_NULL)
+			if (ri_NullCheck(RelationGetDescr(fk_rel), new_row, riinfo, false) != RI_KEYS_NONE_NULL)
 				return false;

 			/*
@@ -2192,7 +2192,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel,
 			 * invalidated before the constraint is to be checked, but we
 			 * should queue the event to apply the check later.
 			 */
-			switch (ri_NullCheck(new_row, riinfo, false))
+			switch (ri_NullCheck(RelationGetDescr(fk_rel), new_row, riinfo, false))
 			{
 				case RI_KEYS_ALL_NULL:
 					return false;
@@ -2486,7 +2486,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel)
 		 * disallows partially-null FK rows.
 		 */
 		if (fake_riinfo.confmatchtype == FKCONSTR_MATCH_FULL &&
-			ri_NullCheck(tuple, &fake_riinfo, false) != RI_KEYS_NONE_NULL)
+			ri_NullCheck(tupdesc, tuple, &fake_riinfo, false) != RI_KEYS_NONE_NULL)
 			ereport(ERROR,
 					(errcode(ERRCODE_FOREIGN_KEY_VIOLATION),
 					 errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"",
@@ -3350,7 +3350,8 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo,
  * ----------
  */
 static int
-ri_NullCheck(HeapTuple tup,
+ri_NullCheck(TupleDesc tupDesc,
+	         HeapTuple tup,
 			 const RI_ConstraintInfo *riinfo, bool rel_is_pk)
 {
 	const int16 *attnums;
@@ -3365,7 +3366,7 @@ ri_NullCheck(HeapTuple tup,

 	for (i = 0; i < riinfo->nkeys; i++)
 	{
-		if (heap_attisnull(tup, attnums[i]))
+		if (heap_attisnull(tup, attnums[i], tupDesc))
 			nonenull = false;
 		else
 			allnull = false;
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 8a81d7a..a2ebedd 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1190,7 +1190,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 	 * versions of the expressions and predicate, because we want to display
 	 * non-const-folded expressions.)
 	 */
-	if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs))
+	if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs, NULL))
 	{
 		Datum		exprsDatum;
 		bool		isnull;
@@ -1356,7 +1356,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno,
 		/*
 		 * If it's a partial index, decompile and append the predicate
 		 */
-		if (!heap_attisnull(ht_idx, Anum_pg_index_indpred))
+		if (!heap_attisnull(ht_idx, Anum_pg_index_indpred, NULL))
 		{
 			Node	   *node;
 			Datum		predDatum;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 79e0b1f..8f321a4 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -70,6 +70,7 @@
 #include "storage/smgr.h"
 #include "utils/array.h"
 #include "utils/builtins.h"
+#include "utils/datum.h"
 #include "utils/fmgroids.h"
 #include "utils/inval.h"
 #include "utils/lsyscache.h"
@@ -482,6 +483,8 @@ RelationBuildTupleDesc(Relation relation)
 	TupleConstr *constr;
 	AttrDefault *attrdef = NULL;
 	int			ndef = 0;
+	int         attnum = 0;
+	bool        has_existdefval = false;

 	/* copy some fields from pg_class row to rd_att */
 	relation->rd_att->tdtypeid = relation->rd_rel->reltype;
@@ -534,7 +537,18 @@ RelationBuildTupleDesc(Relation relation)
 			elog(ERROR, "invalid attribute number %d for %s",
 				 attp->attnum, RelationGetRelationName(relation));

-		memcpy(relation->rd_att->attrs[attp->attnum - 1],
+		/*
+		 * We have a dependency on the attrdef array being filled in
+		 * in ascending attnum order.
+		 * This should be guaranteed by the index driving the scan.
+		 * But we want to be double sure
+		 */
+		if (!(attp->attnum > attnum))
+			elog(ERROR, "attribute numbers not ascending");
+
+		attnum = attp->attnum;
+
+		memcpy(relation->rd_att->attrs[attnum - 1],
 			   attp,
 			   ATTRIBUTE_FIXED_PART_SIZE);

@@ -542,15 +556,67 @@ RelationBuildTupleDesc(Relation relation)
 		if (attp->attnotnull)
 			constr->has_not_null = true;

-		if (attp->atthasdef)
+		/*
+		 * If the column has a current or exist default
+		 * Fill it into the attrdef array
+		 */
+		if (attp->atthasdef || attp->atthasexistdef)
 		{
+			Datum existDefault;
+			bool  existDefNull;
+
 			if (attrdef == NULL)
 				attrdef = (AttrDefault *)
 					MemoryContextAllocZero(CacheMemoryContext,
 										   relation->rd_rel->relnatts *
 										   sizeof(AttrDefault));
-			attrdef[ndef].adnum = attp->attnum;
+			attrdef[ndef].adnum = attnum;
 			attrdef[ndef].adbin = NULL;
+
+			/* Do we have an exist default? */
+			existDefault = SysCacheGetAttr(ATTNUM, pg_attribute_tuple,
+										   Anum_pg_attribute_attexistdefault,
+										   &existDefNull);
+			if (existDefNull)
+			{
+				/* No, then the store a NULL */
+				attrdef[ndef].adexist = PointerGetDatum(NULL);
+				attrdef[ndef].adexistNull = true;
+			}
+			else if (attp->attbyval)
+			{
+				/* Yes, and its of the by-value kind
+				 * Copy in the Datum */
+				memcpy(&attrdef[ndef].adexist,
+					   VARDATA_ANY(existDefault), sizeof(Datum));
+				attrdef[ndef].adexistNull = false;
+				has_existdefval = true;
+			}
+			else if (attp->attlen > 0)
+			{
+				/* Yes, and its fixed length
+				 * Copy it out and have teh Datum point to it. */
+				MemoryContext oldcxt;
+				oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+				attrdef[ndef].adexist = PointerGetDatum(palloc(attp->attlen));
+				memcpy(DatumGetPointer(attrdef[ndef].adexist),
+					   VARDATA_ANY(existDefault), attp->attlen);
+				MemoryContextSwitchTo(oldcxt);
+				attrdef[ndef].adexistNull = false;
+				has_existdefval = true;
+			}
+			else
+			{
+				/* Yes, variable length */
+				MemoryContext oldcxt;
+				oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+				attrdef[ndef].adexist = datumCopy(existDefault,
+												  attp->attbyval,
+												  attp->attlen);
+				attrdef[ndef].adexistNull = false;
+				MemoryContextSwitchTo(oldcxt);
+				has_existdefval = true;
+			}
 			ndef++;
 		}
 		need--;
@@ -605,10 +671,14 @@ RelationBuildTupleDesc(Relation relation)
 			else
 				constr->defval = attrdef;
 			constr->num_defval = ndef;
+			constr->has_existdefval = has_existdefval;
 			AttrDefaultFetch(relation);
 		}
 		else
+		{
 			constr->num_defval = 0;
+			constr->has_existdefval = false;
+		}

 		if (relation->rd_rel->relchecks > 0)	/* CHECKs */
 		{
@@ -3711,10 +3781,6 @@ AttrDefaultFetch(Relation relation)

 	systable_endscan(adscan);
 	heap_close(adrel, AccessShareLock);
-
-	if (found != ndef)
-		elog(WARNING, "%d attrdef record(s) missing for rel %s",
-			 ndef - found, RelationGetRelationName(relation));
 }

 /*
@@ -4052,7 +4118,8 @@ RelationGetIndexList(Relation relation)
 		 */
 		if (!IndexIsValid(index) || !index->indisunique ||
 			!index->indimmediate ||
-			!heap_attisnull(htup, Anum_pg_index_indpred))
+			!heap_attisnull(htup, Anum_pg_index_indpred,
+							GetPgIndexDescriptor()))
 			continue;

 		/* Check to see if is a usable btree index on OID */
@@ -4242,7 +4309,8 @@ RelationGetIndexExpressions(Relation relation)

 	/* Quick exit if there is nothing to do. */
 	if (relation->rd_indextuple == NULL ||
-		heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs))
+		heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs,
+					   GetPgIndexDescriptor()))
 		return NIL;

 	/*
@@ -4303,7 +4371,8 @@ RelationGetIndexPredicate(Relation relation)

 	/* Quick exit if there is nothing to do. */
 	if (relation->rd_indextuple == NULL ||
-		heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred))
+		heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred,
+					   GetPgIndexDescriptor()))
 		return NIL;

 	/*
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 46a55ba..ed02db4 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -237,7 +237,7 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt,
 	 */
 	if (!ignore_security &&
 		(procedureStruct->prosecdef ||
-		 !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig) ||
+		 !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig, NULL) ||
 		 FmgrHookIsNeeded(functionId)))
 	{
 		finfo->fn_addr = fmgr_security_definer;
diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c
index 5d49fe5..00cc1f7 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -1035,8 +1035,8 @@ get_func_result_name(Oid functionId)
 		elog(ERROR, "cache lookup failed for function %u", functionId);

 	/* If there are no named OUT parameters, return NULL */
-	if (heap_attisnull(procTuple, Anum_pg_proc_proargmodes) ||
-		heap_attisnull(procTuple, Anum_pg_proc_proargnames))
+	if (heap_attisnull(procTuple, Anum_pg_proc_proargmodes, NULL) ||
+		heap_attisnull(procTuple, Anum_pg_proc_proargnames, NULL))
 		result = NULL;
 	else
 	{
@@ -1130,8 +1130,8 @@ build_function_result_tupdesc_t(HeapTuple procTuple)
 		return NULL;

 	/* If there are no OUT parameters, return NULL */
-	if (heap_attisnull(procTuple, Anum_pg_proc_proallargtypes) ||
-		heap_attisnull(procTuple, Anum_pg_proc_proargmodes))
+	if (heap_attisnull(procTuple, Anum_pg_proc_proallargtypes, NULL) ||
+		heap_attisnull(procTuple, Anum_pg_proc_proargmodes, NULL))
 		return NULL;

 	/* Get the data out of the tuple */
diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h
index d7e5fad..c1f00ef 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -790,7 +790,7 @@ 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 bool heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc);
 extern Datum nocachegetattr(HeapTuple tup, int attnum,
 			   TupleDesc att);
 extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
@@ -814,5 +814,8 @@ extern void heap_free_minimal_tuple(MinimalTuple mtup);
 extern MinimalTuple heap_copy_minimal_tuple(MinimalTuple mtup);
 extern HeapTuple heap_tuple_from_minimal_tuple(MinimalTuple mtup);
 extern MinimalTuple minimal_tuple_from_heap_tuple(HeapTuple htup);
+extern HeapTuple heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
+extern MinimalTuple minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
+

 #endif   /* HTUP_DETAILS_H */
diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h
index de18f74..f55eccf 100644
--- a/src/include/access/tupdesc.h
+++ b/src/include/access/tupdesc.h
@@ -14,6 +14,7 @@
 #ifndef TUPDESC_H
 #define TUPDESC_H

+#include "postgres.h"
 #include "access/attnum.h"
 #include "catalog/pg_attribute.h"
 #include "nodes/pg_list.h"
@@ -22,7 +23,9 @@
 typedef struct attrDefault
 {
 	AttrNumber	adnum;
+	bool        adexistNull;    /* true if exist default is NULL */
 	char	   *adbin;			/* nodeToString representation of expr */
+	Datum       adexist;        /* exist default */
 } AttrDefault;

 typedef struct constrCheck
@@ -40,6 +43,7 @@ typedef struct tupleConstr
 	ConstrCheck *check;			/* array */
 	uint16		num_defval;
 	uint16		num_check;
+	bool        has_existdefval;
 	bool		has_not_null;
 } TupleConstr;

diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h
index cd3048d..7868b43 100644
--- a/src/include/catalog/catversion.h
+++ b/src/include/catalog/catversion.h
@@ -53,6 +53,6 @@
  */

 /*							yyyymmddN */
-#define CATALOG_VERSION_NO	201610201
+#define CATALOG_VERSION_NO	201610211

 #endif
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index b80d8d8..6f4fd37 100644
--- a/src/include/catalog/heap.h
+++ b/src/include/catalog/heap.h
@@ -23,6 +23,7 @@ typedef struct RawColumnDefault
 {
 	AttrNumber	attnum;			/* attribute to attach default to */
 	Node	   *raw_default;	/* default value (untransformed parse tree) */
+	bool        existDefMode;   /* true if part of add column processing */
 } RawColumnDefault;

 typedef struct CookedConstraint
@@ -103,7 +104,8 @@ extern List *AddRelationNewConstraints(Relation rel,
 						  bool is_internal);

 extern Oid StoreAttrDefault(Relation rel, AttrNumber attnum,
-				 Node *expr, bool is_internal);
+				Node *expr, bool is_internal,
+				bool add_column_mode);

 extern Node *cookDefault(ParseState *pstate,
 			Node *raw_default,
diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h
index 39d8eed..f84debf 100644
--- a/src/include/catalog/pg_attribute.h
+++ b/src/include/catalog/pg_attribute.h
@@ -133,6 +133,9 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
 	/* Has DEFAULT value or not */
 	bool		atthasdef;

+	/* Has exist default or not */
+	bool        atthasexistdef;
+
 	/* Is dropped (ie, logically invisible) or not */
 	bool		attisdropped;

@@ -164,6 +167,9 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK

 	/* Column-level FDW options */
 	text		attfdwoptions[1];
+
+	/* Exist default for added columns */
+    bytea       attexistdefault;
 #endif
 } FormData_pg_attribute;

@@ -188,28 +194,30 @@ typedef FormData_pg_attribute *Form_pg_attribute;
  * ----------------
  */

-#define Natts_pg_attribute				21
-#define Anum_pg_attribute_attrelid		1
-#define Anum_pg_attribute_attname		2
-#define Anum_pg_attribute_atttypid		3
-#define Anum_pg_attribute_attstattarget 4
-#define Anum_pg_attribute_attlen		5
-#define Anum_pg_attribute_attnum		6
-#define Anum_pg_attribute_attndims		7
-#define Anum_pg_attribute_attcacheoff	8
-#define Anum_pg_attribute_atttypmod		9
-#define Anum_pg_attribute_attbyval		10
-#define Anum_pg_attribute_attstorage	11
-#define Anum_pg_attribute_attalign		12
-#define Anum_pg_attribute_attnotnull	13
-#define Anum_pg_attribute_atthasdef		14
-#define Anum_pg_attribute_attisdropped	15
-#define Anum_pg_attribute_attislocal	16
-#define Anum_pg_attribute_attinhcount	17
-#define Anum_pg_attribute_attcollation	18
-#define Anum_pg_attribute_attacl		19
-#define Anum_pg_attribute_attoptions	20
-#define Anum_pg_attribute_attfdwoptions 21
+#define Natts_pg_attribute				  23
+#define Anum_pg_attribute_attrelid	      1
+#define Anum_pg_attribute_attname		  2
+#define Anum_pg_attribute_atttypid		  3
+#define Anum_pg_attribute_attstattarget   4
+#define Anum_pg_attribute_attlen		  5
+#define Anum_pg_attribute_attnum		  6
+#define Anum_pg_attribute_attndims		  7
+#define Anum_pg_attribute_attcacheoff	  8
+#define Anum_pg_attribute_atttypmod		  9
+#define Anum_pg_attribute_attbyval		  10
+#define Anum_pg_attribute_attstorage	  11
+#define Anum_pg_attribute_attalign		  12
+#define Anum_pg_attribute_attnotnull	  13
+#define Anum_pg_attribute_atthasdef		  14
+#define Anum_pg_attribute_atthasexistdef  15
+#define Anum_pg_attribute_attisdropped	  16
+#define Anum_pg_attribute_attislocal	  17
+#define Anum_pg_attribute_attinhcount	  18
+#define Anum_pg_attribute_attcollation	  19
+#define Anum_pg_attribute_attacl		  20
+#define Anum_pg_attribute_attoptions	  21
+#define Anum_pg_attribute_attfdwoptions   22
+#define Anum_pg_attribute_attexistdefault 23


 /* ----------------
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index e57b81c..d00f5fb 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -145,7 +145,7 @@ typedef FormData_pg_class *Form_pg_class;
  */
 DATA(insert OID = 1247 (  pg_type		PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
-DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ ));
+DATA(insert OID = 1249 (  pg_attribute	PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 23 0 f f f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
 DATA(insert OID = 1255 (  pg_proc		PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n 3 1 _null_ _null_ ));
 DESCR("");
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index e124552..60eb03c 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -374,8 +374,6 @@ alter table rewriteme alter column foo type numeric;
 ERROR:  rewrites not allowed
 CONTEXT:  PL/pgSQL function test_evtrig_no_rewrite() line 3 at RAISE
 alter table rewriteme add column baz int default 0;
-ERROR:  rewrites not allowed
-CONTEXT:  PL/pgSQL function test_evtrig_no_rewrite() line 3 at RAISE
 -- test with more than one reason to rewrite a single table
 CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger
 LANGUAGE plpgsql AS $$
@@ -389,7 +387,7 @@ alter table rewriteme
  add column onemore int default 0,
  add column another int default -1,
  alter column foo type numeric(10,4);
-NOTICE:  Table 'rewriteme' is being rewritten (reason = 6)
+NOTICE:  Table 'rewriteme' is being rewritten (reason = 4)
 -- shouldn't trigger a table_rewrite event
 alter table rewriteme alter column foo type numeric(12,4);
 -- typed tables are rewritten when their type changes.  Don't emit table
diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out
new file mode 100644
index 0000000..44806f0
--- /dev/null
+++ b/src/test/regress/expected/fast_default.out
@@ -0,0 +1,446 @@
+--
+-- ALTER TABLE ADD COLUMN DEFAULT test
+--
+SET search_path = fast_default;
+CREATE SCHEMA fast_default;
+CREATE TABLE m(id OID);
+INSERT INTO m VALUES (NULL::OID);
+CREATE FUNCTION set(tabname name) RETURNS VOID
+AS $$
+BEGIN
+  UPDATE m SET id = (SELECT c.relfilenode FROM pg_class AS c, pg_namespace AS s
+           WHERE c.relname = tabname AND c.relnamespace = s.oid AND s.nspname = 'fast_default');
+END;
+$$ LANGUAGE 'plpgsql';
+CREATE FUNCTION comp() RETURNS TEXT
+AS $$
+BEGIN
+  RETURN (SELECT CASE WHEN m.id = c.relfilenode THEN 'Unchanged' ELSE 'Rewritten' END
+            FROM m, pg_class AS c, pg_namespace AS s
+           WHERE c.relname = 't' AND c.relnamespace = s.oid AND s.nspname = 'fast_default');
+END;
+$$ LANGUAGE 'plpgsql';
+-- 1. Test a large sample of dfferent datatypes
+CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1);
+SELECT set('t');
+ set
+-----
+
+(1 row)
+
+INSERT INTO T VALUES (1), (2);
+ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT 'hello',
+              ALTER COLUMN c_int SET DEFAULT 2;
+INSERT INTO T VALUES (3), (4);
+ALTER TABLE T ADD COLUMN c_text TEXT  DEFAULT 'world',
+              ALTER COLUMN c_bpchar SET DEFAULT 'dog';
+INSERT INTO T VALUES (5), (6);
+ALTER TABLE T ADD COLUMN c_date DATE DEFAULT '2016-06-02',
+              ALTER COLUMN c_text SET DEFAULT 'cat';
+INSERT INTO T VALUES (7), (8);
+ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP DEFAULT '2016-09-01 12:00:00',
+              ADD COLUMN c_timestamp_null TIMESTAMP,
+              ALTER COLUMN c_date SET DEFAULT '2010-01-01';
+INSERT INTO T VALUES (9), (10);
+ALTER TABLE T ADD COLUMN c_array TEXT[] DEFAULT '{"This", "is", "the", "real", "world"}',
+              ALTER COLUMN c_timestamp SET DEFAULT '1970-12-31 11:12:13',
+              ALTER COLUMN c_timestamp_null SET DEFAULT '2016-09-29 12:00:00';
+INSERT INTO T VALUES (11), (12);
+ALTER TABLE T ADD COLUMN c_small SMALLINT DEFAULT -5,
+              ADD COLUMN c_small_null SMALLINT,
+              ALTER COLUMN c_array SET DEFAULT '{"This", "is", "no", "fantasy"}';
+INSERT INTO T VALUES (13), (14);
+ALTER TABLE T ADD COLUMN c_big BIGINT DEFAULT 180000000000018,
+              ALTER COLUMN c_small SET DEFAULT 9,
+              ALTER COLUMN c_small_null SET DEFAULT 13;
+INSERT INTO T VALUES (15), (16);
+ALTER TABLE T ADD COLUMN c_num NUMERIC DEFAULT 1.00000000001,
+              ALTER COLUMN c_big SET DEFAULT -9999999999999999;
+INSERT INTO T VALUES (17), (18);
+ALTER TABLE T ADD COLUMN c_time TIME DEFAULT '12:00:00',
+              ALTER COLUMN c_num SET DEFAULT 2.000000000000002;
+INSERT INTO T VALUES (19), (20);
+ALTER TABLE T ADD COLUMN c_interval INTERVAL DEFAULT '1 day',
+              ALTER COLUMN c_time SET DEFAULT '23:59:59';
+INSERT INTO T VALUES (21), (22);
+ALTER TABLE T ALTER COLUMN c_time DROP DEFAULT,
+              ALTER COLUMN c_interval SET DEFAULT '3 hours';
+INSERT INTO T VALUES (23), (24);
+ALTER TABLE T ALTER COLUMN c_bpchar    DROP DEFAULT,
+              ALTER COLUMN c_date      DROP DEFAULT,
+              ALTER COLUMN c_text      DROP DEFAULT,
+              ALTER COLUMN c_timestamp DROP DEFAULT,
+              ALTER COLUMN c_array     DROP DEFAULT,
+              ALTER COLUMN c_small     DROP DEFAULT,
+              ALTER COLUMN c_big       DROP DEFAULT,
+              ALTER COLUMN c_num       DROP DEFAULT,
+              ALTER COLUMN c_interval  DROP DEFAULT;
+INSERT INTO T VALUES (25), (26);
+SELECT * FROM T ORDER BY pk;
+ pk | c_int | c_bpchar | c_text |   c_date   |       c_timestamp        |     c_timestamp_null     |         c_array          | c_small | c_small_null |       c_big       |       c_num       |  c_time  | c_interval
+----+-------+----------+--------+------------+--------------------------+--------------------------+--------------------------+---------+--------------+-------------------+-------------------+----------+------------
+  1 |     1 | hello    | world  | 06-02-2016 | Thu Sep 01 12:00:00 2016 |                          | {This,is,the,real,world} |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+  2 |     1 | hello    | world  | 06-02-2016 | Thu Sep 01 12:00:00 2016 |                          | {This,is,the,real,world} |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+  3 |     2 | hello    | world  | 06-02-2016 | Thu Sep 01 12:00:00 2016 |                          | {This,is,the,real,world} |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+  4 |     2 | hello    | world  | 06-02-2016 | Thu Sep 01 12:00:00 2016 |                          | {This,is,the,real,world} |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+  5 |     2 | dog      | world  | 06-02-2016 | Thu Sep 01 12:00:00 2016 |                          | {This,is,the,real,world} |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+  6 |     2 | dog      | world  | 06-02-2016 | Thu Sep 01 12:00:00 2016 |                          | {This,is,the,real,world} |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+  7 |     2 | dog      | cat    | 06-02-2016 | Thu Sep 01 12:00:00 2016 |                          | {This,is,the,real,world} |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+  8 |     2 | dog      | cat    | 06-02-2016 | Thu Sep 01 12:00:00 2016 |                          | {This,is,the,real,world} |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+  9 |     2 | dog      | cat    | 01-01-2010 | Thu Sep 01 12:00:00 2016 |                          | {This,is,the,real,world} |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+ 10 |     2 | dog      | cat    | 01-01-2010 | Thu Sep 01 12:00:00 2016 |                          | {This,is,the,real,world} |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+ 11 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,the,real,world} |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+ 12 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,the,real,world} |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+ 13 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy}     |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+ 14 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy}     |      -5 |              |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+ 15 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy}     |       9 |           13 |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+ 16 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy}     |       9 |           13 |   180000000000018 |     1.00000000001 | 12:00:00 | @ 1 day
+ 17 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy}     |       9 |           13 | -9999999999999999 |     1.00000000001 | 12:00:00 | @ 1 day
+ 18 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy}     |       9 |           13 | -9999999999999999 |     1.00000000001 | 12:00:00 | @ 1 day
+ 19 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy}     |       9 |           13 | -9999999999999999 | 2.000000000000002 | 12:00:00 | @ 1 day
+ 20 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy}     |       9 |           13 | -9999999999999999 | 2.000000000000002 | 12:00:00 | @ 1 day
+ 21 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy}     |       9 |           13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 1 day
+ 22 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy}     |       9 |           13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 1 day
+ 23 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy}     |       9 |           13 | -9999999999999999 | 2.000000000000002 |          | @ 3 hours
+ 24 |     2 | dog      | cat    | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy}     |       9 |           13 | -9999999999999999 | 2.000000000000002 |          | @ 3 hours
+ 25 |     2 |          |        |            |                          | Thu Sep 29 12:00:00 2016 |                          |         |           13 |                   |                   |          |
+ 26 |     2 |          |        |            |                          | Thu Sep 29 12:00:00 2016 |                          |         |           13 |                   |                   |          |
+(26 rows)
+
+SELECT comp();
+   comp
+-----------
+ Unchanged
+(1 row)
+
+DROP TABLE T;
+-- 2.1 Test expressions in the defaults
+CREATE OR REPLACE FUNCTION foo(a INT) RETURNS TEXT AS $$
+DECLARE res TEXT := '';
+        i INT;
+BEGIN
+  i := 0;
+  WHILE (i < a) LOOP
+    res := res || chr(ascii('a') + i);
+    i := i + 1;
+  END LOOP;
+  RETURN res;
+END; $$ LANGUAGE PLPGSQL STABLE;
+CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT LENGTH(foo(6)));
+SELECT set('t');
+ set
+-----
+
+(1 row)
+
+INSERT INTO T VALUES (1), (2);
+ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT foo(4),
+              ALTER COLUMN c_int SET DEFAULT LENGTH(foo(8));
+INSERT INTO T VALUES (3), (4);
+ALTER TABLE T ADD COLUMN c_text TEXT  DEFAULT foo(6),
+              ALTER COLUMN c_bpchar SET DEFAULT foo(3);
+INSERT INTO T VALUES (5), (6);
+ALTER TABLE T ADD COLUMN c_date DATE DEFAULT '2016-06-02'::DATE  + LENGTH(foo(10)),
+              ALTER COLUMN c_text SET DEFAULT foo(12);
+INSERT INTO T VALUES (7), (8);
+ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP DEFAULT '2016-09-01'::DATE + LENGTH(foo(10)),
+              ALTER COLUMN c_date SET DEFAULT '2010-01-01'::DATE - LENGTH(foo(4));
+INSERT INTO T VALUES (9), (10);
+ALTER TABLE T ADD COLUMN c_array TEXT[] DEFAULT ('{"This", "is", "' || foo(4) || '", "the", "real", "world"}')::TEXT[],
+              ALTER COLUMN c_timestamp SET DEFAULT '1970-12-31'::DATE + LENGTH(foo(30));
+INSERT INTO T VALUES (11), (12);
+ALTER TABLE T ALTER COLUMN c_int DROP DEFAULT,
+              ALTER COLUMN c_array SET DEFAULT ('{"This", "is", "' || foo(1) || '", "fantasy"}')::text[];
+INSERT INTO T VALUES (13), (14);
+ALTER TABLE T ALTER COLUMN c_bpchar    DROP DEFAULT,
+              ALTER COLUMN c_date      DROP DEFAULT,
+              ALTER COLUMN c_text      DROP DEFAULT,
+              ALTER COLUMN c_timestamp DROP DEFAULT,
+              ALTER COLUMN c_array     DROP DEFAULT;
+INSERT INTO T VALUES (15), (16);
+SELECT * FROM T;
+ pk | c_int | c_bpchar |    c_text    |   c_date   |       c_timestamp        |            c_array
+----+-------+----------+--------------+------------+--------------------------+-------------------------------
+  1 |     6 | abcd     | abcdef       | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+  2 |     6 | abcd     | abcdef       | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+  3 |     8 | abcd     | abcdef       | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+  4 |     8 | abcd     | abcdef       | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+  5 |     8 | abc      | abcdef       | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+  6 |     8 | abc      | abcdef       | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+  7 |     8 | abc      | abcdefghijkl | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+  8 |     8 | abc      | abcdefghijkl | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+  9 |     8 | abc      | abcdefghijkl | 12-28-2009 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+ 10 |     8 | abc      | abcdefghijkl | 12-28-2009 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
+ 11 |     8 | abc      | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,abcd,the,real,world}
+ 12 |     8 | abc      | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,abcd,the,real,world}
+ 13 |       | abc      | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,a,fantasy}
+ 14 |       | abc      | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,a,fantasy}
+ 15 |       |          |              |            |                          |
+ 16 |       |          |              |            |                          |
+(16 rows)
+
+SELECT comp();
+   comp
+-----------
+ Unchanged
+(1 row)
+
+DROP TABLE T;
+DROP FUNCTION foo(INT);
+-- 2.2 Limits
+-- 2.2.1 Fall back to full rewrite for volatile expressions
+CREATE TABLE T(pk INT NOT NULL PRIMARY KEY);
+INSERT INTO T VALUES (1);
+SELECT set('t');
+ set
+-----
+
+(1 row)
+
+-- now() is stable, because it returns the transaction timestamp
+ALTER TABLE T ADD COLUMN c1 TIMESTAMP DEFAULT now();
+SELECT comp();
+   comp
+-----------
+ Unchanged
+(1 row)
+
+-- clock_timestamp() is volatile
+ALTER TABLE T ADD COLUMN c2 TIMESTAMP DEFAULT clock_timestamp();
+SELECT comp();
+   comp
+-----------
+ Rewritten
+(1 row)
+
+DROP TABLE T;
+-- 3. Drive simple queries DML
+CREATE TABLE T (pk INT NOT NULL PRIMARY KEY);
+SELECT set('t');
+ set
+-----
+
+(1 row)
+
+INSERT INTO T SELECT * FROM generate_series(1, 10) a;
+ALTER TABLE T ADD COLUMN c_bigint BIGINT NOT NULL DEFAULT -1;
+INSERT INTO T SELECT b, b - 10 FROM generate_series(11, 20) a(b);
+ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'hello';
+INSERT INTO T SELECT b, b - 10, (b + 10)::text FROM generate_series(21, 30) a(b);
+-- 3.a A WHERE clause
+SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1;
+ c_bigint | c_text
+----------+--------
+       -1 | hello
+(1 row)
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1;
+                  QUERY PLAN
+----------------------------------------------
+ Limit
+   Output: c_bigint, c_text
+   ->  Seq Scan on fast_default.t
+         Output: c_bigint, c_text
+         Filter: (t.c_bigint = '-1'::integer)
+(5 rows)
+
+SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1;
+ c_bigint | c_text
+----------+--------
+       -1 | hello
+(1 row)
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1;
+                 QUERY PLAN
+--------------------------------------------
+ Limit
+   Output: c_bigint, c_text
+   ->  Seq Scan on fast_default.t
+         Output: c_bigint, c_text
+         Filter: (t.c_text = 'hello'::text)
+(5 rows)
+
+-- 3.b COALESCE
+SELECT COALESCE(c_bigint, pk), COALESCE(c_text, pk::text) FROM T ORDER BY pk LIMIT 10;
+ coalesce | coalesce
+----------+----------
+       -1 | hello
+       -1 | hello
+       -1 | hello
+       -1 | hello
+       -1 | hello
+       -1 | hello
+       -1 | hello
+       -1 | hello
+       -1 | hello
+       -1 | hello
+(10 rows)
+
+-- 3.c Aggregate function
+SELECT SUM(c_bigint), MAX(c_text), MIN(c_text) FROM T;
+ sum |  max  | min
+-----+-------+-----
+ 201 | hello | 31
+(1 row)
+
+-- 3.d ORDER BY
+SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10;
+ pk | c_bigint | c_text
+----+----------+--------
+  1 |       -1 | hello
+  2 |       -1 | hello
+  3 |       -1 | hello
+  4 |       -1 | hello
+  5 |       -1 | hello
+  6 |       -1 | hello
+  7 |       -1 | hello
+  8 |       -1 | hello
+  9 |       -1 | hello
+ 10 |       -1 | hello
+(10 rows)
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10;
+                  QUERY PLAN
+----------------------------------------------
+ Limit
+   Output: pk, c_bigint, c_text
+   ->  Sort
+         Output: pk, c_bigint, c_text
+         Sort Key: t.c_bigint, t.c_text, t.pk
+         ->  Seq Scan on fast_default.t
+               Output: pk, c_bigint, c_text
+(7 rows)
+
+SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10;
+ pk | c_bigint | c_text
+----+----------+--------
+ 11 |        1 | hello
+ 12 |        2 | hello
+ 13 |        3 | hello
+ 14 |        4 | hello
+ 15 |        5 | hello
+ 16 |        6 | hello
+ 17 |        7 | hello
+ 18 |        8 | hello
+ 19 |        9 | hello
+ 20 |       10 | hello
+(10 rows)
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10;
+                     QUERY PLAN
+----------------------------------------------------
+ Limit
+   Output: pk, c_bigint, c_text
+   ->  Sort
+         Output: pk, c_bigint, c_text
+         Sort Key: t.c_bigint, t.c_text, t.pk
+         ->  Seq Scan on fast_default.t
+               Output: pk, c_bigint, c_text
+               Filter: (t.c_bigint > '-1'::integer)
+(8 rows)
+
+-- 3.y DELETE with RETURNING
+DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *;
+ pk | c_bigint | c_text
+----+----------+--------
+ 10 |       -1 | hello
+ 11 |        1 | hello
+ 12 |        2 | hello
+ 13 |        3 | hello
+ 14 |        4 | hello
+ 15 |        5 | hello
+ 16 |        6 | hello
+ 17 |        7 | hello
+ 18 |        8 | hello
+ 19 |        9 | hello
+ 20 |       10 | hello
+(11 rows)
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE) DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *;
+                        QUERY PLAN
+-----------------------------------------------------------
+ Delete on fast_default.t
+   Output: pk, c_bigint, c_text
+   ->  Bitmap Heap Scan on fast_default.t
+         Output: ctid
+         Recheck Cond: ((t.pk >= 10) AND (t.pk <= 20))
+         ->  Bitmap Index Scan on t_pkey
+               Index Cond: ((t.pk >= 10) AND (t.pk <= 20))
+(7 rows)
+
+-- 3.z UPDATE
+UPDATE T SET c_text = '"' || c_text || '"'  WHERE pk < 10;
+SELECT * FROM T WHERE c_text LIKE '"%"' ORDER BY PK;
+ pk | c_bigint | c_text
+----+----------+---------
+  1 |       -1 | "hello"
+  2 |       -1 | "hello"
+  3 |       -1 | "hello"
+  4 |       -1 | "hello"
+  5 |       -1 | "hello"
+  6 |       -1 | "hello"
+  7 |       -1 | "hello"
+  8 |       -1 | "hello"
+  9 |       -1 | "hello"
+(9 rows)
+
+SELECT comp();
+   comp
+-----------
+ Unchanged
+(1 row)
+
+DROP TABLE T;
+-- 4. Combine with other DDL
+CREATE TABLE T(pk INT NOT NULL PRIMARY KEY);
+SELECT set('t');
+ set
+-----
+
+(1 row)
+
+INSERT INTO T VALUES (1), (2);
+ALTER TABLE T ADD COLUMN c_int INT NOT NULL DEFAULT -1;
+INSERT INTO T VALUES (3), (4);
+ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'Hello';
+INSERT INTO T VALUES (5), (6);
+ALTER TABLE T ALTER COLUMN c_text SET DEFAULT 'world',
+              ALTER COLUMN c_int  SET DEFAULT 1;
+INSERT INTO T VALUES (7), (8);
+SELECT * FROM T ORDER BY pk;
+ pk | c_int | c_text
+----+-------+--------
+  1 |    -1 | Hello
+  2 |    -1 | Hello
+  3 |    -1 | Hello
+  4 |    -1 | Hello
+  5 |    -1 | Hello
+  6 |    -1 | Hello
+  7 |     1 | world
+  8 |     1 | world
+(8 rows)
+
+-- Add an index
+CREATE INDEX i ON T(c_int, c_text);
+SELECT c_text FROM T WHERE c_int = -1;
+ c_text
+--------
+ Hello
+ Hello
+ Hello
+ Hello
+ Hello
+ Hello
+(6 rows)
+
+SELECT comp();
+   comp
+-----------
+ Unchanged
+(1 row)
+
+DROP TABLE T;
+DROP FUNCTION set(name);
+DROP FUNCTION comp();
+DROP TABLE m;
+DROP SCHEMA fast_default;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index 8641769..1bcce97 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -106,7 +106,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo
 # NB: temp.sql does a reconnect which transiently uses 2 connections,
 # so keep this parallel group to at most 19 tests
 # ----------
-test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml
+test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml fast_default

 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 835cf35..a080890 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -169,3 +169,4 @@ test: with
 test: xml
 test: event_trigger
 test: stats
+test: fast_default
diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql
new file mode 100644
index 0000000..cc73055
--- /dev/null
+++ b/src/test/regress/sql/fast_default.sql
@@ -0,0 +1,283 @@
+--
+-- ALTER TABLE ADD COLUMN DEFAULT test
+--
+
+SET search_path = fast_default;
+CREATE SCHEMA fast_default;
+CREATE TABLE m(id OID);
+INSERT INTO m VALUES (NULL::OID);
+
+CREATE FUNCTION set(tabname name) RETURNS VOID
+AS $$
+BEGIN
+  UPDATE m SET id = (SELECT c.relfilenode FROM pg_class AS c, pg_namespace AS s
+           WHERE c.relname = tabname AND c.relnamespace = s.oid AND s.nspname = 'fast_default');
+END;
+$$ LANGUAGE 'plpgsql';
+
+CREATE FUNCTION comp() RETURNS TEXT
+AS $$
+BEGIN
+  RETURN (SELECT CASE WHEN m.id = c.relfilenode THEN 'Unchanged' ELSE 'Rewritten' END
+            FROM m, pg_class AS c, pg_namespace AS s
+           WHERE c.relname = 't' AND c.relnamespace = s.oid AND s.nspname = 'fast_default');
+END;
+$$ LANGUAGE 'plpgsql';
+
+-- 1. Test a large sample of dfferent datatypes
+CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1);
+
+SELECT set('t');
+
+INSERT INTO T VALUES (1), (2);
+
+ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT 'hello',
+              ALTER COLUMN c_int SET DEFAULT 2;
+
+INSERT INTO T VALUES (3), (4);
+
+
+ALTER TABLE T ADD COLUMN c_text TEXT  DEFAULT 'world',
+              ALTER COLUMN c_bpchar SET DEFAULT 'dog';
+
+INSERT INTO T VALUES (5), (6);
+
+ALTER TABLE T ADD COLUMN c_date DATE DEFAULT '2016-06-02',
+              ALTER COLUMN c_text SET DEFAULT 'cat';
+
+INSERT INTO T VALUES (7), (8);
+
+ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP DEFAULT '2016-09-01 12:00:00',
+              ADD COLUMN c_timestamp_null TIMESTAMP,
+              ALTER COLUMN c_date SET DEFAULT '2010-01-01';
+
+INSERT INTO T VALUES (9), (10);
+
+ALTER TABLE T ADD COLUMN c_array TEXT[] DEFAULT '{"This", "is", "the", "real", "world"}',
+              ALTER COLUMN c_timestamp SET DEFAULT '1970-12-31 11:12:13',
+              ALTER COLUMN c_timestamp_null SET DEFAULT '2016-09-29 12:00:00';
+
+INSERT INTO T VALUES (11), (12);
+
+ALTER TABLE T ADD COLUMN c_small SMALLINT DEFAULT -5,
+              ADD COLUMN c_small_null SMALLINT,
+              ALTER COLUMN c_array SET DEFAULT '{"This", "is", "no", "fantasy"}';
+
+INSERT INTO T VALUES (13), (14);
+
+ALTER TABLE T ADD COLUMN c_big BIGINT DEFAULT 180000000000018,
+              ALTER COLUMN c_small SET DEFAULT 9,
+              ALTER COLUMN c_small_null SET DEFAULT 13;
+
+INSERT INTO T VALUES (15), (16);
+
+ALTER TABLE T ADD COLUMN c_num NUMERIC DEFAULT 1.00000000001,
+              ALTER COLUMN c_big SET DEFAULT -9999999999999999;
+
+INSERT INTO T VALUES (17), (18);
+
+ALTER TABLE T ADD COLUMN c_time TIME DEFAULT '12:00:00',
+              ALTER COLUMN c_num SET DEFAULT 2.000000000000002;
+
+INSERT INTO T VALUES (19), (20);
+
+ALTER TABLE T ADD COLUMN c_interval INTERVAL DEFAULT '1 day',
+              ALTER COLUMN c_time SET DEFAULT '23:59:59';
+
+INSERT INTO T VALUES (21), (22);
+
+ALTER TABLE T ALTER COLUMN c_time DROP DEFAULT,
+              ALTER COLUMN c_interval SET DEFAULT '3 hours';
+
+INSERT INTO T VALUES (23), (24);
+
+ALTER TABLE T ALTER COLUMN c_bpchar    DROP DEFAULT,
+              ALTER COLUMN c_date      DROP DEFAULT,
+              ALTER COLUMN c_text      DROP DEFAULT,
+              ALTER COLUMN c_timestamp DROP DEFAULT,
+              ALTER COLUMN c_array     DROP DEFAULT,
+              ALTER COLUMN c_small     DROP DEFAULT,
+              ALTER COLUMN c_big       DROP DEFAULT,
+              ALTER COLUMN c_num       DROP DEFAULT,
+              ALTER COLUMN c_interval  DROP DEFAULT;
+
+INSERT INTO T VALUES (25), (26);
+
+SELECT * FROM T ORDER BY pk;
+
+SELECT comp();
+
+DROP TABLE T;
+
+-- 2.1 Test expressions in the defaults
+CREATE OR REPLACE FUNCTION foo(a INT) RETURNS TEXT AS $$
+DECLARE res TEXT := '';
+        i INT;
+BEGIN
+  i := 0;
+  WHILE (i < a) LOOP
+    res := res || chr(ascii('a') + i);
+    i := i + 1;
+  END LOOP;
+  RETURN res;
+END; $$ LANGUAGE PLPGSQL STABLE;
+
+CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT LENGTH(foo(6)));
+
+SELECT set('t');
+
+INSERT INTO T VALUES (1), (2);
+
+ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT foo(4),
+              ALTER COLUMN c_int SET DEFAULT LENGTH(foo(8));
+
+INSERT INTO T VALUES (3), (4);
+
+ALTER TABLE T ADD COLUMN c_text TEXT  DEFAULT foo(6),
+              ALTER COLUMN c_bpchar SET DEFAULT foo(3);
+
+INSERT INTO T VALUES (5), (6);
+
+ALTER TABLE T ADD COLUMN c_date DATE DEFAULT '2016-06-02'::DATE  + LENGTH(foo(10)),
+              ALTER COLUMN c_text SET DEFAULT foo(12);
+
+INSERT INTO T VALUES (7), (8);
+
+ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP DEFAULT '2016-09-01'::DATE + LENGTH(foo(10)),
+              ALTER COLUMN c_date SET DEFAULT '2010-01-01'::DATE - LENGTH(foo(4));
+
+INSERT INTO T VALUES (9), (10);
+
+ALTER TABLE T ADD COLUMN c_array TEXT[] DEFAULT ('{"This", "is", "' || foo(4) || '", "the", "real", "world"}')::TEXT[],
+              ALTER COLUMN c_timestamp SET DEFAULT '1970-12-31'::DATE + LENGTH(foo(30));
+
+INSERT INTO T VALUES (11), (12);
+
+ALTER TABLE T ALTER COLUMN c_int DROP DEFAULT,
+              ALTER COLUMN c_array SET DEFAULT ('{"This", "is", "' || foo(1) || '", "fantasy"}')::text[];
+
+INSERT INTO T VALUES (13), (14);
+
+ALTER TABLE T ALTER COLUMN c_bpchar    DROP DEFAULT,
+              ALTER COLUMN c_date      DROP DEFAULT,
+              ALTER COLUMN c_text      DROP DEFAULT,
+              ALTER COLUMN c_timestamp DROP DEFAULT,
+              ALTER COLUMN c_array     DROP DEFAULT;
+
+INSERT INTO T VALUES (15), (16);
+
+SELECT * FROM T;
+
+SELECT comp();
+
+DROP TABLE T;
+
+DROP FUNCTION foo(INT);
+
+-- 2.2 Limits
+-- 2.2.1 Fall back to full rewrite for volatile expressions
+CREATE TABLE T(pk INT NOT NULL PRIMARY KEY);
+
+INSERT INTO T VALUES (1);
+
+SELECT set('t');
+
+-- now() is stable, because it returns the transaction timestamp
+ALTER TABLE T ADD COLUMN c1 TIMESTAMP DEFAULT now();
+
+SELECT comp();
+
+-- clock_timestamp() is volatile
+ALTER TABLE T ADD COLUMN c2 TIMESTAMP DEFAULT clock_timestamp();
+
+SELECT comp();
+
+DROP TABLE T;
+
+-- 3. Drive simple queries DML
+CREATE TABLE T (pk INT NOT NULL PRIMARY KEY);
+
+SELECT set('t');
+
+INSERT INTO T SELECT * FROM generate_series(1, 10) a;
+
+ALTER TABLE T ADD COLUMN c_bigint BIGINT NOT NULL DEFAULT -1;
+
+INSERT INTO T SELECT b, b - 10 FROM generate_series(11, 20) a(b);
+
+ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'hello';
+
+INSERT INTO T SELECT b, b - 10, (b + 10)::text FROM generate_series(21, 30) a(b);
+
+-- 3.a A WHERE clause
+SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1;
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1;
+
+SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1;
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1;
+
+
+-- 3.b COALESCE
+SELECT COALESCE(c_bigint, pk), COALESCE(c_text, pk::text) FROM T ORDER BY pk LIMIT 10;
+
+-- 3.c Aggregate function
+SELECT SUM(c_bigint), MAX(c_text), MIN(c_text) FROM T;
+
+-- 3.d ORDER BY
+SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10;
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10;
+
+SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10;
+
+EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10;
+
+-- 3.y DELETE with RETURNING
+DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *;
+EXPLAIN (VERBOSE TRUE, COSTS FALSE) DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *;
+
+-- 3.z UPDATE
+UPDATE T SET c_text = '"' || c_text || '"'  WHERE pk < 10;
+SELECT * FROM T WHERE c_text LIKE '"%"' ORDER BY PK;
+
+SELECT comp();
+
+DROP TABLE T;
+
+
+-- 4. Combine with other DDL
+CREATE TABLE T(pk INT NOT NULL PRIMARY KEY);
+
+SELECT set('t');
+
+INSERT INTO T VALUES (1), (2);
+
+ALTER TABLE T ADD COLUMN c_int INT NOT NULL DEFAULT -1;
+
+INSERT INTO T VALUES (3), (4);
+
+ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'Hello';
+
+INSERT INTO T VALUES (5), (6);
+
+ALTER TABLE T ALTER COLUMN c_text SET DEFAULT 'world',
+              ALTER COLUMN c_int  SET DEFAULT 1;
+
+INSERT INTO T VALUES (7), (8);
+
+SELECT * FROM T ORDER BY pk;
+
+-- Add an index
+CREATE INDEX i ON T(c_int, c_text);
+
+SELECT c_text FROM T WHERE c_int = -1;
+
+SELECT comp();
+
+DROP TABLE T;
+DROP FUNCTION set(name);
+DROP FUNCTION comp();
+DROP TABLE m;
+DROP SCHEMA fast_default;
#2Serge Rielau
serge@rielau.com
In reply to: Serge Rielau (#1)
Re: Fast Default WIP patch for discussion

Hackers,

Posting to this group on a Friday evening was obviously a Bad Idea(tm). :-)

Let me clarify that I’m at this point not looking for any detailed review.
Rather I’m hoping to drive towards design decisions on the below.
So any opining would be much appreciated.

On Oct 21, 2016, at 4:15 PM, Serge Rielau <serge@rielau.com> wrote:
...

Some key design points requiring discussion:
1. Storage of the “exist” (working name) default
Right now the patch stores the default value in its binary form as it would be in the tuple into a BYTEA.
It would be feasible to store the pretty printed literal, however this requires calling the io functions when a
tuple descriptor is built.
2. The exist default is cached alongside the “current” default in the tuple descriptor’s constraint structure.
Seems most natural too me, but debatable.
3. Delayed vs. early expansion of the tuples.
To avoid having to decide when to copy tuple descriptors with or without constraints and defaults
I have opted to expand the tuple at the core entry points.
How do I know I have them all? An omission means wrong results!
4. attisnull()
This routine is used in many places, but to give correct result sit must now be accompanied
by the tuple descriptor. This becomes moderately messy and it’s not always clear where to get that.
Interestingly most usages are related to catalog lookups.
Assuming we have no intention to support fast default for catalog tables we could keep using the
existing attisnull() api for catalog lookups and use a new version (name tbd) for user tables.
5. My head hurts looking at the PK/FK code - it’s not always clear which tuple descriptor belongs
to which tuple
6. Performance of the expansion code.
The current code needs to walk all defaults and then start padding by filling in values.
But the outcome is always the same. We will produce the same payload and the name null map.
It would be feasible to cache an “all defaults tuple”, remember the offsets (and VARWIDTH, HASNULL)
for each attribute and then simply splice the short and default tuples together.
This ought to be faster, but the meta data to cache is not insignificant and the expansion code is messy enough
without this already.
7. Grooming
Obviously we can remove all exist defaults for a table from pg_attribute whenever the table is rewrittem.
That’s easy.
But could we/should we keep track of the short tuples and either eliminate them or drop exist defaults once they
become obsolete because there is no tuple short enough for them to matter.
8. Do we need to worry about toasted defaults?

Thanks
Serge Rielau
salesforce.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#3Euler Taveira
euler@timbira.com.br
In reply to: Serge Rielau (#2)
Re: Fast Default WIP patch for discussion

On 26-10-2016 12:43, Serge Rielau wrote:

Posting to this group on a Friday evening was obviously a Bad Idea(tm). :-)

Serge, add your patch to the next commitfest [1]https://commitfest.postgresql.org/11/ so we don't forget to
review it.

[1]: https://commitfest.postgresql.org/11/

--
Euler Taveira Timbira - http://www.timbira.com.br/
PostgreSQL: Consultoria, Desenvolvimento, Suporte 24x7 e Treinamento

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#4Serge Rielau
serge@rielau.com
In reply to: Euler Taveira (#3)
Re: Fast Default WIP patch for discussion

Euler,

Thanks, I was previously told I should post a WIP patch here.
There are too many open issues to be near committing.
Anyway, I have created a patch.
https://commitfest.postgresql.org/11/843/#
Since this is my first time I do have a couple of questions:
There are entries for a git and a wiki link.
Should I push the patch to some branch, if so which repository?

Thanks
Serge

The form does ask for a git link rather

On Oct 26, 2016, at 8:51 AM, Euler Taveira <euler@timbira.com.br> wrote:

On 26-10-2016 12:43, Serge Rielau wrote:

Posting to this group on a Friday evening was obviously a Bad Idea(tm). :-)

Serge, add your patch to the next commitfest [1] so we don't forget to
review it.

[1] https://commitfest.postgresql.org/11/

--
Euler Taveira Timbira - http://www.timbira.com.br/
PostgreSQL: Consultoria, Desenvolvimento, Suporte 24x7 e Treinamento

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#5Petr Jelinek
petr@2ndquadrant.com
In reply to: Serge Rielau (#4)
Re: Fast Default WIP patch for discussion

Hi,

On 26/10/16 18:22, Serge Rielau wrote:

Euler,

Thanks, I was previously told I should post a WIP patch here.
There are too many open issues to be near committing.
Anyway, I have created a patch.
https://commitfest.postgresql.org/11/843/#

Adding to commitfest is act of asking for review, it does not mean it's
committable.

Since this is my first time I do have a couple of questions:
There are entries for a git and a wiki link.
Should I push the patch to some branch, if so which repository?

That's optional, mostly useful for bigger (or complex) patches or
patches where there is code cooperation between multiple people. Wiki
page for patch sometimes contains basically what your first email said.

BTW it's customary to bottom post, not top post on this list.

--
Petr Jelinek http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Training & Services

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#6Robert Haas
robertmhaas@gmail.com
In reply to: Serge Rielau (#2)
Re: Fast Default WIP patch for discussion

On Wed, Oct 26, 2016 at 11:43 AM, Serge Rielau <serge@rielau.com> wrote:

Posting to this group on a Friday evening was obviously a Bad Idea(tm). :-)

Not really. It's just that complex patches often don't get an
immediate response because people are too busy to look at them and
think about them in detail. If you've added it to the CommitFest, it
will eventually get some attention.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#7Robert Haas
robertmhaas@gmail.com
In reply to: Serge Rielau (#1)
Re: Fast Default WIP patch for discussion

On Fri, Oct 21, 2016 at 7:15 PM, Serge Rielau <serge@rielau.com> wrote:

Some key design points requiring discussion:
1. Storage of the “exist” (working name) default
Right now the patch stores the default value in its binary form as it
would be in the tuple into a BYTEA.
It would be feasible to store the pretty printed literal, however this
requires calling the io functions when a
tuple descriptor is built.

A pretty-printed literal is a terrible idea because input functions
need not be immutable (e.g. timestamptz). I would have expected a
pg_node_tree column containing a CONST node, but your bytea thing
might be OK.

2. The exist default is cached alongside the “current” default in the tuple
descriptor’s constraint structure.
Seems most natural too me, but debatable.

No opinion (yet).

3. Delayed vs. early expansion of the tuples.
To avoid having to decide when to copy tuple descriptors with or without
constraints and defaults
I have opted to expand the tuple at the core entry points.
How do I know I have them all? An omission means wrong results!

Early expansion seems right. "How do I know I have them all?" - and
not only all of the current ones but all future ones - seems like a
core sticking point for this patch.

4. attisnull()
This routine is used in many places, but to give correct result sit must
now be accompanied
by the tuple descriptor. This becomes moderately messy and it’s not
always clear where to get that.
Interestingly most usages are related to catalog lookups.
Assuming we have no intention to support fast default for catalog tables
we could keep using the
existing attisnull() api for catalog lookups and use a new version (name
tbd) for user tables.

attisnull is not a thing. There's heap_attisnull and slot_attisnull.

5. My head hurts looking at the PK/FK code - it’s not always clear which
tuple descriptor belongs
to which tuple

I suggest ibuprofen and a stiff upper lip.

6. Performance of the expansion code.
The current code needs to walk all defaults and then start padding by
filling in values.
But the outcome is always the same. We will produce the same payload and
the name null map.
It would be feasible to cache an “all defaults tuple”, remember the
offsets (and VARWIDTH, HASNULL)
for each attribute and then simply splice the short and default tuples
together.
This ought to be faster, but the meta data to cache is not insignificant
and the expansion code is messy enough
without this already.

You could experiment to figure out if it makes any difference.

7. Grooming
Obviously we can remove all exist defaults for a table from pg_attribute
whenever the table is rewrittem.
That’s easy.

But might cause the table to expand a lot, which would suck.

But could we/should we keep track of the short tuples and either
eliminate them or drop exist defaults once they
become obsolete because there is no tuple short enough for them to
matter.

I wouldn't mess with it.

8. Do we need to worry about toasted defaults?

Presumably.

--
Robert Haas
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#8Serge Rielau
serge@rielau.com
In reply to: Robert Haas (#7)
Re: Fast Default WIP patch for discussion

On Oct 28, 2016, at 5:46 AM, Robert Haas <robertmhaas@gmail.com> wrote:

On Fri, Oct 21, 2016 at 7:15 PM, Serge Rielau <serge@rielau.com> wrote:

Some key design points requiring discussion:
1. Storage of the “exist” (working name) default
Right now the patch stores the default value in its binary form as it
would be in the tuple into a BYTEA.
It would be feasible to store the pretty printed literal, however this
requires calling the io functions when a
tuple descriptor is built.

A pretty-printed literal is a terrible idea because input functions
need not be immutable (e.g. timestamptz).

I did not know that! Interesting.

I would have expected a
pg_node_tree column containing a CONST node, but your bytea thing
might be OK.

I was debating following the example given by the current DEFAULT.
But figured it’s overkill given that no-one user will ever have to look at it.
And in the end a the pretty printed CONST node is no more readable.

3. Delayed vs. early expansion of the tuples.
To avoid having to decide when to copy tuple descriptors with or without
constraints and defaults
I have opted to expand the tuple at the core entry points.
How do I know I have them all? An omission means wrong results!

Early expansion seems right. "How do I know I have them all?" - and
not only all of the current ones but all future ones - seems like a
core sticking point for this patch.

Yes, there is a certain amount of inherent, hard to control, risk towards the future that we must be willing to accept.

4. attisnull()
This routine is used in many places, but to give correct result sit must
now be accompanied
by the tuple descriptor. This becomes moderately messy and it’s not
always clear where to get that.
Interestingly most usages are related to catalog lookups.
Assuming we have no intention to support fast default for catalog tables
we could keep using the
existing attisnull() api for catalog lookups and use a new version (name
tbd) for user tables.

attisnull is not a thing. There's heap_attisnull and slot_attisnull.

My apologies.
Obviously slot_attisnull() is a non issue since slots have tuple descriptors.
It’s heap_attisnull() I am struggling with. It’s rather popular.
Yet, in the large majority of cases exist defaults do not apply, so digging up the descriptor
is rather wasteful.

5. My head hurts looking at the PK/FK code - it’s not always clear which
tuple descriptor belongs
to which tuple

I suggest ibuprofen and a stiff upper lip.

Sound advise.

6. Performance of the expansion code.
The current code needs to walk all defaults and then start padding by
filling in values.
But the outcome is always the same. We will produce the same payload and
the name null map.
It would be feasible to cache an “all defaults tuple”, remember the
offsets (and VARWIDTH, HASNULL)
for each attribute and then simply splice the short and default tuples
together.
This ought to be faster, but the meta data to cache is not insignificant
and the expansion code is messy enough
without this already.

You could experiment to figure out if it makes any difference.

I think my first experiment will be to measure the performance impact of “expressed” vs. "non expressed" defaults
with the current design.
If that is considered excessive I will explore the more complex alternative.

7. Grooming
Obviously we can remove all exist defaults for a table from pg_attribute
whenever the table is rewrittem.
That’s easy.

But might cause the table to expand a lot, which would suck.

Well, a this point I must point back to the original goal which is O(1) ADD COLUMN performance.
The footprint of a rewritten table is no worse after this patch.
The reduced footprint of a table due to a rewrite avoided on ADD COLUMN is boon one must not rely upon.
The existing tuple layout would support an enhancement where trailing defaults may be avoided.
But I believe we would be better served with a more general approach to table compresion.

But could we/should we keep track of the short tuples and either
eliminate them or drop exist defaults once they
become obsolete because there is no tuple short enough for them to
matter.

I wouldn't mess with it.

*phew*

8. Do we need to worry about toasted defaults?

Presumably.

Time for me to dig into that then.

Cheers
Serge RIelau
salesforce.com

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#9Haribabu Kommi
kommi.haribabu@gmail.com
In reply to: Serge Rielau (#8)
Re: Fast Default WIP patch for discussion

On Sat, Oct 29, 2016 at 2:28 AM, Serge Rielau <serge@rielau.com> wrote:

Time for me to dig into that then.

Closed in 2016-11 commitfest with "returned with feedback" status.
Please feel free to update the status once you submit the updated patch.

Regards,
Hari Babu
Fujitsu Australia

#10Andres Freund
andres@anarazel.de
In reply to: Serge Rielau (#8)
Re: Fast Default WIP patch for discussion

Hi Serge,

On 2016-10-28 08:28:11 -0700, Serge Rielau wrote:

Time for me to dig into that then.

Are you planning to update your POC at some point? This'd be a very
welcome improvement.

Regards,

Andres

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

#11Serge Rielau
serge@rielau.com
In reply to: Andres Freund (#10)
Re: Fast Default WIP patch for discussion

Andres,
Yes, I still want to push this in. However I have not had time to get back to it. I’m embarrassed to say that I don’t even know where the comments that were issued occurred.
Cheers Serge

via Newton Mail [https://cloudmagic.com/k/d/mailapp?ct=dx&amp;cv=9.4.52&amp;pv=10.11.6&amp;source=email_footer_2]
On Wed, Apr 5, 2017 at 4:47 PM, Andres Freund <andres@anarazel.de> wrote:
Hi Serge,

On 2016-10-28 08:28:11 -0700, Serge Rielau wrote:

Time for me to dig into that then.

Are you planning to update your POC at some point? This'd be a very
welcome improvement.

Regards,

Andres

#12Andres Freund
andres@anarazel.de
In reply to: Serge Rielau (#11)
Re: Fast Default WIP patch for discussion

Hi,

On 2017-04-05 22:31:15 -0700, Serge Rielau wrote:

Andres,
Yes, I still want to push this in. However I have not had time to get back to it. I’m embarrassed to say that I don’t even know where the comments that were issued occurred.
Cheers Serge

You mean
/messages/by-id/CA+Tgmoa9hm1e+XB5Tc-ANyyZVc7CHFsA4_dZ_MdSJVpSL643Eg@mail.gmail.com
?

Andres

--
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers