]> granicus.if.org Git - postgresql/commitdiff
Fast ALTER TABLE ADD COLUMN with a non-NULL default
authorAndrew Dunstan <andrew@dunslane.net>
Wed, 28 Mar 2018 00:13:52 +0000 (10:43 +1030)
committerAndrew Dunstan <andrew@dunslane.net>
Wed, 28 Mar 2018 00:13:52 +0000 (10:43 +1030)
Currently adding a column to a table with a non-NULL default results in
a rewrite of the table. For large tables this can be both expensive and
disruptive. This patch removes the need for the rewrite as long as the
default value is not volatile. The default expression is evaluated at
the time of the ALTER TABLE and the result stored in a new column
(attmissingval) in pg_attribute, and a new column (atthasmissing) is set
to true. Any existing row when fetched will be supplied with the
attmissingval. New rows will have the supplied value or the default and
so will never need the attmissingval.

Any time the table is rewritten all the atthasmissing and attmissingval
settings for the attributes are cleared, as they are no longer needed.

The most visible code change from this is in heap_attisnull, which
acquires a third TupleDesc argument, allowing it to detect a missing
value if there is one. In many cases where it is known that there will
not be any (e.g.  catalog relations) NULL can be passed for this
argument.

Andrew Dunstan, heavily modified from an original patch from Serge
Rielau.
Reviewed by Tom Lane, Andres Freund, Tomas Vondra and David Rowley.

Discussion: https://postgr.es/m/31e2e921-7002-4c27-59f5-51f08404c858@2ndQuadrant.com

36 files changed:
doc/src/sgml/catalogs.sgml
doc/src/sgml/ref/alter_table.sgml
src/backend/access/common/heaptuple.c
src/backend/access/common/tupdesc.c
src/backend/catalog/aclchk.c
src/backend/catalog/heap.c
src/backend/catalog/index.c
src/backend/commands/cluster.c
src/backend/commands/functioncmds.c
src/backend/commands/indexcmds.c
src/backend/commands/tablecmds.c
src/backend/commands/typecmds.c
src/backend/executor/execExprInterp.c
src/backend/executor/execMain.c
src/backend/executor/execTuples.c
src/backend/executor/execUtils.c
src/backend/optimizer/util/clauses.c
src/backend/optimizer/util/plancat.c
src/backend/rewrite/rewriteHandler.c
src/backend/statistics/extended_stats.c
src/backend/utils/adt/ri_triggers.c
src/backend/utils/adt/ruleutils.c
src/backend/utils/cache/relcache.c
src/backend/utils/fmgr/fmgr.c
src/backend/utils/fmgr/funcapi.c
src/include/access/htup_details.h
src/include/access/tupdesc.h
src/include/access/tupdesc_details.h [new file with mode: 0644]
src/include/catalog/heap.h
src/include/catalog/pg_attribute.h
src/include/catalog/pg_class.h
src/test/regress/expected/event_trigger.out
src/test/regress/expected/fast_default.out [new file with mode: 0644]
src/test/regress/parallel_schedule
src/test/regress/serial_schedule
src/test/regress/sql/fast_default.sql [new file with mode: 0644]

index 95a5b113b98d794dcf5f0342b068a91027a5b3f8..d6a9d8c5808ee71e16060f2f32e11a9169fe14a6 100644 (file)
       </entry>
      </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 with a
+       non-volatile <literal>DEFAULT</literal> value after the row is created.
+       The actual value used is stored in the
+       <structfield>attmissingval</structfield> column.
+      </entry>
+     </row>
+
      <row>
       <entry><structfield>attidentity</structfield></entry>
       <entry><type>char</type></entry>
       </entry>
      </row>
 
+     <row>
+      <entry><structfield>attmissingval</structfield></entry>
+      <entry><type>anyarray</type></entry>
+      <entry></entry>
+      <entry>
+       This column has a one element array containing the value used when the
+       column is entirely missing from the row, as happens when the column is
+       added with a non-volatile <literal>DEFAULT</literal> value after the
+       row is created.  The value is only used when
+       <structfield>atthasmissing</structfield> is true.  If there is no value
+       the column is null.
+      </entry>
+     </row>
+
     </tbody>
    </tgroup>
   </table>
index afe213910c75713e531b6c71db0646e5ce7f7fe0..69f3355ededcbe10d951f46b8914a5ef95adde6c 100644 (file)
@@ -1184,26 +1184,26 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
    </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>
index d4478a2cbad8a330aec9e83ba985bbb1eaacc979..b6ad7356718376a5da2e5e1ed5b6510c9bf4e340 100644 (file)
@@ -58,6 +58,7 @@
 #include "postgres.h"
 
 #include "access/sysattr.h"
+#include "access/tupdesc_details.h"
 #include "access/tuptoaster.h"
 #include "executor/tuptable.h"
 #include "utils/expandeddatum.h"
  * ----------------------------------------------------------------
  */
 
