On 01/16/2018 12:13 AM, David Rowley wrote: > On 2 January 2018 at 05:01, Andrew Dunstan > <andrew.duns...@2ndquadrant.com> wrote: >> New version of the patch that fills in the remaining piece in >> equalTupleDescs. > This no longer applies to current master. Can send an updated patch? >
Yeah, got caught by the bki/pg_attribute changes on Friday. here's an updated version. Thanks for looking. cheers andrew -- Andrew Dunstan https://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml index 3f02202..d5dc14a 100644 --- a/doc/src/sgml/catalogs.sgml +++ b/doc/src/sgml/catalogs.sgml @@ -1150,6 +1150,18 @@ </row> <row> + <entry><structfield>atthasmissing</structfield></entry> + <entry><type>bool</type></entry> + <entry></entry> + <entry> + This column has a value which is used where the column is entirely + missing from the row, as happens when a column is added after the + row is created. The actual value used is stored in the + <structname>attmissingval</structname> column. + </entry> + </row> + + <row> <entry><structfield>attidentity</structfield></entry> <entry><type>char</type></entry> <entry></entry> @@ -1229,6 +1241,18 @@ </entry> </row> + <row> + <entry><structfield>attmissingval</structfield></entry> + <entry><type>bytea</type></entry> + <entry></entry> + <entry> + This column has a binary representation of the value used when the column + is entirely missing from the row, as happens when the column is added after + the row is created. The value is only used when + <structname>atthasmissing</structname> is true. + </entry> + </row> + </tbody> </tgroup> </table> diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml index 7bcf242..780bead 100644 --- a/doc/src/sgml/ref/alter_table.sgml +++ b/doc/src/sgml/ref/alter_table.sgml @@ -1115,26 +1115,28 @@ ALTER TABLE [ IF EXISTS ] <replaceable class="parameter">name</replaceable> </para> <para> - When a column is added with <literal>ADD COLUMN</literal>, all existing - rows in the table are initialized with the column's default value - (NULL if no <literal>DEFAULT</literal> clause is specified). - If there is no <literal>DEFAULT</literal> clause, this is merely a metadata - change and does not require any immediate update of the table's data; - the added NULL values are supplied on readout, instead. + When a column is added with <literal>ADD COLUMN</literal> and a + non-volatile <literal>DEFAULT</literal> is specified, the default + is evaluated at the time of the statement and the result stored in the + table's metadata. That value will be used for the column for + all existing rows. If no <literal>DEFAULT</literal> is specified + NULL is used. In neither case is a rewrite of the table required. </para> <para> - Adding a column with a <literal>DEFAULT</literal> clause or changing the type of - an existing column will require the entire table and its indexes to be - rewritten. As an exception when changing the type of an existing column, - if the <literal>USING</literal> clause does not change the column - contents and the old type is either binary coercible to the new type or - an unconstrained domain over the new type, a table rewrite is not needed; - but any indexes on the affected columns must still be rebuilt. Adding or - removing a system <literal>oid</literal> column also requires rewriting the entire - table. Table and/or index rebuilds may take a significant amount of time - for a large table; and will temporarily require as much as double the disk - space. + Adding a column with a volatile <literal>DEFAULT</literal> or + changing the type of + an existing column will require the entire table and its indexes to be + rewritten. As an exception when changing the type of an existing column, + if the <literal>USING</literal> clause does not change the column + contents and the old type is either binary coercible to the new type or + an unconstrained domain over the new type, a table rewrite is not needed; + but any indexes on the affected columns must still be rebuilt. Adding or + removing a system <literal>oid</literal> column also requires rewriting + the entire table. + Table and/or index rebuilds may take a significant amount of time + for a large table; and will temporarily require as much as double the disk + space. </para> <para> diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index 0a13251..5ea3f1c 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -76,6 +76,116 @@ * ---------------------------------------------------------------- */ +/* + * Return the missing value of an attribute, or NULL if it + * there is no missing value. + */ +static Datum +getmissingattr(TupleDesc tupleDesc, + int attnum, bool *isnull) +{ + int missingnum; + Form_pg_attribute att; + AttrMissing *attrmiss; + + Assert(attnum <= tupleDesc->natts); + Assert(attnum > 0); + + att = TupleDescAttr(tupleDesc, attnum - 1); + + if (att->atthasmissing) + { + Assert(tupleDesc->constr); + Assert(tupleDesc->constr->num_missing > 0); + + attrmiss = tupleDesc->constr->missing; + + /* + * Look through the tupledesc's attribute missing values + * for the one that corresponds to this attribute. + */ + for (missingnum = tupleDesc->constr->num_missing - 1; + missingnum >= 0; missingnum--) + { + if (attrmiss[missingnum].amnum == attnum) + { + if (attrmiss[missingnum].ammissingNull) + { + *isnull = true; + return (Datum) 0; + } + else + { + *isnull = false; + return attrmiss[missingnum].ammissing; + } + } + } + Assert(false); /* should not be able to get here */ + } + + *isnull = true; + return PointerGetDatum(NULL); +} + +/* + * Fill in missing values for a TupleTableSlot + */ +static void +slot_getmissingattrs(TupleTableSlot *slot, int startAttNum) +{ + int tdesc_natts = slot->tts_tupleDescriptor->natts; + AttrMissing *attrmiss; + int missingnum; + int missattnum; + int i; + + if (slot->tts_tupleDescriptor->constr) + { + missingnum = slot->tts_tupleDescriptor->constr->num_missing -1; + attrmiss = slot->tts_tupleDescriptor->constr->missing; + } + else + { + missingnum = -1; + attrmiss = NULL; + } + + for (missattnum = tdesc_natts - 1; missattnum >= startAttNum; missattnum--) + { + /* + * Loop through the list of missing attributes (if any) looking for + * a corresponding entry. Don't assume that the missing values + * are in attribute order. + */ + for (i = missingnum; i >= 0; i--) + { + Assert(attrmiss != NULL); + if (attrmiss[i].amnum - 1 == missattnum) + { + if (attrmiss[i].ammissingNull) + { + slot->tts_values[missattnum] = PointerGetDatum(NULL); + slot->tts_isnull[missattnum] = true; + } + else + { + slot->tts_values[missattnum] = attrmiss[i].ammissing; + slot->tts_isnull[missattnum] = false; + } + break; + } + } + /* + * If there is no corresponding missing entry use NULL. + */ + if (i < 0) + { + slot->tts_values[missattnum] = PointerGetDatum(NULL); + slot->tts_isnull[missattnum] = true; + } + } +} /* * heap_compute_data_size @@ -133,6 +243,131 @@ heap_compute_data_size(TupleDesc tupleDesc, } /* + * Fill in one attribute, either a data value or a bit in the null bitmask + */ +static inline void +fill_val(Form_pg_attribute att, + bits8 **bit, + int *bitmask, + char **dataP, + uint16 *infomask, + Datum datum, + bool isnull) +{ + Size data_length; + char *data = *dataP; + + /* + * If we're building a null bitmap, set the appropriate bit for the + * current column value here. + */ + if (bit != NULL) + { + if (*bitmask != HIGHBIT) + *bitmask <<= 1; + else + { + *bit += 1; + **bit = 0x0; + *bitmask = 1; + } + + if (isnull) + { + *infomask |= HEAP_HASNULL; + return; + } + + **bit |= *bitmask; + } + + Assert(att); + + /* + * XXX we use the att_align macros on the pointer value itself, not on an + * offset. This is a bit of a hack. + */ + if (att->attbyval) + { + /* pass-by-value */ + data = (char *) att_align_nominal(data, att->attalign); + store_att_byval(data, datum, att->attlen); + data_length = att->attlen; + } + else if (att->attlen == -1) + { + /* varlena */ + Pointer val = DatumGetPointer(datum); + + *infomask |= HEAP_HASVARWIDTH; + if (VARATT_IS_EXTERNAL(val)) + { + if (VARATT_IS_EXTERNAL_EXPANDED(val)) + { + /* + * we want to flatten the expanded value so that the + * constructed tuple doesn't depend on it + */ + ExpandedObjectHeader *eoh = DatumGetEOHP(datum); + + data = (char *) att_align_nominal(data, + att->attalign); + data_length = EOH_get_flat_size(eoh); + EOH_flatten_into(eoh, data, data_length); + } + else + { + *infomask |= HEAP_HASEXTERNAL; + /* no alignment, since it's short by definition */ + data_length = VARSIZE_EXTERNAL(val); + memcpy(data, val, data_length); + } + } + else if (VARATT_IS_SHORT(val)) + { + /* no alignment for short varlenas */ + data_length = VARSIZE_SHORT(val); + memcpy(data, val, data_length); + } + else if (VARLENA_ATT_IS_PACKABLE(att) && + VARATT_CAN_MAKE_SHORT(val)) + { + /* convert to short varlena -- no alignment */ + data_length = VARATT_CONVERTED_SHORT_SIZE(val); + SET_VARSIZE_SHORT(data, data_length); + memcpy(data + 1, VARDATA(val), data_length - 1); + } + else + { + /* full 4-byte header varlena */ + data = (char *) att_align_nominal(data, + att->attalign); + data_length = VARSIZE(val); + memcpy(data, val, data_length); + } + } + else if (att->attlen == -2) + { + /* cstring ... never needs alignment */ + *infomask |= HEAP_HASVARWIDTH; + Assert(att->attalign == 'c'); + data_length = strlen(DatumGetCString(datum)) + 1; + memcpy(data, DatumGetPointer(datum), data_length); + } + else + { + /* fixed-length pass-by-reference */ + data = (char *) att_align_nominal(data, att->attalign); + Assert(att->attlen > 0); + data_length = att->attlen; + memcpy(data, DatumGetPointer(datum), data_length); + } + + data += data_length; + *dataP = data; +} + +/* * heap_fill_tuple * Load data portion of a tuple from values/isnull arrays * @@ -172,111 +407,15 @@ heap_fill_tuple(TupleDesc tupleDesc, for (i = 0; i < numberOfAttributes; i++) { - Form_pg_attribute att = TupleDescAttr(tupleDesc, i); - Size data_length; - - if (bit != NULL) - { - if (bitmask != HIGHBIT) - bitmask <<= 1; - else - { - bitP += 1; - *bitP = 0x0; - bitmask = 1; - } - - if (isnull[i]) - { - *infomask |= HEAP_HASNULL; - continue; - } - - *bitP |= bitmask; - } - - /* - * XXX we use the att_align macros on the pointer value itself, not on - * an offset. This is a bit of a hack. - */ - - if (att->attbyval) - { - /* pass-by-value */ - data = (char *) att_align_nominal(data, att->attalign); - store_att_byval(data, values[i], att->attlen); - data_length = att->attlen; - } - else if (att->attlen == -1) - { - /* varlena */ - Pointer val = DatumGetPointer(values[i]); - - *infomask |= HEAP_HASVARWIDTH; - if (VARATT_IS_EXTERNAL(val)) - { - if (VARATT_IS_EXTERNAL_EXPANDED(val)) - { - /* - * we want to flatten the expanded value so that the - * constructed tuple doesn't depend on it - */ - ExpandedObjectHeader *eoh = DatumGetEOHP(values[i]); - - data = (char *) att_align_nominal(data, - att->attalign); - data_length = EOH_get_flat_size(eoh); - EOH_flatten_into(eoh, data, data_length); - } - else - { - *infomask |= HEAP_HASEXTERNAL; - /* no alignment, since it's short by definition */ - data_length = VARSIZE_EXTERNAL(val); - memcpy(data, val, data_length); - } - } - else if (VARATT_IS_SHORT(val)) - { - /* no alignment for short varlenas */ - data_length = VARSIZE_SHORT(val); - memcpy(data, val, data_length); - } - else if (VARLENA_ATT_IS_PACKABLE(att) && - VARATT_CAN_MAKE_SHORT(val)) - { - /* convert to short varlena -- no alignment */ - data_length = VARATT_CONVERTED_SHORT_SIZE(val); - SET_VARSIZE_SHORT(data, data_length); - memcpy(data + 1, VARDATA(val), data_length - 1); - } - else - { - /* full 4-byte header varlena */ - data = (char *) att_align_nominal(data, - att->attalign); - data_length = VARSIZE(val); - memcpy(data, val, data_length); - } - } - else if (att->attlen == -2) - { - /* cstring ... never needs alignment */ - *infomask |= HEAP_HASVARWIDTH; - Assert(att->attalign == 'c'); - data_length = strlen(DatumGetCString(values[i])) + 1; - memcpy(data, DatumGetPointer(values[i]), data_length); - } - else - { - /* fixed-length pass-by-reference */ - data = (char *) att_align_nominal(data, att->attalign); - Assert(att->attlen > 0); - data_length = att->attlen; - memcpy(data, DatumGetPointer(values[i]), data_length); - } - - data += data_length; + Form_pg_attribute attr = TupleDescAttr(tupleDesc, i); + + fill_val(attr, + bitP ? &bitP : NULL, + &bitmask, + &data, + infomask, + values ? values[i] : PointerGetDatum(NULL), + isnull ? isnull[i] : true); } Assert((data - start) == data_size); @@ -293,10 +432,27 @@ heap_fill_tuple(TupleDesc tupleDesc, * ---------------- */ bool -heap_attisnull(HeapTuple tup, int attnum) +heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc) { + /* + * We allow a NULL tupledesc for relations not expected to have + * missing values, such as catalog relations and indexes. + */ + Assert(!tupleDesc || attnum <= tupleDesc->natts); + if (attnum > (int) HeapTupleHeaderGetNatts(tup->t_data)) - return true; + { + if (tupleDesc && + attnum <= tupleDesc->natts && + TupleDescAttr(tupleDesc, attnum - 1)->atthasmissing) + { + return false; + } + else + { + return true; + } + } if (attnum > 0) { @@ -649,6 +805,293 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest) memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len); } +/* + * Expand a tuple with missing values, or NULLs. The source tuple must have + * less attributes than the required number. Only one of targetHeapTuple + * and targetMinimalTuple may be supplied. The other argument must be NULL. + */ +static void +expand_tuple(HeapTuple *targetHeapTuple, + MinimalTuple *targetMinimalTuple, + HeapTuple sourceTuple, + TupleDesc tupleDesc) +{ + AttrMissing *attrmiss = NULL; + int attnum; + int missingnum; + int firstmissingnum = 0; + int num_missing = 0; + bool hasNulls = HeapTupleHasNulls(sourceTuple); + HeapTupleHeader targetTHeader; + HeapTupleHeader sourceTHeader = sourceTuple->t_data; + int sourceNatts = HeapTupleHeaderGetNatts(sourceTHeader); + int natts = tupleDesc->natts; + int sourceIndicatorLen; + int targetIndicatorLen; + Size sourceDataLen = sourceTuple->t_len - sourceTHeader->t_hoff; + Size targetDataLen; + Size len; + int hoff; + bits8 *nullBits = NULL; + int bitMask = 0; + char *targetData; + uint16 *infoMask; + + Assert((targetHeapTuple && !targetMinimalTuple) + || (!targetHeapTuple && targetMinimalTuple)); + + Assert(sourceNatts < natts); + + sourceIndicatorLen = (hasNulls ? BITMAPLEN(sourceNatts) : 0); + + targetDataLen = sourceDataLen; + + if (tupleDesc->constr && + tupleDesc->constr->num_missing) + { + /* + * If there are missing values we want to put them into the tuple. + * Before that we have to compute the extra length for the values + * array and the variable length data. + */ + num_missing = tupleDesc->constr->num_missing; + attrmiss = tupleDesc->constr->missing; + + /* + * Find the first item in attrmiss for which we don't have a value in + * the source (i.e. where its amnum is > sourceNatts). We can ignore + * all the missing entries before that. + */ + for (firstmissingnum = 0; + firstmissingnum < num_missing + && attrmiss[firstmissingnum].amnum <= sourceNatts; + firstmissingnum++) + { /* empty */ + } + + /* + * If there are no more missing values everything else must be + * NULL + */ + if (firstmissingnum >= num_missing) + { + hasNulls = true; + } + + /* + * Now walk the missing attributes one by one. If there is a + * missing value make space for it. Otherwise, it's going to be NULL. + */ + for (attnum = sourceNatts + 1; + attnum <= natts; + attnum++) + { + Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum - 1); + + for (missingnum = firstmissingnum; missingnum < num_missing; missingnum++) + { + + /* + * If there is a missing value for this attribute calculate + * the required space if it's not NULL. + */ + if (attrmiss[missingnum].amnum == attnum) + { + if (attrmiss[missingnum].ammissingNull) + hasNulls = true; + else + { + targetDataLen = att_align_datum(targetDataLen, + att->attalign, + att->attlen, + attrmiss[missingnum].ammissing); + + targetDataLen = att_addlength_pointer(targetDataLen, + att->attlen, + attrmiss[missingnum].ammissing); + } + break; + } + } + if (missingnum > num_missing) + { + /* there is no missing value for this attribute */ + hasNulls = true; + } + } + } /* end if have missing values */ + else + { + /* + * If there are no missing values at all then NULLS must be allowed, + * since some of the attributes are known to be absent. + */ + hasNulls = true; + } + + len = 0; + + if (hasNulls) + { + targetIndicatorLen = BITMAPLEN(natts); + len += targetIndicatorLen; + } + else + targetIndicatorLen = 0; + + if (tupleDesc->tdhasoid) + len += sizeof(Oid); + + /* + * Allocate and zero the space needed. Note that the tuple body and + * HeapTupleData management structure are allocated in one chunk. + */ + if (targetHeapTuple) + { + len += offsetof(HeapTupleHeaderData, t_bits); + hoff = len = MAXALIGN(len); /* align user data safely */ + len += targetDataLen; + + *targetHeapTuple = (HeapTuple) palloc0(HEAPTUPLESIZE + len); + (*targetHeapTuple)->t_data + = targetTHeader + = (HeapTupleHeader) ((char *) *targetHeapTuple + HEAPTUPLESIZE); + (*targetHeapTuple)->t_len = len; + (*targetHeapTuple)->t_tableOid = sourceTuple->t_tableOid; + ItemPointerSetInvalid(&((*targetHeapTuple)->t_self)); + + targetTHeader->t_infomask = sourceTHeader->t_infomask; + targetTHeader->t_hoff = hoff; + HeapTupleHeaderSetNatts(targetTHeader, natts); + HeapTupleHeaderSetDatumLength(targetTHeader, len); + HeapTupleHeaderSetTypeId(targetTHeader, tupleDesc->tdtypeid); + HeapTupleHeaderSetTypMod(targetTHeader, tupleDesc->tdtypmod); + /* We also make sure that t_ctid is invalid unless explicitly set */ + ItemPointerSetInvalid(&(targetTHeader->t_ctid)); + if (targetIndicatorLen > 0) + nullBits = (bits8 *) ((char *) (*targetHeapTuple)->t_data + + offsetof(HeapTupleHeaderData, t_bits)); + targetData = (char *) (*targetHeapTuple)->t_data + hoff; + infoMask = &(targetTHeader->t_infomask); + } + else + { + len += SizeofMinimalTupleHeader; + hoff = len = MAXALIGN(len); /* align user data safely */ + len += targetDataLen; + + *targetMinimalTuple = (MinimalTuple) palloc0(len); + (*targetMinimalTuple)->t_len = len; + (*targetMinimalTuple)->t_hoff = hoff + MINIMAL_TUPLE_OFFSET; + (*targetMinimalTuple)->t_infomask = sourceTHeader->t_infomask; + /* Same macro works for MinimalTuples */ + HeapTupleHeaderSetNatts(*targetMinimalTuple, natts); + if (targetIndicatorLen > 0) + nullBits = (bits8 *) ((char *) *targetMinimalTuple + + offsetof(MinimalTupleData, t_bits)); + targetData = (char *) *targetMinimalTuple + hoff; + infoMask = &((*targetMinimalTuple)->t_infomask); + } + + if (targetIndicatorLen > 0) + { + if (sourceIndicatorLen > 0) + { + /* if bitmap pre-existed copy in - all is set */ + memcpy(nullBits, + ((char *) sourceTHeader) + + offsetof(HeapTupleHeaderData, t_bits), + sourceIndicatorLen); + nullBits += sourceIndicatorLen - 1; + } + else + { + sourceIndicatorLen = BITMAPLEN(sourceNatts); + /* Set NOT NULL for all existing attributes */ + memset(nullBits, 0xff, sourceIndicatorLen); + + nullBits += sourceIndicatorLen - 1; + + if (sourceNatts & 0x07) + { + /* build the mask (inverted!) */ + bitMask = 0xff << (sourceNatts & 0x07); + /* Voila */ + *nullBits = ~bitMask; + } + } + + bitMask = (1 << ((sourceNatts - 1) & 0x07)); + } /* End if have null bitmap */ + + memcpy(targetData, + ((char *) sourceTuple->t_data) + sourceTHeader->t_hoff, + sourceDataLen); + + /* If there are no missing values there is nothing more to do */ + if (firstmissingnum < num_missing) + { + targetData += sourceDataLen; + missingnum = firstmissingnum; + + /* Now fill in the missing values */ + for (attnum = sourceNatts + 1; attnum <= natts; attnum++) + { + + Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum - 1); + + for (missingnum = firstmissingnum; missingnum < num_missing; missingnum++) + { + if (attrmiss[missingnum].amnum == attnum) + { + fill_val(attr, + nullBits ? &nullBits : NULL, + &bitMask, + &targetData, + infoMask, + attrmiss[missingnum].ammissing, + attrmiss[missingnum].ammissingNull); + break; + } + } + if (missingnum > num_missing) + { + fill_val(attr, + &nullBits, + &bitMask, + &targetData, + infoMask, + (Datum) 0, + true); + } + } /* end loop over missing attributes */ + } /* end if there is no missing value */ +} + +/* + * Fill in the missing values for an ordinary HeapTuple + */ +MinimalTuple +minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc) +{ + MinimalTuple minimalTuple; + + expand_tuple(NULL, &minimalTuple, sourceTuple, tupleDesc); + return minimalTuple; +} + +/* + * Fill in the missing values for a minimal HeapTuple + */ +HeapTuple +heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc) +{ + HeapTuple heapTuple; + + expand_tuple(&heapTuple, NULL, sourceTuple, tupleDesc); + return heapTuple; +} + /* ---------------- * heap_copy_tuple_as_datum * @@ -1192,8 +1635,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull) tup = tuple->t_data; if (attnum > HeapTupleHeaderGetNatts(tup)) { - *isnull = true; - return (Datum) 0; + return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull); } /* @@ -1263,13 +1705,13 @@ slot_getallattrs(TupleTableSlot *slot) /* * If tuple doesn't have all the atts indicated by tupleDesc, read the - * rest as null + * rest as NULLS or missing values. */ - for (; attnum < tdesc_natts; attnum++) + if (attnum < tdesc_natts) { - slot->tts_values[attnum] = (Datum) 0; - slot->tts_isnull[attnum] = true; + slot_getmissingattrs(slot, attnum); } + slot->tts_nvalid = tdesc_natts; } @@ -1310,12 +1752,11 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum) /* * If tuple doesn't have all the atts indicated by tupleDesc, read the - * rest as null + * rest as NULLs or missing values */ - for (; attno < attnum; attno++) + if (attno < attnum) { - slot->tts_values[attno] = (Datum) 0; - slot->tts_isnull[attno] = true; + slot_getmissingattrs(slot, attno); } slot->tts_nvalid = attnum; } @@ -1340,7 +1781,7 @@ slot_attisnull(TupleTableSlot *slot, int attnum) elog(ERROR, "cannot extract system attribute from virtual tuple"); if (tuple == &(slot->tts_minhdr)) /* internal error */ elog(ERROR, "cannot extract system attribute from minimal tuple"); - return heap_attisnull(tuple, attnum); + return heap_attisnull(tuple, attnum, tupleDesc); } /* @@ -1363,7 +1804,7 @@ slot_attisnull(TupleTableSlot *slot, int attnum) elog(ERROR, "cannot extract attribute from empty tuple slot"); /* and let the tuple tell it */ - return heap_attisnull(tuple, attnum); + return heap_attisnull(tuple, attnum, tupleDesc); } /* diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index f1f4423..8538359 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -27,6 +27,7 @@ #include "parser/parse_type.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/datum.h" #include "utils/hashutils.h" #include "utils/resowner_private.h" #include "utils/syscache.h" @@ -176,6 +177,24 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc) } } + if((cpy->num_missing = constr->num_missing) > 0) + { + cpy->missing = (AttrMissing *) palloc(cpy->num_missing * sizeof(AttrMissing)); + memcpy(cpy->missing, constr->missing, cpy->num_missing * sizeof(AttrMissing)); + for (i = cpy->num_missing - 1; i >= 0; i--) + { + if (constr->missing[i].ammissing) + { + int attnum = constr->missing[i].amnum - 1; + Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum); + + cpy->missing[i].ammissing = datumCopy(constr->missing[i].ammissing, + attr->attbyval, + attr->attlen); + } + } + } + if ((cpy->num_check = constr->num_check) > 0) { cpy->check = (ConstrCheck *) palloc(cpy->num_check * sizeof(ConstrCheck)); @@ -309,6 +328,20 @@ FreeTupleDesc(TupleDesc tupdesc) } pfree(attrdef); } + if (tupdesc->constr->num_missing > 0) + { + AttrMissing *attrmiss = tupdesc->constr->missing; + + Assert(attrmiss != NULL); + + for (i = tupdesc->constr->num_missing - 1; i >= 0; i--) + { + if (attrmiss[i].ammissing + && !TupleDescAttr(tupdesc, attrmiss[i].amnum - 1)->attbyval) + pfree(DatumGetPointer(attrmiss[i].ammissing)); + } + pfree(attrmiss); + } if (tupdesc->constr->num_check > 0) { ConstrCheck *check = tupdesc->constr->check; @@ -469,6 +502,35 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) if (strcmp(defval1->adbin, defval2->adbin) != 0) return false; } + n = constr1->num_missing; + if (n != (int) constr2->num_missing) + return false; + for (i = 0; i < n; i++) + { + AttrMissing *missval1 = constr1->missing + i; + AttrMissing *missval2 = constr2->missing; + + /* Similar logic to that used for defaults */ + for (j = 0; j < n; missval2++, j++) + { + if (missval1->amnum == missval2->amnum) + break; + } + if (j >= n) + return false; + if (missval1->ammissingNull != missval2->ammissingNull) + return false; + if (missval1->ammissing && missval2->ammissing) + { + Form_pg_attribute missatt1 = TupleDescAttr(tupdesc1, missval1->amnum - 1); + + if (!datumIsEqual(missval1->ammissing, missval2->ammissing, + missatt1->attbyval, missatt1->attlen)) + return false; + } + else if (missval1->ammissing || missval2->ammissing) + return false; + } n = constr1->num_check; if (n != (int) constr2->num_check) return false; @@ -584,6 +646,7 @@ TupleDescInitEntry(TupleDesc desc, att->attnotnull = false; att->atthasdef = false; + att->atthasmissing = false; att->attidentity = '\0'; att->attisdropped = false; att->attislocal = true; @@ -797,7 +860,9 @@ BuildDescForRelation(List *schema) constr->has_not_null = true; constr->defval = NULL; + constr->missing = NULL; constr->num_defval = 0; + constr->num_missing = 0; constr->check = NULL; constr->num_check = 0; desc->constr = constr; diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index fac8061..8640103 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -4444,7 +4444,7 @@ pg_attribute_aclcheck_all(Oid table_oid, Oid roleid, AclMode mode, * grants no privileges, so that we can fall out quickly in the very * common case where attacl is null. */ - if (heap_attisnull(attTuple, Anum_pg_attribute_attacl)) + if (heap_attisnull(attTuple, Anum_pg_attribute_attacl, NULL)) attmask = 0; else attmask = pg_attribute_aclmask(table_oid, curr_att, roleid, diff --git a/src/backend/catalog/genbki.pl b/src/backend/catalog/genbki.pl index ed90a02..3b0be39 100644 --- a/src/backend/catalog/genbki.pl +++ b/src/backend/catalog/genbki.pl @@ -416,7 +416,6 @@ sub morph_row_for_pgattr } elsif ($priornotnull) { - # attnotnull will automatically be set if the type is # fixed-width and prior columns are all NOT NULL --- # compare DefineAttr in bootstrap.c. oidvector and diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 089b796..b9133c9 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -60,9 +60,12 @@ #include "catalog/storage_xlog.h" #include "commands/tablecmds.h" #include "commands/typecmds.h" +#include "executor/executor.h" #include "miscadmin.h" #include "nodes/nodeFuncs.h" +#include "optimizer/clauses.h" #include "optimizer/var.h" +#include "optimizer/planner.h" #include "parser/parse_coerce.h" #include "parser/parse_collate.h" #include "parser/parse_expr.h" @@ -72,6 +75,7 @@ #include "storage/smgr.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" @@ -144,37 +148,37 @@ static List *insert_ordered_unique_oid(List *list, Oid datum); static FormData_pg_attribute a1 = { 0, {"ctid"}, TIDOID, 0, sizeof(ItemPointerData), SelfItemPointerAttributeNumber, 0, -1, -1, - false, 'p', 's', true, false, '\0', false, true, 0 + false, 'p', 's', true, false, false, '\0', false, true, 0 }; static FormData_pg_attribute a2 = { 0, {"oid"}, OIDOID, 0, sizeof(Oid), ObjectIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, '\0', false, true, 0 + true, 'p', 'i', true, false, false, '\0', false, true, 0 }; static FormData_pg_attribute a3 = { 0, {"xmin"}, XIDOID, 0, sizeof(TransactionId), MinTransactionIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, '\0', false, true, 0 + true, 'p', 'i', true, false, false, '\0', false, true, 0 }; static FormData_pg_attribute a4 = { 0, {"cmin"}, CIDOID, 0, sizeof(CommandId), MinCommandIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, '\0', false, true, 0 + true, 'p', 'i', true, false, false, '\0', false, true, 0 }; static FormData_pg_attribute a5 = { 0, {"xmax"}, XIDOID, 0, sizeof(TransactionId), MaxTransactionIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, '\0', false, true, 0 + true, 'p', 'i', true, false, false, '\0', false, true, 0 }; static FormData_pg_attribute a6 = { 0, {"cmax"}, CIDOID, 0, sizeof(CommandId), MaxCommandIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, '\0', false, true, 0 + true, 'p', 'i', true, false, false, '\0', false, true, 0 }; /* @@ -186,7 +190,7 @@ static FormData_pg_attribute a6 = { static FormData_pg_attribute a7 = { 0, {"tableoid"}, OIDOID, 0, sizeof(Oid), TableOidAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, '\0', false, true, 0 + true, 'p', 'i', true, false, false, '\0', false, true, 0 }; static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7}; @@ -623,6 +627,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign); values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull); values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef); + values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing); values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity); values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped); values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal); @@ -633,6 +638,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, nulls[Anum_pg_attribute_attacl - 1] = true; nulls[Anum_pg_attribute_attoptions - 1] = true; nulls[Anum_pg_attribute_attfdwoptions - 1] = true; + nulls[Anum_pg_attribute_attmissingval - 1] = true; tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls); @@ -1927,7 +1933,7 @@ heap_drop_with_catalog(Oid relid) */ Oid StoreAttrDefault(Relation rel, AttrNumber attnum, - Node *expr, bool is_internal) + Node *expr, bool is_internal, bool add_column_mode) { char *adbin; char *adsrc; @@ -1938,9 +1944,20 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, Relation attrrel; HeapTuple atttup; Form_pg_attribute attStruct; + Form_pg_attribute defAttStruct; Oid attrdefOid; ObjectAddress colobject, defobject; + ExprState *exprState; + Expr *expr2 = (Expr *) expr; + EState *estate = NULL; + ExprContext *econtext; + char *missingBuf = NULL; + Datum valuesAtt[Natts_pg_attribute]; + bool nullsAtt[Natts_pg_attribute]; + bool replacesAtt[Natts_pg_attribute]; + Datum missingval = (Datum) 0; + bool missingIsNull = true; /* * Flatten expression to string form for storage. @@ -1956,6 +1973,44 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, false, false); /* + * Compute the missing value + */ + expr2 = expression_planner(expr2); + + exprState = ExecInitExpr(expr2, NULL); + estate = CreateExecutorState(); + econtext = GetPerTupleExprContext(estate); + + if (add_column_mode) + { + missingval = ExecEvalExpr(exprState, econtext, + &missingIsNull); + + defAttStruct = TupleDescAttr(rel->rd_att, attnum - 1); + + if (missingIsNull) + { + missingval = PointerGetDatum(NULL); + } + else if (defAttStruct->attbyval) + { + missingBuf = palloc(VARHDRSZ + sizeof(Datum)); + memcpy(VARDATA(missingBuf), &missingval, sizeof(Datum)); + SET_VARSIZE(missingBuf, VARHDRSZ + sizeof(Datum)); + missingval = PointerGetDatum(missingBuf); + } + else if (defAttStruct->attlen >= 0) + { + missingBuf = palloc(VARHDRSZ + defAttStruct->attlen); + memcpy(VARDATA(missingBuf), DatumGetPointer(missingval), + defAttStruct->attlen); + SET_VARSIZE(missingBuf, + VARHDRSZ + defAttStruct->attlen); + missingval = PointerGetDatum(missingBuf); + } + } + + /* * Make the pg_attrdef entry. */ values[Anum_pg_attrdef_adrelid - 1] = RelationGetRelid(rel); @@ -1995,7 +2050,22 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, attStruct = (Form_pg_attribute) GETSTRUCT(atttup); if (!attStruct->atthasdef) { - attStruct->atthasdef = true; + MemSet(valuesAtt, 0, sizeof(valuesAtt)); + MemSet(nullsAtt, false, sizeof(nullsAtt)); + MemSet(replacesAtt, false, sizeof(replacesAtt)); + valuesAtt[Anum_pg_attribute_atthasdef - 1] = true; + replacesAtt[Anum_pg_attribute_atthasdef - 1] = true; + if (add_column_mode) + { + valuesAtt[Anum_pg_attribute_atthasmissing - 1] = true; + replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true; + valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval; + replacesAtt[Anum_pg_attribute_attmissingval - 1] = true; + nullsAtt[Anum_pg_attribute_attmissingval - 1] = missingIsNull; + } + atttup = heap_modify_tuple(atttup, RelationGetDescr(attrrel), + valuesAtt, nullsAtt, replacesAtt); + CatalogTupleUpdate(attrrel, &atttup->t_self, atttup); } heap_close(attrrel, RowExclusiveLock); @@ -2027,6 +2097,17 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, InvokeObjectPostCreateHookArg(AttrDefaultRelationId, RelationGetRelid(rel), attnum, is_internal); + if (estate) + { + FreeExecutorState(estate); + } + + if (missingBuf) + { + pfree(missingBuf); + missingBuf = NULL; + } + return attrdefOid; } @@ -2179,7 +2260,7 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal) { case CONSTR_DEFAULT: con->conoid = StoreAttrDefault(rel, con->attnum, con->expr, - is_internal); + is_internal, false); break; case CONSTR_CHECK: con->conoid = @@ -2295,7 +2376,14 @@ AddRelationNewConstraints(Relation rel, (IsA(expr, Const) &&((Const *) expr)->constisnull)) continue; - defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal); + /* If the default is volatile we cannot use a missing value */ + if (contain_volatile_functions((Node *) expr)) + { + colDef->missingMode = false; + } + + defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal, + colDef->missingMode); cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); cooked->contype = CONSTR_DEFAULT; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 330488b..57a6a6b 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -353,6 +353,7 @@ ConstructTupleDescriptor(Relation heapRelation, to->attcacheoff = -1; to->attnotnull = false; to->atthasdef = false; + to->atthasmissing = false; to->attidentity = '\0'; to->attislocal = true; to->attinhcount = 0; @@ -1577,7 +1578,8 @@ index_drop(Oid indexId, bool concurrent) if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for index %u", indexId); - hasexprs = !heap_attisnull(tuple, Anum_pg_index_indexprs); + hasexprs = !heap_attisnull(tuple, Anum_pg_index_indexprs, + RelationGetDescr(indexRelation)); CatalogTupleDelete(indexRelation, &tuple->t_self); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index eb73299..0b481a9 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -445,7 +445,7 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck, LOCKMOD * seqscan pass over the table to copy the missing rows, but that seems * expensive and tedious. */ - if (!heap_attisnull(OldIndex->rd_indextuple, Anum_pg_index_indpred)) + if (!heap_attisnull(OldIndex->rd_indextuple, Anum_pg_index_indpred, NULL)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot cluster on partial index \"%s\"", diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 9e6ba92..2152bb4 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -207,8 +207,8 @@ CheckIndexCompatible(Oid oldId, * We don't assess expressions or predicates; assume incompatibility. * Also, if the index is invalid for any reason, treat it as incompatible. */ - if (!(heap_attisnull(tuple, Anum_pg_index_indpred) && - heap_attisnull(tuple, Anum_pg_index_indexprs) && + if (!(heap_attisnull(tuple, Anum_pg_index_indpred, NULL) && + heap_attisnull(tuple, Anum_pg_index_indexprs, NULL) && IndexIsValid(indexForm))) { ReleaseSysCache(tuple); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index f2a928b..36cfed0 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -700,6 +700,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; } @@ -4613,7 +4614,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); @@ -4716,7 +4717,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; @@ -5332,6 +5333,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; @@ -5375,6 +5377,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); rawEnt->attnum = attribute.attnum; rawEnt->raw_default = copyObject(colDef->raw_default); + rawEnt->missingMode = true; /* * This function is intended for CREATE TABLE, so it processes a @@ -5385,6 +5388,15 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, /* Make the additional catalog changes visible */ CommandCounterIncrement(); + + /* + * Did the request for a missing value work? If not we'll have to do + * a rewrite + */ + if (!rawEnt->missingMode) + { + tab->rewrite |= AT_REWRITE_DEFAULT_VAL; + } } /* @@ -5451,16 +5463,26 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, newval->expr = expression_planner(defval); tab->newvals = lappend(tab->newvals, newval); - tab->rewrite |= AT_REWRITE_DEFAULT_VAL; } + if (DomainHasConstraints(typeOid)) + tab->rewrite |= AT_REWRITE_DEFAULT_VAL; + /* - * If the new column is NOT NULL, tell Phase 3 it needs to test that. - * (Note we don't do this for an OID column. OID will be marked not - * null, but since it's filled specially, there's no need to test - * anything.) + * If we add a column that is not null and there is no missing value + * (i.e. the missing value is NULL) then this ADD COLUMN is doomed. + * Unless the table is empty... */ - tab->new_notnull |= colDef->is_not_null; + if (!TupleDescAttr(rel->rd_att, attribute.attnum - 1)->atthasmissing) + { + /* + * If the new column is NOT NULL, tell Phase 3 it needs to test + * that. (Note we don't do this for an OID column. OID will be + * marked not null, but since it's filled specially, there's no + * need to test anything.) + */ + tab->new_notnull |= colDef->is_not_null; + } } /* @@ -5935,6 +5957,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 @@ -8005,8 +8028,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; @@ -9411,7 +9434,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true, true); - StoreAttrDefault(rel, attnum, defaultexpr, true); + StoreAttrDefault(rel, attnum, defaultexpr, true, false); } ObjectAddressSubSet(address, RelationRelationId, diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index a40b3cf..6492438 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -2398,7 +2398,7 @@ AlterDomainNotNull(List *names, bool notNull) int attnum = rtc->atts[i]; Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1); - if (heap_attisnull(tuple, attnum)) + if (heap_attisnull(tuple, attnum, tupdesc)) { /* * In principle the auxiliary information for this diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index f646fd9..d49cf07 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -2453,7 +2453,7 @@ ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op, /* ignore dropped columns */ if (TupleDescAttr(tupDesc, att - 1)->attisdropped) continue; - if (heap_attisnull(&tmptup, att)) + if (heap_attisnull(&tmptup, att, tupDesc)) { /* null field disproves IS NOT NULL */ if (!checkisnull) diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 16822e9..efbaec1 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -2967,8 +2967,16 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate) false, NULL)) elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); - /* successful, copy tuple */ - copyTuple = heap_copytuple(&tuple); + if (HeapTupleHeaderGetNatts(tuple.t_data) < RelationGetDescr(erm->relation)->natts) + { + copyTuple = heap_expand_tuple(&tuple, + RelationGetDescr(erm->relation)); + } + else + { + /* successful, copy tuple */ + copyTuple = heap_copytuple(&tuple); + } ReleaseBuffer(buffer); } diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 5df89e4..68d67e6 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -589,7 +589,15 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot) if (slot->tts_mintuple) return heap_copy_minimal_tuple(slot->tts_mintuple); if (slot->tts_tuple) - return minimal_tuple_from_heap_tuple(slot->tts_tuple); + { + if (TTS_HAS_PHYSICAL_TUPLE(slot) && + HeapTupleHeaderGetNatts(slot->tts_tuple->t_data) + < slot->tts_tupleDescriptor->natts) + return minimal_expand_tuple(slot->tts_tuple, + slot->tts_tupleDescriptor); + else + return minimal_tuple_from_heap_tuple(slot->tts_tuple); + } /* * Otherwise we need to build a tuple from the Datum array. @@ -627,7 +635,22 @@ ExecFetchSlotTuple(TupleTableSlot *slot) * If we have a regular physical tuple then just return it. */ if (TTS_HAS_PHYSICAL_TUPLE(slot)) - return slot->tts_tuple; + { + if (HeapTupleHeaderGetNatts(slot->tts_tuple->t_data) < slot->tts_tupleDescriptor->natts) + { + MemoryContext oldContext = MemoryContextSwitchTo(slot->tts_mcxt); + + slot->tts_tuple = heap_expand_tuple(slot->tts_tuple, + slot->tts_tupleDescriptor); + slot->tts_shouldFree = true; + MemoryContextSwitchTo(oldContext); + return slot->tts_tuple; + } + else + { + return slot->tts_tuple; + } + } /* * Otherwise materialize the slot... diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 89f27ce..2fefa79 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -4491,7 +4491,7 @@ inline_function(Oid funcid, Oid result_type, Oid result_collid, funcform->proretset || funcform->prorettype == InvalidOid || funcform->prorettype == RECORDOID || - !heap_attisnull(func_tuple, Anum_pg_proc_proconfig) || + !heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL) || funcform->pronargs != list_length(args)) return NULL; @@ -5018,7 +5018,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) funcform->provolatile == PROVOLATILE_VOLATILE || funcform->prosecdef || !funcform->proretset || - !heap_attisnull(func_tuple, Anum_pg_proc_proconfig)) + !heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL)) { ReleaseSysCache(func_tuple); return NULL; diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 32e3798..9bfcb84 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1126,7 +1126,7 @@ build_column_default(Relation rel, int attrno) /* * Scan to see if relation has a default for this column. */ - if (rd_att->constr && rd_att->constr->num_defval > 0) + if (att_tup->atthasdef && rd_att->constr && rd_att->constr->num_defval > 0) { AttrDefault *defval = rd_att->constr->defval; int ndef = rd_att->constr->num_defval; diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c index d34d5c3..7eb981a 100644 --- a/src/backend/statistics/extended_stats.c +++ b/src/backend/statistics/extended_stats.c @@ -158,7 +158,7 @@ statext_is_kind_built(HeapTuple htup, char type) elog(ERROR, "unexpected statistics type requested: %d", type); } - return !heap_attisnull(htup, attnum); + return !heap_attisnull(htup, attnum, NULL); } /* diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 8faae1d..30b7e62 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -205,7 +205,7 @@ static void ri_GenerateQual(StringInfo buf, const char *rightop, Oid rightoptype); static void ri_add_cast_to(StringInfo buf, Oid typid); static void ri_GenerateQualCollation(StringInfo buf, Oid collation); -static int ri_NullCheck(HeapTuple tup, +static int ri_NullCheck(TupleDesc tupdesc, HeapTuple tup, const RI_ConstraintInfo *riinfo, bool rel_is_pk); static void ri_BuildQueryKey(RI_QueryKey *key, const RI_ConstraintInfo *riinfo, @@ -308,7 +308,7 @@ RI_FKey_check(TriggerData *trigdata) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); - switch (ri_NullCheck(new_row, riinfo, false)) + switch (ri_NullCheck(RelationGetDescr(fk_rel), new_row, riinfo, false)) { case RI_KEYS_ALL_NULL: @@ -515,7 +515,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, bool result; /* Only called for non-null rows */ - Assert(ri_NullCheck(old_row, riinfo, true) == RI_KEYS_NONE_NULL); + Assert(ri_NullCheck(RelationGetDescr(fk_rel), old_row, riinfo, true) == RI_KEYS_NONE_NULL); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); @@ -725,7 +725,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: - switch (ri_NullCheck(old_row, riinfo, true)) + switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: @@ -912,7 +912,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: - switch (ri_NullCheck(old_row, riinfo, true)) + switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: @@ -1072,7 +1072,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: - switch (ri_NullCheck(old_row, riinfo, true)) + switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: @@ -1286,7 +1286,7 @@ ri_setnull(TriggerData *trigdata) */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: - switch (ri_NullCheck(old_row, riinfo, true)) + switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: @@ -1502,7 +1502,7 @@ ri_setdefault(TriggerData *trigdata) */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: - switch (ri_NullCheck(old_row, riinfo, true)) + switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: @@ -1677,7 +1677,7 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel, * If any old key value is NULL, the row could not have been * referenced by an FK row, so no check is needed. */ - if (ri_NullCheck(old_row, riinfo, true) != RI_KEYS_NONE_NULL) + if (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true) != RI_KEYS_NONE_NULL) return false; /* If all old and new key values are equal, no check is needed */ @@ -1733,7 +1733,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, * If any new key value is NULL, the row must satisfy the * constraint, so no check is needed. */ - if (ri_NullCheck(new_row, riinfo, false) != RI_KEYS_NONE_NULL) + if (ri_NullCheck(RelationGetDescr(fk_rel), new_row, riinfo, false) != RI_KEYS_NONE_NULL) return false; /* @@ -1764,7 +1764,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, * invalidated before the constraint is to be checked, but we * should queue the event to apply the check later. */ - switch (ri_NullCheck(new_row, riinfo, false)) + switch (ri_NullCheck(RelationGetDescr(fk_rel), new_row, riinfo, false)) { case RI_KEYS_ALL_NULL: return false; @@ -2058,7 +2058,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) * disallows partially-null FK rows. */ if (fake_riinfo.confmatchtype == FKCONSTR_MATCH_FULL && - ri_NullCheck(tuple, &fake_riinfo, false) != RI_KEYS_NONE_NULL) + ri_NullCheck(tupdesc, tuple, &fake_riinfo, false) != RI_KEYS_NONE_NULL) ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", @@ -2915,7 +2915,8 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo, * ---------- */ static int -ri_NullCheck(HeapTuple tup, +ri_NullCheck(TupleDesc tupDesc, + HeapTuple tup, const RI_ConstraintInfo *riinfo, bool rel_is_pk) { const int16 *attnums; @@ -2930,7 +2931,7 @@ ri_NullCheck(HeapTuple tup, for (i = 0; i < riinfo->nkeys; i++) { - if (heap_attisnull(tup, attnums[i])) + if (heap_attisnull(tup, attnums[i], tupDesc)) nonenull = false; else allnull = false; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 9cdbb06..1b8560c 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -1230,7 +1230,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, * versions of the expressions and predicate, because we want to display * non-const-folded expressions.) */ - if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs)) + if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs, NULL)) { Datum exprsDatum; bool isnull; @@ -1394,7 +1394,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, /* * If it's a partial index, decompile and append the predicate */ - if (!heap_attisnull(ht_idx, Anum_pg_index_indpred)) + if (!heap_attisnull(ht_idx, Anum_pg_index_indpred, NULL)) { Node *node; Datum predDatum; @@ -1627,7 +1627,7 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags, * versions of the expressions, because we want to display * non-const-folded expressions.) */ - if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs)) + if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs, NULL)) { Datum exprsDatum; bool isnull; diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 00ba33b..907d75b 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -77,6 +77,7 @@ #include "storage/smgr.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" @@ -488,7 +489,10 @@ RelationBuildTupleDesc(Relation relation) int need; TupleConstr *constr; AttrDefault *attrdef = NULL; + AttrMissing *attrmiss = NULL; int ndef = 0; + int nmissing = 0; + int attnum = 0; /* copy some fields from pg_class row to rd_att */ relation->rd_att->tdtypeid = relation->rd_rel->reltype; @@ -541,6 +545,16 @@ RelationBuildTupleDesc(Relation relation) elog(ERROR, "invalid attribute number %d for %s", attp->attnum, RelationGetRelationName(relation)); + /* + * We have a dependency on the attrdef array being filled in in + * ascending attnum order. This should be guaranteed by the index + * driving the scan. But we want to be double sure + */ + if (!(attp->attnum > attnum)) + elog(ERROR, "attribute numbers not ascending"); + + attnum = attp->attnum; + memcpy(TupleDescAttr(relation->rd_att, attp->attnum - 1), attp, ATTRIBUTE_FIXED_PART_SIZE); @@ -549,6 +563,10 @@ RelationBuildTupleDesc(Relation relation) if (attp->attnotnull) constr->has_not_null = true; + /* + * If the column has a default or missin value Fill it into the + * attrdef array + */ if (attp->atthasdef) { if (attrdef == NULL) @@ -556,10 +574,71 @@ RelationBuildTupleDesc(Relation relation) MemoryContextAllocZero(CacheMemoryContext, relation->rd_rel->relnatts * sizeof(AttrDefault)); - attrdef[ndef].adnum = attp->attnum; + attrdef[ndef].adnum = attnum; attrdef[ndef].adbin = NULL; + ndef++; } + if (attp->atthasmissing) + { + Datum missingval; + bool missingNull; + + if (attrmiss == NULL) + attrmiss = (AttrMissing *) + MemoryContextAllocZero(CacheMemoryContext, + relation->rd_rel->relnatts * + sizeof(AttrMissing)); + + /* Do we have a missing value? */ + missingval = SysCacheGetAttr(ATTNUM, pg_attribute_tuple, + Anum_pg_attribute_attmissingval, + &missingNull); + attrmiss[nmissing].amnum = attnum; + if (missingNull) + { + /* No, then the store a NULL */ + attrmiss[nmissing].ammissing = PointerGetDatum(NULL); + attrmiss[nmissing].ammissingNull = true; + } + else if (attp->attbyval) + { + /* + * Yes, and its of the by-value kind Copy in the Datum + */ + memcpy(&attrmiss[nmissing].ammissing, + VARDATA_ANY(missingval), sizeof(Datum)); + attrmiss[nmissing].ammissingNull = false; + } + else if (attp->attlen > 0) + { + /* + * Yes, and its fixed length Copy it out and have teh Datum + * point to it. + */ + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + attrmiss[nmissing].ammissing = PointerGetDatum(palloc(attp->attlen)); + memcpy(DatumGetPointer(attrmiss[nmissing].ammissing), + VARDATA_ANY(missingval), attp->attlen); + MemoryContextSwitchTo(oldcxt); + attrmiss[nmissing].ammissingNull = false; + } + else + { + /* Yes, variable length */ + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + attrmiss[nmissing].ammissing = datumCopy(missingval, + attp->attbyval, + attp->attlen); + attrmiss[nmissing].ammissingNull = false; + MemoryContextSwitchTo(oldcxt); + } + nmissing++; + } need--; if (need == 0) break; @@ -600,7 +679,8 @@ RelationBuildTupleDesc(Relation relation) /* * Set up constraint/default info */ - if (constr->has_not_null || ndef > 0 || relation->rd_rel->relchecks) + if (constr->has_not_null || ndef > 0 || + nmissing > 0 || relation->rd_rel->relchecks) { relation->rd_att->constr = constr; @@ -615,7 +695,19 @@ RelationBuildTupleDesc(Relation relation) AttrDefaultFetch(relation); } else + { constr->num_defval = 0; + } + + if (nmissing > 0) + { + if (nmissing < relation->rd_rel->relnatts) + constr->missing = (AttrMissing *) + repalloc(attrmiss, nmissing * sizeof(AttrMissing)); + else + constr->missing = attrmiss; + } + constr->num_missing = nmissing; if (relation->rd_rel->relchecks > 0) /* CHECKs */ { @@ -4045,10 +4137,6 @@ AttrDefaultFetch(Relation relation) systable_endscan(adscan); heap_close(adrel, AccessShareLock); - - if (found != ndef) - elog(WARNING, "%d attrdef record(s) missing for rel %s", - ndef - found, RelationGetRelationName(relation)); } /* @@ -4387,7 +4475,7 @@ RelationGetIndexList(Relation relation) */ if (!IndexIsValid(index) || !index->indisunique || !index->indimmediate || - !heap_attisnull(htup, Anum_pg_index_indpred)) + !heap_attisnull(htup, Anum_pg_index_indpred, NULL)) continue; /* Check to see if is a usable btree index on OID */ @@ -4682,7 +4770,7 @@ RelationGetIndexExpressions(Relation relation) /* Quick exit if there is nothing to do. */ if (relation->rd_indextuple == NULL || - heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs)) + heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL)) return NIL; /* @@ -4745,7 +4833,7 @@ RelationGetIndexPredicate(Relation relation) /* Quick exit if there is nothing to do. */ if (relation->rd_indextuple == NULL || - heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred)) + heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred, NULL)) return NIL; /* diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 8968a9f..dd3bd5c 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -199,7 +199,7 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt, */ if (!ignore_security && (procedureStruct->prosecdef || - !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig) || + !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig, NULL) || FmgrHookIsNeeded(functionId))) { finfo->fn_addr = fmgr_security_definer; diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index c0076bf..d5580de 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -1091,8 +1091,8 @@ get_func_result_name(Oid functionId) elog(ERROR, "cache lookup failed for function %u", functionId); /* If there are no named OUT parameters, return NULL */ - if (heap_attisnull(procTuple, Anum_pg_proc_proargmodes) || - heap_attisnull(procTuple, Anum_pg_proc_proargnames)) + if (heap_attisnull(procTuple, Anum_pg_proc_proargmodes, NULL) || + heap_attisnull(procTuple, Anum_pg_proc_proargnames, NULL)) result = NULL; else { @@ -1186,8 +1186,8 @@ build_function_result_tupdesc_t(HeapTuple procTuple) return NULL; /* If there are no OUT parameters, return NULL */ - if (heap_attisnull(procTuple, Anum_pg_proc_proallargtypes) || - heap_attisnull(procTuple, Anum_pg_proc_proargmodes)) + if (heap_attisnull(procTuple, Anum_pg_proc_proallargtypes, NULL) || + heap_attisnull(procTuple, Anum_pg_proc_proargmodes, NULL)) return NULL; /* Get the data out of the tuple */ diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h index 2ab1815..fb1ad17 100644 --- a/src/include/access/htup_details.h +++ b/src/include/access/htup_details.h @@ -795,7 +795,7 @@ extern void heap_fill_tuple(TupleDesc tupleDesc, Datum *values, bool *isnull, char *data, Size data_size, uint16 *infomask, bits8 *bit); -extern bool heap_attisnull(HeapTuple tup, int attnum); +extern bool heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc); extern Datum nocachegetattr(HeapTuple tup, int attnum, TupleDesc att); extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, @@ -825,5 +825,8 @@ extern void heap_free_minimal_tuple(MinimalTuple mtup); extern MinimalTuple heap_copy_minimal_tuple(MinimalTuple mtup); extern HeapTuple heap_tuple_from_minimal_tuple(MinimalTuple mtup); extern MinimalTuple minimal_tuple_from_heap_tuple(HeapTuple htup); +extern HeapTuple heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc); +extern MinimalTuple minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc); + #endif /* HTUP_DETAILS_H */ diff --git a/src/include/access/tupdesc.h b/src/include/access/tupdesc.h index 415efba..e62100d 100644 --- a/src/include/access/tupdesc.h +++ b/src/include/access/tupdesc.h @@ -14,6 +14,7 @@ #ifndef TUPDESC_H #define TUPDESC_H +#include "postgres.h" #include "access/attnum.h" #include "catalog/pg_attribute.h" #include "nodes/pg_list.h" @@ -25,6 +26,13 @@ typedef struct attrDefault char *adbin; /* nodeToString representation of expr */ } AttrDefault; +typedef struct attrMissing +{ + AttrNumber amnum; + bool ammissingNull; /* true if missing value is NULL */ + Datum ammissing; /* value when attribute is missing */ +} AttrMissing; + typedef struct constrCheck { char *ccname; @@ -38,8 +46,10 @@ typedef struct tupleConstr { AttrDefault *defval; /* array */ ConstrCheck *check; /* array */ + AttrMissing *missing; /* missing attributes values, NULL if none */ uint16 num_defval; uint16 num_check; + uint16 num_missing; bool has_not_null; } TupleConstr; diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index 9bdc63c..5869a48 100644 --- a/src/include/catalog/heap.h +++ b/src/include/catalog/heap.h @@ -23,6 +23,7 @@ typedef struct RawColumnDefault { AttrNumber attnum; /* attribute to attach default to */ Node *raw_default; /* default value (untransformed parse tree) */ + bool missingMode; /* true if part of add column processing */ } RawColumnDefault; typedef struct CookedConstraint @@ -103,7 +104,8 @@ extern List *AddRelationNewConstraints(Relation rel, bool is_internal); extern Oid StoreAttrDefault(Relation rel, AttrNumber attnum, - Node *expr, bool is_internal); + Node *expr, bool is_internal, + bool add_column_mode); extern Node *cookDefault(ParseState *pstate, Node *raw_default, diff --git a/src/include/catalog/pg_attribute.h b/src/include/catalog/pg_attribute.h index 8159383..830b1cf 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -133,6 +133,9 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK /* Has DEFAULT value or not */ bool atthasdef 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,9 @@ 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 */ + bytea attmissingval BKI_DEFAULT(_null_); #endif } FormData_pg_attribute; @@ -191,30 +197,31 @@ typedef FormData_pg_attribute *Form_pg_attribute; * ---------------- */ -#define Natts_pg_attribute 22 -#define Anum_pg_attribute_attrelid 1 -#define Anum_pg_attribute_attname 2 -#define Anum_pg_attribute_atttypid 3 -#define Anum_pg_attribute_attstattarget 4 -#define Anum_pg_attribute_attlen 5 -#define Anum_pg_attribute_attnum 6 -#define Anum_pg_attribute_attndims 7 -#define Anum_pg_attribute_attcacheoff 8 -#define Anum_pg_attribute_atttypmod 9 -#define Anum_pg_attribute_attbyval 10 -#define Anum_pg_attribute_attstorage 11 -#define Anum_pg_attribute_attalign 12 -#define Anum_pg_attribute_attnotnull 13 -#define Anum_pg_attribute_atthasdef 14 -#define Anum_pg_attribute_attidentity 15 -#define Anum_pg_attribute_attisdropped 16 -#define Anum_pg_attribute_attislocal 17 -#define Anum_pg_attribute_attinhcount 18 -#define Anum_pg_attribute_attcollation 19 -#define Anum_pg_attribute_attacl 20 -#define Anum_pg_attribute_attoptions 21 -#define Anum_pg_attribute_attfdwoptions 22 - +#define Natts_pg_attribute 24 +#define Anum_pg_attribute_attrelid 1 +#define Anum_pg_attribute_attname 2 +#define Anum_pg_attribute_atttypid 3 +#define Anum_pg_attribute_attstattarget 4 +#define Anum_pg_attribute_attlen 5 +#define Anum_pg_attribute_attnum 6 +#define Anum_pg_attribute_attndims 7 +#define Anum_pg_attribute_attcacheoff 8 +#define Anum_pg_attribute_atttypmod 9 +#define Anum_pg_attribute_attbyval 10 +#define Anum_pg_attribute_attstorage 11 +#define Anum_pg_attribute_attalign 12 +#define Anum_pg_attribute_attnotnull 13 +#define Anum_pg_attribute_atthasdef 14 +#define Anum_pg_attribute_atthasmissing 15 +#define Anum_pg_attribute_attidentity 16 +#define Anum_pg_attribute_attisdropped 17 +#define Anum_pg_attribute_attislocal 18 +#define Anum_pg_attribute_attinhcount 19 +#define Anum_pg_attribute_attcollation 20 +#define Anum_pg_attribute_attacl 21 +#define Anum_pg_attribute_attoptions 22 +#define Anum_pg_attribute_attfdwoptions 23 +#define Anum_pg_attribute_attmissingval 24 /* ---------------- * initial contents of pg_attribute diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index e704943..6ad6862 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -149,7 +149,7 @@ typedef FormData_pg_class *Form_pg_class; */ DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f f t n f 3 1 _null_ _null_ _null_)); DESCR(""); -DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 22 0 f f f f f f f t n f 3 1 _null_ _null_ _null_)); +DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 24 0 f f f f f f f t n f 3 1 _null_ _null_ _null_)); DESCR(""); DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 29 0 t f f f f f f t n f 3 1 _null_ _null_ _null_)); DESCR(""); diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out index 906dcb8..c02685c 100644 --- a/src/test/regress/expected/event_trigger.out +++ b/src/test/regress/expected/event_trigger.out @@ -371,8 +371,6 @@ alter table rewriteme alter column foo type numeric; ERROR: rewrites not allowed CONTEXT: PL/pgSQL function test_evtrig_no_rewrite() line 3 at RAISE alter table rewriteme add column baz int default 0; -ERROR: rewrites not allowed -CONTEXT: PL/pgSQL function test_evtrig_no_rewrite() line 3 at RAISE -- test with more than one reason to rewrite a single table CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger LANGUAGE plpgsql AS $$ @@ -386,7 +384,7 @@ alter table rewriteme add column onemore int default 0, add column another int default -1, alter column foo type numeric(10,4); -NOTICE: Table 'rewriteme' is being rewritten (reason = 6) +NOTICE: Table 'rewriteme' is being rewritten (reason = 4) -- shouldn't trigger a table_rewrite event alter table rewriteme alter column foo type numeric(12,4); -- typed tables are rewritten when their type changes. Don't emit table diff --git a/src/test/regress/expected/fast_default.out b/src/test/regress/expected/fast_default.out new file mode 100644 index 0000000..e53e491 --- /dev/null +++ b/src/test/regress/expected/fast_default.out @@ -0,0 +1,515 @@ +-- +-- ALTER TABLE ADD COLUMN DEFAULT test +-- +SET search_path = fast_default; +CREATE SCHEMA fast_default; +CREATE TABLE m(id OID); +INSERT INTO m VALUES (NULL::OID); +CREATE FUNCTION set(tabname name) RETURNS VOID +AS $$ +BEGIN + UPDATE m + SET id = (SELECT c.relfilenode + FROM pg_class AS c, pg_namespace AS s + WHERE c.relname = tabname + AND c.relnamespace = s.oid + AND s.nspname = 'fast_default'); +END; +$$ LANGUAGE 'plpgsql'; +CREATE FUNCTION comp() RETURNS TEXT +AS $$ +BEGIN + RETURN (SELECT CASE + WHEN m.id = c.relfilenode THEN 'Unchanged' + ELSE 'Rewritten' + END + FROM m, pg_class AS c, pg_namespace AS s + WHERE c.relname = 't' + AND c.relnamespace = s.oid + AND s.nspname = 'fast_default'); +END; +$$ LANGUAGE 'plpgsql'; +CREATE FUNCTION log_rewrite() RETURNS event_trigger +LANGUAGE plpgsql as +$func$ + +declare + this_schema text; +begin + select into this_schema relnamespace::regnamespace::text + from pg_class + where oid = pg_event_trigger_table_rewrite_oid(); + if this_schema = 'fast_default' + then + RAISE NOTICE 'rewriting table % for reason %', + pg_event_trigger_table_rewrite_oid()::regclass, + pg_event_trigger_table_rewrite_reason(); + end if; +end; +$func$; +CREATE TABLE has_volatile AS +SELECT * FROM generate_series(1,10) id; +CREATE EVENT TRIGGER has_volatile_rewrite + ON table_rewrite + EXECUTE PROCEDURE log_rewrite(); +-- only the last of these should trigger a rewrite +ALTER TABLE has_volatile ADD col1 int; +ALTER TABLE has_volatile ADD col2 int DEFAULT 1; +ALTER TABLE has_volatile ADD col3 timestamptz DEFAULT current_timestamp; +ALTER TABLE has_volatile ADD col4 int DEFAULT (random() * 10000)::int; +NOTICE: rewriting table has_volatile for reason 2 +-- Test a large sample of dfferent datatypes +CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1); +SELECT set('t'); + set +----- + +(1 row) + +INSERT INTO T VALUES (1), (2); +ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT 'hello', + ALTER COLUMN c_int SET DEFAULT 2; +INSERT INTO T VALUES (3), (4); +ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'world', + ALTER COLUMN c_bpchar SET DEFAULT 'dog'; +INSERT INTO T VALUES (5), (6); +ALTER TABLE T ADD COLUMN c_date DATE DEFAULT '2016-06-02', + ALTER COLUMN c_text SET DEFAULT 'cat'; +INSERT INTO T VALUES (7), (8); +ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP DEFAULT '2016-09-01 12:00:00', + ADD COLUMN c_timestamp_null TIMESTAMP, + ALTER COLUMN c_date SET DEFAULT '2010-01-01'; +INSERT INTO T VALUES (9), (10); +ALTER TABLE T ADD COLUMN c_array TEXT[] + DEFAULT '{"This", "is", "the", "real", "world"}', + ALTER COLUMN c_timestamp SET DEFAULT '1970-12-31 11:12:13', + ALTER COLUMN c_timestamp_null SET DEFAULT '2016-09-29 12:00:00'; +INSERT INTO T VALUES (11), (12); +ALTER TABLE T ADD COLUMN c_small SMALLINT DEFAULT -5, + ADD COLUMN c_small_null SMALLINT, + ALTER COLUMN c_array + SET DEFAULT '{"This", "is", "no", "fantasy"}'; +INSERT INTO T VALUES (13), (14); +ALTER TABLE T ADD COLUMN c_big BIGINT DEFAULT 180000000000018, + ALTER COLUMN c_small SET DEFAULT 9, + ALTER COLUMN c_small_null SET DEFAULT 13; +INSERT INTO T VALUES (15), (16); +ALTER TABLE T ADD COLUMN c_num NUMERIC DEFAULT 1.00000000001, + ALTER COLUMN c_big SET DEFAULT -9999999999999999; +INSERT INTO T VALUES (17), (18); +ALTER TABLE T ADD COLUMN c_time TIME DEFAULT '12:00:00', + ALTER COLUMN c_num SET DEFAULT 2.000000000000002; +INSERT INTO T VALUES (19), (20); +ALTER TABLE T ADD COLUMN c_interval INTERVAL DEFAULT '1 day', + ALTER COLUMN c_time SET DEFAULT '23:59:59'; +INSERT INTO T VALUES (21), (22); +ALTER TABLE T ADD COLUMN c_hugetext TEXT DEFAULT repeat('abcdefg',1000), + ALTER COLUMN c_interval SET DEFAULT '3 hours'; +INSERT INTO T VALUES (23), (24); +ALTER TABLE T ALTER COLUMN c_interval DROP DEFAULT, + ALTER COLUMN c_hugetext SET DEFAULT repeat('poiuyt', 1000); +INSERT INTO T VALUES (25), (26); +ALTER TABLE T ALTER COLUMN c_bpchar DROP DEFAULT, + ALTER COLUMN c_date DROP DEFAULT, + ALTER COLUMN c_text DROP DEFAULT, + ALTER COLUMN c_timestamp DROP DEFAULT, + ALTER COLUMN c_array DROP DEFAULT, + ALTER COLUMN c_small DROP DEFAULT, + ALTER COLUMN c_big DROP DEFAULT, + ALTER COLUMN c_num DROP DEFAULT, + ALTER COLUMN c_time DROP DEFAULT, + ALTER COLUMN c_hugetext DROP DEFAULT; +INSERT INTO T VALUES (27), (28); +SELECT pk, c_int, c_bpchar, c_text, c_date, c_timestamp, + c_timestamp_null, c_array, c_small, c_small_null, + c_big, c_num, c_time, c_interval, + c_hugetext = repeat('abcdefg',1000) as c_hugetext_origdef, + c_hugetext = repeat('poiuyt', 1000) as c_hugetext_newdef +FROM T ORDER BY pk; + pk | c_int | c_bpchar | c_text | c_date | c_timestamp | c_timestamp_null | c_array | c_small | c_small_null | c_big | c_num | c_time | c_interval | c_hugetext_origdef | c_hugetext_newdef +----+-------+----------+--------+------------+--------------------------+--------------------------+--------------------------+---------+--------------+-------------------+-------------------+----------+------------+--------------------+------------------- + 1 | 1 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 2 | 1 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 3 | 2 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 4 | 2 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 5 | 2 | dog | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 6 | 2 | dog | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 7 | 2 | dog | cat | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 8 | 2 | dog | cat | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 9 | 2 | dog | cat | 01-01-2010 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 10 | 2 | dog | cat | 01-01-2010 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 11 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 12 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 13 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 14 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 15 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 16 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 17 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 18 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 1.00000000001 | 12:00:00 | @ 1 day | t | f + 19 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 12:00:00 | @ 1 day | t | f + 20 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 12:00:00 | @ 1 day | t | f + 21 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 1 day | t | f + 22 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 1 day | t | f + 23 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 3 hours | t | f + 24 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 3 hours | t | f + 25 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | | f | t + 26 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | | f | t + 27 | 2 | | | | | Thu Sep 29 12:00:00 2016 | | | 13 | | | | | | + 28 | 2 | | | | | Thu Sep 29 12:00:00 2016 | | | 13 | | | | | | +(28 rows) + +SELECT comp(); + comp +----------- + Unchanged +(1 row) + +DROP TABLE T; +-- Test expressions in the defaults +CREATE OR REPLACE FUNCTION foo(a INT) RETURNS TEXT AS $$ +DECLARE res TEXT := ''; + i INT; +BEGIN + i := 0; + WHILE (i < a) LOOP + res := res || chr(ascii('a') + i); + i := i + 1; + END LOOP; + RETURN res; +END; $$ LANGUAGE PLPGSQL STABLE; +CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT LENGTH(foo(6))); +SELECT set('t'); + set +----- + +(1 row) + +INSERT INTO T VALUES (1), (2); +ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT foo(4), + ALTER COLUMN c_int SET DEFAULT LENGTH(foo(8)); +INSERT INTO T VALUES (3), (4); +ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT foo(6), + ALTER COLUMN c_bpchar SET DEFAULT foo(3); +INSERT INTO T VALUES (5), (6); +ALTER TABLE T ADD COLUMN c_date DATE + DEFAULT '2016-06-02'::DATE + LENGTH(foo(10)), + ALTER COLUMN c_text SET DEFAULT foo(12); +INSERT INTO T VALUES (7), (8); +ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP + DEFAULT '2016-09-01'::DATE + LENGTH(foo(10)), + ALTER COLUMN c_date + SET DEFAULT '2010-01-01'::DATE - LENGTH(foo(4)); +INSERT INTO T VALUES (9), (10); +ALTER TABLE T ADD COLUMN c_array TEXT[] + DEFAULT ('{"This", "is", "' || foo(4) || + '","the", "real", "world"}')::TEXT[], + ALTER COLUMN c_timestamp + SET DEFAULT '1970-12-31'::DATE + LENGTH(foo(30)); +INSERT INTO T VALUES (11), (12); +ALTER TABLE T ALTER COLUMN c_int DROP DEFAULT, + ALTER COLUMN c_array + SET DEFAULT ('{"This", "is", "' || foo(1) || + '", "fantasy"}')::text[]; +INSERT INTO T VALUES (13), (14); +ALTER TABLE T ALTER COLUMN c_bpchar DROP DEFAULT, + ALTER COLUMN c_date DROP DEFAULT, + ALTER COLUMN c_text DROP DEFAULT, + ALTER COLUMN c_timestamp DROP DEFAULT, + ALTER COLUMN c_array DROP DEFAULT; +INSERT INTO T VALUES (15), (16); +SELECT * FROM T; + pk | c_int | c_bpchar | c_text | c_date | c_timestamp | c_array +----+-------+----------+--------------+------------+--------------------------+------------------------------- + 1 | 6 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} + 2 | 6 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} + 3 | 8 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} + 4 | 8 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} + 5 | 8 | abc | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} + 6 | 8 | abc | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} + 7 | 8 | abc | abcdefghijkl | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} + 8 | 8 | abc | abcdefghijkl | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} + 9 | 8 | abc | abcdefghijkl | 12-28-2009 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} + 10 | 8 | abc | abcdefghijkl | 12-28-2009 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world} + 11 | 8 | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,abcd,the,real,world} + 12 | 8 | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,abcd,the,real,world} + 13 | | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,a,fantasy} + 14 | | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,a,fantasy} + 15 | | | | | | + 16 | | | | | | +(16 rows) + +SELECT comp(); + comp +----------- + Unchanged +(1 row) + +DROP TABLE T; +DROP FUNCTION foo(INT); +-- Fall back to full rewrite for volatile expressions +CREATE TABLE T(pk INT NOT NULL PRIMARY KEY); +INSERT INTO T VALUES (1); +SELECT set('t'); + set +----- + +(1 row) + +-- now() is stable, because it returns the transaction timestamp +ALTER TABLE T ADD COLUMN c1 TIMESTAMP DEFAULT now(); +SELECT comp(); + comp +----------- + Unchanged +(1 row) + +-- clock_timestamp() is volatile +ALTER TABLE T ADD COLUMN c2 TIMESTAMP DEFAULT clock_timestamp(); +NOTICE: rewriting table t for reason 2 +SELECT comp(); + comp +----------- + Rewritten +(1 row) + +DROP TABLE T; +-- Simple querie +CREATE TABLE T (pk INT NOT NULL PRIMARY KEY); +SELECT set('t'); + set +----- + +(1 row) + +INSERT INTO T SELECT * FROM generate_series(1, 10) a; +ALTER TABLE T ADD COLUMN c_bigint BIGINT NOT NULL DEFAULT -1; +INSERT INTO T SELECT b, b - 10 FROM generate_series(11, 20) a(b); +ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'hello'; +INSERT INTO T SELECT b, b - 10, (b + 10)::text FROM generate_series(21, 30) a(b); +-- WHERE clause +SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1; + c_bigint | c_text +----------+-------- + -1 | hello +(1 row) + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) +SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1; + QUERY PLAN +---------------------------------------------- + Limit + Output: c_bigint, c_text + -> Seq Scan on fast_default.t + Output: c_bigint, c_text + Filter: (t.c_bigint = '-1'::integer) +(5 rows) + +SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1; + c_bigint | c_text +----------+-------- + -1 | hello +(1 row) + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1; + QUERY PLAN +-------------------------------------------- + Limit + Output: c_bigint, c_text + -> Seq Scan on fast_default.t + Output: c_bigint, c_text + Filter: (t.c_text = 'hello'::text) +(5 rows) + +-- COALESCE +SELECT COALESCE(c_bigint, pk), COALESCE(c_text, pk::text) +FROM T +ORDER BY pk LIMIT 10; + coalesce | coalesce +----------+---------- + -1 | hello + -1 | hello + -1 | hello + -1 | hello + -1 | hello + -1 | hello + -1 | hello + -1 | hello + -1 | hello + -1 | hello +(10 rows) + +-- Aggregate function +SELECT SUM(c_bigint), MAX(c_text), MIN(c_text) FROM T; + sum | max | min +-----+-------+----- + 201 | hello | 31 +(1 row) + +-- ORDER BY +SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10; + pk | c_bigint | c_text +----+----------+-------- + 1 | -1 | hello + 2 | -1 | hello + 3 | -1 | hello + 4 | -1 | hello + 5 | -1 | hello + 6 | -1 | hello + 7 | -1 | hello + 8 | -1 | hello + 9 | -1 | hello + 10 | -1 | hello +(10 rows) + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) +SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10; + QUERY PLAN +---------------------------------------------- + Limit + Output: pk, c_bigint, c_text + -> Sort + Output: pk, c_bigint, c_text + Sort Key: t.c_bigint, t.c_text, t.pk + -> Seq Scan on fast_default.t + Output: pk, c_bigint, c_text +(7 rows) + +-- LIMIT +SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10; + pk | c_bigint | c_text +----+----------+-------- + 11 | 1 | hello + 12 | 2 | hello + 13 | 3 | hello + 14 | 4 | hello + 15 | 5 | hello + 16 | 6 | hello + 17 | 7 | hello + 18 | 8 | hello + 19 | 9 | hello + 20 | 10 | hello +(10 rows) + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) +SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10; + QUERY PLAN +---------------------------------------------------- + Limit + Output: pk, c_bigint, c_text + -> Sort + Output: pk, c_bigint, c_text + Sort Key: t.c_bigint, t.c_text, t.pk + -> Seq Scan on fast_default.t + Output: pk, c_bigint, c_text + Filter: (t.c_bigint > '-1'::integer) +(8 rows) + +-- DELETE with RETURNING +DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *; + pk | c_bigint | c_text +----+----------+-------- + 10 | -1 | hello + 11 | 1 | hello + 12 | 2 | hello + 13 | 3 | hello + 14 | 4 | hello + 15 | 5 | hello + 16 | 6 | hello + 17 | 7 | hello + 18 | 8 | hello + 19 | 9 | hello + 20 | 10 | hello +(11 rows) + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) +DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *; + QUERY PLAN +----------------------------------------------------------- + Delete on fast_default.t + Output: pk, c_bigint, c_text + -> Bitmap Heap Scan on fast_default.t + Output: ctid + Recheck Cond: ((t.pk >= 10) AND (t.pk <= 20)) + -> Bitmap Index Scan on t_pkey + Index Cond: ((t.pk >= 10) AND (t.pk <= 20)) +(7 rows) + +-- UPDATE +UPDATE T SET c_text = '"' || c_text || '"' WHERE pk < 10; +SELECT * FROM T WHERE c_text LIKE '"%"' ORDER BY PK; + pk | c_bigint | c_text +----+----------+--------- + 1 | -1 | "hello" + 2 | -1 | "hello" + 3 | -1 | "hello" + 4 | -1 | "hello" + 5 | -1 | "hello" + 6 | -1 | "hello" + 7 | -1 | "hello" + 8 | -1 | "hello" + 9 | -1 | "hello" +(9 rows) + +SELECT comp(); + comp +----------- + Unchanged +(1 row) + +DROP TABLE T; +-- Combine with other DDL +CREATE TABLE T(pk INT NOT NULL PRIMARY KEY); +SELECT set('t'); + set +----- + +(1 row) + +INSERT INTO T VALUES (1), (2); +ALTER TABLE T ADD COLUMN c_int INT NOT NULL DEFAULT -1; +INSERT INTO T VALUES (3), (4); +ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'Hello'; +INSERT INTO T VALUES (5), (6); +ALTER TABLE T ALTER COLUMN c_text SET DEFAULT 'world', + ALTER COLUMN c_int SET DEFAULT 1; +INSERT INTO T VALUES (7), (8); +SELECT * FROM T ORDER BY pk; + pk | c_int | c_text +----+-------+-------- + 1 | -1 | Hello + 2 | -1 | Hello + 3 | -1 | Hello + 4 | -1 | Hello + 5 | -1 | Hello + 6 | -1 | Hello + 7 | 1 | world + 8 | 1 | world +(8 rows) + +-- Add an index +CREATE INDEX i ON T(c_int, c_text); +SELECT c_text FROM T WHERE c_int = -1; + c_text +-------- + Hello + Hello + Hello + Hello + Hello + Hello +(6 rows) + +SELECT comp(); + comp +----------- + Unchanged +(1 row) + +DROP TABLE T; +DROP FUNCTION set(name); +DROP FUNCTION comp(); +DROP TABLE m; +DROP TABLE has_volatile; +DROP EVENT TRIGGER has_volatile_rewrite; +DROP FUNCTION log_rewrite; +DROP SCHEMA fast_default; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index e224977..8731ce8 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -111,7 +111,7 @@ test: select_views portals_p2 foreign_key cluster dependency guc bitmapops combo # NB: temp.sql does a reconnect which transiently uses 2 connections, # so keep this parallel group to at most 19 tests # ---------- -test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml +test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid conversion truncate alter_table sequence polymorphism rowtypes returning largeobject with xml fast_default # ---------- # Another group of parallel tests diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 9fc5f1a..093edb7 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -186,3 +186,4 @@ test: reloptions test: hash_part test: event_trigger test: stats +test: fast_default diff --git a/src/test/regress/sql/fast_default.sql b/src/test/regress/sql/fast_default.sql new file mode 100644 index 0000000..3afc7b8 --- /dev/null +++ b/src/test/regress/sql/fast_default.sql @@ -0,0 +1,357 @@ +-- +-- ALTER TABLE ADD COLUMN DEFAULT test +-- + +SET search_path = fast_default; +CREATE SCHEMA fast_default; +CREATE TABLE m(id OID); +INSERT INTO m VALUES (NULL::OID); + +CREATE FUNCTION set(tabname name) RETURNS VOID +AS $$ +BEGIN + UPDATE m + SET id = (SELECT c.relfilenode + FROM pg_class AS c, pg_namespace AS s + WHERE c.relname = tabname + AND c.relnamespace = s.oid + AND s.nspname = 'fast_default'); +END; +$$ LANGUAGE 'plpgsql'; + +CREATE FUNCTION comp() RETURNS TEXT +AS $$ +BEGIN + RETURN (SELECT CASE + WHEN m.id = c.relfilenode THEN 'Unchanged' + ELSE 'Rewritten' + END + FROM m, pg_class AS c, pg_namespace AS s + WHERE c.relname = 't' + AND c.relnamespace = s.oid + AND s.nspname = 'fast_default'); +END; +$$ LANGUAGE 'plpgsql'; + +CREATE FUNCTION log_rewrite() RETURNS event_trigger +LANGUAGE plpgsql as +$func$ + +declare + this_schema text; +begin + select into this_schema relnamespace::regnamespace::text + from pg_class + where oid = pg_event_trigger_table_rewrite_oid(); + if this_schema = 'fast_default' + then + RAISE NOTICE 'rewriting table % for reason %', + pg_event_trigger_table_rewrite_oid()::regclass, + pg_event_trigger_table_rewrite_reason(); + end if; +end; +$func$; + +CREATE TABLE has_volatile AS +SELECT * FROM generate_series(1,10) id; + + +CREATE EVENT TRIGGER has_volatile_rewrite + ON table_rewrite + EXECUTE PROCEDURE log_rewrite(); + +-- only the last of these should trigger a rewrite +ALTER TABLE has_volatile ADD col1 int; +ALTER TABLE has_volatile ADD col2 int DEFAULT 1; +ALTER TABLE has_volatile ADD col3 timestamptz DEFAULT current_timestamp; +ALTER TABLE has_volatile ADD col4 int DEFAULT (random() * 10000)::int; + + + +-- Test a large sample of dfferent datatypes +CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1); + +SELECT set('t'); + +INSERT INTO T VALUES (1), (2); + +ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT 'hello', + ALTER COLUMN c_int SET DEFAULT 2; + +INSERT INTO T VALUES (3), (4); + + +ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'world', + ALTER COLUMN c_bpchar SET DEFAULT 'dog'; + +INSERT INTO T VALUES (5), (6); + +ALTER TABLE T ADD COLUMN c_date DATE DEFAULT '2016-06-02', + ALTER COLUMN c_text SET DEFAULT 'cat'; + +INSERT INTO T VALUES (7), (8); + +ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP DEFAULT '2016-09-01 12:00:00', + ADD COLUMN c_timestamp_null TIMESTAMP, + ALTER COLUMN c_date SET DEFAULT '2010-01-01'; + +INSERT INTO T VALUES (9), (10); + +ALTER TABLE T ADD COLUMN c_array TEXT[] + DEFAULT '{"This", "is", "the", "real", "world"}', + ALTER COLUMN c_timestamp SET DEFAULT '1970-12-31 11:12:13', + ALTER COLUMN c_timestamp_null SET DEFAULT '2016-09-29 12:00:00'; + +INSERT INTO T VALUES (11), (12); + +ALTER TABLE T ADD COLUMN c_small SMALLINT DEFAULT -5, + ADD COLUMN c_small_null SMALLINT, + ALTER COLUMN c_array + SET DEFAULT '{"This", "is", "no", "fantasy"}'; + +INSERT INTO T VALUES (13), (14); + +ALTER TABLE T ADD COLUMN c_big BIGINT DEFAULT 180000000000018, + ALTER COLUMN c_small SET DEFAULT 9, + ALTER COLUMN c_small_null SET DEFAULT 13; + +INSERT INTO T VALUES (15), (16); + +ALTER TABLE T ADD COLUMN c_num NUMERIC DEFAULT 1.00000000001, + ALTER COLUMN c_big SET DEFAULT -9999999999999999; + +INSERT INTO T VALUES (17), (18); + +ALTER TABLE T ADD COLUMN c_time TIME DEFAULT '12:00:00', + ALTER COLUMN c_num SET DEFAULT 2.000000000000002; + +INSERT INTO T VALUES (19), (20); + +ALTER TABLE T ADD COLUMN c_interval INTERVAL DEFAULT '1 day', + ALTER COLUMN c_time SET DEFAULT '23:59:59'; + +INSERT INTO T VALUES (21), (22); + +ALTER TABLE T ADD COLUMN c_hugetext TEXT DEFAULT repeat('abcdefg',1000), + ALTER COLUMN c_interval SET DEFAULT '3 hours'; + +INSERT INTO T VALUES (23), (24); + +ALTER TABLE T ALTER COLUMN c_interval DROP DEFAULT, + ALTER COLUMN c_hugetext SET DEFAULT repeat('poiuyt', 1000); + +INSERT INTO T VALUES (25), (26); + +ALTER TABLE T ALTER COLUMN c_bpchar DROP DEFAULT, + ALTER COLUMN c_date DROP DEFAULT, + ALTER COLUMN c_text DROP DEFAULT, + ALTER COLUMN c_timestamp DROP DEFAULT, + ALTER COLUMN c_array DROP DEFAULT, + ALTER COLUMN c_small DROP DEFAULT, + ALTER COLUMN c_big DROP DEFAULT, + ALTER COLUMN c_num DROP DEFAULT, + ALTER COLUMN c_time DROP DEFAULT, + ALTER COLUMN c_hugetext DROP DEFAULT; + +INSERT INTO T VALUES (27), (28); + +SELECT pk, c_int, c_bpchar, c_text, c_date, c_timestamp, + c_timestamp_null, c_array, c_small, c_small_null, + c_big, c_num, c_time, c_interval, + c_hugetext = repeat('abcdefg',1000) as c_hugetext_origdef, + c_hugetext = repeat('poiuyt', 1000) as c_hugetext_newdef +FROM T ORDER BY pk; + +SELECT comp(); + +DROP TABLE T; + +-- Test expressions in the defaults +CREATE OR REPLACE FUNCTION foo(a INT) RETURNS TEXT AS $$ +DECLARE res TEXT := ''; + i INT; +BEGIN + i := 0; + WHILE (i < a) LOOP + res := res || chr(ascii('a') + i); + i := i + 1; + END LOOP; + RETURN res; +END; $$ LANGUAGE PLPGSQL STABLE; + +CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT LENGTH(foo(6))); + +SELECT set('t'); + +INSERT INTO T VALUES (1), (2); + +ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT foo(4), + ALTER COLUMN c_int SET DEFAULT LENGTH(foo(8)); + +INSERT INTO T VALUES (3), (4); + +ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT foo(6), + ALTER COLUMN c_bpchar SET DEFAULT foo(3); + +INSERT INTO T VALUES (5), (6); + +ALTER TABLE T ADD COLUMN c_date DATE + DEFAULT '2016-06-02'::DATE + LENGTH(foo(10)), + ALTER COLUMN c_text SET DEFAULT foo(12); + +INSERT INTO T VALUES (7), (8); + +ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP + DEFAULT '2016-09-01'::DATE + LENGTH(foo(10)), + ALTER COLUMN c_date + SET DEFAULT '2010-01-01'::DATE - LENGTH(foo(4)); + +INSERT INTO T VALUES (9), (10); + +ALTER TABLE T ADD COLUMN c_array TEXT[] + DEFAULT ('{"This", "is", "' || foo(4) || + '","the", "real", "world"}')::TEXT[], + ALTER COLUMN c_timestamp + SET DEFAULT '1970-12-31'::DATE + LENGTH(foo(30)); + +INSERT INTO T VALUES (11), (12); + +ALTER TABLE T ALTER COLUMN c_int DROP DEFAULT, + ALTER COLUMN c_array + SET DEFAULT ('{"This", "is", "' || foo(1) || + '", "fantasy"}')::text[]; + +INSERT INTO T VALUES (13), (14); + +ALTER TABLE T ALTER COLUMN c_bpchar DROP DEFAULT, + ALTER COLUMN c_date DROP DEFAULT, + ALTER COLUMN c_text DROP DEFAULT, + ALTER COLUMN c_timestamp DROP DEFAULT, + ALTER COLUMN c_array DROP DEFAULT; + +INSERT INTO T VALUES (15), (16); + +SELECT * FROM T; + +SELECT comp(); + +DROP TABLE T; + +DROP FUNCTION foo(INT); + +-- Fall back to full rewrite for volatile expressions +CREATE TABLE T(pk INT NOT NULL PRIMARY KEY); + +INSERT INTO T VALUES (1); + +SELECT set('t'); + +-- now() is stable, because it returns the transaction timestamp +ALTER TABLE T ADD COLUMN c1 TIMESTAMP DEFAULT now(); + +SELECT comp(); + +-- clock_timestamp() is volatile +ALTER TABLE T ADD COLUMN c2 TIMESTAMP DEFAULT clock_timestamp(); + +SELECT comp(); + +DROP TABLE T; + +-- Simple querie +CREATE TABLE T (pk INT NOT NULL PRIMARY KEY); + +SELECT set('t'); + +INSERT INTO T SELECT * FROM generate_series(1, 10) a; + +ALTER TABLE T ADD COLUMN c_bigint BIGINT NOT NULL DEFAULT -1; + +INSERT INTO T SELECT b, b - 10 FROM generate_series(11, 20) a(b); + +ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'hello'; + +INSERT INTO T SELECT b, b - 10, (b + 10)::text FROM generate_series(21, 30) a(b); + +-- WHERE clause +SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1; + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) +SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1; + +SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1; + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1; + + +-- COALESCE +SELECT COALESCE(c_bigint, pk), COALESCE(c_text, pk::text) +FROM T +ORDER BY pk LIMIT 10; + +-- Aggregate function +SELECT SUM(c_bigint), MAX(c_text), MIN(c_text) FROM T; + +-- ORDER BY +SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10; + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) +SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10; + +-- LIMIT +SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10; + +EXPLAIN (VERBOSE TRUE, COSTS FALSE) +SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10; + +-- DELETE with RETURNING +DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *; +EXPLAIN (VERBOSE TRUE, COSTS FALSE) +DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *; + +-- UPDATE +UPDATE T SET c_text = '"' || c_text || '"' WHERE pk < 10; +SELECT * FROM T WHERE c_text LIKE '"%"' ORDER BY PK; + +SELECT comp(); + +DROP TABLE T; + + +-- Combine with other DDL +CREATE TABLE T(pk INT NOT NULL PRIMARY KEY); + +SELECT set('t'); + +INSERT INTO T VALUES (1), (2); + +ALTER TABLE T ADD COLUMN c_int INT NOT NULL DEFAULT -1; + +INSERT INTO T VALUES (3), (4); + +ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'Hello'; + +INSERT INTO T VALUES (5), (6); + +ALTER TABLE T ALTER COLUMN c_text SET DEFAULT 'world', + ALTER COLUMN c_int SET DEFAULT 1; + +INSERT INTO T VALUES (7), (8); + +SELECT * FROM T ORDER BY pk; + +-- Add an index +CREATE INDEX i ON T(c_int, c_text); + +SELECT c_text FROM T WHERE c_int = -1; + +SELECT comp(); + +DROP TABLE T; +DROP FUNCTION set(name); +DROP FUNCTION comp(); +DROP TABLE m; +DROP TABLE has_volatile; +DROP EVENT TRIGGER has_volatile_rewrite; +DROP FUNCTION log_rewrite; +DROP SCHEMA fast_default;