mirror of
https://github.com/postgres/postgres.git
synced 2025-04-25 21:42:33 +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:
parent
ef1978d6ed
commit
16828d5c02
@ -1149,6 +1149,19 @@
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><structfield>atthasmissing</structfield></entry>
|
||||
<entry><type>bool</type></entry>
|
||||
<entry></entry>
|
||||
<entry>
|
||||
This column has a value which is used where the column is entirely
|
||||
missing from the row, as happens when a column is added with a
|
||||
non-volatile <literal>DEFAULT</literal> value after the row is created.
|
||||
The actual value used is stored in the
|
||||
<structfield>attmissingval</structfield> column.
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><structfield>attidentity</structfield></entry>
|
||||
<entry><type>char</type></entry>
|
||||
@ -1229,6 +1242,20 @@
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
<row>
|
||||
<entry><structfield>attmissingval</structfield></entry>
|
||||
<entry><type>anyarray</type></entry>
|
||||
<entry></entry>
|
||||
<entry>
|
||||
This column has a one element array containing the value used when the
|
||||
column is entirely missing from the row, as happens when the column is
|
||||
added with a non-volatile <literal>DEFAULT</literal> value after the
|
||||
row is created. The value is only used when
|
||||
<structfield>atthasmissing</structfield> is true. If there is no value
|
||||
the column is null.
|
||||
</entry>
|
||||
</row>
|
||||
|
||||
</tbody>
|
||||
</tgroup>
|
||||
</table>
|
||||
|
@ -1184,26 +1184,26 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When a column is added with <literal>ADD COLUMN</literal>, all existing
|
||||
rows in the table are initialized with the column's default value
|
||||
(NULL if no <literal>DEFAULT</literal> clause is specified).
|
||||
If there is no <literal>DEFAULT</literal> clause, this is merely a metadata
|
||||
change and does not require any immediate update of the table's data;
|
||||
the added NULL values are supplied on readout, instead.
|
||||
When a column is added with <literal>ADD COLUMN</literal> and a
|
||||
non-volatile <literal>DEFAULT</literal> is specified, the default is
|
||||
evaluated at the time of the statement and the result stored in the
|
||||
table's metadata. That value will be used for the column for all existing
|
||||
rows. If no <literal>DEFAULT</literal> is specified, NULL is used. In
|
||||
neither case is a rewrite of the table required.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Adding a column with a <literal>DEFAULT</literal> clause or changing the type of
|
||||
an existing column will require the entire table and its indexes to be
|
||||
rewritten. As an exception when changing the type of an existing column,
|
||||
if the <literal>USING</literal> clause does not change the column
|
||||
contents and the old type is either binary coercible to the new type or
|
||||
an unconstrained domain over the new type, a table rewrite is not needed;
|
||||
but any indexes on the affected columns must still be rebuilt. Adding or
|
||||
removing a system <literal>oid</literal> column also requires rewriting the entire
|
||||
table. Table and/or index rebuilds may take a significant amount of time
|
||||
for a large table; and will temporarily require as much as double the disk
|
||||
space.
|
||||
Adding a column with a volatile <literal>DEFAULT</literal> or
|
||||
changing the type of an existing column will require the entire table and
|
||||
its indexes to be rewritten. As an exception, when changing the type of an
|
||||
existing column, if the <literal>USING</literal> clause does not change
|
||||
the column contents and the old type is either binary coercible to the new
|
||||
type or an unconstrained domain over the new type, a table rewrite is not
|
||||
needed; but any indexes on the affected columns must still be rebuilt.
|
||||
Adding or removing a system <literal>oid</literal> column also requires
|
||||
rewriting the entire table. Table and/or index rebuilds may take a
|
||||
significant amount of time for a large table; and will temporarily require
|
||||
as much as double the disk space.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
@ -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 */
|
||||
|
@ -799,7 +799,7 @@ extern void heap_fill_tuple(TupleDesc tupleDesc,
|
||||
Datum *values, bool *isnull,
|
||||
char *data, Size data_size,
|
||||
uint16 *infomask, bits8 *bit);
|
||||
extern bool heap_attisnull(HeapTuple tup, int attnum);
|
||||
extern bool heap_attisnull(HeapTuple tup, int attnum, TupleDesc tupleDesc);
|
||||
extern Datum nocachegetattr(HeapTuple tup, int attnum,
|
||||
TupleDesc att);
|
||||
extern Datum heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc,
|
||||
@ -830,5 +830,7 @@ extern MinimalTuple heap_copy_minimal_tuple(MinimalTuple mtup);
|
||||
extern HeapTuple heap_tuple_from_minimal_tuple(MinimalTuple mtup);
|
||||
extern MinimalTuple minimal_tuple_from_heap_tuple(HeapTuple htup);
|
||||
extern size_t varsize_any(void *p);
|
||||
extern HeapTuple heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
|
||||
extern MinimalTuple minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc);
|
||||
|
||||
#endif /* HTUP_DETAILS_H */
|
||||
|
@ -25,6 +25,8 @@ typedef struct attrDefault
|
||||
char *adbin; /* nodeToString representation of expr */
|
||||
} AttrDefault;
|
||||
|
||||
typedef struct attrMissing *MissingPtr;
|
||||
|
||||
typedef struct constrCheck
|
||||
{
|
||||
char *ccname;
|
||||
@ -38,6 +40,7 @@ typedef struct tupleConstr
|
||||
{
|
||||
AttrDefault *defval; /* array */
|
||||
ConstrCheck *check; /* array */
|
||||
MissingPtr missing; /* missing attributes values, NULL if none */
|
||||
uint16 num_defval;
|
||||
uint16 num_check;
|
||||
bool has_not_null;
|
||||
|
29
src/include/access/tupdesc_details.h
Normal file
29
src/include/access/tupdesc_details.h
Normal file
@ -0,0 +1,29 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
*
|
||||
* tupdesc_details.h
|
||||
* POSTGRES tuple descriptor definitions we can't include everywhere
|
||||
*
|
||||
*
|
||||
* Portions Copyright (c) 1996-2018, PostgreSQL Global Development Group
|
||||
* Portions Copyright (c) 1994, Regents of the University of California
|
||||
*
|
||||
* src/include/access/tupdesc_details.h
|
||||
*
|
||||
*-------------------------------------------------------------------------
|
||||
*/
|
||||
|
||||
#ifndef TUPDESC_DETAILS_H
|
||||
#define TUPDESC_DETAILS_H
|
||||
|
||||
/*
|
||||
* Structure used to represent value to be used when the attribute is not
|
||||
* present at all in a tuple, i.e. when the column was created after the tuple
|
||||
*/
|
||||
|
||||
typedef struct attrMissing
|
||||
{
|
||||
bool ammissingPresent; /* true if non-NULL missing value exists */
|
||||
Datum ammissing; /* value when attribute is missing */
|
||||
} AttrMissing;
|
||||
|
||||
#endif /* TUPDESC_DETAILS_H */
|
@ -23,6 +23,7 @@ typedef struct RawColumnDefault
|
||||
{
|
||||
AttrNumber attnum; /* attribute to attach default to */
|
||||
Node *raw_default; /* default value (untransformed parse tree) */
|
||||
bool missingMode; /* true if part of add column processing */
|
||||
} RawColumnDefault;
|
||||
|
||||
typedef struct CookedConstraint
|
||||
@ -103,8 +104,11 @@ extern List *AddRelationNewConstraints(Relation rel,
|
||||
bool is_local,
|
||||
bool is_internal);
|
||||
|
||||
extern void RelationClearMissing(Relation rel);
|
||||
|
||||
extern Oid StoreAttrDefault(Relation rel, AttrNumber attnum,
|
||||
Node *expr, bool is_internal);
|
||||
Node *expr, bool is_internal,
|
||||
bool add_column_mode);
|
||||
|
||||
extern Node *cookDefault(ParseState *pstate,
|
||||
Node *raw_default,
|
||||
|
@ -133,6 +133,9 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
|
||||
/* Has DEFAULT value or not */
|
||||
bool atthasdef BKI_DEFAULT(f);
|
||||
|
||||
/* Has a missing value or not */
|
||||
bool atthasmissing BKI_DEFAULT(f);
|
||||
|
||||
/* One of the ATTRIBUTE_IDENTITY_* constants below, or '\0' */
|
||||
char attidentity BKI_DEFAULT("");
|
||||
|
||||
@ -167,6 +170,12 @@ CATALOG(pg_attribute,1249) BKI_BOOTSTRAP BKI_WITHOUT_OIDS BKI_ROWTYPE_OID(75) BK
|
||||
|
||||
/* Column-level FDW options */
|
||||
text attfdwoptions[1] BKI_DEFAULT(_null_);
|
||||
|
||||
/*
|
||||
* Missing value for added columns. This is a one element array which lets
|
||||
* us store a value of the attribute type here.
|
||||
*/
|
||||
anyarray attmissingval BKI_DEFAULT(_null_);
|
||||
#endif
|
||||
} FormData_pg_attribute;
|
||||
|
||||
@ -191,7 +200,7 @@ typedef FormData_pg_attribute *Form_pg_attribute;
|
||||
* ----------------
|
||||
*/
|
||||
|
||||
#define Natts_pg_attribute 22
|
||||
#define Natts_pg_attribute 24
|
||||
#define Anum_pg_attribute_attrelid 1
|
||||
#define Anum_pg_attribute_attname 2
|
||||
#define Anum_pg_attribute_atttypid 3
|
||||
@ -206,15 +215,16 @@ typedef FormData_pg_attribute *Form_pg_attribute;
|
||||
#define Anum_pg_attribute_attalign 12
|
||||
#define Anum_pg_attribute_attnotnull 13
|
||||
#define Anum_pg_attribute_atthasdef 14
|
||||
#define Anum_pg_attribute_attidentity 15
|
||||
#define Anum_pg_attribute_attisdropped 16
|
||||
#define Anum_pg_attribute_attislocal 17
|
||||
#define Anum_pg_attribute_attinhcount 18
|
||||
#define Anum_pg_attribute_attcollation 19
|
||||
#define Anum_pg_attribute_attacl 20
|
||||
#define Anum_pg_attribute_attoptions 21
|
||||
#define Anum_pg_attribute_attfdwoptions 22
|
||||
|
||||
#define Anum_pg_attribute_atthasmissing 15
|
||||
#define Anum_pg_attribute_attidentity 16
|
||||
#define Anum_pg_attribute_attisdropped 17
|
||||
#define Anum_pg_attribute_attislocal 18
|
||||
#define Anum_pg_attribute_attinhcount 19
|
||||
#define Anum_pg_attribute_attcollation 20
|
||||
#define Anum_pg_attribute_attacl 21
|
||||
#define Anum_pg_attribute_attoptions 22
|
||||
#define Anum_pg_attribute_attfdwoptions 23
|
||||
#define Anum_pg_attribute_attmissingval 24
|
||||
|
||||
/* ----------------
|
||||
* initial contents of pg_attribute
|
||||
|
@ -149,7 +149,7 @@ typedef FormData_pg_class *Form_pg_class;
|
||||
*/
|
||||
DATA(insert OID = 1247 ( pg_type PGNSP 71 0 PGUID 0 0 0 0 0 0 0 f f p r 30 0 t f f f f f t n f 0 3 1 _null_ _null_ _null_));
|
||||
DESCR("");
|
||||
DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 22 0 f f f f f f t n f 0 3 1 _null_ _null_ _null_));
|
||||
DATA(insert OID = 1249 ( pg_attribute PGNSP 75 0 PGUID 0 0 0 0 0 0 0 f f p r 24 0 f f f f f f t n f 0 3 1 _null_ _null_ _null_));
|
||||
DESCR("");
|
||||
DATA(insert OID = 1255 ( pg_proc PGNSP 81 0 PGUID 0 0 0 0 0 0 0 f f p r 28 0 t f f f f f t n f 0 3 1 _null_ _null_ _null_));
|
||||
DESCR("");
|
||||
|
@ -389,8 +389,6 @@ alter table rewriteme alter column foo type numeric;
|
||||
ERROR: rewrites not allowed
|
||||
CONTEXT: PL/pgSQL function test_evtrig_no_rewrite() line 3 at RAISE
|
||||
alter table rewriteme add column baz int default 0;
|
||||
ERROR: rewrites not allowed
|
||||
CONTEXT: PL/pgSQL function test_evtrig_no_rewrite() line 3 at RAISE
|
||||
-- test with more than one reason to rewrite a single table
|
||||
CREATE OR REPLACE FUNCTION test_evtrig_no_rewrite() RETURNS event_trigger
|
||||
LANGUAGE plpgsql AS $$
|
||||
@ -404,7 +402,7 @@ alter table rewriteme
|
||||
add column onemore int default 0,
|
||||
add column another int default -1,
|
||||
alter column foo type numeric(10,4);
|
||||
NOTICE: Table 'rewriteme' is being rewritten (reason = 6)
|
||||
NOTICE: Table 'rewriteme' is being rewritten (reason = 4)
|
||||
-- shouldn't trigger a table_rewrite event
|
||||
alter table rewriteme alter column foo type numeric(12,4);
|
||||
-- typed tables are rewritten when their type changes. Don't emit table
|
||||
|
515
src/test/regress/expected/fast_default.out
Normal file
515
src/test/regress/expected/fast_default.out
Normal file
@ -0,0 +1,515 @@
|
||||
--
|
||||
-- ALTER TABLE ADD COLUMN DEFAULT test
|
||||
--
|
||||
SET search_path = fast_default;
|
||||
CREATE SCHEMA fast_default;
|
||||
CREATE TABLE m(id OID);
|
||||
INSERT INTO m VALUES (NULL::OID);
|
||||
CREATE FUNCTION set(tabname name) RETURNS VOID
|
||||
AS $$
|
||||
BEGIN
|
||||
UPDATE m
|
||||
SET id = (SELECT c.relfilenode
|
||||
FROM pg_class AS c, pg_namespace AS s
|
||||
WHERE c.relname = tabname
|
||||
AND c.relnamespace = s.oid
|
||||
AND s.nspname = 'fast_default');
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
CREATE FUNCTION comp() RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN (SELECT CASE
|
||||
WHEN m.id = c.relfilenode THEN 'Unchanged'
|
||||
ELSE 'Rewritten'
|
||||
END
|
||||
FROM m, pg_class AS c, pg_namespace AS s
|
||||
WHERE c.relname = 't'
|
||||
AND c.relnamespace = s.oid
|
||||
AND s.nspname = 'fast_default');
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
CREATE FUNCTION log_rewrite() RETURNS event_trigger
|
||||
LANGUAGE plpgsql as
|
||||
$func$
|
||||
|
||||
declare
|
||||
this_schema text;
|
||||
begin
|
||||
select into this_schema relnamespace::regnamespace::text
|
||||
from pg_class
|
||||
where oid = pg_event_trigger_table_rewrite_oid();
|
||||
if this_schema = 'fast_default'
|
||||
then
|
||||
RAISE NOTICE 'rewriting table % for reason %',
|
||||
pg_event_trigger_table_rewrite_oid()::regclass,
|
||||
pg_event_trigger_table_rewrite_reason();
|
||||
end if;
|
||||
end;
|
||||
$func$;
|
||||
CREATE TABLE has_volatile AS
|
||||
SELECT * FROM generate_series(1,10) id;
|
||||
CREATE EVENT TRIGGER has_volatile_rewrite
|
||||
ON table_rewrite
|
||||
EXECUTE PROCEDURE log_rewrite();
|
||||
-- only the last of these should trigger a rewrite
|
||||
ALTER TABLE has_volatile ADD col1 int;
|
||||
ALTER TABLE has_volatile ADD col2 int DEFAULT 1;
|
||||
ALTER TABLE has_volatile ADD col3 timestamptz DEFAULT current_timestamp;
|
||||
ALTER TABLE has_volatile ADD col4 int DEFAULT (random() * 10000)::int;
|
||||
NOTICE: rewriting table has_volatile for reason 2
|
||||
-- Test a large sample of different datatypes
|
||||
CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1);
|
||||
SELECT set('t');
|
||||
set
|
||||
-----
|
||||
|
||||
(1 row)
|
||||
|
||||
INSERT INTO T VALUES (1), (2);
|
||||
ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT 'hello',
|
||||
ALTER COLUMN c_int SET DEFAULT 2;
|
||||
INSERT INTO T VALUES (3), (4);
|
||||
ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'world',
|
||||
ALTER COLUMN c_bpchar SET DEFAULT 'dog';
|
||||
INSERT INTO T VALUES (5), (6);
|
||||
ALTER TABLE T ADD COLUMN c_date DATE DEFAULT '2016-06-02',
|
||||
ALTER COLUMN c_text SET DEFAULT 'cat';
|
||||
INSERT INTO T VALUES (7), (8);
|
||||
ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP DEFAULT '2016-09-01 12:00:00',
|
||||
ADD COLUMN c_timestamp_null TIMESTAMP,
|
||||
ALTER COLUMN c_date SET DEFAULT '2010-01-01';
|
||||
INSERT INTO T VALUES (9), (10);
|
||||
ALTER TABLE T ADD COLUMN c_array TEXT[]
|
||||
DEFAULT '{"This", "is", "the", "real", "world"}',
|
||||
ALTER COLUMN c_timestamp SET DEFAULT '1970-12-31 11:12:13',
|
||||
ALTER COLUMN c_timestamp_null SET DEFAULT '2016-09-29 12:00:00';
|
||||
INSERT INTO T VALUES (11), (12);
|
||||
ALTER TABLE T ADD COLUMN c_small SMALLINT DEFAULT -5,
|
||||
ADD COLUMN c_small_null SMALLINT,
|
||||
ALTER COLUMN c_array
|
||||
SET DEFAULT '{"This", "is", "no", "fantasy"}';
|
||||
INSERT INTO T VALUES (13), (14);
|
||||
ALTER TABLE T ADD COLUMN c_big BIGINT DEFAULT 180000000000018,
|
||||
ALTER COLUMN c_small SET DEFAULT 9,
|
||||
ALTER COLUMN c_small_null SET DEFAULT 13;
|
||||
INSERT INTO T VALUES (15), (16);
|
||||
ALTER TABLE T ADD COLUMN c_num NUMERIC DEFAULT 1.00000000001,
|
||||
ALTER COLUMN c_big SET DEFAULT -9999999999999999;
|
||||
INSERT INTO T VALUES (17), (18);
|
||||
ALTER TABLE T ADD COLUMN c_time TIME DEFAULT '12:00:00',
|
||||
ALTER COLUMN c_num SET DEFAULT 2.000000000000002;
|
||||
INSERT INTO T VALUES (19), (20);
|
||||
ALTER TABLE T ADD COLUMN c_interval INTERVAL DEFAULT '1 day',
|
||||
ALTER COLUMN c_time SET DEFAULT '23:59:59';
|
||||
INSERT INTO T VALUES (21), (22);
|
||||
ALTER TABLE T ADD COLUMN c_hugetext TEXT DEFAULT repeat('abcdefg',1000),
|
||||
ALTER COLUMN c_interval SET DEFAULT '3 hours';
|
||||
INSERT INTO T VALUES (23), (24);
|
||||
ALTER TABLE T ALTER COLUMN c_interval DROP DEFAULT,
|
||||
ALTER COLUMN c_hugetext SET DEFAULT repeat('poiuyt', 1000);
|
||||
INSERT INTO T VALUES (25), (26);
|
||||
ALTER TABLE T ALTER COLUMN c_bpchar DROP DEFAULT,
|
||||
ALTER COLUMN c_date DROP DEFAULT,
|
||||
ALTER COLUMN c_text DROP DEFAULT,
|
||||
ALTER COLUMN c_timestamp DROP DEFAULT,
|
||||
ALTER COLUMN c_array DROP DEFAULT,
|
||||
ALTER COLUMN c_small DROP DEFAULT,
|
||||
ALTER COLUMN c_big DROP DEFAULT,
|
||||
ALTER COLUMN c_num DROP DEFAULT,
|
||||
ALTER COLUMN c_time DROP DEFAULT,
|
||||
ALTER COLUMN c_hugetext DROP DEFAULT;
|
||||
INSERT INTO T VALUES (27), (28);
|
||||
SELECT pk, c_int, c_bpchar, c_text, c_date, c_timestamp,
|
||||
c_timestamp_null, c_array, c_small, c_small_null,
|
||||
c_big, c_num, c_time, c_interval,
|
||||
c_hugetext = repeat('abcdefg',1000) as c_hugetext_origdef,
|
||||
c_hugetext = repeat('poiuyt', 1000) as c_hugetext_newdef
|
||||
FROM T ORDER BY pk;
|
||||
pk | c_int | c_bpchar | c_text | c_date | c_timestamp | c_timestamp_null | c_array | c_small | c_small_null | c_big | c_num | c_time | c_interval | c_hugetext_origdef | c_hugetext_newdef
|
||||
----+-------+----------+--------+------------+--------------------------+--------------------------+--------------------------+---------+--------------+-------------------+-------------------+----------+------------+--------------------+-------------------
|
||||
1 | 1 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
2 | 1 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
3 | 2 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
4 | 2 | hello | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
5 | 2 | dog | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
6 | 2 | dog | world | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
7 | 2 | dog | cat | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
8 | 2 | dog | cat | 06-02-2016 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
9 | 2 | dog | cat | 01-01-2010 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
10 | 2 | dog | cat | 01-01-2010 | Thu Sep 01 12:00:00 2016 | | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
11 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
12 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,the,real,world} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
13 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
14 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | -5 | | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
15 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
16 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | 180000000000018 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
17 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
18 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 1.00000000001 | 12:00:00 | @ 1 day | t | f
|
||||
19 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 12:00:00 | @ 1 day | t | f
|
||||
20 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 12:00:00 | @ 1 day | t | f
|
||||
21 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 1 day | t | f
|
||||
22 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 1 day | t | f
|
||||
23 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 3 hours | t | f
|
||||
24 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | @ 3 hours | t | f
|
||||
25 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | | f | t
|
||||
26 | 2 | dog | cat | 01-01-2010 | Thu Dec 31 11:12:13 1970 | Thu Sep 29 12:00:00 2016 | {This,is,no,fantasy} | 9 | 13 | -9999999999999999 | 2.000000000000002 | 23:59:59 | | f | t
|
||||
27 | 2 | | | | | Thu Sep 29 12:00:00 2016 | | | 13 | | | | | |
|
||||
28 | 2 | | | | | Thu Sep 29 12:00:00 2016 | | | 13 | | | | | |
|
||||
(28 rows)
|
||||
|
||||
SELECT comp();
|
||||
comp
|
||||
-----------
|
||||
Unchanged
|
||||
(1 row)
|
||||
|
||||
DROP TABLE T;
|
||||
-- Test expressions in the defaults
|
||||
CREATE OR REPLACE FUNCTION foo(a INT) RETURNS TEXT AS $$
|
||||
DECLARE res TEXT := '';
|
||||
i INT;
|
||||
BEGIN
|
||||
i := 0;
|
||||
WHILE (i < a) LOOP
|
||||
res := res || chr(ascii('a') + i);
|
||||
i := i + 1;
|
||||
END LOOP;
|
||||
RETURN res;
|
||||
END; $$ LANGUAGE PLPGSQL STABLE;
|
||||
CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT LENGTH(foo(6)));
|
||||
SELECT set('t');
|
||||
set
|
||||
-----
|
||||
|
||||
(1 row)
|
||||
|
||||
INSERT INTO T VALUES (1), (2);
|
||||
ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT foo(4),
|
||||
ALTER COLUMN c_int SET DEFAULT LENGTH(foo(8));
|
||||
INSERT INTO T VALUES (3), (4);
|
||||
ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT foo(6),
|
||||
ALTER COLUMN c_bpchar SET DEFAULT foo(3);
|
||||
INSERT INTO T VALUES (5), (6);
|
||||
ALTER TABLE T ADD COLUMN c_date DATE
|
||||
DEFAULT '2016-06-02'::DATE + LENGTH(foo(10)),
|
||||
ALTER COLUMN c_text SET DEFAULT foo(12);
|
||||
INSERT INTO T VALUES (7), (8);
|
||||
ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP
|
||||
DEFAULT '2016-09-01'::DATE + LENGTH(foo(10)),
|
||||
ALTER COLUMN c_date
|
||||
SET DEFAULT '2010-01-01'::DATE - LENGTH(foo(4));
|
||||
INSERT INTO T VALUES (9), (10);
|
||||
ALTER TABLE T ADD COLUMN c_array TEXT[]
|
||||
DEFAULT ('{"This", "is", "' || foo(4) ||
|
||||
'","the", "real", "world"}')::TEXT[],
|
||||
ALTER COLUMN c_timestamp
|
||||
SET DEFAULT '1970-12-31'::DATE + LENGTH(foo(30));
|
||||
INSERT INTO T VALUES (11), (12);
|
||||
ALTER TABLE T ALTER COLUMN c_int DROP DEFAULT,
|
||||
ALTER COLUMN c_array
|
||||
SET DEFAULT ('{"This", "is", "' || foo(1) ||
|
||||
'", "fantasy"}')::text[];
|
||||
INSERT INTO T VALUES (13), (14);
|
||||
ALTER TABLE T ALTER COLUMN c_bpchar DROP DEFAULT,
|
||||
ALTER COLUMN c_date DROP DEFAULT,
|
||||
ALTER COLUMN c_text DROP DEFAULT,
|
||||
ALTER COLUMN c_timestamp DROP DEFAULT,
|
||||
ALTER COLUMN c_array DROP DEFAULT;
|
||||
INSERT INTO T VALUES (15), (16);
|
||||
SELECT * FROM T;
|
||||
pk | c_int | c_bpchar | c_text | c_date | c_timestamp | c_array
|
||||
----+-------+----------+--------------+------------+--------------------------+-------------------------------
|
||||
1 | 6 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
|
||||
2 | 6 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
|
||||
3 | 8 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
|
||||
4 | 8 | abcd | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
|
||||
5 | 8 | abc | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
|
||||
6 | 8 | abc | abcdef | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
|
||||
7 | 8 | abc | abcdefghijkl | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
|
||||
8 | 8 | abc | abcdefghijkl | 06-12-2016 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
|
||||
9 | 8 | abc | abcdefghijkl | 12-28-2009 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
|
||||
10 | 8 | abc | abcdefghijkl | 12-28-2009 | Sun Sep 11 00:00:00 2016 | {This,is,abcd,the,real,world}
|
||||
11 | 8 | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,abcd,the,real,world}
|
||||
12 | 8 | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,abcd,the,real,world}
|
||||
13 | | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,a,fantasy}
|
||||
14 | | abc | abcdefghijkl | 12-28-2009 | Sat Jan 30 00:00:00 1971 | {This,is,a,fantasy}
|
||||
15 | | | | | |
|
||||
16 | | | | | |
|
||||
(16 rows)
|
||||
|
||||
SELECT comp();
|
||||
comp
|
||||
-----------
|
||||
Unchanged
|
||||
(1 row)
|
||||
|
||||
DROP TABLE T;
|
||||
DROP FUNCTION foo(INT);
|
||||
-- Fall back to full rewrite for volatile expressions
|
||||
CREATE TABLE T(pk INT NOT NULL PRIMARY KEY);
|
||||
INSERT INTO T VALUES (1);
|
||||
SELECT set('t');
|
||||
set
|
||||
-----
|
||||
|
||||
(1 row)
|
||||
|
||||
-- now() is stable, because it returns the transaction timestamp
|
||||
ALTER TABLE T ADD COLUMN c1 TIMESTAMP DEFAULT now();
|
||||
SELECT comp();
|
||||
comp
|
||||
-----------
|
||||
Unchanged
|
||||
(1 row)
|
||||
|
||||
-- clock_timestamp() is volatile
|
||||
ALTER TABLE T ADD COLUMN c2 TIMESTAMP DEFAULT clock_timestamp();
|
||||
NOTICE: rewriting table t for reason 2
|
||||
SELECT comp();
|
||||
comp
|
||||
-----------
|
||||
Rewritten
|
||||
(1 row)
|
||||
|
||||
DROP TABLE T;
|
||||
-- Simple querie
|
||||
CREATE TABLE T (pk INT NOT NULL PRIMARY KEY);
|
||||
SELECT set('t');
|
||||
set
|
||||
-----
|
||||
|
||||
(1 row)
|
||||
|
||||
INSERT INTO T SELECT * FROM generate_series(1, 10) a;
|
||||
ALTER TABLE T ADD COLUMN c_bigint BIGINT NOT NULL DEFAULT -1;
|
||||
INSERT INTO T SELECT b, b - 10 FROM generate_series(11, 20) a(b);
|
||||
ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'hello';
|
||||
INSERT INTO T SELECT b, b - 10, (b + 10)::text FROM generate_series(21, 30) a(b);
|
||||
-- WHERE clause
|
||||
SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1;
|
||||
c_bigint | c_text
|
||||
----------+--------
|
||||
-1 | hello
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (VERBOSE TRUE, COSTS FALSE)
|
||||
SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1;
|
||||
QUERY PLAN
|
||||
----------------------------------------------
|
||||
Limit
|
||||
Output: c_bigint, c_text
|
||||
-> Seq Scan on fast_default.t
|
||||
Output: c_bigint, c_text
|
||||
Filter: (t.c_bigint = '-1'::integer)
|
||||
(5 rows)
|
||||
|
||||
SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1;
|
||||
c_bigint | c_text
|
||||
----------+--------
|
||||
-1 | hello
|
||||
(1 row)
|
||||
|
||||
EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1;
|
||||
QUERY PLAN
|
||||
--------------------------------------------
|
||||
Limit
|
||||
Output: c_bigint, c_text
|
||||
-> Seq Scan on fast_default.t
|
||||
Output: c_bigint, c_text
|
||||
Filter: (t.c_text = 'hello'::text)
|
||||
(5 rows)
|
||||
|
||||
-- COALESCE
|
||||
SELECT COALESCE(c_bigint, pk), COALESCE(c_text, pk::text)
|
||||
FROM T
|
||||
ORDER BY pk LIMIT 10;
|
||||
coalesce | coalesce
|
||||
----------+----------
|
||||
-1 | hello
|
||||
-1 | hello
|
||||
-1 | hello
|
||||
-1 | hello
|
||||
-1 | hello
|
||||
-1 | hello
|
||||
-1 | hello
|
||||
-1 | hello
|
||||
-1 | hello
|
||||
-1 | hello
|
||||
(10 rows)
|
||||
|
||||
-- Aggregate function
|
||||
SELECT SUM(c_bigint), MAX(c_text), MIN(c_text) FROM T;
|
||||
sum | max | min
|
||||
-----+-------+-----
|
||||
200 | hello | 31
|
||||
(1 row)
|
||||
|
||||
-- ORDER BY
|
||||
SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10;
|
||||
pk | c_bigint | c_text
|
||||
----+----------+--------
|
||||
1 | -1 | hello
|
||||
2 | -1 | hello
|
||||
3 | -1 | hello
|
||||
4 | -1 | hello
|
||||
5 | -1 | hello
|
||||
6 | -1 | hello
|
||||
7 | -1 | hello
|
||||
8 | -1 | hello
|
||||
9 | -1 | hello
|
||||
10 | -1 | hello
|
||||
(10 rows)
|
||||
|
||||
EXPLAIN (VERBOSE TRUE, COSTS FALSE)
|
||||
SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10;
|
||||
QUERY PLAN
|
||||
----------------------------------------------
|
||||
Limit
|
||||
Output: pk, c_bigint, c_text
|
||||
-> Sort
|
||||
Output: pk, c_bigint, c_text
|
||||
Sort Key: t.c_bigint, t.c_text, t.pk
|
||||
-> Seq Scan on fast_default.t
|
||||
Output: pk, c_bigint, c_text
|
||||
(7 rows)
|
||||
|
||||
-- LIMIT
|
||||
SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10;
|
||||
pk | c_bigint | c_text
|
||||
----+----------+--------
|
||||
11 | 1 | hello
|
||||
12 | 2 | hello
|
||||
13 | 3 | hello
|
||||
14 | 4 | hello
|
||||
15 | 5 | hello
|
||||
16 | 6 | hello
|
||||
17 | 7 | hello
|
||||
18 | 8 | hello
|
||||
19 | 9 | hello
|
||||
20 | 10 | hello
|
||||
(10 rows)
|
||||
|
||||
EXPLAIN (VERBOSE TRUE, COSTS FALSE)
|
||||
SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10;
|
||||
QUERY PLAN
|
||||
----------------------------------------------------
|
||||
Limit
|
||||
Output: pk, c_bigint, c_text
|
||||
-> Sort
|
||||
Output: pk, c_bigint, c_text
|
||||
Sort Key: t.c_bigint, t.c_text, t.pk
|
||||
-> Seq Scan on fast_default.t
|
||||
Output: pk, c_bigint, c_text
|
||||
Filter: (t.c_bigint > '-1'::integer)
|
||||
(8 rows)
|
||||
|
||||
-- DELETE with RETURNING
|
||||
DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *;
|
||||
pk | c_bigint | c_text
|
||||
----+----------+--------
|
||||
10 | -1 | hello
|
||||
11 | 1 | hello
|
||||
12 | 2 | hello
|
||||
13 | 3 | hello
|
||||
14 | 4 | hello
|
||||
15 | 5 | hello
|
||||
16 | 6 | hello
|
||||
17 | 7 | hello
|
||||
18 | 8 | hello
|
||||
19 | 9 | hello
|
||||
20 | 10 | hello
|
||||
(11 rows)
|
||||
|
||||
EXPLAIN (VERBOSE TRUE, COSTS FALSE)
|
||||
DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *;
|
||||
QUERY PLAN
|
||||
-----------------------------------------------------------
|
||||
Delete on fast_default.t
|
||||
Output: pk, c_bigint, c_text
|
||||
-> Bitmap Heap Scan on fast_default.t
|
||||
Output: ctid
|
||||
Recheck Cond: ((t.pk >= 10) AND (t.pk <= 20))
|
||||
-> Bitmap Index Scan on t_pkey
|
||||
Index Cond: ((t.pk >= 10) AND (t.pk <= 20))
|
||||
(7 rows)
|
||||
|
||||
-- UPDATE
|
||||
UPDATE T SET c_text = '"' || c_text || '"' WHERE pk < 10;
|
||||
SELECT * FROM T WHERE c_text LIKE '"%"' ORDER BY PK;
|
||||
pk | c_bigint | c_text
|
||||
----+----------+---------
|
||||
1 | -1 | "hello"
|
||||
2 | -1 | "hello"
|
||||
3 | -1 | "hello"
|
||||
4 | -1 | "hello"
|
||||
5 | -1 | "hello"
|
||||
6 | -1 | "hello"
|
||||
7 | -1 | "hello"
|
||||
8 | -1 | "hello"
|
||||
9 | -1 | "hello"
|
||||
(9 rows)
|
||||
|
||||
SELECT comp();
|
||||
comp
|
||||
-----------
|
||||
Unchanged
|
||||
(1 row)
|
||||
|
||||
DROP TABLE T;
|
||||
-- Combine with other DDL
|
||||
CREATE TABLE T(pk INT NOT NULL PRIMARY KEY);
|
||||
SELECT set('t');
|
||||
set
|
||||
-----
|
||||
|
||||
(1 row)
|
||||
|
||||
INSERT INTO T VALUES (1), (2);
|
||||
ALTER TABLE T ADD COLUMN c_int INT NOT NULL DEFAULT -1;
|
||||
INSERT INTO T VALUES (3), (4);
|
||||
ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'Hello';
|
||||
INSERT INTO T VALUES (5), (6);
|
||||
ALTER TABLE T ALTER COLUMN c_text SET DEFAULT 'world',
|
||||
ALTER COLUMN c_int SET DEFAULT 1;
|
||||
INSERT INTO T VALUES (7), (8);
|
||||
SELECT * FROM T ORDER BY pk;
|
||||
pk | c_int | c_text
|
||||
----+-------+--------
|
||||
1 | -1 | Hello
|
||||
2 | -1 | Hello
|
||||
3 | -1 | Hello
|
||||
4 | -1 | Hello
|
||||
5 | -1 | Hello
|
||||
6 | -1 | Hello
|
||||
7 | 1 | world
|
||||
8 | 1 | world
|
||||
(8 rows)
|
||||
|
||||
-- Add an index
|
||||
CREATE INDEX i ON T(c_int, c_text);
|
||||
SELECT c_text FROM T WHERE c_int = -1;
|
||||
c_text
|
||||
--------
|
||||
Hello
|
||||
Hello
|
||||
Hello
|
||||
Hello
|
||||
Hello
|
||||
Hello
|
||||
(6 rows)
|
||||
|
||||
SELECT comp();
|
||||
comp
|
||||
-----------
|
||||
Unchanged
|
||||
(1 row)
|
||||
|
||||
DROP TABLE T;
|
||||
DROP FUNCTION set(name);
|
||||
DROP FUNCTION comp();
|
||||
DROP TABLE m;
|
||||
DROP TABLE has_volatile;
|
||||
DROP EVENT TRIGGER has_volatile_rewrite;
|
||||
DROP FUNCTION log_rewrite;
|
||||
DROP SCHEMA fast_default;
|
@ -116,7 +116,7 @@ test: plancache limit plpgsql copy2 temp domain rangefuncs prepare without_oid c
|
||||
# ----------
|
||||
# Another group of parallel tests
|
||||
# ----------
|
||||
test: identity partition_join partition_prune reloptions hash_part indexing partition_aggregate
|
||||
test: identity partition_join partition_prune reloptions hash_part indexing partition_aggregate fast_default
|
||||
|
||||
# event triggers cannot run concurrently with any test that runs DDL
|
||||
test: event_trigger
|
||||
|
@ -187,5 +187,6 @@ test: reloptions
|
||||
test: hash_part
|
||||
test: indexing
|
||||
test: partition_aggregate
|
||||
test: fast_default
|
||||
test: event_trigger
|
||||
test: stats
|
||||
|
357
src/test/regress/sql/fast_default.sql
Normal file
357
src/test/regress/sql/fast_default.sql
Normal file
@ -0,0 +1,357 @@
|
||||
--
|
||||
-- ALTER TABLE ADD COLUMN DEFAULT test
|
||||
--
|
||||
|
||||
SET search_path = fast_default;
|
||||
CREATE SCHEMA fast_default;
|
||||
CREATE TABLE m(id OID);
|
||||
INSERT INTO m VALUES (NULL::OID);
|
||||
|
||||
CREATE FUNCTION set(tabname name) RETURNS VOID
|
||||
AS $$
|
||||
BEGIN
|
||||
UPDATE m
|
||||
SET id = (SELECT c.relfilenode
|
||||
FROM pg_class AS c, pg_namespace AS s
|
||||
WHERE c.relname = tabname
|
||||
AND c.relnamespace = s.oid
|
||||
AND s.nspname = 'fast_default');
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
|
||||
CREATE FUNCTION comp() RETURNS TEXT
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN (SELECT CASE
|
||||
WHEN m.id = c.relfilenode THEN 'Unchanged'
|
||||
ELSE 'Rewritten'
|
||||
END
|
||||
FROM m, pg_class AS c, pg_namespace AS s
|
||||
WHERE c.relname = 't'
|
||||
AND c.relnamespace = s.oid
|
||||
AND s.nspname = 'fast_default');
|
||||
END;
|
||||
$$ LANGUAGE 'plpgsql';
|
||||
|
||||
CREATE FUNCTION log_rewrite() RETURNS event_trigger
|
||||
LANGUAGE plpgsql as
|
||||
$func$
|
||||
|
||||
declare
|
||||
this_schema text;
|
||||
begin
|
||||
select into this_schema relnamespace::regnamespace::text
|
||||
from pg_class
|
||||
where oid = pg_event_trigger_table_rewrite_oid();
|
||||
if this_schema = 'fast_default'
|
||||
then
|
||||
RAISE NOTICE 'rewriting table % for reason %',
|
||||
pg_event_trigger_table_rewrite_oid()::regclass,
|
||||
pg_event_trigger_table_rewrite_reason();
|
||||
end if;
|
||||
end;
|
||||
$func$;
|
||||
|
||||
CREATE TABLE has_volatile AS
|
||||
SELECT * FROM generate_series(1,10) id;
|
||||
|
||||
|
||||
CREATE EVENT TRIGGER has_volatile_rewrite
|
||||
ON table_rewrite
|
||||
EXECUTE PROCEDURE log_rewrite();
|
||||
|
||||
-- only the last of these should trigger a rewrite
|
||||
ALTER TABLE has_volatile ADD col1 int;
|
||||
ALTER TABLE has_volatile ADD col2 int DEFAULT 1;
|
||||
ALTER TABLE has_volatile ADD col3 timestamptz DEFAULT current_timestamp;
|
||||
ALTER TABLE has_volatile ADD col4 int DEFAULT (random() * 10000)::int;
|
||||
|
||||
|
||||
|
||||
-- Test a large sample of different datatypes
|
||||
CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT 1);
|
||||
|
||||
SELECT set('t');
|
||||
|
||||
INSERT INTO T VALUES (1), (2);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT 'hello',
|
||||
ALTER COLUMN c_int SET DEFAULT 2;
|
||||
|
||||
INSERT INTO T VALUES (3), (4);
|
||||
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'world',
|
||||
ALTER COLUMN c_bpchar SET DEFAULT 'dog';
|
||||
|
||||
INSERT INTO T VALUES (5), (6);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_date DATE DEFAULT '2016-06-02',
|
||||
ALTER COLUMN c_text SET DEFAULT 'cat';
|
||||
|
||||
INSERT INTO T VALUES (7), (8);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP DEFAULT '2016-09-01 12:00:00',
|
||||
ADD COLUMN c_timestamp_null TIMESTAMP,
|
||||
ALTER COLUMN c_date SET DEFAULT '2010-01-01';
|
||||
|
||||
INSERT INTO T VALUES (9), (10);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_array TEXT[]
|
||||
DEFAULT '{"This", "is", "the", "real", "world"}',
|
||||
ALTER COLUMN c_timestamp SET DEFAULT '1970-12-31 11:12:13',
|
||||
ALTER COLUMN c_timestamp_null SET DEFAULT '2016-09-29 12:00:00';
|
||||
|
||||
INSERT INTO T VALUES (11), (12);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_small SMALLINT DEFAULT -5,
|
||||
ADD COLUMN c_small_null SMALLINT,
|
||||
ALTER COLUMN c_array
|
||||
SET DEFAULT '{"This", "is", "no", "fantasy"}';
|
||||
|
||||
INSERT INTO T VALUES (13), (14);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_big BIGINT DEFAULT 180000000000018,
|
||||
ALTER COLUMN c_small SET DEFAULT 9,
|
||||
ALTER COLUMN c_small_null SET DEFAULT 13;
|
||||
|
||||
INSERT INTO T VALUES (15), (16);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_num NUMERIC DEFAULT 1.00000000001,
|
||||
ALTER COLUMN c_big SET DEFAULT -9999999999999999;
|
||||
|
||||
INSERT INTO T VALUES (17), (18);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_time TIME DEFAULT '12:00:00',
|
||||
ALTER COLUMN c_num SET DEFAULT 2.000000000000002;
|
||||
|
||||
INSERT INTO T VALUES (19), (20);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_interval INTERVAL DEFAULT '1 day',
|
||||
ALTER COLUMN c_time SET DEFAULT '23:59:59';
|
||||
|
||||
INSERT INTO T VALUES (21), (22);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_hugetext TEXT DEFAULT repeat('abcdefg',1000),
|
||||
ALTER COLUMN c_interval SET DEFAULT '3 hours';
|
||||
|
||||
INSERT INTO T VALUES (23), (24);
|
||||
|
||||
ALTER TABLE T ALTER COLUMN c_interval DROP DEFAULT,
|
||||
ALTER COLUMN c_hugetext SET DEFAULT repeat('poiuyt', 1000);
|
||||
|
||||
INSERT INTO T VALUES (25), (26);
|
||||
|
||||
ALTER TABLE T ALTER COLUMN c_bpchar DROP DEFAULT,
|
||||
ALTER COLUMN c_date DROP DEFAULT,
|
||||
ALTER COLUMN c_text DROP DEFAULT,
|
||||
ALTER COLUMN c_timestamp DROP DEFAULT,
|
||||
ALTER COLUMN c_array DROP DEFAULT,
|
||||
ALTER COLUMN c_small DROP DEFAULT,
|
||||
ALTER COLUMN c_big DROP DEFAULT,
|
||||
ALTER COLUMN c_num DROP DEFAULT,
|
||||
ALTER COLUMN c_time DROP DEFAULT,
|
||||
ALTER COLUMN c_hugetext DROP DEFAULT;
|
||||
|
||||
INSERT INTO T VALUES (27), (28);
|
||||
|
||||
SELECT pk, c_int, c_bpchar, c_text, c_date, c_timestamp,
|
||||
c_timestamp_null, c_array, c_small, c_small_null,
|
||||
c_big, c_num, c_time, c_interval,
|
||||
c_hugetext = repeat('abcdefg',1000) as c_hugetext_origdef,
|
||||
c_hugetext = repeat('poiuyt', 1000) as c_hugetext_newdef
|
||||
FROM T ORDER BY pk;
|
||||
|
||||
SELECT comp();
|
||||
|
||||
DROP TABLE T;
|
||||
|
||||
-- Test expressions in the defaults
|
||||
CREATE OR REPLACE FUNCTION foo(a INT) RETURNS TEXT AS $$
|
||||
DECLARE res TEXT := '';
|
||||
i INT;
|
||||
BEGIN
|
||||
i := 0;
|
||||
WHILE (i < a) LOOP
|
||||
res := res || chr(ascii('a') + i);
|
||||
i := i + 1;
|
||||
END LOOP;
|
||||
RETURN res;
|
||||
END; $$ LANGUAGE PLPGSQL STABLE;
|
||||
|
||||
CREATE TABLE T(pk INT NOT NULL PRIMARY KEY, c_int INT DEFAULT LENGTH(foo(6)));
|
||||
|
||||
SELECT set('t');
|
||||
|
||||
INSERT INTO T VALUES (1), (2);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_bpchar BPCHAR(5) DEFAULT foo(4),
|
||||
ALTER COLUMN c_int SET DEFAULT LENGTH(foo(8));
|
||||
|
||||
INSERT INTO T VALUES (3), (4);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT foo(6),
|
||||
ALTER COLUMN c_bpchar SET DEFAULT foo(3);
|
||||
|
||||
INSERT INTO T VALUES (5), (6);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_date DATE
|
||||
DEFAULT '2016-06-02'::DATE + LENGTH(foo(10)),
|
||||
ALTER COLUMN c_text SET DEFAULT foo(12);
|
||||
|
||||
INSERT INTO T VALUES (7), (8);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_timestamp TIMESTAMP
|
||||
DEFAULT '2016-09-01'::DATE + LENGTH(foo(10)),
|
||||
ALTER COLUMN c_date
|
||||
SET DEFAULT '2010-01-01'::DATE - LENGTH(foo(4));
|
||||
|
||||
INSERT INTO T VALUES (9), (10);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_array TEXT[]
|
||||
DEFAULT ('{"This", "is", "' || foo(4) ||
|
||||
'","the", "real", "world"}')::TEXT[],
|
||||
ALTER COLUMN c_timestamp
|
||||
SET DEFAULT '1970-12-31'::DATE + LENGTH(foo(30));
|
||||
|
||||
INSERT INTO T VALUES (11), (12);
|
||||
|
||||
ALTER TABLE T ALTER COLUMN c_int DROP DEFAULT,
|
||||
ALTER COLUMN c_array
|
||||
SET DEFAULT ('{"This", "is", "' || foo(1) ||
|
||||
'", "fantasy"}')::text[];
|
||||
|
||||
INSERT INTO T VALUES (13), (14);
|
||||
|
||||
ALTER TABLE T ALTER COLUMN c_bpchar DROP DEFAULT,
|
||||
ALTER COLUMN c_date DROP DEFAULT,
|
||||
ALTER COLUMN c_text DROP DEFAULT,
|
||||
ALTER COLUMN c_timestamp DROP DEFAULT,
|
||||
ALTER COLUMN c_array DROP DEFAULT;
|
||||
|
||||
INSERT INTO T VALUES (15), (16);
|
||||
|
||||
SELECT * FROM T;
|
||||
|
||||
SELECT comp();
|
||||
|
||||
DROP TABLE T;
|
||||
|
||||
DROP FUNCTION foo(INT);
|
||||
|
||||
-- Fall back to full rewrite for volatile expressions
|
||||
CREATE TABLE T(pk INT NOT NULL PRIMARY KEY);
|
||||
|
||||
INSERT INTO T VALUES (1);
|
||||
|
||||
SELECT set('t');
|
||||
|
||||
-- now() is stable, because it returns the transaction timestamp
|
||||
ALTER TABLE T ADD COLUMN c1 TIMESTAMP DEFAULT now();
|
||||
|
||||
SELECT comp();
|
||||
|
||||
-- clock_timestamp() is volatile
|
||||
ALTER TABLE T ADD COLUMN c2 TIMESTAMP DEFAULT clock_timestamp();
|
||||
|
||||
SELECT comp();
|
||||
|
||||
DROP TABLE T;
|
||||
|
||||
-- Simple querie
|
||||
CREATE TABLE T (pk INT NOT NULL PRIMARY KEY);
|
||||
|
||||
SELECT set('t');
|
||||
|
||||
INSERT INTO T SELECT * FROM generate_series(1, 10) a;
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_bigint BIGINT NOT NULL DEFAULT -1;
|
||||
|
||||
INSERT INTO T SELECT b, b - 10 FROM generate_series(11, 20) a(b);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'hello';
|
||||
|
||||
INSERT INTO T SELECT b, b - 10, (b + 10)::text FROM generate_series(21, 30) a(b);
|
||||
|
||||
-- WHERE clause
|
||||
SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1;
|
||||
|
||||
EXPLAIN (VERBOSE TRUE, COSTS FALSE)
|
||||
SELECT c_bigint, c_text FROM T WHERE c_bigint = -1 LIMIT 1;
|
||||
|
||||
SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1;
|
||||
|
||||
EXPLAIN (VERBOSE TRUE, COSTS FALSE) SELECT c_bigint, c_text FROM T WHERE c_text = 'hello' LIMIT 1;
|
||||
|
||||
|
||||
-- COALESCE
|
||||
SELECT COALESCE(c_bigint, pk), COALESCE(c_text, pk::text)
|
||||
FROM T
|
||||
ORDER BY pk LIMIT 10;
|
||||
|
||||
-- Aggregate function
|
||||
SELECT SUM(c_bigint), MAX(c_text), MIN(c_text) FROM T;
|
||||
|
||||
-- ORDER BY
|
||||
SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10;
|
||||
|
||||
EXPLAIN (VERBOSE TRUE, COSTS FALSE)
|
||||
SELECT * FROM T ORDER BY c_bigint, c_text, pk LIMIT 10;
|
||||
|
||||
-- LIMIT
|
||||
SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10;
|
||||
|
||||
EXPLAIN (VERBOSE TRUE, COSTS FALSE)
|
||||
SELECT * FROM T WHERE c_bigint > -1 ORDER BY c_bigint, c_text, pk LIMIT 10;
|
||||
|
||||
-- DELETE with RETURNING
|
||||
DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *;
|
||||
EXPLAIN (VERBOSE TRUE, COSTS FALSE)
|
||||
DELETE FROM T WHERE pk BETWEEN 10 AND 20 RETURNING *;
|
||||
|
||||
-- UPDATE
|
||||
UPDATE T SET c_text = '"' || c_text || '"' WHERE pk < 10;
|
||||
SELECT * FROM T WHERE c_text LIKE '"%"' ORDER BY PK;
|
||||
|
||||
SELECT comp();
|
||||
|
||||
DROP TABLE T;
|
||||
|
||||
|
||||
-- Combine with other DDL
|
||||
CREATE TABLE T(pk INT NOT NULL PRIMARY KEY);
|
||||
|
||||
SELECT set('t');
|
||||
|
||||
INSERT INTO T VALUES (1), (2);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_int INT NOT NULL DEFAULT -1;
|
||||
|
||||
INSERT INTO T VALUES (3), (4);
|
||||
|
||||
ALTER TABLE T ADD COLUMN c_text TEXT DEFAULT 'Hello';
|
||||
|
||||
INSERT INTO T VALUES (5), (6);
|
||||
|
||||
ALTER TABLE T ALTER COLUMN c_text SET DEFAULT 'world',
|
||||
ALTER COLUMN c_int SET DEFAULT 1;
|
||||
|
||||
INSERT INTO T VALUES (7), (8);
|
||||
|
||||
SELECT * FROM T ORDER BY pk;
|
||||
|
||||
-- Add an index
|
||||
CREATE INDEX i ON T(c_int, c_text);
|
||||
|
||||
SELECT c_text FROM T WHERE c_int = -1;
|
||||
|
||||
SELECT comp();
|
||||
|
||||
DROP TABLE T;
|
||||
DROP FUNCTION set(name);
|
||||
DROP FUNCTION comp();
|
||||
DROP TABLE m;
|
||||
DROP TABLE has_volatile;
|
||||
DROP EVENT TRIGGER has_volatile_rewrite;
|
||||
DROP FUNCTION log_rewrite;
|
||||
DROP SCHEMA fast_default;
|
Loading…
x
Reference in New Issue
Block a user