mirror of
https://github.com/postgres/postgres.git
synced 2025-07-02 09:02:37 +03:00
Fast ALTER TABLE ADD COLUMN with a non-NULL default
Currently adding a column to a table with a non-NULL default results in a rewrite of the table. For large tables this can be both expensive and disruptive. This patch removes the need for the rewrite as long as the default value is not volatile. The default expression is evaluated at the time of the ALTER TABLE and the result stored in a new column (attmissingval) in pg_attribute, and a new column (atthasmissing) is set to true. Any existing row when fetched will be supplied with the attmissingval. New rows will have the supplied value or the default and so will never need the attmissingval. Any time the table is rewritten all the atthasmissing and attmissingval settings for the attributes are cleared, as they are no longer needed. The most visible code change from this is in heap_attisnull, which acquires a third TupleDesc argument, allowing it to detect a missing value if there is one. In many cases where it is known that there will not be any (e.g. catalog relations) NULL can be passed for this argument. Andrew Dunstan, heavily modified from an original patch from Serge Rielau. Reviewed by Tom Lane, Andres Freund, Tomas Vondra and David Rowley. Discussion: https://postgr.es/m/31e2e921-7002-4c27-59f5-51f08404c858@2ndQuadrant.com
This commit is contained in:
@ -58,6 +58,7 @@
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/sysattr.h"
|
||||
#include "access/tupdesc_details.h"
|
||||
#include "access/tuptoaster.h"
|
||||
#include "executor/tuptable.h"
|
||||
#include "utils/expandeddatum.h"
|
||||
@ -76,6 +77,74 @@
|
||||
* ----------------------------------------------------------------
|
||||
*/
|
||||
|
||||
/*
|
||||
* Return the missing value of an attribute, or NULL if there isn't one.
|
||||
*/
|
||||
static Datum
|
||||
getmissingattr(TupleDesc tupleDesc,
|
||||
int attnum, bool *isnull)
|
||||
{
|
||||
Form_pg_attribute att;
|
||||
|
||||
Assert(attnum <= tupleDesc->natts);
|
||||
Assert(attnum > 0);
|
||||
|
||||
att = TupleDescAttr(tupleDesc, attnum - 1);
|
||||
|
||||
if (att->atthasmissing)
|
||||
{
|
||||
AttrMissing *attrmiss;
|
||||
|
||||
Assert(tupleDesc->constr);
|
||||
Assert(tupleDesc->constr->missing);
|
||||
|
||||
attrmiss = tupleDesc->constr->missing + (attnum - 1);
|
||||
|
||||
if (attrmiss->ammissingPresent)
|
||||
{
|
||||
*isnull = false;
|
||||
return attrmiss->ammissing;
|
||||
}
|
||||
}
|
||||
|
||||
*isnull = true;
|
||||
return PointerGetDatum(NULL);
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill in missing values for a TupleTableSlot
|
||||
*/
|
||||
static void
|
||||
slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum)
|
||||
{
|
||||
AttrMissing *attrmiss = NULL;
|
||||
int missattnum;
|
||||
|
||||
if (slot->tts_tupleDescriptor->constr)
|
||||
attrmiss = slot->tts_tupleDescriptor->constr->missing;
|
||||
|
||||
|
||||
if (!attrmiss)
|
||||
{
|
||||
/* no missing values array at all, so just fill everything in as NULL */
|
||||
memset(slot->tts_values + startAttNum, 0,
|
||||
(lastAttNum - startAttNum) * sizeof(Datum));
|
||||
memset(slot->tts_isnull + startAttNum, 1,
|
||||
(lastAttNum - startAttNum) * sizeof(bool));
|
||||
}
|
||||
else
|
||||
{
|
||||
/* if there is a missing values array we must process them one by one */
|
||||
for (missattnum = lastAttNum - 1;
|
||||
missattnum >= startAttNum;
|
||||
missattnum--)
|
||||
{
|
||||
slot->tts_values[missattnum] = attrmiss[missattnum].ammissing;
|
||||
slot->tts_isnull[missattnum] =
|
||||
!attrmiss[missattnum].ammissingPresent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* heap_compute_data_size
|
||||
@ -132,6 +201,131 @@ heap_compute_data_size(TupleDesc tupleDesc,
|
||||
return data_length;
|
||||
}
|
||||
|
||||
/*
|
||||
* Per-attribute helper for heap_fill_tuple and other routines building tuples.
|
||||
*
|
||||
* Fill in either a data value or a bit in the null bitmask
|
||||
*/
|
||||
static inline void
|
||||
fill_val(Form_pg_attribute att,
|
||||
bits8 **bit,
|
||||
int *bitmask,
|
||||
char **dataP,
|
||||
uint16 *infomask,
|
||||
Datum datum,
|
||||
bool isnull)
|
||||
{
|
||||
Size data_length;
|
||||
char *data = *dataP;
|
||||
|
||||
/*
|
||||
* If we're building a null bitmap, set the appropriate bit for the
|
||||
* current column value here.
|
||||
*/
|
||||
if (bit != NULL)
|
||||
{
|
||||
if (*bitmask != HIGHBIT)
|
||||
*bitmask <<= 1;
|
||||
else
|
||||
{
|
||||
*bit += 1;
|
||||
**bit = 0x0;
|
||||
*bitmask = 1;
|
||||
}
|
||||
|
||||
if (isnull)
|
||||
{
|
||||
*infomask |= HEAP_HASNULL;
|
||||
return;
|
||||
}
|
||||
|
||||
**bit |= *bitmask;
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX we use the att_align macros on the pointer value itself, not on an
|
||||
* offset. This is a bit of a hack.
|
||||
*/
|
||||
if (att->attbyval)
|
||||
{
|
||||
/* pass-by-value */
|
||||
data = (char *) att_align_nominal(data, att->attalign);
|
||||
store_att_byval(data, datum, att->attlen);
|
||||
data_length = att->attlen;
|
||||
}
|
||||
else if (att->attlen == -1)
|
||||
{
|
||||
/* varlena */
|
||||
Pointer val = DatumGetPointer(datum);
|
||||
|
||||
*infomask |= HEAP_HASVARWIDTH;
|
||||
if (VARATT_IS_EXTERNAL(val))
|
||||
{
|
||||
if (VARATT_IS_EXTERNAL_EXPANDED(val))
|
||||
{
|
||||
/*
|
||||
* we want to flatten the expanded value so that the
|
||||
* constructed tuple doesn't depend on it
|
||||
*/
|
||||
ExpandedObjectHeader *eoh = DatumGetEOHP(datum);
|
||||
|
||||
data = (char *) att_align_nominal(data,
|
||||
att->attalign);
|
||||
data_length = EOH_get_flat_size(eoh);
|
||||
EOH_flatten_into(eoh, data, data_length);
|
||||
}
|
||||
else
|
||||
{
|
||||
*infomask |= HEAP_HASEXTERNAL;
|
||||
/* no alignment, since it's short by definition */
|
||||
data_length = VARSIZE_EXTERNAL(val);
|
||||
memcpy(data, val, data_length);
|
||||
}
|
||||
}
|
||||
else if (VARATT_IS_SHORT(val))
|
||||
{
|
||||
/* no alignment for short varlenas */
|
||||
data_length = VARSIZE_SHORT(val);
|
||||
memcpy(data, val, data_length);
|
||||
}
|
||||
else if (VARLENA_ATT_IS_PACKABLE(att) &&
|
||||
VARATT_CAN_MAKE_SHORT(val))
|
||||
{
|
||||
/* convert to short varlena -- no alignment */
|
||||
data_length = VARATT_CONVERTED_SHORT_SIZE(val);
|
||||
SET_VARSIZE_SHORT(data, data_length);
|
||||
memcpy(data + 1, VARDATA(val), data_length - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* full 4-byte header varlena */
|
||||
data = (char *) att_align_nominal(data,
|
||||
att->attalign);
|
||||
data_length = VARSIZE(val);
|
||||
memcpy(data, val, data_length);
|
||||
}
|
||||
}
|
||||
else if (att->attlen == -2)
|
||||
{
|
||||
/* cstring ... never needs alignment */
|
||||
*infomask |= HEAP_HASVARWIDTH;
|
||||
Assert(att->attalign == 'c');
|
||||
data_length = strlen(DatumGetCString(datum)) + 1;
|
||||
memcpy(data, DatumGetPointer(datum), data_length);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* fixed-length pass-by-reference */
|
||||
data = (char *) att_align_nominal(data, att->attalign);
|
||||
Assert(att->attlen > 0);
|
||||
data_length = att->attlen;
|
||||
memcpy(data, DatumGetPointer(datum), data_length);
|
||||
}
|
||||
|
||||
data += data_length;
|
||||
*dataP = data;
|
||||
}
|
||||
|
||||
/*
|
||||
* heap_fill_tuple
|
||||
* Load data portion of a tuple from values/isnull arrays
|
||||
@ -172,111 +366,15 @@ heap_fill_tuple(TupleDesc tupleDesc,
|
||||
|
||||
for (i = 0; i < numberOfAttributes; i++)
|
||||
{
|
||||
Form_pg_attribute att = TupleDescAttr(tupleDesc, i);
|
||||
Size data_length;
|
||||
Form_pg_attribute attr = TupleDescAttr(tupleDesc, i);
|
||||
|
||||
if (bit != NULL)
|
||||
{
|
||||
if (bitmask != HIGHBIT)
|
||||
bitmask <<= 1;
|
||||
else
|
||||
{
|
||||
bitP += 1;
|
||||
*bitP = 0x0;
|
||||
bitmask = 1;
|
||||
}
|
||||
|
||||
if (isnull[i])
|
||||
{
|
||||
*infomask |= HEAP_HASNULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
*bitP |= bitmask;
|
||||
}
|
||||
|
||||
/*
|
||||
* XXX we use the att_align macros on the pointer value itself, not on
|
||||
* an offset. This is a bit of a hack.
|
||||
*/
|
||||
|
||||
if (att->attbyval)
|
||||
{
|
||||
/* pass-by-value */
|
||||
data = (char *) att_align_nominal(data, att->attalign);
|
||||
store_att_byval(data, values[i], att->attlen);
|
||||
data_length = att->attlen;
|
||||
}
|
||||
else if (att->attlen == -1)
|
||||
{
|
||||
/* varlena */
|
||||
Pointer val = DatumGetPointer(values[i]);
|
||||
|
||||
*infomask |= HEAP_HASVARWIDTH;
|
||||
if (VARATT_IS_EXTERNAL(val))
|
||||
{
|
||||
if (VARATT_IS_EXTERNAL_EXPANDED(val))
|
||||
{
|
||||
/*
|
||||
* we want to flatten the expanded value so that the
|
||||
* constructed tuple doesn't depend on it
|
||||
*/
|
||||
ExpandedObjectHeader *eoh = DatumGetEOHP(values[i]);
|
||||
|
||||
data = (char *) att_align_nominal(data,
|
||||
att->attalign);
|
||||
data_length = EOH_get_flat_size(eoh);
|
||||
EOH_flatten_into(eoh, data, data_length);
|
||||
}
|
||||
else
|
||||
{
|
||||
*infomask |= HEAP_HASEXTERNAL;
|
||||
/* no alignment, since it's short by definition */
|
||||
data_length = VARSIZE_EXTERNAL(val);
|
||||
memcpy(data, val, data_length);
|
||||
}
|
||||
}
|
||||
else if (VARATT_IS_SHORT(val))
|
||||
{
|
||||
/* no alignment for short varlenas */
|
||||
data_length = VARSIZE_SHORT(val);
|
||||
memcpy(data, val, data_length);
|
||||
}
|
||||
else if (VARLENA_ATT_IS_PACKABLE(att) &&
|
||||
VARATT_CAN_MAKE_SHORT(val))
|
||||
{
|
||||
/* convert to short varlena -- no alignment */
|
||||
data_length = VARATT_CONVERTED_SHORT_SIZE(val);
|
||||
SET_VARSIZE_SHORT(data, data_length);
|
||||
memcpy(data + 1, VARDATA(val), data_length - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* full 4-byte header varlena */
|
||||
data = (char *) att_align_nominal(data,
|
||||
att->attalign);
|
||||
data_length = VARSIZE(val);
|
||||
memcpy(data, val, data_length);
|
||||
}
|
||||
}
|
||||
else if (att->attlen == -2)
|
||||
{
|
||||
/* cstring ... never needs alignment */
|
||||
*infomask |= HEAP_HASVARWIDTH;
|
||||
Assert(att->attalign == 'c');
|
||||
data_length = strlen(DatumGetCString(values[i])) + 1;
|
||||
memcpy(data, DatumGetPointer(values[i]), data_length);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* fixed-length pass-by-reference */
|
||||
data = (char *) att_align_nominal(data, att->attalign);
|
||||
Assert(att->attlen > 0);
|
||||
data_length = att->attlen;
|
||||
memcpy(data, DatumGetPointer(values[i]), data_length);
|
||||
}
|
||||
|
||||
data += data_length;
|
||||
fill_val(attr,
|
||||
bitP ? &bitP : NULL,
|
||||
&bitmask,
|
||||
&data,
|
||||
infomask,
|
||||
values ? values[i] : PointerGetDatum(NULL),
|
||||
isnull ? isnull[i] : true);
|
||||
}
|
||||
|
||||
Assert((data - start) == data_size);
|
||||
@ -293,10 +391,20 @@ heap_fill_tuple(TupleDesc tupleDesc,
|
||||
* ----------------
|
||||
*/
|
||||
bool
|
||||
heap_attisnull(HeapTuple tup, int attnum)
|
||||
heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc)
|
||||
{
|
||||
/*
|
||||
* We allow a NULL tupledesc for relations not expected to have missing
|
||||
* values, such as catalog relations and indexes.
|
||||
*/
|
||||
Assert(!tupleDesc || attnum <= tupleDesc->natts);
|
||||
if (attnum > (int) HeapTupleHeaderGetNatts(tup->t_data))
|
||||
return true;
|
||||
{
|
||||
if (tupleDesc && TupleDescAttr(tupleDesc, attnum - 1)->atthasmissing)
|
||||
return false;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
if (attnum > 0)
|
||||
{
|
||||
@ -649,6 +757,274 @@ heap_copytuple_with_tuple(HeapTuple src, HeapTuple dest)
|
||||
memcpy((char *) dest->t_data, (char *) src->t_data, src->t_len);
|
||||
}
|
||||
|
||||
/*
|
||||
* Expand a tuple which has less attributes than required. For each attribute
|
||||
* not present in the sourceTuple, if there is a missing value that will be
|
||||
* used. Otherwise the attribute will be set to NULL.
|
||||
*
|
||||
* The source tuple must have less attributes than the required number.
|
||||
*
|
||||
* Only one of targetHeapTuple and targetMinimalTuple may be supplied. The
|
||||
* other argument must be NULL.
|
||||
*/
|
||||
static void
|
||||
expand_tuple(HeapTuple *targetHeapTuple,
|
||||
MinimalTuple *targetMinimalTuple,
|
||||
HeapTuple sourceTuple,
|
||||
TupleDesc tupleDesc)
|
||||
{
|
||||
AttrMissing *attrmiss = NULL;
|
||||
int attnum;
|
||||
int firstmissingnum = 0;
|
||||
bool hasNulls = HeapTupleHasNulls(sourceTuple);
|
||||
HeapTupleHeader targetTHeader;
|
||||
HeapTupleHeader sourceTHeader = sourceTuple->t_data;
|
||||
int sourceNatts = HeapTupleHeaderGetNatts(sourceTHeader);
|
||||
int natts = tupleDesc->natts;
|
||||
int sourceNullLen;
|
||||
int targetNullLen;
|
||||
Size sourceDataLen = sourceTuple->t_len - sourceTHeader->t_hoff;
|
||||
Size targetDataLen;
|
||||
Size len;
|
||||
int hoff;
|
||||
bits8 *nullBits = NULL;
|
||||
int bitMask = 0;
|
||||
char *targetData;
|
||||
uint16 *infoMask;
|
||||
|
||||
Assert((targetHeapTuple && !targetMinimalTuple)
|
||||
|| (!targetHeapTuple && targetMinimalTuple));
|
||||
|
||||
Assert(sourceNatts < natts);
|
||||
|
||||
sourceNullLen = (hasNulls ? BITMAPLEN(sourceNatts) : 0);
|
||||
|
||||
targetDataLen = sourceDataLen;
|
||||
|
||||
if (tupleDesc->constr &&
|
||||
tupleDesc->constr->missing)
|
||||
{
|
||||
/*
|
||||
* If there are missing values we want to put them into the tuple.
|
||||
* Before that we have to compute the extra length for the values
|
||||
* array and the variable length data.
|
||||
*/
|
||||
attrmiss = tupleDesc->constr->missing;
|
||||
|
||||
/*
|
||||
* Find the first item in attrmiss for which we don't have a value in
|
||||
* the source. We can ignore all the missing entries before that.
|
||||
*/
|
||||
for (firstmissingnum = sourceNatts;
|
||||
firstmissingnum < natts;
|
||||
firstmissingnum++)
|
||||
{
|
||||
if (attrmiss[firstmissingnum].ammissingPresent)
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* If there are no more missing values everything else must be NULL
|
||||
*/
|
||||
if (firstmissingnum >= natts)
|
||||
{
|
||||
hasNulls = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
/*
|
||||
* Now walk the missing attributes. If there is a missing value
|
||||
* make space for it. Otherwise, it's going to be NULL.
|
||||
*/
|
||||
for (attnum = firstmissingnum;
|
||||
attnum < natts;
|
||||
attnum++)
|
||||
{
|
||||
if (attrmiss[attnum].ammissingPresent)
|
||||
{
|
||||
Form_pg_attribute att = TupleDescAttr(tupleDesc, attnum);
|
||||
|
||||
targetDataLen = att_align_datum(targetDataLen,
|
||||
att->attalign,
|
||||
att->attlen,
|
||||
attrmiss[attnum].ammissing);
|
||||
|
||||
targetDataLen = att_addlength_pointer(targetDataLen,
|
||||
att->attlen,
|
||||
attrmiss[attnum].ammissing);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* no missing value, so it must be null */
|
||||
hasNulls = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} /* end if have missing values */
|
||||
else
|
||||
{
|
||||
/*
|
||||
* If there are no missing values at all then NULLS must be allowed,
|
||||
* since some of the attributes are known to be absent.
|
||||
*/
|
||||
hasNulls = true;
|
||||
}
|
||||
|
||||
len = 0;
|
||||
|
||||
if (hasNulls)
|
||||
{
|
||||
targetNullLen = BITMAPLEN(natts);
|
||||
len += targetNullLen;
|
||||
}
|
||||
else
|
||||
targetNullLen = 0;
|
||||
|
||||
if (tupleDesc->tdhasoid)
|
||||
len += sizeof(Oid);
|
||||
|
||||
/*
|
||||
* Allocate and zero the space needed. Note that the tuple body and
|
||||
* HeapTupleData management structure are allocated in one chunk.
|
||||
*/
|
||||
if (targetHeapTuple)
|
||||
{
|
||||
len += offsetof(HeapTupleHeaderData, t_bits);
|
||||
hoff = len = MAXALIGN(len); /* align user data safely */
|
||||
len += targetDataLen;
|
||||
|
||||
*targetHeapTuple = (HeapTuple) palloc0(HEAPTUPLESIZE + len);
|
||||
(*targetHeapTuple)->t_data
|
||||
= targetTHeader
|
||||
= (HeapTupleHeader) ((char *) *targetHeapTuple + HEAPTUPLESIZE);
|
||||
(*targetHeapTuple)->t_len = len;
|
||||
(*targetHeapTuple)->t_tableOid = sourceTuple->t_tableOid;
|
||||
ItemPointerSetInvalid(&((*targetHeapTuple)->t_self));
|
||||
|
||||
targetTHeader->t_infomask = sourceTHeader->t_infomask;
|
||||
targetTHeader->t_hoff = hoff;
|
||||
HeapTupleHeaderSetNatts(targetTHeader, natts);
|
||||
HeapTupleHeaderSetDatumLength(targetTHeader, len);
|
||||
HeapTupleHeaderSetTypeId(targetTHeader, tupleDesc->tdtypeid);
|
||||
HeapTupleHeaderSetTypMod(targetTHeader, tupleDesc->tdtypmod);
|
||||
/* We also make sure that t_ctid is invalid unless explicitly set */
|
||||
ItemPointerSetInvalid(&(targetTHeader->t_ctid));
|
||||
if (targetNullLen > 0)
|
||||
nullBits = (bits8 *) ((char *) (*targetHeapTuple)->t_data
|
||||
+ offsetof(HeapTupleHeaderData, t_bits));
|
||||
targetData = (char *) (*targetHeapTuple)->t_data + hoff;
|
||||
infoMask = &(targetTHeader->t_infomask);
|
||||
}
|
||||
else
|
||||
{
|
||||
len += SizeofMinimalTupleHeader;
|
||||
hoff = len = MAXALIGN(len); /* align user data safely */
|
||||
len += targetDataLen;
|
||||
|
||||
*targetMinimalTuple = (MinimalTuple) palloc0(len);
|
||||
(*targetMinimalTuple)->t_len = len;
|
||||
(*targetMinimalTuple)->t_hoff = hoff + MINIMAL_TUPLE_OFFSET;
|
||||
(*targetMinimalTuple)->t_infomask = sourceTHeader->t_infomask;
|
||||
/* Same macro works for MinimalTuples */
|
||||
HeapTupleHeaderSetNatts(*targetMinimalTuple, natts);
|
||||
if (targetNullLen > 0)
|
||||
nullBits = (bits8 *) ((char *) *targetMinimalTuple
|
||||
+ offsetof(MinimalTupleData, t_bits));
|
||||
targetData = (char *) *targetMinimalTuple + hoff;
|
||||
infoMask = &((*targetMinimalTuple)->t_infomask);
|
||||
}
|
||||
|
||||
if (targetNullLen > 0)
|
||||
{
|
||||
if (sourceNullLen > 0)
|
||||
{
|
||||
/* if bitmap pre-existed copy in - all is set */
|
||||
memcpy(nullBits,
|
||||
((char *) sourceTHeader)
|
||||
+ offsetof(HeapTupleHeaderData, t_bits),
|
||||
sourceNullLen);
|
||||
nullBits += sourceNullLen - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
sourceNullLen = BITMAPLEN(sourceNatts);
|
||||
/* Set NOT NULL for all existing attributes */
|
||||
memset(nullBits, 0xff, sourceNullLen);
|
||||
|
||||
nullBits += sourceNullLen - 1;
|
||||
|
||||
if (sourceNatts & 0x07)
|
||||
{
|
||||
/* build the mask (inverted!) */
|
||||
bitMask = 0xff << (sourceNatts & 0x07);
|
||||
/* Voila */
|
||||
*nullBits = ~bitMask;
|
||||
}
|
||||
}
|
||||
|
||||
bitMask = (1 << ((sourceNatts - 1) & 0x07));
|
||||
} /* End if have null bitmap */
|
||||
|
||||
memcpy(targetData,
|
||||
((char *) sourceTuple->t_data) + sourceTHeader->t_hoff,
|
||||
sourceDataLen);
|
||||
|
||||
targetData += sourceDataLen;
|
||||
|
||||
/* Now fill in the missing values */
|
||||
for (attnum = sourceNatts; attnum < natts; attnum++)
|
||||
{
|
||||
|
||||
Form_pg_attribute attr = TupleDescAttr(tupleDesc, attnum);
|
||||
|
||||
if (attrmiss[attnum].ammissingPresent)
|
||||
{
|
||||
fill_val(attr,
|
||||
nullBits ? &nullBits : NULL,
|
||||
&bitMask,
|
||||
&targetData,
|
||||
infoMask,
|
||||
attrmiss[attnum].ammissing,
|
||||
false);
|
||||
}
|
||||
else
|
||||
{
|
||||
fill_val(attr,
|
||||
&nullBits,
|
||||
&bitMask,
|
||||
&targetData,
|
||||
infoMask,
|
||||
(Datum) 0,
|
||||
true);
|
||||
}
|
||||
} /* end loop over missing attributes */
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill in the missing values for a minimal HeapTuple
|
||||
*/
|
||||
MinimalTuple
|
||||
minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc)
|
||||
{
|
||||
MinimalTuple minimalTuple;
|
||||
|
||||
expand_tuple(NULL, &minimalTuple, sourceTuple, tupleDesc);
|
||||
return minimalTuple;
|
||||
}
|
||||
|
||||
/*
|
||||
* Fill in the missing values for an ordinary HeapTuple
|
||||
*/
|
||||
HeapTuple
|
||||
heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc)
|
||||
{
|
||||
HeapTuple heapTuple;
|
||||
|
||||
expand_tuple(&heapTuple, NULL, sourceTuple, tupleDesc);
|
||||
return heapTuple;
|
||||
}
|
||||
|
||||
/* ----------------
|
||||
* heap_copy_tuple_as_datum
|
||||
*
|
||||
@ -1012,13 +1388,10 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc,
|
||||
|
||||
/*
|
||||
* If tuple doesn't have all the atts indicated by tupleDesc, read the
|
||||
* rest as null
|
||||
* rest as nulls or missing values as appropriate.
|
||||
*/
|
||||
for (; attnum < tdesc_natts; attnum++)
|
||||
{
|
||||
values[attnum] = (Datum) 0;
|
||||
isnull[attnum] = true;
|
||||
}
|
||||
values[attnum] = getmissingattr(tupleDesc, attnum + 1, &isnull[attnum]);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1183,7 +1556,8 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
|
||||
elog(ERROR, "cannot extract attribute from empty tuple slot");
|
||||
|
||||
/*
|
||||
* return NULL if attnum is out of range according to the tuple
|
||||
* return NULL or missing value if attnum is out of range according to the
|
||||
* tuple
|
||||
*
|
||||
* (We have to check this separately because of various inheritance and
|
||||
* table-alteration scenarios: the tuple could be either longer or shorter
|
||||
@ -1191,10 +1565,7 @@ slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull)
|
||||
*/
|
||||
tup = tuple->t_data;
|
||||
if (attnum > HeapTupleHeaderGetNatts(tup))
|
||||
{
|
||||
*isnull = true;
|
||||
return (Datum) 0;
|
||||
}
|
||||
return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull);
|
||||
|
||||
/*
|
||||
* check if target attribute is null: no point in groveling through tuple
|
||||
@ -1263,13 +1634,11 @@ slot_getallattrs(TupleTableSlot *slot)
|
||||
|
||||
/*
|
||||
* If tuple doesn't have all the atts indicated by tupleDesc, read the
|
||||
* rest as null
|
||||
* rest as NULLS or missing values.
|
||||
*/
|
||||
for (; attnum < tdesc_natts; attnum++)
|
||||
{
|
||||
slot->tts_values[attnum] = (Datum) 0;
|
||||
slot->tts_isnull[attnum] = true;
|
||||
}
|
||||
if (attnum < tdesc_natts)
|
||||
slot_getmissingattrs(slot, attnum, tdesc_natts);
|
||||
|
||||
slot->tts_nvalid = tdesc_natts;
|
||||
}
|
||||
|
||||
@ -1310,13 +1679,11 @@ slot_getsomeattrs(TupleTableSlot *slot, int attnum)
|
||||
|
||||
/*
|
||||
* If tuple doesn't have all the atts indicated by tupleDesc, read the
|
||||
* rest as null
|
||||
* rest as NULLs or missing values
|
||||
*/
|
||||
for (; attno < attnum; attno++)
|
||||
{
|
||||
slot->tts_values[attno] = (Datum) 0;
|
||||
slot->tts_isnull[attno] = true;
|
||||
}
|
||||
if (attno < attnum)
|
||||
slot_getmissingattrs(slot, attno, attnum);
|
||||
|
||||
slot->tts_nvalid = attnum;
|
||||
}
|
||||
|
||||
@ -1340,7 +1707,7 @@ slot_attisnull(TupleTableSlot *slot, int attnum)
|
||||
elog(ERROR, "cannot extract system attribute from virtual tuple");
|
||||
if (tuple == &(slot->tts_minhdr)) /* internal error */
|
||||
elog(ERROR, "cannot extract system attribute from minimal tuple");
|
||||
return heap_attisnull(tuple, attnum);
|
||||
return heap_attisnull(tuple, attnum, tupleDesc);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1363,7 +1730,7 @@ slot_attisnull(TupleTableSlot *slot, int attnum)
|
||||
elog(ERROR, "cannot extract attribute from empty tuple slot");
|
||||
|
||||
/* and let the tuple tell it */
|
||||
return heap_attisnull(tuple, attnum);
|
||||
return heap_attisnull(tuple, attnum, tupleDesc);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -60,9 +60,12 @@
|
||||
#include "catalog/storage_xlog.h"
|
||||
#include "commands/tablecmds.h"
|
||||
#include "commands/typecmds.h"
|
||||
#include "executor/executor.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "optimizer/clauses.h"
|
||||
#include "optimizer/var.h"
|
||||
#include "optimizer/planner.h"
|
||||
#include "parser/parse_coerce.h"
|
||||
#include "parser/parse_collate.h"
|
||||
#include "parser/parse_expr.h"
|
||||
@ -72,6 +75,7 @@
|
||||
#include "storage/smgr.h"
|
||||
#include "utils/acl.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/datum.h"
|
||||
#include "utils/fmgroids.h"
|
||||
#include "utils/inval.h"
|
||||
#include "utils/lsyscache.h"
|
||||
@ -144,37 +148,37 @@ static List *insert_ordered_unique_oid(List *list, Oid datum);
|
||||
static FormData_pg_attribute a1 = {
|
||||
0, {"ctid"}, TIDOID, 0, sizeof(ItemPointerData),
|
||||
SelfItemPointerAttributeNumber, 0, -1, -1,
|
||||
false, 'p', 's', true, false, '\0', false, true, 0
|
||||
false, 'p', 's', true, false, false, '\0', false, true, 0
|
||||
};
|
||||
|
||||
static FormData_pg_attribute a2 = {
|
||||
0, {"oid"}, OIDOID, 0, sizeof(Oid),
|
||||
ObjectIdAttributeNumber, 0, -1, -1,
|
||||
true, 'p', 'i', true, false, '\0', false, true, 0
|
||||
true, 'p', 'i', true, false, false, '\0', false, true, 0
|
||||
};
|
||||
|
||||
static FormData_pg_attribute a3 = {
|
||||
0, {"xmin"}, XIDOID, 0, sizeof(TransactionId),
|
||||
MinTransactionIdAttributeNumber, 0, -1, -1,
|
||||
true, 'p', 'i', true, false, '\0', false, true, 0
|
||||
true, 'p', 'i', true, false, false, '\0', false, true, 0
|
||||
};
|
||||
|
||||
static FormData_pg_attribute a4 = {
|
||||
0, {"cmin"}, CIDOID, 0, sizeof(CommandId),
|
||||
MinCommandIdAttributeNumber, 0, -1, -1,
|
||||
true, 'p', 'i', true, false, '\0', false, true, 0
|
||||
true, 'p', 'i', true, false, false, '\0', false, true, 0
|
||||
};
|
||||
|
||||
static FormData_pg_attribute a5 = {
|
||||
0, {"xmax"}, XIDOID, 0, sizeof(TransactionId),
|
||||
MaxTransactionIdAttributeNumber, 0, -1, -1,
|
||||
true, 'p', 'i', true, false, '\0', false, true, 0
|
||||
true, 'p', 'i', true, false, false, '\0', false, true, 0
|
||||
};
|
||||
|
||||
static FormData_pg_attribute a6 = {
|
||||
0, {"cmax"}, CIDOID, 0, sizeof(CommandId),
|
||||
MaxCommandIdAttributeNumber, 0, -1, -1,
|
||||
true, 'p', 'i', true, false, '\0', false, true, 0
|
||||
true, 'p', 'i', true, false, false, '\0', false, true, 0
|
||||
};
|
||||
|
||||
/*
|
||||
@ -186,7 +190,7 @@ static FormData_pg_attribute a6 = {
|
||||
static FormData_pg_attribute a7 = {
|
||||
0, {"tableoid"}, OIDOID, 0, sizeof(Oid),
|
||||
TableOidAttributeNumber, 0, -1, -1,
|
||||
true, 'p', 'i', true, false, '\0', false, true, 0
|
||||
true, 'p', 'i', true, false, false, '\0', false, true, 0
|
||||
};
|
||||
|
||||
static const Form_pg_attribute SysAtt[] = {&a1, &a2, &a3, &a4, &a5, &a6, &a7};
|
||||
@ -624,6 +628,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel,
|
||||
values[Anum_pg_attribute_attalign - 1] = CharGetDatum(new_attribute->attalign);
|
||||
values[Anum_pg_attribute_attnotnull - 1] = BoolGetDatum(new_attribute->attnotnull);
|
||||
values[Anum_pg_attribute_atthasdef - 1] = BoolGetDatum(new_attribute->atthasdef);
|
||||
values[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(new_attribute->atthasmissing);
|
||||
values[Anum_pg_attribute_attidentity - 1] = CharGetDatum(new_attribute->attidentity);
|
||||
values[Anum_pg_attribute_attisdropped - 1] = BoolGetDatum(new_attribute->attisdropped);
|
||||
values[Anum_pg_attribute_attislocal - 1] = BoolGetDatum(new_attribute->attislocal);
|
||||
@ -634,6 +639,7 @@ InsertPgAttributeTuple(Relation pg_attribute_rel,
|
||||
nulls[Anum_pg_attribute_attacl - 1] = true;
|
||||
nulls[Anum_pg_attribute_attoptions - 1] = true;
|
||||
nulls[Anum_pg_attribute_attfdwoptions - 1] = true;
|
||||
nulls[Anum_pg_attribute_attmissingval - 1] = true;
|
||||
|
||||
tup = heap_form_tuple(RelationGetDescr(pg_attribute_rel), values, nulls);
|
||||
|
||||
@ -1925,14 +1931,91 @@ heap_drop_with_catalog(Oid relid)
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* RelationClearMissing
|
||||
*
|
||||
* Set atthasmissing and attmissingval to false/null for all attributes
|
||||
* where they are currently set. This can be safely and usefully done if
|
||||
* the table is rewritten (e.g. by VACUUM FULL or CLUSTER) where we know there
|
||||
* are no rows left with less than a full complement of attributes.
|
||||
*
|
||||
* The caller must have an AccessExclusive lock on the relation.
|
||||
*/
|
||||
void
|
||||
RelationClearMissing(Relation rel)
|
||||
{
|
||||
Relation attr_rel;
|
||||
Oid relid = RelationGetRelid(rel);
|
||||
int natts = RelationGetNumberOfAttributes(rel);
|
||||
int attnum;
|
||||
Datum repl_val[Natts_pg_attribute];
|
||||
bool repl_null[Natts_pg_attribute];
|
||||
bool repl_repl[Natts_pg_attribute];
|
||||
Form_pg_attribute attrtuple;
|
||||
HeapTuple tuple,
|
||||
newtuple;
|
||||
|
||||
memset(repl_val, 0, sizeof(repl_val));
|
||||
memset(repl_null, false, sizeof(repl_null));
|
||||
memset(repl_repl, false, sizeof(repl_repl));
|
||||
|
||||
repl_val[Anum_pg_attribute_atthasmissing - 1] = BoolGetDatum(false);
|
||||
repl_null[Anum_pg_attribute_attmissingval - 1] = true;
|
||||
|
||||
repl_repl[Anum_pg_attribute_atthasmissing - 1] = true;
|
||||
repl_repl[Anum_pg_attribute_attmissingval - 1] = true;
|
||||
|
||||
|
||||
/* Get a lock on pg_attribute */
|
||||
attr_rel = heap_open(AttributeRelationId, RowExclusiveLock);
|
||||
|
||||
/* process each non-system attribute, including any dropped columns */
|
||||
for (attnum = 1; attnum <= natts; attnum++)
|
||||
{
|
||||
tuple = SearchSysCache2(ATTNUM,
|
||||
ObjectIdGetDatum(relid),
|
||||
Int16GetDatum(attnum));
|
||||
if (!HeapTupleIsValid(tuple)) /* shouldn't happen */
|
||||
elog(ERROR, "cache lookup failed for attribute %d of relation %u",
|
||||
attnum, relid);
|
||||
|
||||
attrtuple = (Form_pg_attribute) GETSTRUCT(tuple);
|
||||
|
||||
/* ignore any where atthasmissing is not true */
|
||||
if (attrtuple->atthasmissing)
|
||||
{
|
||||
newtuple = heap_modify_tuple(tuple, RelationGetDescr(attr_rel),
|
||||
repl_val, repl_null, repl_repl);
|
||||
|
||||
CatalogTupleUpdate(attr_rel, &newtuple->t_self, newtuple);
|
||||
|
||||
heap_freetuple(newtuple);
|
||||
}
|
||||
|
||||
ReleaseSysCache(tuple);
|
||||
}
|
||||
|
||||
/*
|
||||
* Our update of the pg_attribute rows will force a relcache rebuild, so
|
||||
* there's nothing else to do here.
|
||||
*/
|
||||
heap_close(attr_rel, RowExclusiveLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Store a default expression for column attnum of relation rel.
|
||||
*
|
||||
* Returns the OID of the new pg_attrdef tuple.
|
||||
*
|
||||
* add_column_mode must be true if we are storing the default for a new
|
||||
* attribute, and false if it's for an already existing attribute. The reason
|
||||
* for this is that the missing value must never be updated after it is set,
|
||||
* which can only be when a column is added to the table. Otherwise we would
|
||||
* in effect be changing existing tuples.
|
||||
*/
|
||||
Oid
|
||||
StoreAttrDefault(Relation rel, AttrNumber attnum,
|
||||
Node *expr, bool is_internal)
|
||||
Node *expr, bool is_internal, bool add_column_mode)
|
||||
{
|
||||
char *adbin;
|
||||
char *adsrc;
|
||||
@ -2000,8 +2083,69 @@ StoreAttrDefault(Relation rel, AttrNumber attnum,
|
||||
attStruct = (Form_pg_attribute) GETSTRUCT(atttup);
|
||||
if (!attStruct->atthasdef)
|
||||
{
|
||||
attStruct->atthasdef = true;
|
||||
Form_pg_attribute defAttStruct;
|
||||
|
||||
ExprState *exprState;
|
||||
Expr *expr2 = (Expr *) expr;
|
||||
EState *estate = NULL;
|
||||
ExprContext *econtext;
|
||||
Datum valuesAtt[Natts_pg_attribute];
|
||||
bool nullsAtt[Natts_pg_attribute];
|
||||
bool replacesAtt[Natts_pg_attribute];
|
||||
Datum missingval = (Datum) 0;
|
||||
bool missingIsNull = true;
|
||||
|
||||
MemSet(valuesAtt, 0, sizeof(valuesAtt));
|
||||
MemSet(nullsAtt, false, sizeof(nullsAtt));
|
||||
MemSet(replacesAtt, false, sizeof(replacesAtt));
|
||||
valuesAtt[Anum_pg_attribute_atthasdef - 1] = true;
|
||||
replacesAtt[Anum_pg_attribute_atthasdef - 1] = true;
|
||||
|
||||
if (add_column_mode)
|
||||
{
|
||||
expr2 = expression_planner(expr2);
|
||||
estate = CreateExecutorState();
|
||||
exprState = ExecPrepareExpr(expr2, estate);
|
||||
econtext = GetPerTupleExprContext(estate);
|
||||
|
||||
missingval = ExecEvalExpr(exprState, econtext,
|
||||
&missingIsNull);
|
||||
|
||||
FreeExecutorState(estate);
|
||||
|
||||
defAttStruct = TupleDescAttr(rel->rd_att, attnum - 1);
|
||||
|
||||
if (missingIsNull)
|
||||
{
|
||||
/* if the default evaluates to NULL, just store a NULL array */
|
||||
missingval = (Datum) 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* otherwise make a one-element array of the value */
|
||||
missingval = PointerGetDatum(
|
||||
construct_array(&missingval,
|
||||
1,
|
||||
defAttStruct->atttypid,
|
||||
defAttStruct->attlen,
|
||||
defAttStruct->attbyval,
|
||||
defAttStruct->attalign));
|
||||
}
|
||||
|
||||
valuesAtt[Anum_pg_attribute_atthasmissing - 1] = !missingIsNull;
|
||||
replacesAtt[Anum_pg_attribute_atthasmissing - 1] = true;
|
||||
valuesAtt[Anum_pg_attribute_attmissingval - 1] = missingval;
|
||||
replacesAtt[Anum_pg_attribute_attmissingval - 1] = true;
|
||||
nullsAtt[Anum_pg_attribute_attmissingval - 1] = missingIsNull;
|
||||
}
|
||||
atttup = heap_modify_tuple(atttup, RelationGetDescr(attrrel),
|
||||
valuesAtt, nullsAtt, replacesAtt);
|
||||
|
||||
CatalogTupleUpdate(attrrel, &atttup->t_self, atttup);
|
||||
|
||||
if (!missingIsNull)
|
||||
pfree(DatumGetPointer(missingval));
|
||||
|
||||
}
|
||||
heap_close(attrrel, RowExclusiveLock);
|
||||
heap_freetuple(atttup);
|
||||
@ -2185,7 +2329,7 @@ StoreConstraints(Relation rel, List *cooked_constraints, bool is_internal)
|
||||
{
|
||||
case CONSTR_DEFAULT:
|
||||
con->conoid = StoreAttrDefault(rel, con->attnum, con->expr,
|
||||
is_internal);
|
||||
is_internal, false);
|
||||
break;
|
||||
case CONSTR_CHECK:
|
||||
con->conoid =
|
||||
@ -2301,7 +2445,12 @@ AddRelationNewConstraints(Relation rel,
|
||||
(IsA(expr, Const) &&((Const *) expr)->constisnull))
|
||||
continue;
|
||||
|
||||
defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal);
|
||||
/* If the DEFAULT is volatile we cannot use a missing value */
|
||||
if (colDef->missingMode && contain_volatile_functions((Node *) expr))
|
||||
colDef->missingMode = false;
|
||||
|
||||
defOid = StoreAttrDefault(rel, colDef->attnum, expr, is_internal,
|
||||
colDef->missingMode);
|
||||
|
||||
cooked = (CookedConstraint *) palloc(sizeof(CookedConstraint));
|
||||
cooked->contype = CONSTR_DEFAULT;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
@ -714,6 +714,7 @@ DefineRelation(CreateStmt *stmt, char relkind, Oid ownerId,
|
||||
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
|
||||
rawEnt->attnum = attnum;
|
||||
rawEnt->raw_default = colDef->raw_default;
|
||||
rawEnt->missingMode = false;
|
||||
rawDefaults = lappend(rawDefaults, rawEnt);
|
||||
attr->atthasdef = true;
|
||||
}
|
||||
@ -4682,7 +4683,7 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
|
||||
{
|
||||
int attn = lfirst_int(l);
|
||||
|
||||
if (heap_attisnull(tuple, attn + 1))
|
||||
if (heap_attisnull(tuple, attn + 1, newTupDesc))
|
||||
{
|
||||
Form_pg_attribute attr = TupleDescAttr(newTupDesc, attn);
|
||||
|
||||
@ -4785,7 +4786,7 @@ ATGetQueueEntry(List **wqueue, Relation rel)
|
||||
tab = (AlteredTableInfo *) palloc0(sizeof(AlteredTableInfo));
|
||||
tab->relid = relid;
|
||||
tab->relkind = rel->rd_rel->relkind;
|
||||
tab->oldDesc = CreateTupleDescCopy(RelationGetDescr(rel));
|
||||
tab->oldDesc = CreateTupleDescCopyConstr(RelationGetDescr(rel));
|
||||
tab->newrelpersistence = RELPERSISTENCE_PERMANENT;
|
||||
tab->chgPersistence = false;
|
||||
|
||||
@ -5404,6 +5405,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
||||
attribute.attalign = tform->typalign;
|
||||
attribute.attnotnull = colDef->is_not_null;
|
||||
attribute.atthasdef = false;
|
||||
attribute.atthasmissing = false;
|
||||
attribute.attidentity = colDef->identity;
|
||||
attribute.attisdropped = false;
|
||||
attribute.attislocal = colDef->is_local;
|
||||
@ -5448,6 +5450,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
||||
rawEnt->attnum = attribute.attnum;
|
||||
rawEnt->raw_default = copyObject(colDef->raw_default);
|
||||
|
||||
/*
|
||||
* Attempt to skip a complete table rewrite by storing the specified
|
||||
* DEFAULT value outside of the heap. This may be disabled inside
|
||||
* AddRelationNewConstraints if the optimization cannot be applied.
|
||||
*/
|
||||
rawEnt->missingMode = true;
|
||||
|
||||
/*
|
||||
* This function is intended for CREATE TABLE, so it processes a
|
||||
* _list_ of defaults, but we just do one.
|
||||
@ -5457,6 +5466,13 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
||||
|
||||
/* Make the additional catalog changes visible */
|
||||
CommandCounterIncrement();
|
||||
|
||||
/*
|
||||
* Did the request for a missing value work? If not we'll have to do
|
||||
* a rewrite
|
||||
*/
|
||||
if (!rawEnt->missingMode)
|
||||
tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
|
||||
}
|
||||
|
||||
/*
|
||||
@ -5502,6 +5518,9 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
||||
nve->typeId = typeOid;
|
||||
|
||||
defval = (Expr *) nve;
|
||||
|
||||
/* must do a rewrite for identity columns */
|
||||
tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
|
||||
}
|
||||
else
|
||||
defval = (Expr *) build_column_default(rel, attribute.attnum);
|
||||
@ -5537,16 +5556,21 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
|
||||
newval->expr = expression_planner(defval);
|
||||
|
||||
tab->newvals = lappend(tab->newvals, newval);
|
||||
tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the new column is NOT NULL, tell Phase 3 it needs to test that.
|
||||
* (Note we don't do this for an OID column. OID will be marked not
|
||||
* null, but since it's filled specially, there's no need to test
|
||||
* anything.)
|
||||
*/
|
||||
tab->new_notnull |= colDef->is_not_null;
|
||||
if (DomainHasConstraints(typeOid))
|
||||
tab->rewrite |= AT_REWRITE_DEFAULT_VAL;
|
||||
|
||||
if (!TupleDescAttr(rel->rd_att, attribute.attnum - 1)->atthasmissing)
|
||||
{
|
||||
/*
|
||||
* If the new column is NOT NULL, and there is no missing value,
|
||||
* tell Phase 3 it needs to test that. (Note we don't do this for
|
||||
* an OID column. OID will be marked not null, but since it's
|
||||
* filled specially, there's no need to test anything.)
|
||||
*/
|
||||
tab->new_notnull |= colDef->is_not_null;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
@ -6022,6 +6046,7 @@ ATExecColumnDefault(Relation rel, const char *colName,
|
||||
rawEnt = (RawColumnDefault *) palloc(sizeof(RawColumnDefault));
|
||||
rawEnt->attnum = attnum;
|
||||
rawEnt->raw_default = newDefault;
|
||||
rawEnt->missingMode = false;
|
||||
|
||||
/*
|
||||
* This function is intended for CREATE TABLE, so it processes a
|
||||
@ -8109,8 +8134,8 @@ transformFkeyCheckAttrs(Relation pkrel,
|
||||
if (indexStruct->indnatts == numattrs &&
|
||||
indexStruct->indisunique &&
|
||||
IndexIsValid(indexStruct) &&
|
||||
heap_attisnull(indexTuple, Anum_pg_index_indpred) &&
|
||||
heap_attisnull(indexTuple, Anum_pg_index_indexprs))
|
||||
heap_attisnull(indexTuple, Anum_pg_index_indpred, NULL) &&
|
||||
heap_attisnull(indexTuple, Anum_pg_index_indexprs, NULL))
|
||||
{
|
||||
Datum indclassDatum;
|
||||
bool isnull;
|
||||
@ -9516,7 +9541,7 @@ ATExecAlterColumnType(AlteredTableInfo *tab, Relation rel,
|
||||
RemoveAttrDefault(RelationGetRelid(rel), attnum, DROP_RESTRICT, true,
|
||||
true);
|
||||
|
||||
StoreAttrDefault(rel, attnum, defaultexpr, true);
|
||||
StoreAttrDefault(rel, attnum, defaultexpr, true, false);
|
||||
}
|
||||
|
||||
ObjectAddressSubSet(address, RelationRelationId,
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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...
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
82
src/backend/utils/cache/relcache.c
vendored
82
src/backend/utils/cache/relcache.c
vendored
@ -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;
|
||||
|
||||
/*
|
||||
|
@ -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 */
|
||||
|
@ -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 */
|
||||
|
Reference in New Issue
Block a user