diff options
Diffstat (limited to 'src/backend')
23 files changed, 918 insertions, 210 deletions
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index d4478a2cbad..b6ad7356718 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -58,6 +58,7 @@ #include "postgres.h" #include "access/sysattr.h" +#include "access/tupdesc_details.h" #include "access/tuptoaster.h" #include "executor/tuptable.h" #include "utils/expandeddatum.h" @@ -76,6 +77,74 @@ * ---------------------------------------------------------------- */ +/* + * Return the missing value of an attribute, or NULL if there isn't one. + */ +static Datum +getmissingattr(TupleDesc tupleDesc, + int attnum, bool *isnull) +{ + Form_pg_attribute att; + + Assert(attnum <= tupleDesc->natts); + Assert(attnum > 0); + + att = TupleDescAttr(tupleDesc, attnum - 1); + + if (att->atthasmissing) + { + AttrMissing *attrmiss; + + Assert(tupleDesc->constr); + Assert(tupleDesc->constr->missing); + + attrmiss = tupleDesc->constr->missing + (attnum - 1); + + if (attrmiss->ammissingPresent) + { + *isnull = false; + return attrmiss->ammissing; + } + } + + *isnull = true; + return PointerGetDatum(NULL); +} + +/* + * Fill in missing values for a TupleTableSlot + */ +static void +slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum) +{ + AttrMissing *attrmiss = NULL; + int missattnum; + + if (slot->tts_tupleDescriptor->constr) + attrmiss = slot->tts_tupleDescriptor->constr->missing; + + + if (!attrmiss) + { + /* no missing values array at all, so just fill everything in as NULL */ + memset(slot->tts_values + startAttNum, 0, + (lastAttNum - startAttNum) * sizeof(Datum)); + memset(slot->tts_isnull + startAttNum, 1, + (lastAttNum - startAttNum) * sizeof(bool)); + } + else + { + /* if there is a missing values array we must process them one by one */ + for (missattnum = lastAttNum - 1; + missattnum >= startAttNum; + missattnum--) + { + slot->tts_values[missattnum] = attrmiss[missattnum].ammissing; + slot->tts_isnull[missattnum] = + !attrmiss[missattnum].ammissingPresent; + } + } +} /* * heap_compute_data_size @@ -133,6 +202,131 @@ heap_compute_data_size(TupleDesc tupleDesc, } /* + * Per-attribute helper for heap_fill_tuple and other routines building tuples. + * + * Fill in either a data value or a bit in the null bitmask + */ +static inline void +fill_val(Form_pg_attribute att, + bits8 **bit, + int *bitmask, + char **dataP, + uint16 *infomask, + Datum datum, + bool isnull) +{ + Size data_length; + char *data = *dataP; + + /* + * If we're building a null bitmap, set the appropriate bit for the + * current column value here. + */ + if (bit != NULL) + { + if (*bitmask != HIGHBIT) + *bitmask <<= 1; + else + { + *bit += 1; + **bit = 0x0; + *bitmask = 1; + } + + if (isnull) + { + *infomask |= HEAP_HASNULL; + return; + } + + **bit |= *bitmask; + } + + /* + * XXX we use the att_align macros on the pointer value itself, not on an + * offset. This is a bit of a hack. + */ + if (att->attbyval) + { + /* pass-by-value */ + data = (char *) att_align_nominal(data, att->attalign); + store_att_byval(data, datum, att->attlen); + data_length = att->attlen; + } + else if (att->attlen == -1) + { + /* varlena */ + Pointer val = DatumGetPointer(datum); + + *infomask |= HEAP_HASVARWIDTH; + if (VARATT_IS_EXTERNAL(val)) + { + if (VARATT_IS_EXTERNAL_EXPANDED(val)) + { + /* + * we want to flatten the expanded value so that the + * constructed tuple doesn't depend on it + */ + ExpandedObjectHeader *eoh = DatumGetEOHP(datum); + + data = (char *) att_align_nominal(data, + att->attalign); + data_length = EOH_get_flat_size(eoh); + EOH_flatten_into(eoh, data, data_length); + } + else + { + *infomask |= HEAP_HASEXTERNAL; + /* no alignment, since it's short by definition */ + data_length = VARSIZE_EXTERNAL(val); + memcpy(data, val, data_length); + } + } + else if (VARATT_IS_SHORT(val)) + { + /* no alignment for short varlenas */ + data_length = VARSIZE_SHORT(val); + memcpy(data, val, data_length); + } + else if (VARLENA_ATT_IS_PACKABLE(att) && + VARATT_CAN_MAKE_SHORT(val)) + { + /* convert to short varlena -- no alignment */ + data_length = VARATT_CONVERTED_SHORT_SIZE(val); + SET_VARSIZE_SHORT(data, data_length); + memcpy(data + 1, VARDATA(val), data_length - 1); + } + else + { + /* full 4-byte header varlena */ + data = (char *) att_align_nominal(data, + att->attalign); + data_length = VARSIZE(val); + memcpy(data, val, data_length); + } + } + else if (att->attlen == -2) + { + /* cstring ... never needs alignment */ + *infomask |= HEAP_HASVARWIDTH; + Assert(att->attalign == 'c'); + data_length = strlen(DatumGetCString(datum)) + 1; + memcpy(data, DatumGetPointer(datum), data_length); + } + else + { + /* fixed-length pass-by-reference */ + data = (char *) att_align_nominal(data, att->attalign); + Assert(att->attlen > 0); + data_length = att->attlen; + memcpy(data, DatumGetPointer(datum), data_length); + } + + data += data_length; + *dataP = data; +} + +/* * heap_fill_tuple * Load data portion of a tuple from values/isnull arrays * @@ -172,111 +366,15 @@ heap_fill_tuple(TupleDesc tupleDesc, for (i = 0; i < numberOfAttributes; i++) { - Form_pg_attribute att = TupleDescAttr(tupleDesc, i); - Size data_length; - - if (bit != NULL) - { - if (bitmask != HIGHBIT) - bitmask <<= 1; - else - { - bitP += 1; - *bitP = 0x0; - bitmask = 1; - } - - if (isnull[i]) - { - *infomask |= HEAP_HASNULL; - continue; - } - - *bitP |= bitmask; - } - - /* - * XXX we use the att_align macros on the pointer value itself, not on - * an offset. This is a bit of a hack. - */ - - if (att->attbyval) - { - /* pass-by-value */ - data = (char *) att_align_nominal(data, att->attalign); - store_att_byval(data, values[i], att->attlen); - data_length = att->attlen; - } - else if (att->attlen == -1) - { - /* varlena */ - Pointer val = DatumGetPointer(values[i]); - - *infomask |= HEAP_HASVARWIDTH; - if (VARATT_IS_EXTERNAL(val)) - { - if (VARATT_IS_EXTERNAL_EXPANDED(val)) - { - /* - * we want to flatten the expanded value so that the - * constructed tuple doesn't depend on it - */ - ExpandedObjectHeader *eoh = DatumGetEOHP(values[i]); - - data = (char *) att_align_nominal(data, - att->attalign); - data_length = EOH_get_flat_size(eoh); - EOH_flatten_into(eoh, data, data_length); - } - else - { - *infomask |= HEAP_HASEXTERNAL; - /* no alignment, since it's short by definition */ - data_length = VARSIZE_EXTERNAL(val); - memcpy(data, val, data_length); - } - } - else if (VARATT_IS_SHORT(val)) - { - /* no alignment for short varlenas */ - data_length = VARSIZE_SHORT(val); - memcpy(data, val, data_length); - } - else if (VARLENA_ATT_IS_PACKABLE(att) && - VARATT_CAN_MAKE_SHORT(val)) - { - /* convert to short varlena -- no alignment */ - data_length = VARATT_CONVERTED_SHORT_SIZE(val); - SET_VARSIZE_SHORT(data, data_length); - memcpy(data + 1, VARDATA(val), data_length - 1); - } - else - { - /* full 4-byte header varlena */ - data = (char *) att_align_nominal(data, - att->attalign); - data_length = VARSIZE(val); - memcpy(data, val, data_length); - } - } - else if (att->attlen == -2) - { - /* cstring ... never needs alignment */ - *infomask |= HEAP_HASVARWIDTH; - Assert(att->attalign == 'c'); - data_length = strlen(DatumGetCString(values[i])) + 1; - memcpy(data, DatumGetPointer(values[i]), data_length); - } - else - { - /* fixed-length pass-by-reference */ - data = (char *) att_align_nominal(data, att->attalign); - Assert(att->attlen > 0); - data_length = att->attlen; - memcpy(data, DatumGetPointer(values[i]), data_length); - } - - data += data_length; + Form_pg_attribute attr = TupleDescAttr(tupleDesc, i); + + fill_val(attr, + bitP ? &bitP : NULL, + &bitmask, + &data, + infomask, + values ? values[i] : PointerGetDatum(NULL), + isnull ? isnull[i] : true); } Assert((data - start) == data_size); @@ -293,10 +391,20 @@ heap_fill_tuple(TupleDesc tupleDesc, * ---------------- */ bool -heap_attisnull(HeapTuple tup, int attnum) +heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc) { + /* + * We allow a NULL tupledesc for relations not expected to have missing + * values, such as catalog relations and indexes. + */ + Assert(!tupleDesc || attnum <= tupleDesc->natts); if (attnum > (int) HeapTupleHeaderGetNatts(tup->t_data)) - return true; + { + if (tupleDesc && TupleDescAttr(tupleDesc, attnum - 1)->atthasmissing) + return false; + else + return true; + } if (attnum > 0) { @@ -649,6 +757,274 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest) memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len); } +/* + * Expand a tuple which has less attributes than required. For each attribute + * not present in the sourceTuple, if there is a missing value that will be + * used. Otherwise the attribute will be set to NULL. + * + * The source tuple must have less attributes than the required number. + * + * Only one of targetHeapTuple and targetMinimalTuple may be supplied. The + * other argument must be NULL. + */ +static void +expand_tuple(HeapTuple *targetHeapTuple, + MinimalTuple *targetMinimalTuple, + HeapTuple sourceTuple, + TupleDesc tupleDesc) +{ + AttrMissing *attrmiss = NULL; + int attnum; + int firstmissingnum = 0; + bool hasNulls = HeapTupleHasNulls(sourceTuple); + HeapTupleHeader targetTHeader; + HeapTupleHeader sourceTHeader = sourceTuple->t_data; + int sourceNatts = HeapTupleHeaderGetNatts(sourceTHeader); + int natts = tupleDesc->natts; + int sourceNullLen; + int targetNullLen; + Size sourceDataLen = sourceTuple->t_len - sourceTHeader->t_hoff; + Size targetDataLen; + Size len; + int hoff; + bits8 *nullBits = NULL; + int bitMask = 0; + char *targetData; + uint16 *infoMask; + + Assert((targetHeapTuple && !targetMinimalTuple) + || (!targetHeapTuple && targetMinimalTuple)); + + Assert(sourceNatts < natts); + + sourceNullLen = (hasNulls ? BITMAPLEN(sourceNatts) : 0); + + targetDataLen = sourceDataLen; + + if (tupleDesc->constr && + tupleDesc->constr->missing) + { + /* + * If there are missing values we want to put them into the tuple. + * Before that we have to compute the extra length for the values + * array and the variable length data. + */ + attrmiss = tupleDesc->constr->missing; + + /* + * Find the first item in attrmiss for which we don't have a value in + * the source. We can ignore all the missing entries before that. + */ + for (firstmissingnum = sourceNatts; + firstmissingnum < natts; + firstmissingnum++) + { + if (attrmiss[firstmissingnum].ammissingPresent) + break; + } + + /* + * If there are no more missing values everything else must be NULL + */ + if (firstmissingnum >= natts) + { + hasNulls = true; + } + else + { + + /* + * Now walk the missing attributes. If there is a missing value + * make space for it. Otherwise, it's going to be NULL. + */ + for (attnum = firstmissingnum; + attnum < natts; + attnum++) + { + if (attrmiss[attnum].ammissingPresent) + { + Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum); + + targetDataLen = att_align_datum(targetDataLen, + att->attalign, + att->attlen, + attrmiss[attnum].ammissing); + + targetDataLen = att_addlength_pointer(targetDataLen, + att->attlen, + attrmiss[attnum].ammissing); + } + else + { + /* no missing value, so it must be null */ + hasNulls = true; + } + } + } + } /* end if have missing values */ + else + { + /* + * If there are no missing values at all then NULLS must be allowed, + * since some of the attributes are known to be absent. + */ + hasNulls = true; + } + + len = 0; + + if (hasNulls) + { + targetNullLen = BITMAPLEN(natts); + len += targetNullLen; + } + else + targetNullLen = 0; + + if (tupleDesc->tdhasoid) + len += sizeof(Oid); + + /* + * Allocate and zero the space needed. Note that the tuple body and + * HeapTupleData management structure are allocated in one chunk. + */ + if (targetHeapTuple) + { + len += offsetof(HeapTupleHeaderData, t_bits); + hoff = len = MAXALIGN(len); /* align user data safely */ + len += targetDataLen; + + *targetHeapTuple = (HeapTuple) palloc0(HEAPTUPLESIZE + len); + (*targetHeapTuple)->t_data + = targetTHeader + = (HeapTupleHeader) ((char *) *targetHeapTuple + HEAPTUPLESIZE); + (*targetHeapTuple)->t_len = len; + (*targetHeapTuple)->t_tableOid = sourceTuple->t_tableOid; + ItemPointerSetInvalid(&((*targetHeapTuple)->t_self)); + + targetTHeader->t_infomask = sourceTHeader->t_infomask; + targetTHeader->t_hoff = hoff; + HeapTupleHeaderSetNatts(targetTHeader, natts); + HeapTupleHeaderSetDatumLength(targetTHeader, len); + HeapTupleHeaderSetTypeId(targetTHeader, tupleDesc->tdtypeid); + HeapTupleHeaderSetTypMod(targetTHeader, tupleDesc->tdtypmod); + /* We also make sure that t_ctid is invalid unless explicitly set */ + ItemPointerSetInvalid(&(targetTHeader->t_ctid)); + if (targetNullLen > 0) + nullBits = (bits8 *) ((char *) (*targetHeapTuple)->t_data + + offsetof(HeapTupleHeaderData, t_bits)); + targetData = (char *) (*targetHeapTuple)->t_data + hoff; + infoMask = &(targetTHeader->t_infomask); + } + else + { + len += SizeofMinimalTupleHeader; + hoff = len = MAXALIGN(len); /* align user data safely */ + len += targetDataLen; + + *targetMinimalTuple = (MinimalTuple) palloc0(len); + (*targetMinimalTuple)->t_len = len; + (*targetMinimalTuple)->t_hoff = hoff + MINIMAL_TUPLE_OFFSET; + (*targetMinimalTuple)->t_infomask = sourceTHeader->t_infomask; + /* Same macro works for MinimalTuples */ + HeapTupleHeaderSetNatts(*targetMinimalTuple, natts); + if (targetNullLen > 0) + nullBits = (bits8 *) ((char *) *targetMinimalTuple + + offsetof(MinimalTupleData, t_bits)); + targetData = (char *) *targetMinimalTuple + hoff; + infoMask = &((*targetMinimalTuple)->t_infomask); + } + + if (targetNullLen > 0) + { + if (sourceNullLen > 0) + { + /* if bitmap pre-existed copy in - all is set */ + memcpy(nullBits, + ((char *) sourceTHeader) + + offsetof(HeapTupleHeaderData, t_bits), + sourceNullLen); + nullBits += sourceNullLen - 1; + } + else + { + sourceNullLen = BITMAPLEN(sourceNatts); + /* Set NOT NULL for all existing attributes */ + memset(nullBits, 0xff, sourceNullLen); + + nullBits += sourceNullLen - 1; + + if (sourceNatts & 0x07) + { + /* build the mask (inverted!) */ + bitMask = 0xff << (sourceNatts & 0x07); + /* Voila */ + *nullBits = ~bitMask; + } + } + + bitMask = (1 << ((sourceNatts - 1) & 0x07)); + } /* End if have null bitmap */ + + memcpy(targetData, + ((char *) sourceTuple->t_data) + sourceTHeader->t_hoff, + sourceDataLen); + + targetData += sourceDataLen; + + /* Now fill in the missing values */ + for (attnum = sourceNatts; attnum < natts; attnum++) + { + + Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum); + + if (attrmiss[attnum].ammissingPresent) + { + fill_val(attr, + nullBits ? &nullBits : NULL, + &bitMask, + &targetData, + infoMask, + attrmiss[attnum].ammissing, + false); + } + else + { + fill_val(attr, + &nullBits, + &bitMask, + &targetData, + infoMask, + (Datum) 0, + true); + } + } /* end loop over missing attributes */ +} + +/* + * Fill in the missing values for a minimal HeapTuple + */ +MinimalTuple +minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc) +{ + MinimalTuple minimalTuple; + + expand_tuple(NULL, &minimalTuple, sourceTuple, tupleDesc); + return minimalTuple; +} + +/* + * Fill in the missing values for an ordinary HeapTuple + */ +HeapTuple +heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc) +{ + HeapTuple heapTuple; + + expand_tuple(&heapTuple, NULL, sourceTuple, tupleDesc); + return heapTuple; +} + /* ---------------- * heap_copy_tuple_as_datum * @@ -1012,13 +1388,10 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc, /* * If tuple doesn't have all the atts indicated by tupleDesc, read the - * rest as null + * rest as nulls or missing values as appropriate. */ for (; attnum < tdesc_natts; attnum++) - { - values[attnum] = (Datum) 0; - isnull[attnum] = true; - } + values[attnum] = getmissingattr(tupleDesc, attnum + 1, &isnull[attnum]); } /* @@ -1183,7 +1556,8 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull) elog(ERROR, "cannot extract attribute from empty tuple slot"); /* - * return NULL if attnum is out of range according to the tuple + * return NULL or missing value if attnum is out of range according to the + * tuple * * (We have to check this separately because of various inheritance and * table-alteration scenarios: the tuple could be either longer or shorter @@ -1191,10 +1565,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull) */ tup = tuple->t_data; if (attnum > HeapTupleHeaderGetNatts(tup)) - { - *isnull = true; - return (Datum) 0; - } + return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull); /* * check if target attribute is null: no point in groveling through tuple @@ -1263,13 +1634,11 @@ slot_getallattrs(TupleTableSlot *slot) /* * If tuple doesn't have all the atts indicated by tupleDesc, read the - * rest as null + * rest as NULLS or missing values. */ - for (; attnum < tdesc_natts; attnum++) - { - slot->tts_values[attnum] = (Datum) 0; - slot->tts_isnull[attnum] = true; - } + if (attnum < tdesc_natts) + slot_getmissingattrs(slot, attnum, tdesc_natts); + slot->tts_nvalid = tdesc_natts; } @@ -1310,13 +1679,11 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum) /* * If tuple doesn't have all the atts indicated by tupleDesc, read the - * rest as null + * rest as NULLs or missing values */ - for (; attno < attnum; attno++) - { - slot->tts_values[attno] = (Datum) 0; - slot->tts_isnull[attno] = true; - } + if (attno < attnum) + slot_getmissingattrs(slot, attno, attnum); + slot->tts_nvalid = attnum; } @@ -1340,7 +1707,7 @@ slot_attisnull(TupleTableSlot *slot, int attnum) elog(ERROR, "cannot extract system attribute from virtual tuple"); if (tuple == &(slot->tts_minhdr)) /* internal error */ elog(ERROR, "cannot extract system attribute from minimal tuple"); - return heap_attisnull(tuple, attnum); + return heap_attisnull(tuple, attnum, tupleDesc); } /* @@ -1363,7 +1730,7 @@ slot_attisnull(TupleTableSlot *slot, int attnum) elog(ERROR, "cannot extract attribute from empty tuple slot"); /* and let the tuple tell it */ - return heap_attisnull(tuple, attnum); + return heap_attisnull(tuple, attnum, tupleDesc); } /* diff --git a/src/backend/access/common/tupdesc.c b/src/backend/access/common/tupdesc.c index f1f44230cd7..2658399484b 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -21,12 +21,14 @@ #include "access/hash.h" #include "access/htup_details.h" +#include "access/tupdesc_details.h" #include "catalog/pg_collation.h" #include "catalog/pg_type.h" #include "miscadmin.h" #include "parser/parse_type.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/datum.h" #include "utils/hashutils.h" #include "utils/resowner_private.h" #include "utils/syscache.h" @@ -129,6 +131,7 @@ CreateTupleDescCopy(TupleDesc tupdesc) att->attnotnull = false; att->atthasdef = false; + att->atthasmissing = false; att->attidentity = '\0'; } @@ -176,6 +179,23 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc) } } + if (constr->missing) + { + cpy->missing = (AttrMissing *) palloc(tupdesc->natts * sizeof(AttrMissing)); + memcpy(cpy->missing, constr->missing, tupdesc->natts * sizeof(AttrMissing)); + for (i = tupdesc->natts - 1; i >= 0; i--) + { + if (constr->missing[i].ammissingPresent) + { + Form_pg_attribute attr = TupleDescAttr(tupdesc, i); + + cpy->missing[i].ammissing = datumCopy(constr->missing[i].ammissing, + attr->attbyval, + attr->attlen); + } + } + } + if ((cpy->num_check = constr->num_check) > 0) { cpy->check = (ConstrCheck *) palloc(cpy->num_check * sizeof(ConstrCheck)); @@ -227,6 +247,7 @@ TupleDescCopy(TupleDesc dst, TupleDesc src) att->attnotnull = false; att->atthasdef = false; + att->atthasmissing = false; att->attidentity = '\0'; } dst->constr = NULL; @@ -279,6 +300,7 @@ TupleDescCopyEntry(TupleDesc dst, AttrNumber dstAttno, /* since we're not copying constraints or defaults, clear these */ dstAtt->attnotnull = false; dstAtt->atthasdef = false; + dstAtt->atthasmissing = false; dstAtt->attidentity = '\0'; } @@ -309,6 +331,18 @@ FreeTupleDesc(TupleDesc tupdesc) } pfree(attrdef); } + if (tupdesc->constr->missing) + { + AttrMissing *attrmiss = tupdesc->constr->missing; + + for (i = tupdesc->natts - 1; i >= 0; i--) + { + if (attrmiss[i].ammissingPresent + && !TupleDescAttr(tupdesc, i)->attbyval) + pfree(DatumGetPointer(attrmiss[i].ammissing)); + } + pfree(attrmiss); + } if (tupdesc->constr->num_check > 0) { ConstrCheck *check = tupdesc->constr->check; @@ -469,6 +503,29 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) if (strcmp(defval1->adbin, defval2->adbin) != 0) return false; } + if (constr1->missing) + { + if (!constr2->missing) + return false; + for (i = 0; i < tupdesc1->natts; i++) + { + AttrMissing *missval1 = constr1->missing + i; + AttrMissing *missval2 = constr2->missing + i; + + if (missval1->ammissingPresent != missval2->ammissingPresent) + return false; + if (missval1->ammissingPresent) + { + Form_pg_attribute missatt1 = TupleDescAttr(tupdesc1, i); + + if (!datumIsEqual(missval1->ammissing, missval2->ammissing, + missatt1->attbyval, missatt1->attlen)) + return false; + } + } + } + else if (constr2->missing) + return false; n = constr1->num_check; if (n != (int) constr2->num_check) return false; @@ -584,6 +641,7 @@ TupleDescInitEntry(TupleDesc desc, att->attnotnull = false; att->atthasdef = false; + att->atthasmissing = false; att->attidentity = '\0'; att->attisdropped = false; att->attislocal = true; @@ -642,6 +700,7 @@ TupleDescInitBuiltinEntry(TupleDesc desc, att->attnotnull = false; att->atthasdef = false; + att->atthasmissing = false; att->attidentity = '\0'; att->attisdropped = false; att->attislocal = true; @@ -797,6 +856,7 @@ BuildDescForRelation(List *schema) constr->has_not_null = true; constr->defval = NULL; + constr->missing = NULL; constr->num_defval = 0; constr->check = NULL; constr->num_check = 0; diff --git a/src/backend/catalog/aclchk.c b/src/backend/catalog/aclchk.c index 06485397969..83000575ce7 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -4588,7 +4588,7 @@ pg_attribute_aclcheck_all(Oid table_oid, Oid roleid, AclMode mode, * grants no privileges, so that we can fall out quickly in the very * common case where attacl is null. */ - if (heap_attisnull(attTuple, Anum_pg_attribute_attacl)) + if (heap_attisnull(attTuple, Anum_pg_attribute_attacl, NULL)) attmask = 0; else attmask = pg_attribute_aclmask(table_oid, curr_att, roleid, diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index b69bb1e2a41..a1def77944c 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}; @@ -624,6 +628,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign); values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull); values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef); + values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing); values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity); values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped); values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal); @@ -634,6 +639,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel, nulls[Anum_pg_attribute_attacl - 1] = true; nulls[Anum_pg_attribute_attoptions - 1] = true; nulls[Anum_pg_attribute_attfdwoptions - 1] = true; + nulls[Anum_pg_attribute_attmissingval - 1] = true; tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls); @@ -1926,13 +1932,90 @@ heap_drop_with_catalog(Oid relid) /* + * RelationClearMissing + * + * Set atthasmissing and attmissingval to false/null for all attributes + * where they are currently set. This can be safely and usefully done if + * the table is rewritten (e.g. by VACUUM FULL or CLUSTER) where we know there + * are no rows left with less than a full complement of attributes. + * + * The caller must have an AccessExclusive lock on the relation. + */ +void +RelationClearMissing(Relation rel) +{ + Relation attr_rel; + Oid relid = RelationGetRelid(rel); + int natts = RelationGetNumberOfAttributes(rel); + int attnum; + Datum repl_val[Natts_pg_attribute]; + bool repl_null[Natts_pg_attribute]; + bool repl_repl[Natts_pg_attribute]; + Form_pg_attribute attrtuple; + HeapTuple tuple, + newtuple; + + memset(repl_val, 0, sizeof(repl_val)); + memset(repl_null, false, sizeof(repl_null)); + memset(repl_repl, false, sizeof(repl_repl)); + + repl_val[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(false); + repl_null[Anum_pg_attribute_attmissingval - 1] = true; + + repl_repl[Anum_pg_attribute_atthasmissing - 1] = true; + repl_repl[Anum_pg_attribute_attmissingval - 1] = true; + + + /* Get a lock on pg_attribute */ + attr_rel = heap_open(AttributeRelationId, RowExclusiveLock); + + /* process each non-system attribute, including any dropped columns */ + for (attnum = 1; attnum <= natts; attnum++) + { + tuple = SearchSysCache2(ATTNUM, + ObjectIdGetDatum(relid), + Int16GetDatum(attnum)); + if (!HeapTupleIsValid(tuple)) /* shouldn't happen */ + elog(ERROR, "cache lookup failed for attribute %d of relation %u", + attnum, relid); + + attrtuple = (Form_pg_attribute) GETSTRUCT(tuple); + + /* ignore any where atthasmissing is not true */ + if (attrtuple->atthasmissing) + { + newtuple = heap_modify_tuple(tuple, RelationGetDescr(attr_rel), + repl_val, repl_null, repl_repl); + + CatalogTupleUpdate(attr_rel, &newtuple->t_self, newtuple); + + heap_freetuple(newtuple); + } + + ReleaseSysCache(tuple); + } + + /* + * Our update of the pg_attribute rows will force a relcache rebuild, so + * there's nothing else to do here. + */ + heap_close(attr_rel, RowExclusiveLock); +} + +/* * Store a default expression for column attnum of relation rel. * * Returns the OID of the new pg_attrdef tuple. + * + * add_column_mode must be true if we are storing the default for a new + * attribute, and false if it's for an already existing attribute. The reason + * for this is that the missing value must never be updated after it is set, + * which can only be when a column is added to the table. Otherwise we would + * in effect be changing existing tuples. */ Oid StoreAttrDefault(Relation rel, AttrNumber attnum, - Node *expr, bool is_internal) + Node *expr, bool is_internal, bool add_column_mode) { char *adbin; char *adsrc; @@ -2000,8 +2083,69 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, attStruct = (Form_pg_attribute) GETSTRUCT(atttup); if (!attStruct->atthasdef) { - attStruct->atthasdef = true; + Form_pg_attribute defAttStruct; + + ExprState *exprState; + Expr *expr2 = (Expr *) expr; + EState *estate = NULL; + ExprContext *econtext; + Datum valuesAtt[Natts_pg_attribute]; + bool nullsAtt[Natts_pg_attribute]; + bool replacesAtt[Natts_pg_attribute]; + Datum missingval = (Datum) 0; + bool missingIsNull = true; + + MemSet(valuesAtt, 0, sizeof(valuesAtt)); + MemSet(nullsAtt, false, sizeof(nullsAtt)); + MemSet(replacesAtt, false, sizeof(replacesAtt)); + valuesAtt[Anum_pg_attribute_atthasdef - 1] = true; + replacesAtt[Anum_pg_attribute_atthasdef - 1] = true; + + if (add_column_mode) + { + expr2 = expression_planner(expr2); + estate = CreateExecutorState(); + exprState = ExecPrepareExpr(expr2, estate); + econtext = GetPerTupleExprContext(estate); + + missingval = ExecEvalExpr(exprState, econtext, + &missingIsNull); + + FreeExecutorState(estate); + + defAttStruct = TupleDescAttr(rel->rd_att, attnum - 1); + + if (missingIsNull) + { + /* if the default evaluates to NULL, just store a NULL array */ + missingval = (Datum) 0; + } + else + { + /* otherwise make a one-element array of the value */ + missingval = PointerGetDatum( + construct_array(&missingval, + 1, + defAttStruct->atttypid, + defAttStruct->attlen, + defAttStruct->attbyval, + defAttStruct->attalign)); + } + + valuesAtt[Anum_pg_attribute_atthasmissing - 1] = !missingIsNull; + replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true; + valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval; + replacesAtt[Anum_pg_attribute_attmissingval - 1] = true; + nullsAtt[Anum_pg_attribute_attmissingval - 1] = missingIsNull; + } + atttup = heap_modify_tuple(atttup, RelationGetDescr(attrrel), + valuesAtt, nullsAtt, replacesAtt); + CatalogTupleUpdate(attrrel, &atttup->t_self, atttup); + + if (!missingIsNull) + pfree(DatumGetPointer(missingval)); + } heap_close(attrrel, RowExclusiveLock); heap_freetuple(atttup); @@ -2185,7 +2329,7 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal) { case CONSTR_DEFAULT: con->conoid = StoreAttrDefault(rel, con->attnum, con->expr, - is_internal); + is_internal, false); break; case CONSTR_CHECK: con->conoid = @@ -2301,7 +2445,12 @@ AddRelationNewConstraints(Relation rel, (IsA(expr, Const) &&((Const *) expr)->constisnull)) continue; - defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal); + /* If the DEFAULT is volatile we cannot use a missing value */ + if (colDef->missingMode && contain_volatile_functions((Node *) expr)) + colDef->missingMode = false; + + defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal, + colDef->missingMode); cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); cooked->contype = CONSTR_DEFAULT; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index dee1a8ac783..bc99a60d347 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -372,6 +372,7 @@ ConstructTupleDescriptor(Relation heapRelation, to->attcacheoff = -1; to->attnotnull = false; to->atthasdef = false; + to->atthasmissing = false; to->attidentity = '\0'; to->attislocal = true; to->attinhcount = 0; @@ -1655,7 +1656,8 @@ index_drop(Oid indexId, bool concurrent) if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for index %u", indexId); - hasexprs = !heap_attisnull(tuple, Anum_pg_index_indexprs); + hasexprs = !heap_attisnull(tuple, Anum_pg_index_indexprs, + RelationGetDescr(indexRelation)); CatalogTupleDelete(indexRelation, &tuple->t_self); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index 57f3917fdc4..639b6992d53 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -453,7 +453,7 @@ check_index_is_clusterable(Relation OldHeap, Oid indexOid, bool recheck, LOCKMOD * seqscan pass over the table to copy the missing rows, but that seems * expensive and tedious. */ - if (!heap_attisnull(OldIndex->rd_indextuple, Anum_pg_index_indpred)) + if (!heap_attisnull(OldIndex->rd_indextuple, Anum_pg_index_indpred, NULL)) ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot cluster on partial index \"%s\"", @@ -1669,6 +1669,16 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, } relation_close(newrel, NoLock); } + + /* if it's not a catalog table, clear any missing attribute settings */ + if (!is_system_catalog) + { + Relation newrel; + + newrel = heap_open(OIDOldHeap, NoLock); + RelationClearMissing(newrel); + relation_close(newrel, NoLock); + } } diff --git a/src/backend/commands/functioncmds.c b/src/backend/commands/functioncmds.c index 86fa8c0dd74..c46493dd88b 100644 --- a/src/backend/commands/functioncmds.c +++ b/src/backend/commands/functioncmds.c @@ -2252,7 +2252,7 @@ ExecuteCallStmt(CallStmt *stmt, ParamListInfo params, bool atomic, DestReceiver tp = SearchSysCache1(PROCOID, ObjectIdGetDatum(fexpr->funcid)); if (!HeapTupleIsValid(tp)) elog(ERROR, "cache lookup failed for function %u", fexpr->funcid); - if (!heap_attisnull(tp, Anum_pg_proc_proconfig)) + if (!heap_attisnull(tp, Anum_pg_proc_proconfig, NULL)) callcontext->atomic = true; ReleaseSysCache(tp); diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index 0a2ab500238..01859707940 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -215,8 +215,8 @@ CheckIndexCompatible(Oid oldId, * We don't assess expressions or predicates; assume incompatibility. * Also, if the index is invalid for any reason, treat it as incompatible. */ - if (!(heap_attisnull(tuple, Anum_pg_index_indpred) && - heap_attisnull(tuple, Anum_pg_index_indexprs) && + if (!(heap_attisnull(tuple, Anum_pg_index_indpred, NULL) && + heap_attisnull(tuple, Anum_pg_index_indexprs, NULL) && IndexIsValid(indexForm))) { ReleaseSysCache(tuple); diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index e74fb1f4691..83a881eff38 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -714,6 +714,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); rawEnt->attnum = attnum; rawEnt->raw_default = colDef->raw_default; + rawEnt->missingMode = false; rawDefaults = lappend(rawDefaults, rawEnt); attr->atthasdef = true; } @@ -4682,7 +4683,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode) { int attn = lfirst_int(l); - if (heap_attisnull(tuple, attn + 1)) + if (heap_attisnull(tuple, attn + 1, newTupDesc)) { Form_pg_attribute attr = TupleDescAttr(newTupDesc, attn); @@ -4785,7 +4786,7 @@ ATGetQueueEntry(List **wqueue, Relation rel) tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo)); tab->relid = relid; tab->relkind = rel->rd_rel->relkind; - tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel)); + tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel)); tab->newrelpersistence = RELPERSISTENCE_PERMANENT; tab->chgPersistence = false; @@ -5404,6 +5405,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, attribute.attalign = tform->typalign; attribute.attnotnull = colDef->is_not_null; attribute.atthasdef = false; + attribute.atthasmissing = false; attribute.attidentity = colDef->identity; attribute.attisdropped = false; attribute.attislocal = colDef->is_local; @@ -5449,6 +5451,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, rawEnt->raw_default = copyObject(colDef->raw_default); /* + * Attempt to skip a complete table rewrite by storing the specified + * DEFAULT value outside of the heap. This may be disabled inside + * AddRelationNewConstraints if the optimization cannot be applied. + */ + rawEnt->missingMode = true; + + /* * This function is intended for CREATE TABLE, so it processes a * _list_ of defaults, but we just do one. */ @@ -5457,6 +5466,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, /* Make the additional catalog changes visible */ CommandCounterIncrement(); + + /* + * Did the request for a missing value work? If not we'll have to do + * a rewrite + */ + if (!rawEnt->missingMode) + tab->rewrite |= AT_REWRITE_DEFAULT_VAL; } /* @@ -5502,6 +5518,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, nve->typeId = typeOid; defval = (Expr *) nve; + + /* must do a rewrite for identity columns */ + tab->rewrite |= AT_REWRITE_DEFAULT_VAL; } else defval = (Expr *) build_column_default(rel, attribute.attnum); @@ -5537,16 +5556,21 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, newval->expr = expression_planner(defval); tab->newvals = lappend(tab->newvals, newval); - tab->rewrite |= AT_REWRITE_DEFAULT_VAL; } - /* - * If the new column is NOT NULL, tell Phase 3 it needs to test that. - * (Note we don't do this for an OID column. OID will be marked not - * null, but since it's filled specially, there's no need to test - * anything.) - */ - tab->new_notnull |= colDef->is_not_null; + if (DomainHasConstraints(typeOid)) + tab->rewrite |= AT_REWRITE_DEFAULT_VAL; + + if (!TupleDescAttr(rel->rd_att, attribute.attnum - 1)->atthasmissing) + { + /* + * If the new column is NOT NULL, and there is no missing value, + * tell Phase 3 it needs to test that. (Note we don't do this for + * an OID column. OID will be marked not null, but since it's + * filled specially, there's no need to test anything.) + */ + tab->new_notnull |= colDef->is_not_null; + } } /* @@ -6022,6 +6046,7 @@ ATExecColumnDefault(Relation rel, const char *colName, rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); rawEnt->attnum = attnum; rawEnt->raw_default = newDefault; + rawEnt->missingMode = false; /* * This function is intended for CREATE TABLE, so it processes a @@ -8109,8 +8134,8 @@ transformFkeyCheckAttrs(Relation pkrel, if (indexStruct->indnatts == numattrs && indexStruct->indisunique && IndexIsValid(indexStruct) && - heap_attisnull(indexTuple, Anum_pg_index_indpred) && - heap_attisnull(indexTuple, Anum_pg_index_indexprs)) + heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) && + heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL)) { Datum indclassDatum; bool isnull; @@ -9516,7 +9541,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel, RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true, true); - StoreAttrDefault(rel, attnum, defaultexpr, true); + StoreAttrDefault(rel, attnum, defaultexpr, true, false); } ObjectAddressSubSet(address, RelationRelationId, diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 25221965e9e..2fdcb7f3fd3 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -2397,7 +2397,7 @@ AlterDomainNotNull(List *names, bool notNull) int attnum = rtc->atts[i]; Form_pg_attribute attr = TupleDescAttr(tupdesc, attnum - 1); - if (heap_attisnull(tuple, attnum)) + if (heap_attisnull(tuple, attnum, tupdesc)) { /* * In principle the auxiliary information for this diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index f7bcf6370b5..e530b262dae 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -2505,7 +2505,7 @@ ExecEvalRowNullInt(ExprState *state, ExprEvalStep *op, /* ignore dropped columns */ if (TupleDescAttr(tupDesc, att - 1)->attisdropped) continue; - if (heap_attisnull(&tmptup, att)) + if (heap_attisnull(&tmptup, att, tupDesc)) { /* null field disproves IS NOT NULL */ if (!checkisnull) diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 68f6450ee64..9a107aba561 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -2980,8 +2980,17 @@ EvalPlanQualFetchRowMarks(EPQState *epqstate) false, NULL)) elog(ERROR, "failed to fetch tuple for EvalPlanQual recheck"); - /* successful, copy tuple */ - copyTuple = heap_copytuple(&tuple); + if (HeapTupleHeaderGetNatts(tuple.t_data) < + RelationGetDescr(erm->relation)->natts) + { + copyTuple = heap_expand_tuple(&tuple, + RelationGetDescr(erm->relation)); + } + else + { + /* successful, copy tuple */ + copyTuple = heap_copytuple(&tuple); + } ReleaseBuffer(buffer); } diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index acd1b97b0e6..78cfcadea03 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -625,7 +625,15 @@ ExecCopySlotMinimalTuple(TupleTableSlot *slot) if (slot->tts_mintuple) return heap_copy_minimal_tuple(slot->tts_mintuple); if (slot->tts_tuple) - return minimal_tuple_from_heap_tuple(slot->tts_tuple); + { + if (TTS_HAS_PHYSICAL_TUPLE(slot) && + HeapTupleHeaderGetNatts(slot->tts_tuple->t_data) + < slot->tts_tupleDescriptor->natts) + return minimal_expand_tuple(slot->tts_tuple, + slot->tts_tupleDescriptor); + else + return minimal_tuple_from_heap_tuple(slot->tts_tuple); + } /* * Otherwise we need to build a tuple from the Datum array. @@ -663,7 +671,23 @@ ExecFetchSlotTuple(TupleTableSlot *slot) * If we have a regular physical tuple then just return it. */ if (TTS_HAS_PHYSICAL_TUPLE(slot)) - return slot->tts_tuple; + { + if (HeapTupleHeaderGetNatts(slot->tts_tuple->t_data) < + slot->tts_tupleDescriptor->natts) + { + MemoryContext oldContext = MemoryContextSwitchTo(slot->tts_mcxt); + + slot->tts_tuple = heap_expand_tuple(slot->tts_tuple, + slot->tts_tupleDescriptor); + slot->tts_shouldFree = true; + MemoryContextSwitchTo(oldContext); + return slot->tts_tuple; + } + else + { + return slot->tts_tuple; + } + } /* * Otherwise materialize the slot... diff --git a/src/backend/executor/execUtils.c b/src/backend/executor/execUtils.c index 14b07b5d449..b963cae730c 100644 --- a/src/backend/executor/execUtils.c +++ b/src/backend/executor/execUtils.c @@ -511,6 +511,8 @@ tlist_matches_tupdesc(PlanState *ps, List *tlist, Index varno, TupleDesc tupdesc return false; /* out of order */ if (att_tup->attisdropped) return false; /* table contains dropped columns */ + if (att_tup->atthasmissing) + return false; /* table contains cols with missing values */ /* * Note: usually the Var's type should match the tupdesc exactly, but diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c index 93e5658a354..ed6b680ed86 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->prosecdef || funcform->proretset || funcform->prorettype == RECORDOID || - !heap_attisnull(func_tuple, Anum_pg_proc_proconfig) || + !heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL) || funcform->pronargs != list_length(args)) return NULL; @@ -5031,7 +5031,7 @@ inline_set_returning_function(PlannerInfo *root, RangeTblEntry *rte) funcform->prorettype == VOIDOID || funcform->prosecdef || !funcform->proretset || - !heap_attisnull(func_tuple, Anum_pg_proc_proconfig)) + !heap_attisnull(func_tuple, Anum_pg_proc_proconfig, NULL)) { ReleaseSysCache(func_tuple); return NULL; diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c index bd3a0c4a0ab..0231f8bf7c6 100644 --- a/src/backend/optimizer/util/plancat.c +++ b/src/backend/optimizer/util/plancat.c @@ -1493,8 +1493,8 @@ relation_excluded_by_constraints(PlannerInfo *root, * in order. The executor can special-case such tlists to avoid a projection * step at runtime, so we use such tlists preferentially for scan nodes. * - * Exception: if there are any dropped columns, we punt and return NIL. - * Ideally we would like to handle the dropped-column case too. However this + * Exception: if there are any dropped or missing columns, we punt and return + * NIL. Ideally we would like to handle these cases too. However this * creates problems for ExecTypeFromTL, which may be asked to build a tupdesc * for a tlist that includes vars of no-longer-existent types. In theory we * could dig out the required info from the pg_attribute entries of the @@ -1533,9 +1533,9 @@ build_physical_tlist(PlannerInfo *root, RelOptInfo *rel) Form_pg_attribute att_tup = TupleDescAttr(relation->rd_att, attrno - 1); - if (att_tup->attisdropped) + if (att_tup->attisdropped || att_tup->atthasmissing) { - /* found a dropped col, so punt */ + /* found a dropped or missing col, so punt */ tlist = NIL; break; } diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 66253fc3d3e..361bde42614 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1126,7 +1126,8 @@ build_column_default(Relation rel, int attrno) /* * Scan to see if relation has a default for this column. */ - if (rd_att->constr && rd_att->constr->num_defval > 0) + if (att_tup->atthasdef && rd_att->constr && + rd_att->constr->num_defval > 0) { AttrDefault *defval = rd_att->constr->defval; int ndef = rd_att->constr->num_defval; diff --git a/src/backend/statistics/extended_stats.c b/src/backend/statistics/extended_stats.c index 6c836f68a98..2df5f7dc3a5 100644 --- a/src/backend/statistics/extended_stats.c +++ b/src/backend/statistics/extended_stats.c @@ -159,7 +159,7 @@ statext_is_kind_built(HeapTuple htup, char type) elog(ERROR, "unexpected statistics type requested: %d", type); } - return !heap_attisnull(htup, attnum); + return !heap_attisnull(htup, attnum, NULL); } /* diff --git a/src/backend/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index 4d7fee0ecb9..3bb708f8638 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -204,7 +204,7 @@ static void ri_GenerateQual(StringInfo buf, Oid opoid, const char *rightop, Oid rightoptype); static void ri_GenerateQualCollation(StringInfo buf, Oid collation); -static int ri_NullCheck(HeapTuple tup, +static int ri_NullCheck(TupleDesc tupdesc, HeapTuple tup, const RI_ConstraintInfo *riinfo, bool rel_is_pk); static void ri_BuildQueryKey(RI_QueryKey *key, const RI_ConstraintInfo *riinfo, @@ -307,7 +307,7 @@ RI_FKey_check(TriggerData *trigdata) (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("MATCH PARTIAL not yet implemented"))); - switch (ri_NullCheck(new_row, riinfo, false)) + switch (ri_NullCheck(RelationGetDescr(fk_rel), new_row, riinfo, false)) { case RI_KEYS_ALL_NULL: @@ -514,7 +514,7 @@ ri_Check_Pk_Match(Relation pk_rel, Relation fk_rel, bool result; /* Only called for non-null rows */ - Assert(ri_NullCheck(old_row, riinfo, true) == RI_KEYS_NONE_NULL); + Assert(ri_NullCheck(RelationGetDescr(fk_rel), old_row, riinfo, true) == RI_KEYS_NONE_NULL); if (SPI_connect() != SPI_OK_CONNECT) elog(ERROR, "SPI_connect failed"); @@ -724,7 +724,7 @@ ri_restrict(TriggerData *trigdata, bool is_no_action) */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: - switch (ri_NullCheck(old_row, riinfo, true)) + switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: @@ -911,7 +911,7 @@ RI_FKey_cascade_del(PG_FUNCTION_ARGS) */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: - switch (ri_NullCheck(old_row, riinfo, true)) + switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: @@ -1071,7 +1071,7 @@ RI_FKey_cascade_upd(PG_FUNCTION_ARGS) */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: - switch (ri_NullCheck(old_row, riinfo, true)) + switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: @@ -1285,7 +1285,7 @@ ri_setnull(TriggerData *trigdata) */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: - switch (ri_NullCheck(old_row, riinfo, true)) + switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: @@ -1501,7 +1501,7 @@ ri_setdefault(TriggerData *trigdata) */ case FKCONSTR_MATCH_SIMPLE: case FKCONSTR_MATCH_FULL: - switch (ri_NullCheck(old_row, riinfo, true)) + switch (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true)) { case RI_KEYS_ALL_NULL: case RI_KEYS_SOME_NULL: @@ -1676,7 +1676,7 @@ RI_FKey_pk_upd_check_required(Trigger *trigger, Relation pk_rel, * If any old key value is NULL, the row could not have been * referenced by an FK row, so no check is needed. */ - if (ri_NullCheck(old_row, riinfo, true) != RI_KEYS_NONE_NULL) + if (ri_NullCheck(RelationGetDescr(pk_rel), old_row, riinfo, true) != RI_KEYS_NONE_NULL) return false; /* If all old and new key values are equal, no check is needed */ @@ -1732,7 +1732,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, * If any new key value is NULL, the row must satisfy the * constraint, so no check is needed. */ - if (ri_NullCheck(new_row, riinfo, false) != RI_KEYS_NONE_NULL) + if (ri_NullCheck(RelationGetDescr(fk_rel), new_row, riinfo, false) != RI_KEYS_NONE_NULL) return false; /* @@ -1763,7 +1763,7 @@ RI_FKey_fk_upd_check_required(Trigger *trigger, Relation fk_rel, * invalidated before the constraint is to be checked, but we * should queue the event to apply the check later. */ - switch (ri_NullCheck(new_row, riinfo, false)) + switch (ri_NullCheck(RelationGetDescr(fk_rel), new_row, riinfo, false)) { case RI_KEYS_ALL_NULL: return false; @@ -2057,7 +2057,7 @@ RI_Initial_Check(Trigger *trigger, Relation fk_rel, Relation pk_rel) * disallows partially-null FK rows. */ if (fake_riinfo.confmatchtype == FKCONSTR_MATCH_FULL && - ri_NullCheck(tuple, &fake_riinfo, false) != RI_KEYS_NONE_NULL) + ri_NullCheck(tupdesc, tuple, &fake_riinfo, false) != RI_KEYS_NONE_NULL) ereport(ERROR, (errcode(ERRCODE_FOREIGN_KEY_VIOLATION), errmsg("insert or update on table \"%s\" violates foreign key constraint \"%s\"", @@ -2860,7 +2860,8 @@ ri_ReportViolation(const RI_ConstraintInfo *riinfo, * ---------- */ static int -ri_NullCheck(HeapTuple tup, +ri_NullCheck(TupleDesc tupDesc, + HeapTuple tup, const RI_ConstraintInfo *riinfo, bool rel_is_pk) { const int16 *attnums; @@ -2875,7 +2876,7 @@ ri_NullCheck(HeapTuple tup, for (i = 0; i < riinfo->nkeys; i++) { - if (heap_attisnull(tup, attnums[i])) + if (heap_attisnull(tup, attnums[i], tupDesc)) nonenull = false; else allnull = false; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index b0559ca5bcd..f8fc7f83f9b 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -1242,7 +1242,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, * versions of the expressions and predicate, because we want to display * non-const-folded expressions.) */ - if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs)) + if (!heap_attisnull(ht_idx, Anum_pg_index_indexprs, NULL)) { Datum exprsDatum; bool isnull; @@ -1410,7 +1410,7 @@ pg_get_indexdef_worker(Oid indexrelid, int colno, /* * If it's a partial index, decompile and append the predicate */ - if (!heap_attisnull(ht_idx, Anum_pg_index_indpred)) + if (!heap_attisnull(ht_idx, Anum_pg_index_indpred, NULL)) { Node *node; Datum predDatum; @@ -1644,7 +1644,7 @@ pg_get_partkeydef_worker(Oid relid, int prettyFlags, * versions of the expressions, because we want to display * non-const-folded expressions.) */ - if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs)) + if (!heap_attisnull(tuple, Anum_pg_partitioned_table_partexprs, NULL)) { Datum exprsDatum; bool isnull; diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index de502f9bc9e..48f92dc430d 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -36,6 +36,7 @@ #include "access/nbtree.h" #include "access/reloptions.h" #include "access/sysattr.h" +#include "access/tupdesc_details.h" #include "access/xact.h" #include "access/xlog.h" #include "catalog/catalog.h" @@ -79,6 +80,7 @@ #include "storage/smgr.h" #include "utils/array.h" #include "utils/builtins.h" +#include "utils/datum.h" #include "utils/fmgroids.h" #include "utils/inval.h" #include "utils/lsyscache.h" @@ -495,6 +497,7 @@ RelationBuildTupleDesc(Relation relation) int need; TupleConstr *constr; AttrDefault *attrdef = NULL; + AttrMissing *attrmiss = NULL; int ndef = 0; /* copy some fields from pg_class row to rd_att */ @@ -540,15 +543,17 @@ RelationBuildTupleDesc(Relation relation) while (HeapTupleIsValid(pg_attribute_tuple = systable_getnext(pg_attribute_scan))) { Form_pg_attribute attp; + int attnum; attp = (Form_pg_attribute) GETSTRUCT(pg_attribute_tuple); - if (attp->attnum <= 0 || - attp->attnum > relation->rd_rel->relnatts) + attnum = attp->attnum; + if (attnum <= 0 || attnum > relation->rd_rel->relnatts) elog(ERROR, "invalid attribute number %d for %s", attp->attnum, RelationGetRelationName(relation)); - memcpy(TupleDescAttr(relation->rd_att, attp->attnum - 1), + + memcpy(TupleDescAttr(relation->rd_att, attnum - 1), attp, ATTRIBUTE_FIXED_PART_SIZE); @@ -556,6 +561,7 @@ RelationBuildTupleDesc(Relation relation) if (attp->attnotnull) constr->has_not_null = true; + /* If the column has a default, fill it into the attrdef array */ if (attp->atthasdef) { if (attrdef == NULL) @@ -563,10 +569,63 @@ RelationBuildTupleDesc(Relation relation) MemoryContextAllocZero(CacheMemoryContext, relation->rd_rel->relnatts * sizeof(AttrDefault)); - attrdef[ndef].adnum = attp->attnum; + attrdef[ndef].adnum = attnum; attrdef[ndef].adbin = NULL; + ndef++; } + + /* Likewise for a missing value */ + if (attp->atthasmissing) + { + Datum missingval; + bool missingNull; + + /* Do we have a missing value? */ + missingval = heap_getattr(pg_attribute_tuple, + Anum_pg_attribute_attmissingval, + pg_attribute_desc->rd_att, + &missingNull); + if (!missingNull) + { + /* Yes, fetch from the array */ + MemoryContext oldcxt; + bool is_null; + int one = 1; + Datum missval; + + if (attrmiss == NULL) + attrmiss = (AttrMissing *) + MemoryContextAllocZero(CacheMemoryContext, + relation->rd_rel->relnatts * + sizeof(AttrMissing)); + + missval = array_get_element(missingval, + 1, + &one, + -1, + attp->attlen, + attp->attbyval, + attp->attalign, + &is_null); + Assert(!is_null); + if (attp->attbyval) + { + /* for copy by val just copy the datum direct */ + attrmiss[attnum - 1].ammissing = missval; + } + else + { + /* otherwise copy in the correct context */ + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + attrmiss[attnum - 1].ammissing = datumCopy(missval, + attp->attbyval, + attp->attlen); + MemoryContextSwitchTo(oldcxt); + } + attrmiss[attnum - 1].ammissingPresent = true; + } + } need--; if (need == 0) break; @@ -607,7 +666,8 @@ RelationBuildTupleDesc(Relation relation) /* * Set up constraint/default info */ - if (constr->has_not_null || ndef > 0 || relation->rd_rel->relchecks) + if (constr->has_not_null || ndef > 0 || + attrmiss || relation->rd_rel->relchecks) { relation->rd_att->constr = constr; @@ -624,6 +684,8 @@ RelationBuildTupleDesc(Relation relation) else constr->num_defval = 0; + constr->missing = attrmiss; + if (relation->rd_rel->relchecks > 0) /* CHECKs */ { constr->num_check = relation->rd_rel->relchecks; @@ -4063,10 +4125,6 @@ AttrDefaultFetch(Relation relation) systable_endscan(adscan); heap_close(adrel, AccessShareLock); - - if (found != ndef) - elog(WARNING, "%d attrdef record(s) missing for rel %s", - ndef - found, RelationGetRelationName(relation)); } /* @@ -4405,7 +4463,7 @@ RelationGetIndexList(Relation relation) */ if (!IndexIsValid(index) || !index->indisunique || !index->indimmediate || - !heap_attisnull(htup, Anum_pg_index_indpred)) + !heap_attisnull(htup, Anum_pg_index_indpred, NULL)) continue; /* Check to see if is a usable btree index on OID */ @@ -4700,7 +4758,7 @@ RelationGetIndexExpressions(Relation relation) /* Quick exit if there is nothing to do. */ if (relation->rd_indextuple == NULL || - heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs)) + heap_attisnull(relation->rd_indextuple, Anum_pg_index_indexprs, NULL)) return NIL; /* @@ -4762,7 +4820,7 @@ RelationGetIndexPredicate(Relation relation) /* Quick exit if there is nothing to do. */ if (relation->rd_indextuple == NULL || - heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred)) + heap_attisnull(relation->rd_indextuple, Anum_pg_index_indpred, NULL)) return NIL; /* diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index ae511e9a23b..aa188bdeffa 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -200,7 +200,7 @@ fmgr_info_cxt_security(Oid functionId, FmgrInfo *finfo, MemoryContext mcxt, */ if (!ignore_security && (procedureStruct->prosecdef || - !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig) || + !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig, NULL) || FmgrHookIsNeeded(functionId))) { finfo->fn_addr = fmgr_security_definer; @@ -294,7 +294,7 @@ fmgr_symbol(Oid functionId, char **mod, char **fn) /* */ if (procedureStruct->prosecdef || - !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig) || + !heap_attisnull(procedureTuple, Anum_pg_proc_proconfig, NULL) || FmgrHookIsNeeded(functionId)) { *mod = NULL; /* core binary */ diff --git a/src/backend/utils/fmgr/funcapi.c b/src/backend/utils/fmgr/funcapi.c index 20f60392afe..d5984b415ed 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 */ |
