diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2004-06-05 01:55:05 +0000 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2004-06-05 01:55:05 +0000 |
commit | ae93e5fd6e8a7e2321e87d23165d9d7660cde598 (patch) | |
tree | a8e22c4835283c61e137285ba2dabfe3feead1a9 /src/backend/access | |
parent | 8f2ea8b7b53a02078ba0393e6892ac5356a3631e (diff) | |
download | postgresql-ae93e5fd6e8a7e2321e87d23165d9d7660cde598.tar.gz postgresql-ae93e5fd6e8a7e2321e87d23165d9d7660cde598.zip |
Make the world very nearly safe for composite-type columns in tables.
1. Solve the problem of not having TOAST references hiding inside composite
values by establishing the rule that toasting only goes one level deep:
a tuple can contain toasted fields, but a composite-type datum that is
to be inserted into a tuple cannot. Enforcing this in heap_formtuple
is relatively cheap and it avoids a large increase in the cost of running
the tuptoaster during final storage of a row.
2. Fix some interesting problems in expansion of inherited queries that
reference whole-row variables. We never really did this correctly before,
but it's now relatively painless to solve by expanding the parent's
whole-row Var into a RowExpr() selecting the proper columns from the
child.
If you dike out the preventive check in CheckAttributeType(),
composite-type columns now seem to actually work. However, we surely
cannot ship them like this --- without I/O for composite types, you
can't get pg_dump to dump tables containing them. So a little more
work still to do.
Diffstat (limited to 'src/backend/access')
-rw-r--r-- | src/backend/access/common/heaptuple.c | 35 | ||||
-rw-r--r-- | src/backend/access/heap/tuptoaster.c | 133 |
2 files changed, 157 insertions, 11 deletions
diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index 266cf3bdcc2..88a9631e8cc 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -9,7 +9,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.91 2004/06/04 20:35:21 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.92 2004/06/05 01:55:04 tgl Exp $ * * NOTES * The old interface functions have been converted to macros @@ -21,6 +21,7 @@ #include "postgres.h" #include "access/heapam.h" +#include "access/tuptoaster.h" #include "catalog/pg_type.h" @@ -567,8 +568,9 @@ heap_formtuple(TupleDesc tupleDescriptor, unsigned long len; int hoff; bool hasnull = false; - int i; + Form_pg_attribute *att = tupleDescriptor->attrs; int numberOfAttributes = tupleDescriptor->natts; + int i; if (numberOfAttributes > MaxTupleAttributeNumber) ereport(ERROR, @@ -577,17 +579,34 @@ heap_formtuple(TupleDesc tupleDescriptor, numberOfAttributes, MaxTupleAttributeNumber))); /* - * Determine total space needed + * Check for nulls and embedded tuples; expand any toasted attributes + * in embedded tuples. This preserves the invariant that toasting can + * only go one level deep. + * + * We can skip calling toast_flatten_tuple_attribute() if the attribute + * couldn't possibly be of composite type. All composite datums are + * varlena and have alignment 'd'; furthermore they aren't arrays. + * Also, if an attribute is already toasted, it must have been sent to + * disk already and so cannot contain toasted attributes. */ for (i = 0; i < numberOfAttributes; i++) { if (nulls[i] != ' ') - { hasnull = true; - break; + else if (att[i]->attlen == -1 && + att[i]->attalign == 'd' && + att[i]->attndims == 0 && + !VARATT_IS_EXTENDED(values[i])) + { + values[i] = toast_flatten_tuple_attribute(values[i], + att[i]->atttypid, + att[i]->atttypmod); } } + /* + * Determine total space needed + */ len = offsetof(HeapTupleHeaderData, t_bits); if (hasnull) @@ -744,7 +763,11 @@ heap_deformtuple(HeapTuple tuple, bool slow = false; /* can we use/set attcacheoff? */ natts = tup->t_natts; - /* This min() operation is pure paranoia */ + /* + * In inheritance situations, it is possible that the given tuple actually + * has more fields than the caller is expecting. Don't run off the end + * of the caller's arrays. + */ natts = Min(natts, tdesc_natts); tp = (char *) tup + tup->t_hoff; diff --git a/src/backend/access/heap/tuptoaster.c b/src/backend/access/heap/tuptoaster.c index f206a3f28eb..c36cc42309e 100644 --- a/src/backend/access/heap/tuptoaster.c +++ b/src/backend/access/heap/tuptoaster.c @@ -8,7 +8,7 @@ * * * IDENTIFICATION - * $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.42 2004/06/04 20:35:21 tgl Exp $ + * $PostgreSQL: pgsql/src/backend/access/heap/tuptoaster.c,v 1.43 2004/06/05 01:55:04 tgl Exp $ * * * INTERFACE ROUTINES @@ -35,6 +35,7 @@ #include "utils/builtins.h" #include "utils/fmgroids.h" #include "utils/pg_lzcompress.h" +#include "utils/typcache.h" #undef TOAST_DEBUG @@ -458,10 +459,10 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup) * still in the tuple must be someone else's we cannot reuse. * Expand it to plain (and, probably, toast it again below). */ - if (VARATT_IS_EXTERNAL(DatumGetPointer(toast_values[i]))) + if (VARATT_IS_EXTERNAL(new_value)) { - toast_values[i] = PointerGetDatum(heap_tuple_untoast_attr( - (varattrib *) DatumGetPointer(toast_values[i]))); + new_value = heap_tuple_untoast_attr(new_value); + toast_values[i] = PointerGetDatum(new_value); toast_free[i] = true; need_change = true; need_free = true; @@ -470,7 +471,7 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup) /* * Remember the size of this attribute */ - toast_sizes[i] = VARATT_SIZE(DatumGetPointer(toast_values[i])); + toast_sizes[i] = VARATT_SIZE(new_value); } else { @@ -786,6 +787,128 @@ toast_insert_or_update(Relation rel, HeapTuple newtup, HeapTuple oldtup) /* ---------- + * toast_flatten_tuple_attribute - + * + * If a Datum is of composite type, "flatten" it to contain no toasted fields. + * This must be invoked on any potentially-composite field that is to be + * inserted into a tuple. Doing this preserves the invariant that toasting + * goes only one level deep in a tuple. + * ---------- + */ +Datum +toast_flatten_tuple_attribute(Datum value, + Oid typeId, int32 typeMod) +{ + TupleDesc tupleDesc; + HeapTupleHeader olddata; + HeapTupleHeader new_data; + int32 new_len; + HeapTupleData tmptup; + Form_pg_attribute *att; + int numAttrs; + int i; + bool need_change = false; + bool has_nulls = false; + Datum toast_values[MaxTupleAttributeNumber]; + char toast_nulls[MaxTupleAttributeNumber]; + bool toast_free[MaxTupleAttributeNumber]; + + /* + * See if it's a composite type, and get the tupdesc if so. + */ + tupleDesc = lookup_rowtype_tupdesc_noerror(typeId, typeMod, true); + if (tupleDesc == NULL) + return value; /* not a composite type */ + + att = tupleDesc->attrs; + numAttrs = tupleDesc->natts; + + /* + * Break down the tuple into fields. + */ + olddata = DatumGetHeapTupleHeader(value); + Assert(typeId == HeapTupleHeaderGetTypeId(olddata)); + Assert(typeMod == HeapTupleHeaderGetTypMod(olddata)); + /* Build a temporary HeapTuple control structure */ + tmptup.t_len = HeapTupleHeaderGetDatumLength(olddata); + ItemPointerSetInvalid(&(tmptup.t_self)); + tmptup.t_tableOid = InvalidOid; + tmptup.t_data = olddata; + + Assert(numAttrs <= MaxTupleAttributeNumber); + heap_deformtuple(&tmptup, tupleDesc, toast_values, toast_nulls); + + memset(toast_free, 0, numAttrs * sizeof(bool)); + + for (i = 0; i < numAttrs; i++) + { + /* + * Look at non-null varlena attributes + */ + if (toast_nulls[i] == 'n') + has_nulls = true; + else if (att[i]->attlen == -1) + { + varattrib *new_value; + + new_value = (varattrib *) DatumGetPointer(toast_values[i]); + if (VARATT_IS_EXTENDED(new_value)) + { + new_value = heap_tuple_untoast_attr(new_value); + toast_values[i] = PointerGetDatum(new_value); + toast_free[i] = true; + need_change = true; + } + } + } + + /* + * If nothing to untoast, just return the original tuple. + */ + if (!need_change) + return value; + + /* + * Calculate the new size of the tuple. Header size should not + * change, but data size might. + */ + new_len = offsetof(HeapTupleHeaderData, t_bits); + if (has_nulls) + new_len += BITMAPLEN(numAttrs); + if (olddata->t_infomask & HEAP_HASOID) + new_len += sizeof(Oid); + new_len = MAXALIGN(new_len); + Assert(new_len == olddata->t_hoff); + new_len += ComputeDataSize(tupleDesc, toast_values, toast_nulls); + + new_data = (HeapTupleHeader) palloc0(new_len); + + /* + * Put the tuple header and the changed values into place + */ + memcpy(new_data, olddata, olddata->t_hoff); + + HeapTupleHeaderSetDatumLength(new_data, new_len); + + DataFill((char *) new_data + olddata->t_hoff, + tupleDesc, + toast_values, + toast_nulls, + &(new_data->t_infomask), + has_nulls ? new_data->t_bits : NULL); + + /* + * Free allocated temp values + */ + for (i = 0; i < numAttrs; i++) + if (toast_free[i]) + pfree(DatumGetPointer(toast_values[i])); + + return PointerGetDatum(new_data); +} + + +/* ---------- * toast_compress_datum - * * Create a compressed version of a varlena datum |