+/*
+ * Return the missing value of an attribute, or NULL if there isn't one.
+ */
+static Datum
+getmissingattr(TupleDesc tupleDesc,
+                          int attnum, bool *isnull)
+{
+       Form_pg_attribute att;
+
+       Assert(attnum <= tupleDesc->natts);
+       Assert(attnum > 0);
+
+       att = TupleDescAttr(tupleDesc, attnum - 1);
+
+       if (att->atthasmissing)
+       {
+               AttrMissing *attrmiss;
+
+               Assert(tupleDesc->constr);
+               Assert(tupleDesc->constr->missing);
+
+               attrmiss = tupleDesc->constr->missing + (attnum - 1);
+
+               if (attrmiss->ammissingPresent)
+               {
+                       *isnull = false;
+                       return attrmiss->ammissing;
+               }
+       }
+
+       *isnull = true;
+       return PointerGetDatum(NULL);
+}
+
+/*
+ * Fill in missing values for a TupleTableSlot
+ */
+static void
+slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum)
+{
+       AttrMissing *attrmiss = NULL;
+       int                     missattnum;
+
+       if (slot->tts_tupleDescriptor->constr)
+               attrmiss = slot->tts_tupleDescriptor->constr->missing;
+
+
+       if (!attrmiss)
+       {
+               /* no missing values array at all, so just fill everything in as NULL */
+               memset(slot->tts_values + startAttNum, 0,
+                          (lastAttNum - startAttNum) * sizeof(Datum));
+               memset(slot->tts_isnull + startAttNum, 1,
+                          (lastAttNum - startAttNum) * sizeof(bool));
+       }
+       else
+       {
+               /* if there is a missing values array we must process them one by one */
+               for (missattnum = lastAttNum - 1;
+                        missattnum >= startAttNum;
+                        missattnum--)
+               {
+                       slot->tts_values[missattnum] = attrmiss[missattnum].ammissing;
+                       slot->tts_isnull[missattnum] =
+                               !attrmiss[missattnum].ammissingPresent;
+               }
+       }
+}
 
 /*
  * heap_compute_data_size
@@ -132,6 +201,131 @@ heap_compute_data_size(TupleDesc tupleDesc,
        return data_length;
 }
 
+/*
+ * Per-attribute helper for heap_fill_tuple and other routines building tuples.
+ *
+ * Fill in 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;
+       }
+
+       /*
+        * 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 +366,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 +391,20 @@ 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 && TupleDescAttr(tupleDesc, attnum - 1)->atthasmissing)
+                       return false;
+               else
+                       return true;
+       }
 
        if (attnum > 0)
        {
@@ -649,6 +757,274 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest)
        memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len);
 }
 
+/*
+ * Expand a tuple which has less attributes than required. For each attribute
+ * not present in the sourceTuple, if there is a missing value that will be
+ * used. Otherwise the attribute will be set to NULL.
+ *
+ * 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                     firstmissingnum = 0;
+       bool            hasNulls = HeapTupleHasNulls(sourceTuple);
+       HeapTupleHeader targetTHeader;
+       HeapTupleHeader sourceTHeader = sourceTuple->t_data;
+       int                     sourceNatts = HeapTupleHeaderGetNatts(sourceTHeader);
+       int                     natts = tupleDesc->natts;
+       int                     sourceNullLen;
+       int                     targetNullLen;
+       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);
+
+       sourceNullLen = (hasNulls ? BITMAPLEN(sourceNatts) : 0);
+
+       targetDataLen = sourceDataLen;
+
+       if (tupleDesc->constr &&
+               tupleDesc->constr->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.
+                */
+               attrmiss = tupleDesc->constr->missing;
+
+               /*
+                * Find the first item in attrmiss for which we don't have a value in
+                * the source. We can ignore all the missing entries before that.
+                */
+               for (firstmissingnum = sourceNatts;
+                        firstmissingnum < natts;
+                        firstmissingnum++)
+               {
+                       if (attrmiss[firstmissingnum].ammissingPresent)
+                               break;
+               }
+
+               /*
+                * If there are no more missing values everything else must be NULL
+                */
+               if (firstmissingnum >= natts)
+               {
+                       hasNulls = true;
+               }
+               else
+               {
+
+                       /*
+                        * Now walk the missing attributes. If there is a missing value
+                        * make space for it. Otherwise, it's going to be NULL.
+                        */
+                       for (attnum = firstmissingnum;
+                                attnum < natts;
+                                attnum++)
+                       {
+                               if (attrmiss[attnum].ammissingPresent)
+                               {
+                                       Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum);
+
+                                       targetDataLen = att_align_datum(targetDataLen,
+                                                                                                       att->attalign,
+                                                                                                       att->attlen,
+                                                                                                       attrmiss[attnum].ammissing);
+
+                                       targetDataLen = att_addlength_pointer(targetDataLen,
+                                                                                                                 att->attlen,
+                                                                                                                 attrmiss[attnum].ammissing);
+                               }
+                               else
+                               {
+                                       /* no missing value, so it must be null */
+                                       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)
+       {
+               targetNullLen = BITMAPLEN(natts);
+               len += targetNullLen;
+       }
+       else
+               targetNullLen = 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 (targetNullLen > 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 (targetNullLen > 0)
+                       nullBits = (bits8 *) ((char *) *targetMinimalTuple
+                                                                 + offsetof(MinimalTupleData, t_bits));
+               targetData = (char *) *targetMinimalTuple + hoff;
+               infoMask = &((*targetMinimalTuple)->t_infomask);
+       }
+
+       if (targetNullLen > 0)
+       {
+               if (sourceNullLen > 0)
+               {
+                       /* if bitmap pre-existed copy in - all is set */
+                       memcpy(nullBits,
+                                  ((char *) sourceTHeader)
+                                  + offsetof(HeapTupleHeaderData, t_bits),
+                                  sourceNullLen);
+                       nullBits += sourceNullLen - 1;
+               }
+               else
+               {
+                       sourceNullLen = BITMAPLEN(sourceNatts);
+                       /* Set NOT NULL for all existing attributes */
+                       memset(nullBits, 0xff, sourceNullLen);
+
+                       nullBits += sourceNullLen - 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);
+
+       targetData += sourceDataLen;
+
+       /* Now fill in the missing values */
+       for (attnum = sourceNatts; attnum < natts; attnum++)
+       {
+
+               Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum);
+
+               if (attrmiss[attnum].ammissingPresent)
+               {
+                       fill_val(attr,
+                                        nullBits ? &nullBits : NULL,
+                                        &bitMask,
+                                        &targetData,
+                                        infoMask,
+                                        attrmiss[attnum].ammissing,
+                                        false);
+               }
+               else
+               {
+                       fill_val(attr,
+                                        &nullBits,
+                                        &bitMask,
+                                        &targetData,
+                                        infoMask,
+                                        (Datum) 0,
+                                        true);
+               }
+       }                                                       /* end loop over missing attributes */
+}
+
+/*
+ * Fill in the missing values for a minimal 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 an ordinary HeapTuple
+ */
+HeapTuple
+heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc)
+{
+       HeapTuple       heapTuple;
+
+       expand_tuple(&heapTuple, NULL, sourceTuple, tupleDesc);
+       return heapTuple;
+}
+
 /* ----------------
  *             heap_copy_tuple_as_datum
  *
@@ -1012,13 +1388,10 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
 
        /*
         * If tuple doesn't have all the atts indicated by tupleDesc, read the
-        * rest as null
+        * rest as nulls or missing values as appropriate.
         */
        for (; attnum < tdesc_natts; attnum++)
