commit 863379cc8478422aaeb55a73536ab286d9bbfa29 Author: Serge Rielau Date: Wed Oct 19 17:06:12 2016 -0700 fast_defaults diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index 6d0f3f3..8880ed3 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -76,6 +76,95 @@ * ---------------------------------------------------------------- */ +/* + * Pad an attribute beyond the size of the tuple + * with NULL or the exist default + */ +static Datum getmissingattr(TupleDesc tupleDesc, + int attnum, bool *isnull) +{ + int defnum; + Form_pg_attribute att; + AttrDefault *attrdef; + + Assert(attnum <= tupleDesc->natts); + Assert(attnum > 0); + + att = tupleDesc->attrs[attnum - 1]; + + if(att->atthasexistdef) + { + Assert(tupleDesc->constr); + Assert(tupleDesc->constr->num_defval > 0); + Assert(tupleDesc->constr->has_existdefval); + + attrdef = tupleDesc->constr->defval; + for (defnum = tupleDesc->constr->num_defval - 1; + defnum >= 0; defnum--) + { + if (attrdef[defnum].adnum == attnum) + { + if (attrdef[defnum].adexistNull) + { + *isnull = true; + return (Datum) 0; + } + else + { + *isnull = false; + return attrdef[defnum].adexist; + } + } + } + Assert(defnum >= 0); + } + + *isnull = true; + return PointerGetDatum(NULL); +} + +static void slot_getdefaultattrs(TupleTableSlot *slot, int startAttNum) +{ + int tdesc_natts = slot->tts_tupleDescriptor->natts; + int defattnum; + int defnum; + AttrDefault *attrdef; + + if (slot->tts_tupleDescriptor->constr + && slot->tts_tupleDescriptor->constr->has_existdefval) + { + defnum = slot->tts_tupleDescriptor->constr->num_defval - 1; + attrdef = slot->tts_tupleDescriptor->constr->defval; + } + else + { + defnum = -1; + attrdef = NULL; + } + + for (defattnum = tdesc_natts - 1; defattnum >= startAttNum; defattnum--) + { + if (defnum >= 0 && attrdef[defnum].adnum - 1 == defattnum) + { + if (attrdef[defnum].adexistNull) + { + slot->tts_values[defattnum] = PointerGetDatum(NULL); + slot->tts_isnull[defattnum] = true; + } + else + { + slot->tts_values[defattnum] = attrdef[defnum].adexist; + slot->tts_isnull[defattnum] = false; + } + defnum--; + } + else + { + slot->tts_values[defattnum] = PointerGetDatum(NULL); + slot->tts_isnull[defattnum] = true; + } + } +} /* * heap_compute_data_size @@ -133,6 +222,127 @@ heap_compute_data_size(TupleDesc tupleDesc, return data_length; } +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 @@ -174,110 +384,13 @@ heap_fill_tuple(TupleDesc tupleDesc, for (i = 0; i < numberOfAttributes; 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[i]->attbyval) - { - /* pass-by-value */ - data = (char *) att_align_nominal(data, att[i]->attalign); - store_att_byval(data, values[i], att[i]->attlen); - data_length = att[i]->attlen; - } - else if (att[i]->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[i]->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[i]) && - 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[i]->attalign); - data_length = VARSIZE(val); - memcpy(data, val, data_length); - } - } - else if (att[i]->attlen == -2) - { - /* cstring ... never needs alignment */ - *infomask |= HEAP_HASVARWIDTH; - Assert(att[i]->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[i]->attalign); - Assert(att[i]->attlen > 0); - data_length = att[i]->attlen; - memcpy(data, DatumGetPointer(values[i]), data_length); - } - - data += data_length; + fill_val(att[i], + bitP ? &bitP : NULL, + &bitmask, + &data, + infomask, + values ? values[i] : PointerGetDatum(NULL), + isnull ? isnull[i] : true); } Assert((data - start) == data_size); @@ -294,10 +407,24 @@ 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 catalog relations */ + Assert(tupleDesc || attnum <= tupleDesc->natts); + if (attnum > (int) HeapTupleHeaderGetNatts(tup->t_data)) - return true; + { + if (tupleDesc && + attnum <= tupleDesc->natts && + tupleDesc->attrs[attnum - 1]->atthasexistdef) + { + return false; + } + else + { + return true; + } + } if (attnum > 0) { @@ -646,6 +773,253 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest) memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len); } +static void +expand_tuple(HeapTuple *targetHeapTuple, + MinimalTuple *targetMinimalTuple, + HeapTuple sourceTuple, + TupleDesc tupleDesc) +{ + Form_pg_attribute *att = NULL; + AttrDefault *attrdef = NULL; + int attnum; + int defnum; + int firstdefnum = 0; + int numdef = 0; + bool hasNulls = HeapTupleHasNulls(sourceTuple); + bool hasVarWidth = HeapTupleHasVarWidth(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; + char *targetData; + uint16 *infoMask; + + Assert(targetHeapTuple && !targetMinimalTuple + || !targetHeapTuple && targetMinimalTuple); + + Assert(sourceNatts < natts); + + sourceIndicatorLen = (hasNulls ? BITMAPLEN(sourceNatts) : 0); + + targetDataLen = sourceDataLen; + + if (tupleDesc->constr && + tupleDesc->constr->has_existdefval) + { + /* If so we want to ingest the exist defaults into the tuple + * Compute the extra length for the values array and the variable length data + */ + numdef = tupleDesc->constr->num_defval; + att = tupleDesc->attrs; + attrdef = tupleDesc->constr->defval; + + /* + * Unfortunately the data length computation is + * directional we have to walk the attributes incrementally + */ + + for (firstdefnum = 0; + firstdefnum < numdef + && attrdef[firstdefnum].adnum <= sourceNatts; + firstdefnum++) + { /* empty */ } + + for (defnum = firstdefnum, attnum = sourceNatts + 1; attnum <= natts; attnum++) + { + if (defnum >= numdef) + { + hasNulls = true; + break; + } + + if (attrdef[defnum].adnum == attnum) + { + if (attrdef[defnum].adexistNull) + hasNulls = true; + else + { + targetDataLen = att_align_datum(targetDataLen, + att[attnum - 1]->attalign, + att[attnum - 1]->attlen, + attrdef[defnum].adexist); + + targetDataLen = att_addlength_pointer(targetDataLen, + att[attnum - 1]->attlen, + attrdef[defnum].adexist); + if (att[attnum - 1]->attlen == -1 || + att[attnum - 1]->attlen == -2) + hasVarWidth = true; + } + defnum++; + } + else + hasNulls = true; + } + } /* end if Have exist defaults */ + else + { + 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 exist defaults there is nothing more to do */ + if (firstdefnum < numdef) + { + targetData += sourceDataLen; + defnum = firstdefnum; + + /* Now fill in the defaults */ + for (attnum = sourceNatts + 1; + attnum <= natts && defnum < tupleDesc->constr->num_defval; + attnum++) + { + if (attrdef[defnum].adnum == attnum) + { + fill_val(att[attnum -1], + nullBits ? &nullBits : NULL, + &bitMask, + &targetData, + infoMask, + attrdef[defnum].adexist, + attrdef[defnum].adexistNull); + defnum++; + } + else + { + fill_val(att[attnum -1], + &nullBits, + &bitMask, + &targetData, + infoMask, + (Datum) 0, + true); + } + } /* end loop over defaulted attributes */ + } /* end if there is no exist default */ +} + +MinimalTuple +minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc) +{ + MinimalTuple minimalTuple; + expand_tuple(NULL, &minimalTuple, sourceTuple, tupleDesc); + return minimalTuple; +} + +HeapTuple +heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc) +{ + HeapTuple heapTuple; + expand_tuple(&heapTuple, NULL, sourceTuple, tupleDesc); + return heapTuple; +} + /* ---------------- * heap_copy_tuple_as_datum * @@ -1125,8 +1499,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); } /* @@ -1196,13 +1569,13 @@ slot_getallattrs(TupleTableSlot *slot) /* * If tuple doesn't have all the atts indicated by tupleDesc, read the - * rest as null + * rest as defaults */ - for (; attnum < tdesc_natts; attnum++) + if (attnum < tdesc_natts) { - slot->tts_values[attnum] = (Datum) 0; - slot->tts_isnull[attnum] = true; + slot_getdefaultattrs(slot, attnum); } + slot->tts_nvalid = tdesc_natts; } @@ -1243,12 +1616,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 defaults */ - for (; attno < attnum; attno++) + if (attno < attnum) { - slot->tts_values[attno] = (Datum) 0; - slot->tts_isnull[attno] = true; + slot_getdefaultattrs(slot, attno); } slot->tts_nvalid = attnum; } @@ -1273,7 +1645,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); } /* @@ -1296,7 +1668,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 b56d0e3..52b5f79 100644 --- a/src/backend/access/common/tupdesc.c +++ b/src/backend/access/common/tupdesc.c @@ -25,6 +25,7 @@ #include "parser/parse_type.h" #include "utils/acl.h" #include "utils/builtins.h" +#include "utils/datum.h" #include "utils/resowner_private.h" #include "utils/syscache.h" @@ -190,6 +191,13 @@ CreateTupleDescCopyConstr(TupleDesc tupdesc) { if (constr->defval[i].adbin) cpy->defval[i].adbin = pstrdup(constr->defval[i].adbin); + if (constr->defval[i].adexist) + { + int attnum = constr->defval[i].adnum - 1; + cpy->defval[i].adexist = datumCopy(constr->defval[i].adexist, + tupdesc->attrs[attnum]->attbyval, + tupdesc->attrs[attnum]->attlen); + } } } @@ -282,6 +290,9 @@ FreeTupleDesc(TupleDesc tupdesc) { if (attrdef[i].adbin) pfree(attrdef[i].adbin); + if (attrdef[i].adexist + && !tupdesc->attrs[attrdef[i].adnum -1]->attbyval) + pfree(DatumGetPointer(attrdef[i].adexist)); } pfree(attrdef); } @@ -350,9 +361,10 @@ DecrTupleDescRefCount(TupleDesc tupdesc) bool equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) { - int i, - j, - n; + int i, + j, + n1, + n2; if (tupdesc1->natts != tupdesc2->natts) return false; @@ -420,10 +432,9 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) return false; if (constr1->has_not_null != constr2->has_not_null) return false; - n = constr1->num_defval; - if (n != (int) constr2->num_defval) - return false; - for (i = 0; i < n; i++) + n1 = constr1->num_defval; + n2 = constr2->num_defval; + for (i = 0; i < n1; i++) { AttrDefault *defval1 = constr1->defval + i; AttrDefault *defval2 = constr2->defval; @@ -433,20 +444,24 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) * catalogs in the same order; so use the adnum field to identify * the matching item to compare. */ - for (j = 0; j < n; defval2++, j++) + for (j = 0; j < n2; defval2++, j++) { if (defval1->adnum == defval2->adnum) break; } - if (j >= n) + if (j >= n2) + return false; + if ((!defval1->adbin && defval2->adbin) || + (defval1->adbin && !defval2->adbin)) return false; - if (strcmp(defval1->adbin, defval2->adbin) != 0) + if (defval1->adbin && defval2->adbin && + strcmp(defval1->adbin, defval2->adbin) != 0) return false; } - n = constr1->num_check; - if (n != (int) constr2->num_check) + n1 = constr1->num_check; + if (n1 != (int) constr2->num_check) return false; - for (i = 0; i < n; i++) + for (i = 0; i < n1; i++) { ConstrCheck *check1 = constr1->check + i; ConstrCheck *check2 = constr2->check; @@ -456,7 +471,7 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) * same order; match them up by name and contents. (The name * *should* be unique, but...) */ - for (j = 0; j < n; check2++, j++) + for (j = 0; j < n2; check2++, j++) { if (strcmp(check1->ccname, check2->ccname) == 0 && strcmp(check1->ccbin, check2->ccbin) == 0 && @@ -464,7 +479,7 @@ equalTupleDescs(TupleDesc tupdesc1, TupleDesc tupdesc2) check1->ccnoinherit == check2->ccnoinherit) break; } - if (j >= n) + if (j >= n1) return false; } } @@ -533,6 +548,7 @@ TupleDescInitEntry(TupleDesc desc, att->attnotnull = false; att->atthasdef = false; + att->atthasexistdef = false; att->attisdropped = false; att->attislocal = true; att->attinhcount = 0; @@ -657,6 +673,7 @@ BuildDescForRelation(List *schema) constr->has_not_null = true; constr->defval = NULL; constr->num_defval = 0; + constr->has_existdefval = false; 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 c0df671..037a0ce 100644 --- a/src/backend/catalog/aclchk.c +++ b/src/backend/catalog/aclchk.c @@ -4377,7 +4377,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 26d1652..e76c10e 100644 --- a/src/backend/catalog/genbki.pl +++ b/src/backend/catalog/genbki.pl @@ -409,12 +409,14 @@ sub emit_pgattr_row attcacheoff => '-1', atttypmod => '-1', atthasdef => 'f', + atthasexistdef=> 'f', attisdropped => 'f', attislocal => 't', attinhcount => '0', attacl => '_null_', attoptions => '_null_', - attfdwoptions => '_null_'); + attfdwoptions => '_null_', + attexistdefault => '_null_'); return { %PGATTR_DEFAULTS, %row }; } @@ -445,6 +447,7 @@ sub emit_schemapg_row delete $row->{attacl}; delete $row->{attoptions}; delete $row->{attfdwoptions}; + delete $row->{attexistdefault}; # Expand booleans from 'f'/'t' to 'false'/'true'. # Some values might be other macros (eg FLOAT4PASSBYVAL), don't change. diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 0cf7b9e..233c20f 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -56,9 +56,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" @@ -67,6 +70,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" @@ -139,37 +143,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, false, true, 0 + false, 'p', 's', true, false, false, false, true, 0 }; static FormData_pg_attribute a2 = { 0, {"oid"}, OIDOID, 0, sizeof(Oid), ObjectIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, false, true, 0 + true, 'p', 'i', true, false, false, false, true, 0 }; static FormData_pg_attribute a3 = { 0, {"xmin"}, XIDOID, 0, sizeof(TransactionId), MinTransactionIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, false, true, 0 + true, 'p', 'i', true, false, false, false, true, 0 }; static FormData_pg_attribute a4 = { 0, {"cmin"}, CIDOID, 0, sizeof(CommandId), MinCommandIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, false, true, 0 + true, 'p', 'i', true, false, false, false, true, 0 }; static FormData_pg_attribute a5 = { 0, {"xmax"}, XIDOID, 0, sizeof(TransactionId), MaxTransactionIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, false, true, 0 + true, 'p', 'i', true, false, false, false, true, 0 }; static FormData_pg_attribute a6 = { 0, {"cmax"}, CIDOID, 0, sizeof(CommandId), MaxCommandIdAttributeNumber, 0, -1, -1, - true, 'p', 'i', true, false, false, true, 0 + true, 'p', 'i', true, false, false, false, true, 0 }; /* @@ -181,7 +185,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, false, true, 0 + true, 'p', 'i', true, false, false, false, true, 0 }; static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7}; @@ -625,6 +629,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_atthasexistdef - 1] = BoolGetDatum(new_attribute->atthasexistdef); values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped); values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal); values[Anum_pg_attribute_attinhcount - 1] = Int32GetDatum(new_attribute->attinhcount); @@ -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_attexistdefault - 1] = true; tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls); @@ -1860,7 +1866,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; @@ -1871,9 +1877,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 *existBuf = NULL; + Datum valuesAtt[Natts_pg_attribute]; + bool nullsAtt[Natts_pg_attribute]; + bool replacesAtt[Natts_pg_attribute]; + Datum existDefault = (Datum) 0; + bool existDefIsNull = true; /* * Flatten expression to string form for storage. @@ -1887,6 +1904,44 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, deparse_context_for(RelationGetRelationName(rel), RelationGetRelid(rel)), false, false); + /* + * Compute the exist default + */ + expr2 = expression_planner(expr2); + + exprState = ExecInitExpr(expr2, NULL); + estate = CreateExecutorState(); + econtext = GetPerTupleExprContext(estate); + + if (add_column_mode) + { + existDefault = ExecEvalExpr(exprState, econtext, + &existDefIsNull, + NULL); + + defAttStruct = rel->rd_att->attrs[attnum - 1]; + + if (existDefIsNull) + { + existDefault = PointerGetDatum(NULL); + } + else if (defAttStruct->attbyval) + { + existBuf = palloc(VARHDRSZ + sizeof(Datum)); + memcpy(VARDATA(existBuf), &existDefault, sizeof(Datum)); + SET_VARSIZE(existBuf, VARHDRSZ + sizeof(Datum)); + existDefault = PointerGetDatum(existBuf); + } + else if (defAttStruct->attlen >= 0) + { + existBuf = palloc(VARHDRSZ + defAttStruct->attlen); + memcpy(VARDATA(existBuf), DatumGetPointer(existDefault), + defAttStruct->attlen); + SET_VARSIZE(existBuf, + VARHDRSZ + defAttStruct->attlen); + existDefault = PointerGetDatum(existBuf); + } + } /* * Make the pg_attrdef entry. @@ -1930,7 +1985,23 @@ 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_atthasexistdef - 1] = true; + replacesAtt[Anum_pg_attribute_atthasexistdef - 1] = true; + valuesAtt[Anum_pg_attribute_atthasexistdef - 1] = true; + replacesAtt[Anum_pg_attribute_atthasexistdef - 1] = true; + valuesAtt[Anum_pg_attribute_attexistdefault -1] = existDefault; + replacesAtt[Anum_pg_attribute_attexistdefault -1] = true; + nullsAtt[Anum_pg_attribute_attexistdefault -1] = existDefIsNull; + } + atttup = heap_modify_tuple(atttup, RelationGetDescr(attrrel), + valuesAtt, nullsAtt, replacesAtt); simple_heap_update(attrrel, &atttup->t_self, atttup); /* keep catalog indexes current */ CatalogUpdateIndexes(attrrel, atttup); @@ -1964,6 +2035,17 @@ StoreAttrDefault(Relation rel, AttrNumber attnum, InvokeObjectPostCreateHookArg(AttrDefaultRelationId, RelationGetRelid(rel), attnum, is_internal); + if (estate) + { + FreeExecutorState(estate); + } + + if (existBuf) + { + pfree(existBuf); + existBuf = NULL; + } + return attrdefOid; } @@ -2105,7 +2187,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 = @@ -2221,7 +2303,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 an exist default */ + if (contain_volatile_functions((Node *) expr)) + { + colDef->existDefMode = false; + } + + defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal, + colDef->existDefMode); cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint)); cooked->contype = CONSTR_DEFAULT; diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 08b646d..8188e2f 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->atthasexistdef = false; to->attislocal = true; to->attinhcount = 0; to->attcollation = collationObjectId[i]; @@ -1575,7 +1576,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)); simple_heap_delete(indexRelation, &tuple->t_self); diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index dc1f79f..6956e99 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -445,7 +445,8 @@ 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, + RelationGetDescr(OldIndex))) 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 85817c6..1cf750a 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -206,8 +206,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 f822ed9..9461368 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -630,6 +630,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId, rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); rawEnt->attnum = attnum; rawEnt->raw_default = colDef->raw_default; + rawEnt->existDefMode = false; rawDefaults = lappend(rawDefaults, rawEnt); descriptor->attrs[attnum - 1]->atthasdef = true; } @@ -4180,7 +4181,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)) ereport(ERROR, (errcode(ERRCODE_NOT_NULL_VIOLATION), errmsg("column \"%s\" contains null values", @@ -4267,7 +4268,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; @@ -4850,6 +4851,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, attribute.attalign = tform->typalign; attribute.attnotnull = colDef->is_not_null; attribute.atthasdef = false; + attribute.atthasexistdef = false; attribute.attisdropped = false; attribute.attislocal = colDef->is_local; attribute.attinhcount = colDef->inhcount; @@ -4895,6 +4897,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->existDefMode = true; /* * This function is intended for CREATE TABLE, so it processes a @@ -4905,6 +4908,15 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel, /* Make the additional catalog changes visible */ CommandCounterIncrement(); + + /* + * Did the request for an exist default work? + * If not we'll have to do a rewrite + */ + if (!rawEnt->existDefMode) + { + tab->rewrite |= AT_REWRITE_DEFAULT_VAL; + } } /* @@ -4971,16 +4983,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 exist default + * I.e. the exist default is NULL then this ADD COLUMN is doomed. + * Unless the table is empty... */ - tab->new_notnull |= colDef->is_not_null; + if (!rel->rd_att->attrs[attribute.attnum - 1]->atthasexistdef) + { + /* + * 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; + } } /* @@ -5390,6 +5412,7 @@ ATExecColumnDefault(Relation rel, const char *colName, rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault)); rawEnt->attnum = attnum; rawEnt->raw_default = newDefault; + rawEnt->existDefMode = false; /* * This function is intended for CREATE TABLE, so it processes a @@ -7165,8 +7188,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, RelationGetDescr(pkrel)) && + heap_attisnull(indexTuple, Anum_pg_index_indexprs, RelationGetDescr(pkrel))) { Datum indclassDatum; bool isnull; @@ -8438,7 +8461,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 056933a..559a741 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -2324,7 +2324,7 @@ AlterDomainNotNull(List *names, bool notNull) { int attnum = rtc->atts[i]; - if (heap_attisnull(tuple, attnum)) + if (heap_attisnull(tuple, attnum, tupdesc)) { /* * In principle the auxiliary information for this diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 32bb3f9..58156b7 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -2633,8 +2633,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/execQual.c b/src/backend/executor/execQual.c index 743e7d6..ed3396b 100644 --- a/src/backend/executor/execQual.c +++ b/src/backend/executor/execQual.c @@ -1064,7 +1064,7 @@ ExecEvalWholeRowSlow(WholeRowVarExprState *wrvstate, ExprContext *econtext, if (!vattr->attisdropped) continue; /* already checked non-dropped cols */ - if (heap_attisnull(tuple, i + 1)) + if (heap_attisnull(tuple, i + 1, tupleDesc)) continue; /* null is always okay */ if (vattr->attlen != sattr->attlen || vattr->attalign != sattr->attalign) @@ -3948,7 +3948,7 @@ ExecEvalNullTest(NullTestState *nstate, /* ignore dropped columns */ if (tupDesc->attrs[att - 1]->attisdropped) continue; - if (heap_attisnull(&tmptup, att)) + if (heap_attisnull(&tmptup, att, tupDesc)) { /* null field disproves IS NOT NULL */ if (ntest->nulltesttype == IS_NOT_NULL) diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index 533050d..6116056 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -592,8 +592,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. */ @@ -630,8 +637,21 @@ 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 663ffe0..7f02a3f 100644 --- a/src/backend/optimizer/util/clauses.c +++ b/src/backend/optimizer/util/clauses.c @@ -4356,7 +4356,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; @@ -4879,7 +4879,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 b828e3c..e352adb 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -1034,7 +1034,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/utils/adt/ri_triggers.c b/src/backend/utils/adt/ri_triggers.c index b476500..614aadb 100644 --- a/src/backend/utils/adt/ri_triggers.c +++ b/src/backend/utils/adt/ri_triggers.c @@ -209,7 +209,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, @@ -315,7 +315,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(pk_rel), new_row, riinfo, false)) { case RI_KEYS_ALL_NULL: @@ -522,7 +522,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"); @@ -682,7 +682,7 @@ ri_restrict_del(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: @@ -897,7 +897,7 @@ ri_restrict_upd(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: @@ -1076,7 +1076,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: @@ -1236,7 +1236,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: @@ -1413,7 +1413,7 @@ RI_FKey_setnull_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: @@ -1580,7 +1580,7 @@ RI_FKey_setnull_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: @@ -1753,7 +1753,7 @@ RI_FKey_setdefault_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: @@ -1935,7 +1935,7 @@ RI_FKey_setdefault_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: @@ -2105,7 +2105,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 */ @@ -2161,7 +2161,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; /* @@ -2192,7 +2192,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; @@ -2486,7 +2486,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\"", @@ -3350,7 +3350,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; @@ -3365,7 +3366,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 8a81d7a..a2ebedd 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -1190,7 +1190,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; @@ -1356,7 +1356,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; diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 79e0b1f..8f321a4 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -70,6 +70,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" @@ -482,6 +483,8 @@ RelationBuildTupleDesc(Relation relation) TupleConstr *constr; AttrDefault *attrdef = NULL; int ndef = 0; + int attnum = 0; + bool has_existdefval = false; /* copy some fields from pg_class row to rd_att */ relation->rd_att->tdtypeid = relation->rd_rel->reltype; @@ -534,7 +537,18 @@ RelationBuildTupleDesc(Relation relation) elog(ERROR, "invalid attribute number %d for %s", attp->attnum, RelationGetRelationName(relation)); - memcpy(relation->rd_att->attrs[attp->attnum - 1], + /* + * 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(relation->rd_att->attrs[attnum - 1], attp, ATTRIBUTE_FIXED_PART_SIZE); @@ -542,15 +556,67 @@ RelationBuildTupleDesc(Relation relation) if (attp->attnotnull) constr->has_not_null = true; - if (attp->atthasdef) + /* + * If the column has a current or exist default + * Fill it into the attrdef array + */ + if (attp->atthasdef || attp->atthasexistdef) { + Datum existDefault; + bool existDefNull; + if (attrdef == NULL) attrdef = (AttrDefault *) MemoryContextAllocZero(CacheMemoryContext, relation->rd_rel->relnatts * sizeof(AttrDefault)); - attrdef[ndef].adnum = attp->attnum; + attrdef[ndef].adnum = attnum; attrdef[ndef].adbin = NULL; + + /* Do we have an exist default? */ + existDefault = SysCacheGetAttr(ATTNUM, pg_attribute_tuple, + Anum_pg_attribute_attexistdefault, + &existDefNull); + if (existDefNull) + { + /* No, then the store a NULL */ + attrdef[ndef].adexist = PointerGetDatum(NULL); + attrdef[ndef].adexistNull = true; + } + else if (attp->attbyval) + { + /* Yes, and its of the by-value kind + * Copy in the Datum */ + memcpy(&attrdef[ndef].adexist, + VARDATA_ANY(existDefault), sizeof(Datum)); + attrdef[ndef].adexistNull = false; + has_existdefval = true; + } + 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); + attrdef[ndef].adexist = PointerGetDatum(palloc(attp->attlen)); + memcpy(DatumGetPointer(attrdef[ndef].adexist), + VARDATA_ANY(existDefault), attp->attlen); + MemoryContextSwitchTo(oldcxt); + attrdef[ndef].adexistNull = false; + has_existdefval = true; + } + else + { + /* Yes, variable length */ + MemoryContext oldcxt; + oldcxt = MemoryContextSwitchTo(CacheMemoryContext); + attrdef[ndef].adexist = datumCopy(existDefault, + attp->attbyval, + attp->attlen); + attrdef[ndef].adexistNull = false; + MemoryContextSwitchTo(oldcxt); + has_existdefval = true; + } ndef++; } need--; @@ -605,10 +671,14 @@ RelationBuildTupleDesc(Relation relation) else constr->defval = attrdef; constr->num_defval = ndef; + constr->has_existdefval = has_existdefval; AttrDefaultFetch(relation); } else + { constr->num_defval = 0; + constr->has_existdefval = false; + } if (relation->rd_rel->relchecks > 0) /* CHECKs */ { @@ -3711,10 +3781,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)); } /* @@ -4052,7 +4118,8 @@ RelationGetIndexList(Relation relation) */ if (!IndexIsValid(index) || !index->indisunique || !index->indimmediate || - !heap_attisnull(htup, Anum_pg_index_indpred)) + !heap_attisnull(htup, Anum_pg_index_indpred, + GetPgIndexDescriptor())) continue; /* Check to see if is a usable btree index on OID */ @@ -4242,7 +4309,8 @@ 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, + GetPgIndexDescriptor())) return NIL; /* @@ -4303,7 +4371,8 @@ 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, + GetPgIndexDescriptor())) return NIL; /* diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 46a55ba..ed02db4 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -237,7 +237,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 5d49fe5..00cc1f7 100644 --- a/src/backend/utils/fmgr/funcapi.c +++ b/src/backend/utils/fmgr/funcapi.c @@ -1035,8 +1035,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 { @@ -1130,8 +1130,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 d7e5fad..c1f00ef 100644 --- a/src/include/access/htup_details.h +++ b/src/include/access/htup_details.h @@ -790,7 +790,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, @@ -814,5 +814,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 de18f74..f55eccf 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" @@ -22,7 +23,9 @@ typedef struct attrDefault { AttrNumber adnum; + bool adexistNull; /* true if exist default is NULL */ char *adbin; /* nodeToString representation of expr */ + Datum adexist; /* exist default */ } AttrDefault; typedef struct constrCheck @@ -40,6 +43,7 @@ typedef struct tupleConstr ConstrCheck *check; /* array */ uint16 num_defval; uint16 num_check; + bool has_existdefval; bool has_not_null; } TupleConstr; diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index cd3048d..7868b43 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 201610201 +#define CATALOG_VERSION_NO 201610211 #endif diff --git a/src/include/catalog/heap.h b/src/include/catalog/heap.h index b80d8d8..6f4fd37 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 existDefMode; /* 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 39d8eed..f84debf 100644 --- a/src/include/catalog/pg_attribute.h +++ b/src/include/catalog/pg_attribute.h @@ -133,6 +133,9 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK /* Has DEFAULT value or not */ bool atthasdef; + /* Has exist default or not */ + bool atthasexistdef; + /* Is dropped (ie, logically invisible) or not */ bool attisdropped; @@ -164,6 +167,9 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK /* Column-level FDW options */ text attfdwoptions[1]; + + /* Exist default for added columns */ + bytea attexistdefault; #endif } FormData_pg_attribute; @@ -188,28 +194,30 @@ typedef FormData_pg_attribute *Form_pg_attribute; * ---------------- */ -#define Natts_pg_attribute 21 -#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_attisdropped 15 -#define Anum_pg_attribute_attislocal 16 -#define Anum_pg_attribute_attinhcount 17 -#define Anum_pg_attribute_attcollation 18 -#define Anum_pg_attribute_attacl 19 -#define Anum_pg_attribute_attoptions 20 -#define Anum_pg_attribute_attfdwoptions 21 +#define Natts_pg_attribute 23 +#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_atthasexistdef 15 +#define Anum_pg_attribute_attisdropped 16 +#define Anum_pg_attribute_attislocal 17 +#define Anum_pg_attribute_attinhcount 18 +#define Anum_pg_attribute_attcollation 19 +#define Anum_pg_attribute_attacl 20 +#define Anum_pg_attribute_attoptions 21 +#define Anum_pg_attribute_attfdwoptions 22 +#define Anum_pg_attribute_attexistdefault 23 /* ---------------- diff --git a/src/include/catalog/pg_class.h b/src/include/catalog/pg_class.h index e57b81c..d00f5fb 100644 --- a/src/include/catalog/pg_class.h +++ b/src/include/catalog/pg_class.h @@ -145,7 +145,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 3 1 _null_ _null_ )); DESCR(""); -DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 21 0 f f f f f f f t n 3 1 _null_ _null_ )); +DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 23 0 f f f f f f f t n 3 1 _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 3 1 _null_ _null_ )); DESCR(""); diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out index e124552..60eb03c 100644 --- a/src/test/regress/expected/event_trigger.out +++ b/src/test/regress/expected/event_trigger.out @@ -374,8 +374,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 $$ @@ -389,7 +387,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..44806f0 --- /dev/null +++ b/src/test/regress/expected/fast_default.out @@ -0,0 +1,446 @@ +-- +-- 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'; +-- 1. 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 ALTER COLUMN c_time DROP DEFAULT, + ALTER COLUMN c_interval SET DEFAULT '3 hours'; +INSERT INTO T VALUES (23), (24); +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_interval DROP DEFAULT; +INSERT INTO T VALUES (25), (26); +SELECT * 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 +----+-------+----------+--------+------------+--------------------------+--------------------------+--------------------------+---------+--------------+-------------------+-------------------+----------+------------ + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 + 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 | | @ 3 hours + 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 | | @ 3 hours + 25 | 2 | | | | | Thu Sep 29 12:00:00 2016 | | | 13 | | | | + 26 | 2 | | | | | Thu Sep 29 12:00:00 2016 | | | 13 | | | | +(26 rows) + +SELECT comp(); + comp +----------- + Unchanged +(1 row) + +DROP TABLE T; +-- 2.1 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); +-- 2.2 Limits +-- 2.2.1 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(); +SELECT comp(); + comp +----------- + Rewritten +(1 row) + +DROP TABLE T; +-- 3. Drive simple queries DML +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); +-- 3.a A 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) + +-- 3.b 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) + +-- 3.c Aggregate function +SELECT SUM(c_bigint), MAX(c_text), MIN(c_text) FROM T; + sum | max | min +-----+-------+----- + 201 | hello | 31 +(1 row) + +-- 3.d 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) + +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) + +-- 3.y 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) + +-- 3.z 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; +-- 4. 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 SCHEMA fast_default; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index 8641769..1bcce97 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -106,7 +106,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 # event triggers cannot run concurrently with any test that runs DDL test: event_trigger diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 835cf35..a080890 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -169,3 +169,4 @@ test: with test: xml 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..cc73055 --- /dev/null +++ b/src/test/regress/sql/fast_default.sql @@ -0,0 +1,283 @@ +-- +-- 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'; + +-- 1. 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 ALTER COLUMN c_time DROP DEFAULT, + ALTER COLUMN c_interval SET DEFAULT '3 hours'; + +INSERT INTO T VALUES (23), (24); + +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_interval DROP DEFAULT; + +INSERT INTO T VALUES (25), (26); + +SELECT * FROM T ORDER BY pk; + +SELECT comp(); + +DROP TABLE T; + +-- 2.1 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); + +-- 2.2 Limits +-- 2.2.1 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; + +-- 3. Drive simple queries DML +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); + +-- 3.a A 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; + + +-- 3.b COALESCE +SELECT COALESCE(c_bigint, pk), COALESCE(c_text, pk::text) FROM T ORDER BY pk LIMIT 10; + +-- 3.c Aggregate function +SELECT SUM(c_bigint), MAX(c_text), MIN(c_text) FROM T; + +-- 3.d 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; + +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; + +-- 3.y 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 *; + +-- 3.z 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; + + +-- 4. 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 SCHEMA fast_default;