On 12/31/2017 06:48 PM, Andrew Dunstan wrote:
>
> Here is a new version of the patch. It separates the missing values
> constructs and logic from the default values constructs and logic. The
> missing values now sit alongside the default values in tupleConstr. In
> some places the separation makes the logic a good bit cleaner.
>
> Some of the logic is also reworked to remove an assumption that the
> missing values structure is populated in attnum order, Also code to
> support the missing stuctures is added to equalTupleDescs.
>
> We still have that one extra place where we need to call
> CreateTupleDescCopyConstr instead of CreateTupleDescCopy. I haven't seen
> an easy way around that.
>
New version of the patch that fills in the remaining piece in
equalTupleDescs.
cheers
andrew
--
Andrew Dunstan https://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 3f02202..d5dc14a 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1150,6 +1150,18 @@
</row>
<row>
+ <entry><structfield>atthasmissing</structfield></entry>
+ <entry><type>bool</type></entry>
+ <entry></entry>
+ <entry>
+ This column has a value which is used where the column is entirely
+ missing from the row, as happens when a column is added after the
+ row is created. The actual value used is stored in the
+ <structname>attmissingval</structname> column.
+ </entry>
+ </row>
+
+ <row>
<entry><structfield>attidentity</structfield></entry>
<entry><type>char</type></entry>
<entry></entry>
@@ -1229,6 +1241,18 @@
</entry>
</row>
+ <row>
+ <entry><structfield>attmissingval</structfield></entry>
+ <entry><type>bytea</type></entry>
+ <entry></entry>
+ <entry>
+ This column has a binary representation of the value used when the column
+ is entirely missing from the row, as happens when the column is added after
+ the row is created. The value is only used when
+ <structname>atthasmissing</structname> is true.
+ </entry>
+ </row>
+
</tbody>
</tgroup>
</table>
diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index 7bcf242..780bead 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -1115,26 +1115,28 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable>
</para>
<para>
- When a column is added with <literal>ADD COLUMN</literal>, all existing
- rows in the table are initialized with the column's default value
- (NULL if no <literal>DEFAULT</literal> clause is specified).
- If there is no <literal>DEFAULT</literal> clause, this is merely a metadata
- change and does not require any immediate update of the table's data;
- the added NULL values are supplied on readout, instead.
+ When a column is added with <literal>ADD COLUMN</literal> and a
+ non-volatile <literal>DEFAULT</literal> is specified, the default
+ is evaluated at the time of the statement and the result stored in the
+ table's metadata. That value will be used for the column for
+ all existing rows. If no <literal>DEFAULT</literal> is specified
+ NULL is used. In neither case is a rewrite of the table required.
</para>
<para>
- Adding a column with a <literal>DEFAULT</literal> clause or changing the type of
- an existing column will require the entire table and its indexes to be
- rewritten. As an exception when changing the type of an existing column,
- if the <literal>USING</literal> clause does not change the column
- contents and the old type is either binary coercible to the new type or
- an unconstrained domain over the new type, a table rewrite is not needed;
- but any indexes on the affected columns must still be rebuilt. Adding or
- removing a system <literal>oid</literal> column also requires rewriting the entire
- table. Table and/or index rebuilds may take a significant amount of time
- for a large table; and will temporarily require as much as double the disk
- space.
+ Adding a column with a volatile <literal>DEFAULT</literal> or
+ changing the type of
+ an existing column will require the entire table and its indexes to be
+ rewritten. As an exception when changing the type of an existing column,
+ if the <literal>USING</literal> clause does not change the column
+ contents and the old type is either binary coercible to the new type or
+ an unconstrained domain over the new type, a table rewrite is not needed;
+ but any indexes on the affected columns must still be rebuilt. Adding or
+ removing a system <literal>oid</literal> column also requires rewriting
+ the entire table.
+ Table and/or index rebuilds may take a significant amount of time
+ for a large table; and will temporarily require as much as double the disk
+ space.
</para>
<para>
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c
index a1a9d99..6aab16f 100644
--- a/src/backend/access/common/heaptuple.c
+++ b/src/backend/access/common/heaptuple.c
@@ -76,6 +76,116 @@
* ----------------------------------------------------------------
*/
+/*
+ * Return the missing value of an attribute, or NULL if it
+ * there is no missing value.
+ */
+static Datum
+getmissingattr(TupleDesc tupleDesc,
+ int attnum, bool *isnull)
+{
+ int missingnum;
+ Form_pg_attribute att;
+ AttrMissing *attrmiss;
+
+ Assert(attnum <= tupleDesc->natts);
+ Assert(attnum > 0);
+
+ att = TupleDescAttr(tupleDesc, attnum - 1);
+
+ if (att->atthasmissing)
+ {
+ Assert(tupleDesc->constr);
+ Assert(tupleDesc->constr->num_missing > 0);
+
+ attrmiss = tupleDesc->constr->missing;
+
+ /*
+ * Look through the tupledesc's attribute missing values
+ * for the one that corresponds to this attribute.
+ */
+ for (missingnum = tupleDesc->constr->num_missing - 1;
+ missingnum >= 0; missingnum--)
+ {
+ if (attrmiss[missingnum].amnum == attnum)
+ {
+ if (attrmiss[missingnum].ammissingNull)
+ {
+ *isnull = true;
+ return (Datum) 0;
+ }
+ else
+ {
+ *isnull = false;
+ return attrmiss[missingnum].ammissing;
+ }
+ }
+ }
+ Assert(false); /* should not be able to get here */
+ }
+
+ *isnull = true;
+ return PointerGetDatum(NULL);
+}
+
+/*
+ * Fill in missing values for a TupleTableSlot
+ */
+static void
+slot_getmissingattrs(TupleTableSlot *slot, int startAttNum)
+{
+ int tdesc_natts = slot->tts_tupleDescriptor->natts;
+ AttrMissing *attrmiss;
+ int missingnum;
+ int missattnum;
+ int i;
+
+ if (slot->tts_tupleDescriptor->constr)
+ {
+ missingnum = slot->tts_tupleDescriptor->constr->num_missing -1;
+ attrmiss = slot->tts_tupleDescriptor->constr->missing;
+ }
+ else
+ {
+ missingnum = -1;
+ attrmiss = NULL;
+ }
+
+ for (missattnum = tdesc_natts - 1; missattnum >= startAttNum; missattnum--)
+ {
+ /*
+ * Loop through the list of missing attributes (if any) looking for
+ * a corresponding entry. Don't assume that the missing values
+ * are in attribute order.
+ */
+ for (i = missingnum; i >= 0; i--)
+ {
+ Assert(attrmiss != NULL);
+ if (attrmiss[i].amnum - 1 == missattnum)
+ {
+ if (attrmiss[i].ammissingNull)
+ {
+ slot->tts_values[missattnum] = PointerGetDatum(NULL);
+ slot->tts_isnull[missattnum] = true;
+ }
+ else
+ {
+ slot->tts_values[missattnum] = attrmiss[i].ammissing;
+ slot->tts_isnull[missattnum] = false;
+ }
+ break;
+ }
+ }
+ /*
+ * If there is no corresponding missing entry use NULL.
+ */
+ if (i < 0)
+ {
+ slot->tts_values[missattnum] = PointerGetDatum(NULL);
+ slot->tts_isnull[missattnum] = true;
+ }
+ }
+}
/*
* heap_compute_data_size
@@ -133,6 +243,131 @@ heap_compute_data_size(TupleDesc tupleDesc,
}
/*
+ * Fill in one attribute, either a data value or a bit in the null bitmask
+ */
+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
*
@@ -172,111 +407,15 @@ heap_fill_tuple(TupleDesc tupleDesc,
for (i = 0; i < numberOfAttributes; i++)
{
- Form_pg_attribute att = TupleDescAttr(tupleDesc, 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->attbyval)
- {
- /* pass-by-value */
- data = (char *) att_align_nominal(data, att->attalign);
- store_att_byval(data, values[i], att->attlen);
- data_length = att->attlen;
- }
- else if (att->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->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(values[i])) + 1;
- memcpy(data, DatumGetPointer(values[i]), 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(values[i]), data_length);
- }
-
- data += data_length;
+ Form_pg_attribute attr = TupleDescAttr(tupleDesc, i);
+
+ fill_val(attr,
+ bitP ? &bitP : NULL,
+ &bitmask,
+ &data,
+ infomask,
+ values ? values[i] : PointerGetDatum(NULL),
+ isnull ? isnull[i] : true);
}
Assert((data - start) == data_size);
@@ -293,10 +432,27 @@ 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 relations not expected to have
+ * missing values, such as catalog relations and indexes.
+ */
+ Assert(!tupleDesc || attnum <= tupleDesc->natts);
+
if (attnum > (int) HeapTupleHeaderGetNatts(tup->t_data))
- return true;
+ {
+ if (tupleDesc &&
+ attnum <= tupleDesc->natts &&
+ TupleDescAttr(tupleDesc, attnum - 1)->atthasmissing)
+ {
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
if (attnum > 0)
{
@@ -649,6 +805,293 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest)
memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len);
}
+/*
+ * Expand a tuple with missing values, or NULLs. The source tuple must have
+ * less attributes than the required number. Only one of targetHeapTuple
+ * and targetMinimalTuple may be supplied. The other argument must be NULL.
+ */
+static void
+expand_tuple(HeapTuple *targetHeapTuple,
+ MinimalTuple *targetMinimalTuple,
+ HeapTuple sourceTuple,
+ TupleDesc tupleDesc)
+{
+ AttrMissing *attrmiss = NULL;
+ int attnum;
+ int missingnum;
+ int firstmissingnum = 0;
+ int num_missing = 0;
+ bool hasNulls = HeapTupleHasNulls(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 = 0;
+ char *targetData;
+ uint16 *infoMask;
+
+ Assert((targetHeapTuple && !targetMinimalTuple)
+ || (!targetHeapTuple && targetMinimalTuple));
+
+ Assert(sourceNatts < natts);
+
+ sourceIndicatorLen = (hasNulls ? BITMAPLEN(sourceNatts) : 0);
+
+ targetDataLen = sourceDataLen;
+
+ if (tupleDesc->constr &&
+ tupleDesc->constr->num_missing)
+ {
+ /*
+ * If there are missing values we want to put them into the tuple.
+ * Before that we have to compute the extra length for the values
+ * array and the variable length data.
+ */
+ num_missing = tupleDesc->constr->num_missing;
+ attrmiss = tupleDesc->constr->missing;
+
+ /*
+ * Find the first item in attrmiss for which we don't have a value in
+ * the source (i.e. where its amnum is > sourceNatts). We can ignore
+ * all the missing entries before that.
+ */
+ for (firstmissingnum = 0;
+ firstmissingnum < num_missing
+ && attrmiss[firstmissingnum].amnum <= sourceNatts;
+ firstmissingnum++)
+ { /* empty */
+ }
+
+ /*
+ * If there are no more missing values everything else must be
+ * NULL
+ */
+ if (firstmissingnum >= num_missing)
+ {
+ hasNulls = true;
+ }
+
+ /*
+ * Now walk the missing attributes one by one. If there is a
+ * missing value make space for it. Otherwise, it's going to be NULL.
+ */
+ for (attnum = sourceNatts + 1;
+ attnum <= natts;
+ attnum++)
+ {
+ Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum - 1);
+
+ for (missingnum = firstmissingnum; missingnum < num_missing; missingnum++)
+ {
+
+ /*
+ * If there is a missing value for this attribute calculate
+ * the required space if it's not NULL.
+ */
+ if (attrmiss[missingnum].amnum == attnum)
+ {
+ if (attrmiss[missingnum].ammissingNull)
+ hasNulls = true;
+ else
+ {
+ targetDataLen = att_align_datum(targetDataLen,
+ att->attalign,
+ att->attlen,
+ attrmiss[missingnum].ammissing);
+
+ targetDataLen = att_addlength_pointer(targetDataLen,
+ att->attlen,
+ attrmiss[missingnum].ammissing);
+ }
+ break;
+ }
+ }
+ if (missingnum > num_missing)
+ {
+ /* there is no missing value for this attribute */
+ hasNulls = true;
+ }
+ }
+ } /* end if have missing values */
+ else
+ {
+ /*
+ * If there are no missing values at all then NULLS must be allowed,
+ * since some of the attributes are known to be absent.
+ */
+ 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 missing values there is nothing more to do */
+ if (firstmissingnum < num_missing)
+ {
+ targetData += sourceDataLen;
+ missingnum = firstmissingnum;
+
+ /* Now fill in the missing values */
+ for (attnum = sourceNatts + 1; attnum <= natts; attnum++)
+ {
+
+ Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum - 1);
+
+ for (missingnum = firstmissingnum; missingnum < num_missing; missingnum++)
+ {
+ if (attrmiss[missingnum].amnum == attnum)
+ {
+ fill_val(attr,
+ nullBits ? &nullBits : NULL,
+ &bitMask,
+ &targetData,
+ infoMask,
+ attrmiss[missingnum].ammissing,
+ attrmiss[missingnum].ammissingNull);
+ break;
+ }
+ }
+ if (missingnum > num_missing)
+ {
+ fill_val(attr,
+ &nullBits,
+ &bitMask,
+ &targetData,
+ infoMask,
+ (Datum) 0,
+ true);
+ }
+ } /* end loop over missing attributes */
+ } /* end if there is no missing value */
+}
+
+/*
+ * Fill in the missing values for an ordinary HeapTuple
+ */
+MinimalTuple
+minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc)
+{
+ MinimalTuple minimalTuple;
+
+ expand_tuple(NULL, &minimalTuple, sourceTuple, tupleDesc);
+ return minimalTuple;
+}
+
+/*
+ * Fill in the missing values for a minimal HeapTuple
+ */
+HeapTuple
+heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc)
+{
+ HeapTuple heapTuple;
+
+ expand_tuple(&heapTuple, NULL, sourceTuple, tupleDesc);
+ return heapTuple;
+}
+
/* ----------------
* heap_copy_tuple_as_datum
*
@@ -1192,8 +1635,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);
}
/*
@@ -1263,13 +1705,13 @@ slot_getallattrs(TupleTableSlot *slot)
/*
* If tuple doesn't have all the atts indicated by tupleDesc, read the
- * rest as null
+ * rest as NULLS or missing values.
*/
- for (; attnum < tdesc_natts; attnum++)
+ if (attnum < tdesc_natts)
{
- slot->tts_values[attnum] = (Datum) 0;
- slot->tts_isnull[attnum] = true;
+ slot_getmissingattrs(slot, attnum);
}
+
slot->tts_nvalid = tdesc_natts;
}
@@ -1310,12 +1752,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 NULLs or missing values
*/
- for (; attno < attnum; attno++)
+ if (attno < attnum)
{
- slot->tts_values[attno] = (Datum) 0;
- slot->tts_isnull[attno] = true;
+ slot_getmissingattrs(slot, attno);
}
slot->tts_nvalid = attnum;
}
@@ -1340,7 +1781,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);
}
/*
@@ -1363,7 +1804,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 9e37ca7..d604a1a 100644
--- a/src/backend/access/common/tupdesc.c
+++ b/src/backend/access/common/tupdesc.c
@@ -27,6 +27,7 @@
#include "parser/parse_type.h"
#include "utils/acl.h"
#include "utils/builtins.h"
+#include "utils/datum.h"
#include "utils/hashutils.h"
#include "utils/resowner_private.h"
#include "utils/syscache.h"
@@ -160,6 +161,24 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
}
}
+ if((cpy->num_missing = constr->num_missing) > 0)
+ {
+ cpy->missing = (AttrMissing *) palloc(cpy->num_missing * sizeof(AttrMissing));
+ memcpy(cpy->missing, constr->missing, cpy->num_missing * sizeof(AttrMissing));
+ for (i = cpy->num_missing - 1; i >= 0; i--)
+ {
+ if (constr->missing[i].ammissing)
+ {
+ int attnum = constr->missing[i].amnum - 1;
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum);
+
+ cpy->missing[i].ammissing = datumCopy(constr->missing[i].ammissing,
+ attr->attbyval,
+ attr->attlen);
+ }
+ }
+ }
+
if ((cpy->num_check = constr->num_check) > 0)
{
cpy->check = (ConstrCheck *) palloc(cpy->num_check * sizeof(ConstrCheck));
@@ -271,6 +290,20 @@ FreeTupleDesc(TupleDesc tupdesc)
}
pfree(attrdef);
}
+ if (tupdesc->constr->num_missing > 0)
+ {
+ AttrMissing *attrmiss = tupdesc->constr->missing;
+
+ Assert(attrmiss != NULL);
+
+ for (i = tupdesc->constr->num_missing - 1; i >= 0; i--)
+ {
+ if (attrmiss[i].ammissing
+ && !TupleDescAttr(tupdesc, attrmiss[i].amnum - 1)->attbyval)
+ pfree(DatumGetPointer(attrmiss[i].ammissing));
+ }
+ pfree(attrmiss);
+ }
if (tupdesc->constr->num_check > 0)
{
ConstrCheck *check = tupdesc->constr->check;
@@ -431,6 +464,35 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
if (strcmp(defval1->adbin, defval2->adbin) != 0)
return false;
}
+ n = constr1->num_missing;
+ if (n != (int) constr2->num_missing)
+ return false;
+ for (i = 0; i < n; i++)
+ {
+ AttrMissing *missval1 = constr1->missing + i;
+ AttrMissing *missval2 = constr2->missing;
+
+ /* Similar logic to that used for defaults */
+ for (j = 0; j < n; missval2++, j++)
+ {
+ if (missval1->amnum == missval2->amnum)
+ break;
+ }
+ if (j >= n)
+ return false;
+ if (missval1->ammissingNull != missval2->ammissingNull)
+ return false;
+ if (missval1->ammissing && missval2->ammissing)
+ {
+ Form_pg_attribute missatt1 = TupleDescAttr(tupdesc1, missval1->amnum - 1);
+
+ if (!datumIsEqual(missval1->ammissing, missval2->ammissing,
+ missatt1->attbyval, missatt1->attlen))
+ return false;
+ }
+ else if (missval1->ammissing || missval2->ammissing)
+ return false;
+ }
n = constr1->num_check;
if (n != (int) constr2->num_check)
return false;
@@ -546,6 +608,7 @@ TupleDescInitEntry(TupleDesc desc,
att->attnotnull = false;
att->atthasdef = false;
+ att->atthasmissing = false;
att->attidentity = '\0';
att->attisdropped = false;
att->attislocal = true;
@@ -759,7 +822,9 @@ BuildDescForRelation(List *schema)
constr->has_not_null = true;
constr->defval = NULL;
+ constr->missing = NULL;
constr->num_defval = 0;
+ constr->num_missing = 0;
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 e481cf3..f49c281 100644
--- a/src/backend/catalog/aclchk.c
+++ b/src/backend/catalog/aclchk.c
@@ -4444,7 +4444,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 5b5b04f..ee9da4a 100644
--- a/src/backend/catalog/genbki.pl
+++ b/src/backend/catalog/genbki.pl
@@ -450,13 +450,15 @@ sub emit_pgattr_row
attcacheoff => '-1',
atttypmod => '-1',
atthasdef => 'f',
+ atthasmissing => 'f',
attidentity => '',
attisdropped => 'f',
attislocal => 't',
attinhcount => '0',
attacl => '_null_',
attoptions => '_null_',
- attfdwoptions => '_null_');
+ attfdwoptions => '_null_',
+ attmissingval => '_null_');
return { %PGATTR_DEFAULTS, %row };
}
@@ -492,6 +494,7 @@ sub emit_schemapg_row
delete $row->{attacl};
delete $row->{attoptions};
delete $row->{attfdwoptions};
+ delete $row->{attmissingval};
# 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 4319fc6..7b020a5 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -60,9 +60,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"
@@ -72,6 +75,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"
@@ -144,37 +148,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, '\0', false, true, 0
+ false, 'p', 's', true, false, false, '\0', false, true, 0
};
static FormData_pg_attribute a2 = {
0, {"oid"}, OIDOID, 0, sizeof(Oid),
ObjectIdAttributeNumber, 0, -1, -1,
- true, 'p', 'i', true, false, '\0', false, true, 0
+ true, 'p', 'i', true, false, false, '\0', false, true, 0
};
static FormData_pg_attribute a3 = {
0, {"xmin"}, XIDOID, 0, sizeof(TransactionId),
MinTransactionIdAttributeNumber, 0, -1, -1,
- true, 'p', 'i', true, false, '\0', false, true, 0
+ true, 'p', 'i', true, false, false, '\0', false, true, 0
};
static FormData_pg_attribute a4 = {
0, {"cmin"}, CIDOID, 0, sizeof(CommandId),
MinCommandIdAttributeNumber, 0, -1, -1,
- true, 'p', 'i', true, false, '\0', false, true, 0
+ true, 'p', 'i', true, false, false, '\0', false, true, 0
};
static FormData_pg_attribute a5 = {
0, {"xmax"}, XIDOID, 0, sizeof(TransactionId),
MaxTransactionIdAttributeNumber, 0, -1, -1,
- true, 'p', 'i', true, false, '\0', false, true, 0
+ true, 'p', 'i', true, false, false, '\0', false, true, 0
};
static FormData_pg_attribute a6 = {
0, {"cmax"}, CIDOID, 0, sizeof(CommandId),
MaxCommandIdAttributeNumber, 0, -1, -1,
- true, 'p', 'i', true, false, '\0', false, true, 0
+ true, 'p', 'i', true, false, false, '\0', false, true, 0
};
/*
@@ -186,7 +190,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, '\0', false, true, 0
+ true, 'p', 'i', true, false, false, '\0', false, true, 0
};
static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7};
@@ -623,6 +627,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_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing);
values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity);
values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
@@ -633,6 +638,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_attmissingval - 1] = true;
tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
@@ -1927,7 +1933,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;
@@ -1938,9 +1944,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 *missingBuf = NULL;
+ Datum valuesAtt[Natts_pg_attribute];
+ bool nullsAtt[Natts_pg_attribute];
+ bool replacesAtt[Natts_pg_attribute];
+ Datum missingval = (Datum) 0;
+ bool missingIsNull = true;
/*
* Flatten expression to string form for storage.
@@ -1956,6 +1973,44 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
false, false);
/*
+ * Compute the missing value
+ */
+ expr2 = expression_planner(expr2);
+
+ exprState = ExecInitExpr(expr2, NULL);
+ estate = CreateExecutorState();
+ econtext = GetPerTupleExprContext(estate);
+
+ if (add_column_mode)
+ {
+ missingval = ExecEvalExpr(exprState, econtext,
+ &missingIsNull);
+
+ defAttStruct = TupleDescAttr(rel->rd_att, attnum - 1);
+
+ if (missingIsNull)
+ {
+ missingval = PointerGetDatum(NULL);
+ }
+ else if (defAttStruct->attbyval)
+ {
+ missingBuf = palloc(VARHDRSZ + sizeof(Datum));
+ memcpy(VARDATA(missingBuf), &missingval, sizeof(Datum));
+ SET_VARSIZE(missingBuf, VARHDRSZ + sizeof(Datum));
+ missingval = PointerGetDatum(missingBuf);
+ }
+ else if (defAttStruct->attlen >= 0)
+ {
+ missingBuf = palloc(VARHDRSZ + defAttStruct->attlen);
+ memcpy(VARDATA(missingBuf), DatumGetPointer(missingval),
+ defAttStruct->attlen);
+ SET_VARSIZE(missingBuf,
+ VARHDRSZ + defAttStruct->attlen);
+ missingval = PointerGetDatum(missingBuf);
+ }
+ }
+
+ /*
* Make the pg_attrdef entry.
*/
values[Anum_pg_attrdef_adrelid - 1] = RelationGetRelid(rel);
@@ -1995,7 +2050,22 @@ 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_atthasmissing - 1] = true;
+ replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true;
+ valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval;
+ replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
+ nullsAtt[Anum_pg_attribute_attmissingval - 1] = missingIsNull;
+ }
+ atttup = heap_modify_tuple(atttup, RelationGetDescr(attrrel),
+ valuesAtt, nullsAtt, replacesAtt);
+
CatalogTupleUpdate(attrrel, &atttup->t_self, atttup);
}
heap_close(attrrel, RowExclusiveLock);
@@ -2027,6 +2097,17 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
InvokeObjectPostCreateHookArg(AttrDefaultRelationId,
RelationGetRelid(rel), attnum, is_internal);
+ if (estate)
+ {
+ FreeExecutorState(estate);
+ }
+
+ if (missingBuf)
+ {
+ pfree(missingBuf);
+ missingBuf = NULL;
+ }
+
return attrdefOid;
}
@@ -2179,7 +2260,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 =
@@ -2295,7 +2376,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 a missing value */
+ if (contain_volatile_functions((Node *) expr))
+ {
+ colDef->missingMode = false;
+ }
+
+ defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal,
+ colDef->missingMode);
cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
cooked->contype = CONSTR_DEFAULT;
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 0125c18..6e7ae6a 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->atthasmissing = false;
to->attidentity = '\0';
to->attislocal = true;
to->attinhcount = 0;
@@ -1577,7 +1578,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));
CatalogTupleDelete(indexRelation, &tuple->t_self);
diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c
index 1c5669a..d0f26e3 100644
--- a/src/backend/commands/cluster.c
+++ b/src/backend/commands/cluster.c
@@ -445,7 +445,7 @@ 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, NULL))
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 97091dd..7f1c157 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -207,8 +207,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 d979ce2..9693923 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -701,6 +701,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
rawEnt->attnum = attnum;
rawEnt->raw_default = colDef->raw_default;
+ rawEnt->missingMode = false;
rawDefaults = lappend(rawDefaults, rawEnt);
attr->atthasdef = true;
}
@@ -4614,7 +4615,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))
{
Form_pg_attribute attr = TupleDescAttr(newTupDesc, attn);
@@ -4717,7 +4718,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;
@@ -5333,6 +5334,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
attribute.attalign = tform->typalign;
attribute.attnotnull = colDef->is_not_null;
attribute.atthasdef = false;
+ attribute.atthasmissing = false;
attribute.attidentity = colDef->identity;
attribute.attisdropped = false;
attribute.attislocal = colDef->is_local;
@@ -5376,6 +5378,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->missingMode = true;
/*
* This function is intended for CREATE TABLE, so it processes a
@@ -5386,6 +5389,15 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
/* Make the additional catalog changes visible */
CommandCounterIncrement();
+
+ /*
+ * Did the request for a missing value work? If not we'll have to do
+ * a rewrite
+ */
+ if (!rawEnt->missingMode)
+ {
+ tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
+ }
}
/*
@@ -5452,16 +5464,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 missing value
+ * (i.e. the missing value is NULL) then this ADD COLUMN is doomed.
+ * Unless the table is empty...
*/
- tab->new_notnull |= colDef->is_not_null;
+ if (!TupleDescAttr(rel->rd_att, attribute.attnum - 1)->atthasmissing)
+ {
+ /*
+ * 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;
+ }
}
/*
@@ -5936,6 +5958,7 @@ ATExecColumnDefault(Relation rel, const char *colName,
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
rawEnt->attnum = attnum;
rawEnt->raw_default = newDefault;
+ rawEnt->missingMode = false;
/*
* This function is intended for CREATE TABLE, so it processes a
@@ -8066,8 +8089,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, NULL) &&
+ heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
{
Datum indclassDatum;
bool isnull;
@@ -9470,7 +9493,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 f86af4c..417f894 100644
--- a/src/backend/commands/typecmds.c
+++ b/src/backend/commands/typecmds.c
@@ -2398,7 +2398,7 @@ AlterDomainNotNull(List *names, bool notNull)
int attnum = rtc->atts[i];
Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1);
- if (heap_attisnull(tuple, attnum))
+ if (heap_attisnull(tuple, attnum, tupdesc))
{
/*
* In principle the auxiliary information for this
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index fa4ab30..db96811 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -2212,7 +2212,7 @@ ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op,
/* ignore dropped columns */
if (TupleDescAttr(tupDesc, att - 1)->attisdropped)
continue;
- if (heap_attisnull(&tmptup, att))
+ if (heap_attisnull(&tmptup, att, tupDesc))
{
/* null field disproves IS NOT NULL */
if (!checkisnull)
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index dbaa47f..38a6380 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -2952,8 +2952,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/execTuples.c b/src/backend/executor/execTuples.c
index 51d2c5d..9140e1b 100644
--- a/src/backend/executor/execTuples.c
+++ b/src/backend/executor/execTuples.c
@@ -589,7 +589,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.
@@ -627,7 +635,22 @@ 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 9ca384d..14aec10 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -4416,7 +4416,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
funcform->proretset ||
funcform->prorettype == InvalidOid ||
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;
@@ -4943,7 +4943,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 e93552a..6b2e614 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1126,7 +1126,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/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c
index 26c2aed..62af0ed 100644
--- a/src/backend/statistics/extended_stats.c
+++ b/src/backend/statistics/extended_stats.c
@@ -158,7 +158,7 @@ statext_is_kind_built(HeapTuple htup, char type)
elog(ERROR, "unexpected statistics type requested: %d", type);
}
- return !heap_attisnull(htup, attnum);
+ return !heap_attisnull(htup, attnum, NULL);
}
/*
diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c
index b1ae9e5..06e2bf4 100644
--- a/src/backend/utils/adt/ri_triggers.c
+++ b/src/backend/utils/adt/ri_triggers.c
@@ -205,7 +205,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,
@@ -308,7 +308,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(fk_rel), new_row, riinfo, false))
{
case RI_KEYS_ALL_NULL:
@@ -515,7 +515,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");
@@ -725,7 +725,7 @@ ri_restrict(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:
@@ -912,7 +912,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:
@@ -1072,7 +1072,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:
@@ -1286,7 +1286,7 @@ ri_setnull(TriggerData *trigdata)
*/
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:
@@ -1502,7 +1502,7 @@ ri_setdefault(TriggerData *trigdata)
*/
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:
@@ -1677,7 +1677,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 */
@@ -1733,7 +1733,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;
/*
@@ -1764,7 +1764,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;
@@ -2058,7 +2058,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\"",
@@ -2915,7 +2915,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;
@@ -2930,7 +2931,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 8514c21..36a5236 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -1230,7 +1230,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;
@@ -1394,7 +1394,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;
@@ -1627,7 +1627,7 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags,
* versions of the expressions, because we want to display
* non-const-folded expressions.)
*/
- if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs))
+ if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs, NULL))
{
Datum exprsDatum;
bool isnull;
diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c
index 1d0cc6c..072bb3b 100644
--- a/src/backend/utils/cache/relcache.c
+++ b/src/backend/utils/cache/relcache.c
@@ -77,6 +77,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"
@@ -488,7 +489,10 @@ RelationBuildTupleDesc(Relation relation)
int need;
TupleConstr *constr;
AttrDefault *attrdef = NULL;
+ AttrMissing *attrmiss = NULL;
int ndef = 0;
+ int nmissing = 0;
+ int attnum = 0;
/* copy some fields from pg_class row to rd_att */
relation->rd_att->tdtypeid = relation->rd_rel->reltype;
@@ -541,6 +545,16 @@ RelationBuildTupleDesc(Relation relation)
elog(ERROR, "invalid attribute number %d for %s",
attp->attnum, RelationGetRelationName(relation));
+ /*
+ * 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(TupleDescAttr(relation->rd_att, attp->attnum - 1),
attp,
ATTRIBUTE_FIXED_PART_SIZE);
@@ -549,6 +563,10 @@ RelationBuildTupleDesc(Relation relation)
if (attp->attnotnull)
constr->has_not_null = true;
+ /*
+ * If the column has a default or missin value Fill it into the
+ * attrdef array
+ */
if (attp->atthasdef)
{
if (attrdef == NULL)
@@ -556,10 +574,71 @@ RelationBuildTupleDesc(Relation relation)
MemoryContextAllocZero(CacheMemoryContext,
relation->rd_rel->relnatts *
sizeof(AttrDefault));
- attrdef[ndef].adnum = attp->attnum;
+ attrdef[ndef].adnum = attnum;
attrdef[ndef].adbin = NULL;
+
ndef++;
}
+ if (attp->atthasmissing)
+ {
+ Datum missingval;
+ bool missingNull;
+
+ if (attrmiss == NULL)
+ attrmiss = (AttrMissing *)
+ MemoryContextAllocZero(CacheMemoryContext,
+ relation->rd_rel->relnatts *
+ sizeof(AttrMissing));
+
+ /* Do we have a missing value? */
+ missingval = SysCacheGetAttr(ATTNUM, pg_attribute_tuple,
+ Anum_pg_attribute_attmissingval,
+ &missingNull);
+ attrmiss[nmissing].amnum = attnum;
+ if (missingNull)
+ {
+ /* No, then the store a NULL */
+ attrmiss[nmissing].ammissing = PointerGetDatum(NULL);
+ attrmiss[nmissing].ammissingNull = true;
+ }
+ else if (attp->attbyval)
+ {
+ /*
+ * Yes, and its of the by-value kind Copy in the Datum
+ */
+ memcpy(&attrmiss[nmissing].ammissing,
+ VARDATA_ANY(missingval), sizeof(Datum));
+ attrmiss[nmissing].ammissingNull = false;
+ }
+ 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);
+ attrmiss[nmissing].ammissing = PointerGetDatum(palloc(attp->attlen));
+ memcpy(DatumGetPointer(attrmiss[nmissing].ammissing),
+ VARDATA_ANY(missingval), attp->attlen);
+ MemoryContextSwitchTo(oldcxt);
+ attrmiss[nmissing].ammissingNull = false;
+ }
+ else
+ {
+ /* Yes, variable length */
+ MemoryContext oldcxt;
+
+ oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+ attrmiss[nmissing].ammissing = datumCopy(missingval,
+ attp->attbyval,
+ attp->attlen);
+ attrmiss[nmissing].ammissingNull = false;
+ MemoryContextSwitchTo(oldcxt);
+ }
+ nmissing++;
+ }
need--;
if (need == 0)
break;
@@ -600,7 +679,8 @@ RelationBuildTupleDesc(Relation relation)
/*
* Set up constraint/default info
*/
- if (constr->has_not_null || ndef > 0 || relation->rd_rel->relchecks)
+ if (constr->has_not_null || ndef > 0 ||
+ nmissing > 0 || relation->rd_rel->relchecks)
{
relation->rd_att->constr = constr;
@@ -615,7 +695,19 @@ RelationBuildTupleDesc(Relation relation)
AttrDefaultFetch(relation);
}
else
+ {
constr->num_defval = 0;
+ }
+
+ if (nmissing > 0)
+ {
+ if (nmissing < relation->rd_rel->relnatts)
+ constr->missing = (AttrMissing *)
+ repalloc(attrmiss, nmissing * sizeof(AttrMissing));
+ else
+ constr->missing = attrmiss;
+ }
+ constr->num_missing = nmissing;
if (relation->rd_rel->relchecks > 0) /* CHECKs */
{
@@ -4045,10 +4137,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));
}
/*
@@ -4387,7 +4475,7 @@ RelationGetIndexList(Relation relation)
*/
if (!IndexIsValid(index) || !index->indisunique ||
!index->indimmediate ||
- !heap_attisnull(htup, Anum_pg_index_indpred))
+ !heap_attisnull(htup, Anum_pg_index_indpred, NULL))
continue;
/* Check to see if is a usable btree index on OID */
@@ -4682,7 +4770,7 @@ 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, NULL))
return NIL;
/*
@@ -4745,7 +4833,7 @@ 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, NULL))
return NIL;
/*
diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c
index 975c968..ef490d9 100644
--- a/src/backend/utils/fmgr/fmgr.c
+++ b/src/backend/utils/fmgr/fmgr.c
@@ -199,7 +199,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 24a3950..86e876b 100644
--- a/src/backend/utils/fmgr/funcapi.c
+++ b/src/backend/utils/fmgr/funcapi.c
@@ -1091,8 +1091,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
{
@@ -1186,8 +1186,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 b0d4c54..fc089a8 100644
--- a/src/include/access/htup_details.h
+++ b/src/include/access/htup_details.h
@@ -795,7 +795,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,
@@ -825,5 +825,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 2be5af1..d488320 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"
@@ -25,6 +26,13 @@ typedef struct attrDefault
char *adbin; /* nodeToString representation of expr */
} AttrDefault;
+typedef struct attrMissing
+{
+ AttrNumber amnum;
+ bool ammissingNull; /* true if missing value is NULL */
+ Datum ammissing; /* value when attribute is missing */
+} AttrMissing;
+
typedef struct constrCheck
{
char *ccname;
@@ -38,8 +46,10 @@ typedef struct tupleConstr
{
AttrDefault *defval; /* array */
ConstrCheck *check; /* array */
+ AttrMissing *missing; /* missing attributes values, NULL if none */
uint16 num_defval;
uint16 num_check;
+ uint16 num_missing;
bool has_not_null;
} TupleConstr;
diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h
index 0fae022..6292a9d 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 missingMode; /* 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 bcf28e8..1a5ff96 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 a missing value or not */
+ bool atthasmissing;
+
/* One of the ATTRIBUTE_IDENTITY_* constants below, or '\0' */
char attidentity;
@@ -167,6 +170,9 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
/* Column-level FDW options */
text attfdwoptions[1];
+
+ /* Missing value for added columns */
+ bytea attmissingval;
#endif
} FormData_pg_attribute;
@@ -191,30 +197,31 @@ typedef FormData_pg_attribute *Form_pg_attribute;
* ----------------
*/
-#define Natts_pg_attribute 22
-#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_attidentity 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 Natts_pg_attribute 24
+#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_atthasmissing 15
+#define Anum_pg_attribute_attidentity 16
+#define Anum_pg_attribute_attisdropped 17
+#define Anum_pg_attribute_attislocal 18
+#define Anum_pg_attribute_attinhcount 19
+#define Anum_pg_attribute_attcollation 20
+#define Anum_pg_attribute_attacl 21
+#define Anum_pg_attribute_attoptions 22
+#define Anum_pg_attribute_attfdwoptions 23
+#define Anum_pg_attribute_attmissingval 24
/* ----------------
* initial contents of pg_attribute
diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h
index b256657..ce8458a 100644
--- a/src/include/catalog/pg_class.h
+++ b/src/include/catalog/pg_class.h
@@ -149,7 +149,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 f 3 1 _null_ _null_ _null_));
DESCR("");
-DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 22 0 f f f f f f f t n f 3 1 _null_ _null_ _null_));
+DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 24 0 f f f f f f f t n f 3 1 _null_ _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 f 3 1 _null_ _null_ _null_));
DESCR("");
diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 906dcb8..c02685c 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -371,8 +371,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 $$
@@ -386,7 +384,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..e53e491
--- /dev/null
+++ b/src/test/regress/expected/fast_default.out
@@ -0,0 +1,515 @@
+--
+-- 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';
+CREATE FUNCTION log_rewrite() RETURNS event_trigger
+LANGUAGE plpgsql as
+$func$
+
+declare
+ this_schema text;
+begin
+ select into this_schema relnamespace::regnamespace::text
+ from pg_class
+ where oid = pg_event_trigger_table_rewrite_oid();
+ if this_schema = 'fast_default'
+ then
+ RAISE NOTICE 'rewriting table % for reason %',
+ pg_event_trigger_table_rewrite_oid()::regclass,
+ pg_event_trigger_table_rewrite_reason();
+ end if;
+end;
+$func$;
+CREATE TABLE has_volatile AS
+SELECT * FROM generate_series(1,10) id;
+CREATE EVENT TRIGGER has_volatile_rewrite
+ ON table_rewrite
+ EXECUTE PROCEDURE log_rewrite();
+-- only the last of these should trigger a rewrite
+ALTER TABLE has_volatile ADD col1 int;
+ALTER TABLE has_volatile ADD col2 int DEFAULT 1;
+ALTER TABLE has_volatile ADD col3 timestamptz DEFAULT current_timestamp;
+ALTER TABLE has_volatile ADD col4 int DEFAULT (random() * 10000)::int;
+NOTICE: rewriting table has_volatile for reason 2
+-- 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 ADD COLUMN c_hugetext TEXT DEFAULT repeat('abcdefg',1000),
+ ALTER COLUMN c_interval SET DEFAULT '3 hours';
+INSERT INTO T VALUES (23), (24);
+ALTER TABLE T ALTER COLUMN c_interval DROP DEFAULT,
+ ALTER COLUMN c_hugetext SET DEFAULT repeat('poiuyt', 1000);
+INSERT INTO T VALUES (25), (26);
+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_time DROP DEFAULT,
+ ALTER COLUMN c_hugetext DROP DEFAULT;
+INSERT INTO T VALUES (27), (28);
+SELECT 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,
+ c_hugetext = repeat('abcdefg',1000) as c_hugetext_origdef,
+ c_hugetext = repeat('poiuyt', 1000) as c_hugetext_newdef
+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 | c_hugetext_origdef | c_hugetext_newdef
+----+-------+----------+--------+------------+--------------------------+--------------------------+--------------------------+---------+--------------+-------------------+-------------------+----------+------------+--------------------+-------------------
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | t | f
+ 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 | 23:59:59 | @ 3 hours | t | f
+ 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 | 23:59:59 | @ 3 hours | t | f
+ 25 | 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 | | f | t
+ 26 | 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 | | f | t
+ 27 | 2 | | | | | Thu Sep 29 12:00:00 2016 | | | 13 | | | | | |
+ 28 | 2 | | | | | Thu Sep 29 12:00:00 2016 | | | 13 | | | | | |
+(28 rows)
+
+SELECT comp();
+ comp
+-----------
+ Unchanged
+(1 row)
+
+DROP TABLE T;
+-- 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);
+-- 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();
+NOTICE: rewriting table t for reason 2
+SELECT comp();
+ comp
+-----------
+ Rewritten
+(1 row)
+
+DROP TABLE T;
+-- Simple querie
+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);
+-- 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)
+
+-- 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)
+
+-- Aggregate function
+SELECT SUM(c_bigint), MAX(c_text), MIN(c_text) FROM T;
+ sum | max | min
+-----+-------+-----
+ 201 | hello | 31
+(1 row)
+
+-- 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)
+
+-- LIMIT
+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)
+
+-- 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)
+
+-- 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;
+-- 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 TABLE has_volatile;
+DROP EVENT TRIGGER has_volatile_rewrite;
+DROP FUNCTION log_rewrite;
+DROP SCHEMA fast_default;
diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule
index e224977..8731ce8 100644
--- a/src/test/regress/parallel_schedule
+++ b/src/test/regress/parallel_schedule
@@ -111,7 +111,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
# ----------
# Another group of parallel tests
diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule
index 9fc5f1a..093edb7 100644
--- a/src/test/regress/serial_schedule
+++ b/src/test/regress/serial_schedule
@@ -186,3 +186,4 @@ test: reloptions
test: hash_part
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..3afc7b8
--- /dev/null
+++ b/src/test/regress/sql/fast_default.sql
@@ -0,0 +1,357 @@
+--
+-- 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';
+
+CREATE FUNCTION log_rewrite() RETURNS event_trigger
+LANGUAGE plpgsql as
+$func$
+
+declare
+ this_schema text;
+begin
+ select into this_schema relnamespace::regnamespace::text
+ from pg_class
+ where oid = pg_event_trigger_table_rewrite_oid();
+ if this_schema = 'fast_default'
+ then
+ RAISE NOTICE 'rewriting table % for reason %',
+ pg_event_trigger_table_rewrite_oid()::regclass,
+ pg_event_trigger_table_rewrite_reason();
+ end if;
+end;
+$func$;
+
+CREATE TABLE has_volatile AS
+SELECT * FROM generate_series(1,10) id;
+
+
+CREATE EVENT TRIGGER has_volatile_rewrite
+ ON table_rewrite
+ EXECUTE PROCEDURE log_rewrite();
+
+-- only the last of these should trigger a rewrite
+ALTER TABLE has_volatile ADD col1 int;
+ALTER TABLE has_volatile ADD col2 int DEFAULT 1;
+ALTER TABLE has_volatile ADD col3 timestamptz DEFAULT current_timestamp;
+ALTER TABLE has_volatile ADD col4 int DEFAULT (random() * 10000)::int;
+
+
+
+-- 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 ADD COLUMN c_hugetext TEXT DEFAULT repeat('abcdefg',1000),
+ ALTER COLUMN c_interval SET DEFAULT '3 hours';
+
+INSERT INTO T VALUES (23), (24);
+
+ALTER TABLE T ALTER COLUMN c_interval DROP DEFAULT,
+ ALTER COLUMN c_hugetext SET DEFAULT repeat('poiuyt', 1000);
+
+INSERT INTO T VALUES (25), (26);
+
+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_time DROP DEFAULT,
+ ALTER COLUMN c_hugetext DROP DEFAULT;
+
+INSERT INTO T VALUES (27), (28);
+
+SELECT 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,
+ c_hugetext = repeat('abcdefg',1000) as c_hugetext_origdef,
+ c_hugetext = repeat('poiuyt', 1000) as c_hugetext_newdef
+FROM T ORDER BY pk;
+
+SELECT comp();
+
+DROP TABLE T;
+
+-- 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);
+
+-- 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;
+
+-- Simple querie
+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);
+
+-- 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;
+
+
+-- COALESCE
+SELECT COALESCE(c_bigint, pk), COALESCE(c_text, pk::text)
+FROM T
+ORDER BY pk LIMIT 10;
+
+-- Aggregate function
+SELECT SUM(c_bigint), MAX(c_text), MIN(c_text) FROM T;
+
+-- 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;
+
+-- LIMIT
+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;
+
+-- 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 *;
+
+-- 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;
+
+
+-- 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 TABLE has_volatile;
+DROP EVENT TRIGGER has_volatile_rewrite;
+DROP FUNCTION log_rewrite;
+DROP SCHEMA fast_default;