-       {
-               values[attnum] = (Datum) 0;
-               isnull[attnum] = true;
-       }
+               values[attnum] = getmissingattr(tupleDesc, attnum + 1, &isnull[attnum]);
 }
 
 /*
@@ -1183,7 +1556,8 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
                elog(ERROR, "cannot extract attribute from empty tuple slot");
 
        /*
-        * return NULL if attnum is out of range according to the tuple
+        * return NULL or missing value if attnum is out of range according to the
+        * tuple
         *
         * (We have to check this separately because of various inheritance and
         * table-alteration scenarios: the tuple could be either longer or shorter
@@ -1191,10 +1565,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);
 
        /*
         * check if target attribute is null: no point in groveling through tuple
@@ -1263,13 +1634,11 @@ 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++)
-       {
-               slot->tts_values[attnum] = (Datum) 0;
-               slot->tts_isnull[attnum] = true;
-       }
+       if (attnum < tdesc_natts)
+               slot_getmissingattrs(slot, attnum, tdesc_natts);
+
        slot->tts_nvalid = tdesc_natts;
 }
 
@@ -1310,13 +1679,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++)
-       {
-               slot->tts_values[attno] = (Datum) 0;
-               slot->tts_isnull[attno] = true;
-       }
+       if (attno < attnum)
+               slot_getmissingattrs(slot, attno, attnum);
+
        slot->tts_nvalid = attnum;
 }
 
@@ -1340,7 +1707,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 +1730,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);
 }
 
 /*
index f1f44230cd7691d5cdaaea6643fddf30ffe5fa26..2658399484b61d8cac849a3b18e903714def3baa 100644 (file)
 
 #include "access/hash.h"
 #include "access/htup_details.h"
+#include "access/tupdesc_details.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_type.h"
 #include "miscadmin.h"
 #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"
@@ -129,6 +131,7 @@ CreateTupleDescCopy(TupleDesc tupdesc)
 
                att->attnotnull = false;
                att->atthasdef = false;
+               att->atthasmissing = false;
                att->attidentity = '\0';
        }
 
@@ -176,6 +179,23 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc)
                        }
                }
 
+               if (constr->missing)
+               {
+                       cpy->missing = (AttrMissing *) palloc(tupdesc->natts * sizeof(AttrMissing));
+                       memcpy(cpy->missing, constr->missing, tupdesc->natts * sizeof(AttrMissing));
+                       for (i = tupdesc->natts - 1; i >= 0; i--)
+                       {
+                               if (constr->missing[i].ammissingPresent)
+                               {
+                                       Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+
+                                       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));
@@ -227,6 +247,7 @@ TupleDescCopy(TupleDesc dst, TupleDesc src)
 
                att->attnotnull = false;
                att->atthasdef = false;
+               att->atthasmissing = false;
                att->attidentity = '\0';
        }
        dst->constr = NULL;
@@ -279,6 +300,7 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno,
        /* since we're not copying constraints or defaults, clear these */
        dstAtt->attnotnull = false;
        dstAtt->atthasdef = false;
+       dstAtt->atthasmissing = false;
        dstAtt->attidentity = '\0';
 }
 
@@ -309,6 +331,18 @@ FreeTupleDesc(TupleDesc tupdesc)
                        }
                        pfree(attrdef);
                }
+               if (tupdesc->constr->missing)
+               {
+                       AttrMissing *attrmiss = tupdesc->constr->missing;
+
+                       for (i = tupdesc->natts - 1; i >= 0; i--)
+                       {
+                               if (attrmiss[i].ammissingPresent
+                                       && !TupleDescAttr(tupdesc, i)->attbyval)
+                                       pfree(DatumGetPointer(attrmiss[i].ammissing));
+                       }
+                       pfree(attrmiss);
+               }
                if (tupdesc->constr->num_check > 0)
                {
                        ConstrCheck *check = tupdesc->constr->check;
@@ -469,6 +503,29 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2)
                        if (strcmp(defval1->adbin, defval2->adbin) != 0)
                                return false;
                }
