Fast ALTER TABLE ADD COLUMN with a non-NULL default

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
This commit is contained in:
Andrew Dunstan 2018-03-28 10:43:52 +10:30
parent ef1978d6ed
commit 16828d5c02
36 changed files with 1897 additions and 243 deletions

View File

@ -1149,6 +1149,19 @@
</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>
@ -1229,6 +1242,20 @@
</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>

View 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>

View 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"
@ -76,6 +77,74 @@
* ----------------------------------------------------------------
*/
/*
* 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;
Form_pg_attribute attr = TupleDescAttr(tupleDesc, i);
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;
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);
}
/*

View File

@ -21,12 +21,14 @@
#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;

View 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,

View File

@ -60,9 +60,12 @@
#include "catalog/storage_xlog.h"
#include "commands/tablecmds.h"
#include "commands/typecmds.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/var.h"
#include "optimizer/planner.h"
#include "parser/parse_coerce.h"
#include "parser/parse_collate.h"
#include "parser/parse_expr.h"
@ -72,6 +75,7 @@
#include "storage/smgr.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/datum.h"
#include "utils/fmgroids.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
@ -144,37 +148,37 @@ static List *insert_ordered_unique_oid(List *list, Oid datum);
static FormData_pg_attribute a1 = {
0, {"ctid"}, TIDOID, 0, sizeof(ItemPointerData),
SelfItemPointerAttributeNumber, 0, -1, -1,
false, 'p', 's', true, false, '\0', false, true, 0
false, 'p', 's', true, false, false, '\0', false, true, 0
};
static FormData_pg_attribute a2 = {
0, {"oid"}, OIDOID, 0, sizeof(Oid),
ObjectIdAttributeNumber, 0, -1, -1,
true, 'p', 'i', true, false, '\0', false, true, 0
true, 'p', 'i', true, false, false, '\0', false, true, 0
};
static FormData_pg_attribute a3 = {
0, {"xmin"}, XIDOID, 0, sizeof(TransactionId),
MinTransactionIdAttributeNumber, 0, -1, -1,
true, 'p', 'i', true, false, '\0', false, true, 0
true, 'p', 'i', true, false, false, '\0', false, true, 0
};
static FormData_pg_attribute a4 = {
0, {"cmin"}, CIDOID, 0, sizeof(CommandId),
MinCommandIdAttributeNumber, 0, -1, -1,
true, 'p', 'i', true, false, '\0', false, true, 0
true, 'p', 'i', true, false, false, '\0', false, true, 0
};
static FormData_pg_attribute a5 = {
0, {"xmax"}, XIDOID, 0, sizeof(TransactionId),
MaxTransactionIdAttributeNumber, 0, -1, -1,
true, 'p', 'i', true, false, '\0', false, true, 0
true, 'p', 'i', true, false, false, '\0', false, true, 0
};
static FormData_pg_attribute a6 = {
0, {"cmax"}, CIDOID, 0, sizeof(CommandId),
MaxCommandIdAttributeNumber, 0, -1, -1,
true, 'p', 'i', true, false, '\0', false, true, 0
true, 'p', 'i', true, false, false, '\0', false, true, 0
};
/*
@ -186,7 +190,7 @@ static FormData_pg_attribute a6 = {
static FormData_pg_attribute a7 = {
0, {"tableoid"}, OIDOID, 0, sizeof(Oid),
TableOidAttributeNumber, 0, -1, -1,
true, 'p', 'i', true, false, '\0', false, true, 0
true, 'p', 'i', true, false, false, '\0', false, true, 0
};
static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7};
@ -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;

View 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);

View 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);
}
}

View 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);

View 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);

View 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,

View 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

View 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)

View 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);
}

View 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...

View 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

View 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;

View 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;
}

View 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;

View 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);
}
/*

View 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;

View 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;

View 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;
/*

View 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 */

View 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 */

View 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 */

View 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;

View File

@ -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 */

View 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,

View 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

View 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("");

View 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

View File

@ -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;

View 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

View File

@ -187,5 +187,6 @@ test: reloptions
test: hash_part
test: indexing
test: partition_aggregate
test: fast_default
test: event_trigger
test: stats

View File

@ -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;