Fast Default WIP patch for discussion
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/>
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;
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
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
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
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
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
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
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 tupleI 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
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
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
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&cv=9.4.52&pv=10.11.6&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
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