+               if (constr1->missing)
+               {
+                       if (!constr2->missing)
+                               return false;
+                       for (i = 0; i < tupdesc1->natts; i++)
+                       {
+                               AttrMissing *missval1 = constr1->missing + i;
+                               AttrMissing *missval2 = constr2->missing + i;
+
+                               if (missval1->ammissingPresent != missval2->ammissingPresent)
+                                       return false;
+                               if (missval1->ammissingPresent)
+                               {
+                                       Form_pg_attribute missatt1 = TupleDescAttr(tupdesc1, i);
+
+                                       if (!datumIsEqual(missval1->ammissing, missval2->ammissing,
+                                                                         missatt1->attbyval, missatt1->attlen))
+                                               return false;
+                               }
+                       }
+               }
+               else if (constr2->missing)
+                       return false;
                n = constr1->num_check;
                if (n != (int) constr2->num_check)
                        return false;
@@ -584,6 +641,7 @@ TupleDescInitEntry(TupleDesc desc,
 
        att->attnotnull = false;
        att->atthasdef = false;
+       att->atthasmissing = false;
        att->attidentity = '\0';
        att->attisdropped = false;
        att->attislocal = true;
@@ -642,6 +700,7 @@ TupleDescInitBuiltinEntry(TupleDesc desc,
 
        att->attnotnull = false;
        att->atthasdef = false;
+       att->atthasmissing = false;
        att->attidentity = '\0';
        att->attisdropped = false;
        att->attislocal = true;
@@ -797,6 +856,7 @@ BuildDescForRelation(List *schema)
 
                constr->has_not_null = true;
                constr->defval = NULL;
+               constr->missing = NULL;
                constr->num_defval = 0;
                constr->check = NULL;
                constr->num_check = 0;
index 06485397969843b0adb25df822aeb55c5f5a3d59..83000575ce76e694f445970b4d6d9c24449c76a5 100644 (file)
@@ -4588,7 +4588,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,
index b69bb1e2a41ae17886926a6dd333815494d6aa13..a1def77944cd9fe2867f9a897976b74ee25a149a 100644 (file)
 #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};
@@ -624,6 +628,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);
@@ -634,6 +639,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel,
        nulls[Anum_pg_attribute_attacl - 1] = true;
        nulls[Anum_pg_attribute_attoptions - 1] = true;
        nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
+       nulls[Anum_pg_attribute_attmissingval - 1] = true;
 
        tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
 
@@ -1925,14 +1931,91 @@ heap_drop_with_catalog(Oid relid)
 }
 
 
+/*
+ * RelationClearMissing
+ *
+ * Set atthasmissing and attmissingval to false/null for all attributes
+ * where they are currently set. This can be safely and usefully done if
+ * the table is rewritten (e.g. by VACUUM FULL or CLUSTER) where we know there
+ * are no rows left with less than a full complement of attributes.
+ *
+ * The caller must have an AccessExclusive lock on the relation.
+ */
+void
+RelationClearMissing(Relation rel)
+{
+       Relation        attr_rel;
+       Oid                     relid = RelationGetRelid(rel);
+       int                     natts = RelationGetNumberOfAttributes(rel);
+       int                     attnum;
+       Datum           repl_val[Natts_pg_attribute];
+       bool            repl_null[Natts_pg_attribute];
+       bool            repl_repl[Natts_pg_attribute];
+       Form_pg_attribute attrtuple;
+       HeapTuple       tuple,
+                               newtuple;
+
+       memset(repl_val, 0, sizeof(repl_val));
+       memset(repl_null, false, sizeof(repl_null));
+       memset(repl_repl, false, sizeof(repl_repl));
+
+       repl_val[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(false);
+       repl_null[Anum_pg_attribute_attmissingval - 1] = true;
+
+       repl_repl[Anum_pg_attribute_atthasmissing - 1] = true;
+       repl_repl[Anum_pg_attribute_attmissingval - 1] = true;
+
+
+       /* Get a lock on pg_attribute */
+       attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
+
+       /* process each non-system attribute, including any dropped columns */
+       for (attnum = 1; attnum <= natts; attnum++)
+       {
+               tuple = SearchSysCache2(ATTNUM,
+                                                               ObjectIdGetDatum(relid),
+                                                               Int16GetDatum(attnum));
+               if (!HeapTupleIsValid(tuple))   /* shouldn't happen */
+                       elog(ERROR, "cache lookup failed for attribute %d of relation %u",
+                                attnum, relid);
+
+               attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
+
+               /* ignore any where atthasmissing is not true */
+               if (attrtuple->atthasmissing)
+               {
+                       newtuple = heap_modify_tuple(tuple, RelationGetDescr(attr_rel),
+                                                                                repl_val, repl_null, repl_repl);
+
+                       CatalogTupleUpdate(attr_rel, &newtuple->t_self, newtuple);
+
+                       heap_freetuple(newtuple);
+               }
+
+               ReleaseSysCache(tuple);
+       }
+
+       /*
+        * Our update of the pg_attribute rows will force a relcache rebuild, so
+        * there's nothing else to do here.
+        */
+       heap_close(attr_rel, RowExclusiveLock);
+}
+
 /*
  * Store a default expression for column attnum of relation rel.
  *
  * Returns the OID of the new pg_attrdef tuple.
+ *
+ * add_column_mode must be true if we are storing the default for a new
+ * attribute, and false if it's for an already existing attribute. The reason
+ * for this is that the missing value must never be updated after it is set,
+ * which can only be when a column is added to the table. Otherwise we would
+ * in effect be changing existing tuples.
  */
 Oid
 StoreAttrDefault(Relation rel, AttrNumber attnum,
-                                Node *expr, bool is_internal)
+                                Node *expr, bool is_internal, bool add_column_mode)
 {
        char       *adbin;
        char       *adsrc;
@@ -2000,8 +2083,69 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
        attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
        if (!attStruct->atthasdef)
        {
-               attStruct->atthasdef = true;
+               Form_pg_attribute defAttStruct;
+
+               ExprState  *exprState;
+               Expr       *expr2 = (Expr *) expr;
+               EState     *estate = NULL;
+               ExprContext *econtext;
+               Datum           valuesAtt[Natts_pg_attribute];
+               bool            nullsAtt[Natts_pg_attribute];
+               bool            replacesAtt[Natts_pg_attribute];
+               Datum           missingval = (Datum) 0;
+               bool            missingIsNull = 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)
+               {
+                       expr2 = expression_planner(expr2);
+                       estate = CreateExecutorState();
+                       exprState = ExecPrepareExpr(expr2, estate);
+                       econtext = GetPerTupleExprContext(estate);
+
+                       missingval = ExecEvalExpr(exprState, econtext,
+                                                                         &missingIsNull);
+
+                       FreeExecutorState(estate);
+
+                       defAttStruct = TupleDescAttr(rel->rd_att, attnum - 1);
+
+                       if (missingIsNull)
+                       {
+                               /* if the default evaluates to NULL, just store a NULL array */
+                               missingval = (Datum) 0;
+                       }
+                       else
+                       {
+                               /* otherwise make a one-element array of the value */
+                               missingval = PointerGetDatum(
+                                                                                        construct_array(&missingval,
+                                                                                                                        1,
+                                                                                                                        defAttStruct->atttypid,
+                                                                                                                        defAttStruct->attlen,
+                                                                                                                        defAttStruct->attbyval,
+                                                                                                                        defAttStruct->attalign));
+                       }
+
+                       valuesAtt[Anum_pg_attribute_atthasmissing - 1] = !missingIsNull;
+                       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);
+
+               if (!missingIsNull)
+                       pfree(DatumGetPointer(missingval));
+
        }
        heap_close(attrrel, RowExclusiveLock);
        heap_freetuple(atttup);
@@ -2185,7 +2329,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 =
@@ -2301,7 +2445,12 @@ 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 (colDef->missingMode && 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;
index dee1a8ac7832e109125cc108b467d099f27b87eb..bc99a60d347f35ff100dd2e1b272d983514098c7 100644 (file)
@@ -372,6 +372,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;
@@ -1655,7 +1656,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);
 
index 57f3917fdc4de36a9ea455907af864be56f73438..639b6992d537f6916ddc6f0df15ab55b8696c02a 100644 (file)
@@ -453,7 +453,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\"",
@@ -1669,6 +1669,16 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap,
                }
                relation_close(newrel, NoLock);
        }
+
+       /* if it's not a catalog table, clear any missing attribute settings */
+       if (!is_system_catalog)
+       {
+               Relation        newrel;
+
+               newrel = heap_open(OIDOldHeap, NoLock);
+               RelationClearMissing(newrel);
+               relation_close(newrel, NoLock);
+       }
 }
 
 
index 86fa8c0dd7415e56893910b019da64241a2bd900..c46493dd88bfc7c974981eea1831bf0ac84feb23 100644 (file)
@@ -2252,7 +2252,7 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver
        tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid));
        if (!HeapTupleIsValid(tp))
                elog(ERROR, "cache lookup failed for function %u", fexpr->funcid);
-       if (!heap_attisnull(tp, Anum_pg_proc_proconfig))
+       if (!heap_attisnull(tp, Anum_pg_proc_proconfig, NULL))
                callcontext->atomic = true;
        ReleaseSysCache(tp);
 
index 0a2ab50023850abaff685a6609b36f3ea44d1bff..01859707940917521900fb0d3eeab547a7a87493 100644 (file)
@@ -215,8 +215,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);
index e74fb1f46919bf31a71cc3020eb50586501f47cc..83a881eff38d241c51952662ae8f54ff69fe5fd2 100644 (file)
@@ -714,6 +714,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;
                }
@@ -4682,7 +4683,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);
 
@@ -4785,7 +4786,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;
 
@@ -5404,6 +5405,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;
@@ -5448,6 +5450,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
                rawEnt->attnum = attribute.attnum;
                rawEnt->raw_default = copyObject(colDef->raw_default);
 
+               /*
+                * Attempt to skip a complete table rewrite by storing the specified
+                * DEFAULT value outside of the heap.  This may be disabled inside
+                * AddRelationNewConstraints if the optimization cannot be applied.
+                */
+               rawEnt->missingMode = true;
+
                /*
                 * This function is intended for CREATE TABLE, so it processes a
                 * _list_ of defaults, but we just do one.
@@ -5457,6 +5466,13 @@ 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;
        }
 
        /*
@@ -5502,6 +5518,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
                        nve->typeId = typeOid;
 
                        defval = (Expr *) nve;
+
+                       /* must do a rewrite for identity columns */
+                       tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
                }
                else
                        defval = (Expr *) build_column_default(rel, attribute.attnum);
@@ -5537,16 +5556,21 @@ 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 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;
+               if (DomainHasConstraints(typeOid))
+                       tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
+
+               if (!TupleDescAttr(rel->rd_att, attribute.attnum - 1)->atthasmissing)
+               {
+                       /*
+                        * If the new column is NOT NULL, and there is no missing value,
+                        * 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;
+               }
        }
 
        /*
@@ -6022,6 +6046,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
@@ -8109,8 +8134,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;
@@ -9516,7 +9541,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,
index 25221965e9e35219ca0effcb3fcdd54e8422a285..2fdcb7f3fd3db7f900ea4ade6c0d284979dcfab4 100644 (file)
@@ -2397,7 +2397,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
index f7bcf6370b5e194ba84e28daebb4bed52e741ceb..e530b262dae624317a1ea753a4c055cb6eefcda8 100644 (file)
@@ -2505,7 +2505,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)
index 68f6450ee64943a3e994440eb781d07331592c6d..9a107aba5619c31601dfa3d079818dfeb29530d6 100644 (file)
@@ -2980,8 +2980,17 @@ 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);
                        }
 
index acd1b97b0e61752a1dbc7358dc1276cdb9865407..78cfcadea0387c3c23c91fdebeb54509296526d5 100644 (file)
@@ -625,7 +625,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.
@@ -663,7 +671,23 @@ 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...
index 14b07b5d449ce0d2f3975d2b85ecf61bd45d3545..b963cae730c26758d3b51812f461cbf3d1577232 100644 (file)
@@ -511,6 +511,8 @@ tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, TupleDesc tupdesc
                        return false;           /* out of order */
                if (att_tup->attisdropped)
                        return false;           /* table contains dropped columns */
+               if (att_tup->atthasmissing)
+                       return false;           /* table contains cols with missing values */
 
                /*
                 * Note: usually the Var's type should match the tupdesc exactly, but
index 93e5658a3543868cdcf4fbfc7f96329c8a1bad53..ed6b680ed8601fe18d6cc7eed4362cc9dc331c38 100644 (file)
@@ -4491,7 +4491,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid,
                funcform->prosecdef ||
                funcform->proretset ||
                funcform->prorettype == RECORDOID ||
-               !heap_attisnull(func_tuple, Anum_pg_proc_proconfig) ||
+               !heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL) ||
                funcform->pronargs != list_length(args))
                return NULL;
 
@@ -5031,7 +5031,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte)
                funcform->prorettype == VOIDOID ||
                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;
index bd3a0c4a0ab99020834268c6827b4c40f0d81782..0231f8bf7c68840bff9bdd34553d2b0033b0950e 100644 (file)
@@ -1493,8 +1493,8 @@ relation_excluded_by_constraints(PlannerInfo *root,
  * in order.  The executor can special-case such tlists to avoid a projection
  * step at runtime, so we use such tlists preferentially for scan nodes.
  *
- * Exception: if there are any dropped columns, we punt and return NIL.
- * Ideally we would like to handle the dropped-column case too.  However this
+ * Exception: if there are any dropped or missing columns, we punt and return
+ * NIL.  Ideally we would like to handle these cases too.  However this
  * creates problems for ExecTypeFromTL, which may be asked to build a tupdesc
  * for a tlist that includes vars of no-longer-existent types.  In theory we
  * could dig out the required info from the pg_attribute entries of the
@@ -1533,9 +1533,9 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel)
                                Form_pg_attribute att_tup = TupleDescAttr(relation->rd_att,
                                                                                                                  attrno - 1);
 
-                               if (att_tup->attisdropped)
+                               if (att_tup->attisdropped || att_tup->atthasmissing)
                                {
-                                       /* found a dropped col, so punt */
+                                       /* found a dropped or missing col, so punt */
                                        tlist = NIL;
                                        break;
                                }
index 66253fc3d3ebf2242e04ccdd25eda3d28f48dbab..361bde42614193a07989246bcb10f4b10bca19f2 100644 (file)
@@ -1126,7 +1126,8 @@ 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;
index 6c836f68a987d5f2fa84e8017b1a5fc0f740595f..2df5f7dc3a571af529f8df137b2ea316f45dc06e 100644 (file)
@@ -159,7 +159,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);
 }
 
 /*
index 4d7fee0ecb9b71aa08ec6c7493ecf1b4b01265ef..3bb708f86389316f7362c0a2faee15dab0d83648 100644 (file)
@@ -204,7 +204,7 @@ static void ri_GenerateQual(StringInfo buf,
                                Oid opoid,
                                const char *rightop, Oid rightoptype);
 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,
@@ -307,7 +307,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:
 
@@ -514,7 +514,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");
@@ -724,7 +724,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:
@@ -911,7 +911,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:
@@ -1071,7 +1071,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:
@@ -1285,7 +1285,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:
@@ -1501,7 +1501,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:
@@ -1676,7 +1676,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 */
@@ -1732,7 +1732,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;
 
                        /*
@@ -1763,7 +1763,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;
@@ -2057,7 +2057,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\"",
@@ -2860,7 +2860,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;
@@ -2875,7 +2876,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;
index b0559ca5bcd2c71d32272a687b1eebcfec48a193..f8fc7f83f9b14ff87ac330f723d356e266e45acd 100644 (file)
@@ -1242,7 +1242,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;
@@ -1410,7 +1410,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;
@@ -1644,7 +1644,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;
index de502f9bc9ea4d6bd8ae52bb54e4d8a5bf467194..48f92dc430d407380becabf3b6223e07432ae1ef 100644 (file)
@@ -36,6 +36,7 @@
 #include "access/nbtree.h"
 #include "access/reloptions.h"
 #include "access/sysattr.h"
+#include "access/tupdesc_details.h"
 #include "access/xact.h"
 #include "access/xlog.h"
 #include "catalog/catalog.h"
@@ -79,6 +80,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"
@@ -495,6 +497,7 @@ RelationBuildTupleDesc(Relation relation)
        int                     need;
        TupleConstr *constr;
        AttrDefault *attrdef = NULL;
+       AttrMissing *attrmiss = NULL;
        int                     ndef = 0;
 
        /* copy some fields from pg_class row to rd_att */
@@ -540,15 +543,17 @@ RelationBuildTupleDesc(Relation relation)
        while (HeapTupleIsValid(pg_attribute_tuple = systable_getnext(pg_attribute_scan)))
        {
                Form_pg_attribute attp;
+               int                     attnum;
 
                attp = (Form_pg_attribute) GETSTRUCT(pg_attribute_tuple);
 
-               if (attp->attnum <= 0 ||
-                       attp->attnum > relation->rd_rel->relnatts)
+               attnum = attp->attnum;
+               if (attnum <= 0 || attnum > relation->rd_rel->relnatts)
                        elog(ERROR, "invalid attribute number %d for %s",
                                 attp->attnum, RelationGetRelationName(relation));
 
-               memcpy(TupleDescAttr(relation->rd_att, attp->attnum - 1),
+
+               memcpy(TupleDescAttr(relation->rd_att, attnum - 1),
                           attp,
                           ATTRIBUTE_FIXED_PART_SIZE);
 
@@ -556,6 +561,7 @@ RelationBuildTupleDesc(Relation relation)
                if (attp->attnotnull)
                        constr->has_not_null = true;
 
+               /* If the column has a default, fill it into the attrdef array */
                if (attp->atthasdef)
                {
                        if (attrdef == NULL)
@@ -563,10 +569,63 @@ 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++;
                }
+
+               /* Likewise for a missing value */
+               if (attp->atthasmissing)
+               {
+                       Datum           missingval;
+                       bool            missingNull;
+
+                       /* Do we have a missing value? */
+                       missingval = heap_getattr(pg_attribute_tuple,
+                                                                         Anum_pg_attribute_attmissingval,
+                                                                         pg_attribute_desc->rd_att,
+                                                                         &missingNull);
+                       if (!missingNull)
+                       {
+                               /* Yes, fetch from the array */
+                               MemoryContext oldcxt;
+                               bool            is_null;
+                               int                     one = 1;
+                               Datum           missval;
+
+                               if (attrmiss == NULL)
+                                       attrmiss = (AttrMissing *)
+                                               MemoryContextAllocZero(CacheMemoryContext,
+                                                                                          relation->rd_rel->relnatts *
+                                                                                          sizeof(AttrMissing));
+
+                               missval = array_get_element(missingval,
+                                                                                       1,
+                                                                                       &one,
+                                                                                       -1,
+                                                                                       attp->attlen,
+                                                                                       attp->attbyval,
+                                                                                       attp->attalign,
+                                                                                       &is_null);
+                               Assert(!is_null);
+                               if (attp->attbyval)
+                               {
+                                       /* for copy by val just copy the datum direct */
+                                       attrmiss[attnum - 1].ammissing = missval;
+                               }
+                               else
+                               {
+                                       /* otherwise copy in the correct context */
+                                       oldcxt = MemoryContextSwitchTo(CacheMemoryContext);
+                                       attrmiss[attnum - 1].ammissing = datumCopy(missval,
+                                                                                                                          attp->attbyval,
+                                                                                                                          attp->attlen);
+                                       MemoryContextSwitchTo(oldcxt);
+                               }
+                               attrmiss[attnum - 1].ammissingPresent = true;
+                       }
+               }
                need--;
                if (need == 0)
                        break;
@@ -607,7 +666,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 ||
+               attrmiss || relation->rd_rel->relchecks)
        {
                relation->rd_att->constr = constr;
 
@@ -624,6 +684,8 @@ RelationBuildTupleDesc(Relation relation)
                else
                        constr->num_defval = 0;
 
+               constr->missing = attrmiss;
+
                if (relation->rd_rel->relchecks > 0)    /* CHECKs */
                {
                        constr->num_check = relation->rd_rel->relchecks;
@@ -4063,10 +4125,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));
 }
 
 /*
@@ -4405,7 +4463,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 */
@@ -4700,7 +4758,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;
 
        /*
@@ -4762,7 +4820,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;
 
        /*
index ae511e9a23bffd4d0fce7f0e0dd052a94bb10a5f..aa188bdeffa90d4f8e75e14de90ca4f9b11f741a 100644 (file)
@@ -200,7 +200,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;
@@ -294,7 +294,7 @@ fmgr_symbol(Oid functionId, char **mod, char **fn)
        /*
         */
        if (procedureStruct->prosecdef ||
-               !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig) ||
+               !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig, NULL) ||
                FmgrHookIsNeeded(functionId))
        {
                *mod = NULL; /* core binary */
index 20f60392afee149727f51d9b332168b18eaf0b5c..d5984b415edda4128dac03bd431ba40c9f94af9c 100644 (file)
@@ -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 */
index 67342ef63dc304ac6676d7cc81e8698f7ecc0ee3..cebaea097d16ce532c20d9f0070e92bac51f87b1 100644 (file)
@@ -799,7 +799,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,
@@ -830,5 +830,7 @@ 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 size_t varsize_any(void *p);
+extern HeapTuple heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
+extern MinimalTuple minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
 
 #endif                                                 /* HTUP_DETAILS_H */
index 415efbab97e59751cdaf86b573ab25602ee502fb..708160f645edd027b15660e29eec256d8e99b337 100644 (file)
@@ -25,6 +25,8 @@ typedef struct attrDefault
        char       *adbin;                      /* nodeToString representation of expr */
 } AttrDefault;
 
+typedef struct attrMissing *MissingPtr;
+
 typedef struct constrCheck
 {
        char       *ccname;
@@ -38,6 +40,7 @@ typedef struct tupleConstr
 {
        AttrDefault *defval;            /* array */
        ConstrCheck *check;                     /* array */
+       MissingPtr      missing;                /* missing attributes values, NULL if none */
        uint16          num_defval;
        uint16          num_check;
        bool            has_not_null;
diff --git a/src/include/access/tupdesc_details.h b/src/include/access/tupdesc_details.h
new file mode 100644 (file)
index 0000000..741e996
--- /dev/null
@@ -0,0 +1,29 @@
+/*-------------------------------------------------------------------------
+ *
+ * tupdesc_details.h
+ *       POSTGRES tuple descriptor definitions we can't include everywhere
+ *
+ *
+ * Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/access/tupdesc_details.h
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#ifndef TUPDESC_DETAILS_H
+#define TUPDESC_DETAILS_H
+
+/*
+ * Structure used to represent value to be used when the attribute is not
+ * present at all in a tuple, i.e. when the column was created after the tuple
+ */
+
+typedef struct attrMissing
+{
+       bool            ammissingPresent;       /* true if non-NULL missing value exists */
+       Datum           ammissing;              /* value when attribute is missing */
+} AttrMissing;
+
+#endif                                                 /* TUPDESC_DETAILS_H */
index 3308fa3dfd5a1dd3aad4af5add99da1f85a1086d..59fc0524947f5eb3feaeed0859b1ded917a2b7a1 100644 (file)
@@ -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,8 +104,11 @@ extern List *AddRelationNewConstraints(Relation rel,
                                                  bool is_local,
                                                  bool is_internal);
 
+extern void RelationClearMissing(Relation rel);
+
 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,
index 8159383834d26c25a36a4c013b551829c92e518f..5bb64f7c31dc97f8c83bf1b1f9525928db9b3413 100644 (file)
@@ -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 BKI_DEFAULT(f);
 
+       /* Has a missing value or not */
+       bool            atthasmissing BKI_DEFAULT(f);
+
        /* One of the ATTRIBUTE_IDENTITY_* constants below, or '\0' */
        char            attidentity BKI_DEFAULT("");
 
@@ -167,6 +170,12 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
 
        /* Column-level FDW options */
        text            attfdwoptions[1] BKI_DEFAULT(_null_);
+
+       /*
+        * Missing value for added columns. This is a one element array which lets
+        * us store a value of the attribute type here.
+        */
+       anyarray        attmissingval BKI_DEFAULT(_null_);
 #endif
 } FormData_pg_attribute;
 
@@ -191,7 +200,7 @@ typedef FormData_pg_attribute *Form_pg_attribute;
  * ----------------
  */
 
-#define Natts_pg_attribute                             22
+#define Natts_pg_attribute                             24
 #define Anum_pg_attribute_attrelid             1
 #define Anum_pg_attribute_attname              2
 #define Anum_pg_attribute_atttypid             3
@@ -206,15 +215,16 @@ typedef FormData_pg_attribute *Form_pg_attribute;
 #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 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
index 85cdb99f1f642b002d89c7a61bda73252e3f13cf..135f33d0f30e5d563b0a1558ab776e5ef4c02ed5 100644 (file)
@@ -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 t n f 0 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 t n f 0 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 t n f 0 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 28 0 t f f f f f t n f 0 3 1 _null_ _null_ _null_));
 DESCR("");
index 88c680308155207b2996dd259eb13541bb5e27ea..008e859d4c2f4f287de5777b599eab24a4d888e6 100644 (file)
@@ -389,8 +389,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 $$
@@ -404,7 +402,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 (file)
index 0000000..9e74068
--- /dev/null
@@ -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 different 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 
+-----+-------+-----
+ 200 | 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;
index fda54d4b67d63f99c0286f10af2318d346890007..d858a0e7db4cdf5da39960299ab9fe52cd7b12b5 100644 (file)
@@ -116,7 +116,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid c
 # ----------
 # Another group of parallel tests
 # ----------
-test: identity partition_join partition_prune reloptions hash_part indexing partition_aggregate
+test: identity partition_join partition_prune reloptions hash_part indexing partition_aggregate fast_default
 
 # event triggers cannot run concurrently with any test that runs DDL
 test: event_trigger
index f79e31d02864da2124b315fff75f1e829b5cd079..99f8ca37ba9b7d9896255e96bf3ea008fd1fd388 100644 (file)
@@ -187,5 +187,6 @@ test: reloptions
 test: hash_part
 test: indexing
 test: partition_aggregate
+test: fast_default
 test: event_trigger
 test: stats
diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql
new file mode 100644 (file)
index 0000000..6a3f9b3
--- /dev/null
@@ -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 different 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;