/* * DatumGetExpandedArray: get a writable expanded array from an input argument * * Caution: if the input is a read/write pointer, this returns the input * argument; so callers must be sure that their changes are "safe", that is * they cannot leave the array in a corrupt state. */ ExpandedArrayHeader * DatumGetExpandedArray(Datum d) { /* If it's a writable expanded array already, just return it */ if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) { ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d); Assert(eah->ea_magic == EA_MAGIC); return eah; } /* Else expand the hard way */ d = expand_array(d, CurrentMemoryContext, NULL); return (ExpandedArrayHeader *) DatumGetEOHP(d); }
/* * Delete an expanded object (must be referenced by a R/W pointer). */ void DeleteExpandedObject(Datum d) { ExpandedObjectHeader *eohptr = DatumGetEOHP(d); /* Assert caller gave a R/W pointer */ Assert(VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))); /* Kill it */ MemoryContextDelete(eohptr->eoh_context); }
/*------------------------------------------------------------------------- * datumCopy * * Make a copy of a non-NULL datum. * * If the datatype is pass-by-reference, memory is obtained with palloc(). * * If the value is a reference to an expanded object, we flatten into memory * obtained with palloc(). We need to copy because one of the main uses of * this function is to copy a datum out of a transient memory context that's * about to be destroyed, and the expanded object is probably in a child * context that will also go away. Moreover, many callers assume that the * result is a single pfree-able chunk. *------------------------------------------------------------------------- */ Datum datumCopy(Datum value, bool typByVal, int typLen) { Datum res; if (typByVal) res = value; else if (typLen == -1) { /* It is a varlena datatype */ struct varlena *vl = (struct varlena *) DatumGetPointer(value); if (!vl) return PointerGetDatum(NULL); if (VARATT_IS_EXTERNAL_EXPANDED(vl)) { /* Flatten into the caller's memory context */ ExpandedObjectHeader *eoh = DatumGetEOHP(value); Size resultsize; char *resultptr; resultsize = EOH_get_flat_size(eoh); resultptr = (char *) palloc(resultsize); EOH_flatten_into(eoh, (void *) resultptr, resultsize); res = PointerGetDatum(resultptr); } else { /* Otherwise, just copy the varlena datum verbatim */ Size realSize; char *resultptr; realSize = (Size) VARSIZE_ANY(vl); resultptr = (char *) palloc(realSize); memcpy(resultptr, vl, realSize); res = PointerGetDatum(resultptr); } } else { /* Pass by reference, but not varlena, so not toasted */ Size realSize; char *resultptr; realSize = datumGetSize(value, typByVal, typLen); resultptr = (char *) palloc(realSize); memcpy(resultptr, DatumGetPointer(value), realSize); res = PointerGetDatum(resultptr); } return res; }
/* * heap_compute_data_size * Determine size of the data area of a tuple to be constructed */ Size heap_compute_data_size(TupleDesc tupleDesc, Datum *values, bool *isnull) { Size data_length = 0; int i; int numberOfAttributes = tupleDesc->natts; Form_pg_attribute *att = tupleDesc->attrs; for (i = 0; i < numberOfAttributes; i++) { Datum val; Form_pg_attribute atti; if (isnull[i]) continue; val = values[i]; atti = att[i]; if (ATT_IS_PACKABLE(atti) && VARATT_CAN_MAKE_SHORT(DatumGetPointer(val))) { /* * we're anticipating converting to a short varlena header, so * adjust length and don't count any alignment */ data_length += VARATT_CONVERTED_SHORT_SIZE(DatumGetPointer(val)); } else if (atti->attlen == -1 && VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val))) { /* * we want to flatten the expanded value so that the constructed * tuple doesn't depend on it */ data_length = att_align_nominal(data_length, atti->attalign); data_length += EOH_get_flat_size(DatumGetEOHP(val)); } else { data_length = att_align_datum(data_length, atti->attalign, atti->attlen, val); data_length = att_addlength_datum(data_length, atti->attlen, val); } } return data_length; }
/* * Transfer ownership of an expanded object to a new___ parent memory context. * The object must be referenced by a R/W pointer, and what we return is * always its "standard" R/W pointer, which is certain to have the same * lifespan as the object itself. (The passed-in pointer might not, and * in any case wouldn't provide a unique identifier if it's not that one.) */ Datum TransferExpandedObject(Datum d, MemoryContext new_parent) { ExpandedObjectHeader *eohptr = DatumGetEOHP(d); /* Assert caller gave a R/W pointer */ Assert(VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))); /* Transfer ownership */ MemoryContextSetParent(eohptr->eoh_context, new_parent); /* Return the object's standard read-write pointer */ return EOHPGetRWDatum(eohptr); }
/* * As above, when caller has the ability to cache element type info */ ExpandedArrayHeader * DatumGetExpandedArrayX(Datum d, ArrayMetaState *metacache) { /* If it's a writable expanded array already, just return it */ if (VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) { ExpandedArrayHeader *eah = (ExpandedArrayHeader *) DatumGetEOHP(d); Assert(eah->ea_magic == EA_MAGIC); /* Update cache if provided */ if (metacache) { metacache->element_type = eah->element_type; metacache->typlen = eah->typlen; metacache->typbyval = eah->typbyval; metacache->typalign = eah->typalign; } return eah; } /* Else expand using caller's cache if any */ d = expand_array(d, CurrentMemoryContext, metacache); return (ExpandedArrayHeader *) DatumGetEOHP(d); }
/* * DatumGetAnyArrayP: return either an expanded array or a detoasted varlena * array. The result must not be modified in-place. */ AnyArrayType * DatumGetAnyArrayP(Datum d) { ExpandedArrayHeader *eah; /* * If it's an expanded array (RW or RO), return the header pointer. */ if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(d))) { eah = (ExpandedArrayHeader *) DatumGetEOHP(d); Assert(eah->ea_magic == EA_MAGIC); return (AnyArrayType *) eah; } /* Else do regular detoasting as needed */ return (AnyArrayType *) PG_DETOAST_DATUM(d); }
/*------------------------------------------------------------------------- * datumSerialize * * Serialize a possibly-NULL datum into caller-provided storage. * * Note: "expanded" objects are flattened so as to produce a self-contained * representation, but other sorts of toast pointers are transferred as-is. * This is because the intended use of this function is to pass the value * to another process within the same database server. The other process * could not access an "expanded" object within this process's memory, but * we assume it can dereference the same TOAST pointers this one can. * * The format is as follows: first, we write a 4-byte header word, which * is either the length of a pass-by-reference datum, -1 for a * pass-by-value datum, or -2 for a NULL. If the value is NULL, nothing * further is written. If it is pass-by-value, sizeof(Datum) bytes * follow. Otherwise, the number of bytes indicated by the header word * follow. The caller is responsible for ensuring that there is enough * storage to store the number of bytes that will be written; use * datumEstimateSpace() to find out how many will be needed. * *start_address is updated to point to the byte immediately following * those written. *------------------------------------------------------------------------- */ void datumSerialize(Datum value, bool isnull, bool typByVal, int typLen, char **start_address) { ExpandedObjectHeader *eoh = NULL; int header; /* Write header word. */ if (isnull) header = -2; else if (typByVal) header = -1; else if (typLen == -1 && VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(value))) { eoh = DatumGetEOHP(value); header = EOH_get_flat_size(eoh); } else header = datumGetSize(value, typByVal, typLen); memcpy(*start_address, &header, sizeof(int)); *start_address += sizeof(int); /* If not null, write payload bytes. */ if (!isnull) { if (typByVal) { memcpy(*start_address, &value, sizeof(Datum)); *start_address += sizeof(Datum); } else if (eoh) { EOH_flatten_into(eoh, (void *) *start_address, header); *start_address += header; } else { memcpy(*start_address, DatumGetPointer(value), header); *start_address += header; } } }
/* * If the Datum represents a R/W expanded object, change it to R/O. * Otherwise return the original Datum. */ Datum MakeExpandedObjectReadOnly(Datum d, bool isnull, int16 typlen) { ExpandedObjectHeader *eohptr; /* Nothing to do if it's NULL or not a varlena type */ if (isnull || typlen != -1) return d; /* Nothing to do if not a read-write expanded-object pointer */ if (!VARATT_IS_EXTERNAL_EXPANDED_RW(DatumGetPointer(d))) return d; /* Now safe to extract the object pointer */ eohptr = DatumGetEOHP(d); /* Return the built-in read-only pointer instead of given pointer */ return EOHPGetRODatum(eohptr); }
/*------------------------------------------------------------------------- * datumEstimateSpace * * Compute the amount of space that datumSerialize will require for a * particular Datum. *------------------------------------------------------------------------- */ Size datumEstimateSpace(Datum value, bool isnull, bool typByVal, int typLen) { Size sz = sizeof(int); if (!isnull) { /* no need to use add_size, can't overflow */ if (typByVal) sz += sizeof(Datum); else if (typLen == -1 && VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(value))) { /* Expanded objects need to be flattened, see comment below */ sz += EOH_get_flat_size(DatumGetEOHP(value)); } else sz += datumGetSize(value, typByVal, typLen); } return sz; }
/*------------------------------------------------------------------------- * datumEstimateSpace * * Compute the amount of space that datumSerialize will require for a * particular Datum. *------------------------------------------------------------------------- */ Size datumEstimateSpace(Datum value, bool isnull, bool typByVal, int typLen) { Size sz = sizeof(int); if (!isnull) { /* no need to use add_size, can't overflow */ if (typByVal) sz += sizeof(Datum); else if (VARATT_IS_EXTERNAL_EXPANDED(value)) { ExpandedObjectHeader *eoh = DatumGetEOHP(value); sz += EOH_get_flat_size(eoh); } else sz += datumGetSize(value, typByVal, typLen); } return sz; }
/* * heap_fill_tuple * Load data portion of a tuple from values/isnull arrays * * We also fill the null bitmap (if any) and set the infomask bits * that reflect the tuple's data contents. * * NOTE: it is now REQUIRED that the caller have pre-zeroed the data area. */ void heap_fill_tuple(TupleDesc tupleDesc, Datum *values, bool *isnull, char *data, Size data_size, uint16 *infomask, bits8 *bit) { bits8 *bitP; int bitmask; int i; int numberOfAttributes = tupleDesc->natts; Form_pg_attribute *att = tupleDesc->attrs; #ifdef USE_ASSERT_CHECKING char *start = data; #endif if (bit != NULL) { bitP = &bit[-1]; bitmask = HIGHBIT; } else { /* just to keep compiler quiet */ bitP = NULL; bitmask = 0; } *infomask &= ~(HEAP_HASNULL | HEAP_HASVARWIDTH | HEAP_HASEXTERNAL); for (i = 0; i < numberOfAttributes; i++) { Size data_length; if (bit != NULL) { if (bitmask != HIGHBIT) bitmask <<= 1; else { bitP += 1; *bitP = 0x0; bitmask = 1; } if (isnull[i]) { *infomask |= HEAP_HASNULL; continue; } *bitP |= bitmask; } /* * XXX we use the att_align macros on the pointer value itself, not on * an offset. This is a bit of a hack. */ if (att[i]->attbyval) { /* pass-by-value */ data = (char *) att_align_nominal(data, att[i]->attalign); store_att_byval(data, values[i], att[i]->attlen); data_length = att[i]->attlen; } else if (att[i]->attlen == -1) { /* varlena */ Pointer val = DatumGetPointer(values[i]); *infomask |= HEAP_HASVARWIDTH; if (VARATT_IS_EXTERNAL(val)) { if (VARATT_IS_EXTERNAL_EXPANDED(val)) { /* * we want to flatten the expanded value so that the * constructed tuple doesn't depend on it */ ExpandedObjectHeader *eoh = DatumGetEOHP(values[i]); data = (char *) att_align_nominal(data, att[i]->attalign); data_length = EOH_get_flat_size(eoh); EOH_flatten_into(eoh, data, data_length); } else { *infomask |= HEAP_HASEXTERNAL; /* no alignment, since it's short by definition */ data_length = VARSIZE_EXTERNAL(val); memcpy(data, val, data_length); } } else if (VARATT_IS_SHORT(val)) { /* no alignment for short varlenas */ data_length = VARSIZE_SHORT(val); memcpy(data, val, data_length); } else if (VARLENA_ATT_IS_PACKABLE(att[i]) && VARATT_CAN_MAKE_SHORT(val)) { /* convert to short varlena -- no alignment */ data_length = VARATT_CONVERTED_SHORT_SIZE(val); SET_VARSIZE_SHORT(data, data_length); memcpy(data + 1, VARDATA(val), data_length - 1); } else { /* full 4-byte header varlena */ data = (char *) att_align_nominal(data, att[i]->attalign); data_length = VARSIZE(val); memcpy(data, val, data_length); } } else if (att[i]->attlen == -2) { /* cstring ... never needs alignment */ *infomask |= HEAP_HASVARWIDTH; Assert(att[i]->attalign == 'c'); data_length = strlen(DatumGetCString(values[i])) + 1; memcpy(data, DatumGetPointer(values[i]), data_length); } else { /* fixed-length pass-by-reference */ data = (char *) att_align_nominal(data, att[i]->attalign); Assert(att[i]->attlen > 0); data_length = att[i]->attlen; memcpy(data, DatumGetPointer(values[i]), data_length); } data += data_length; } Assert((data - start) == data_size); }
/* * 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; }
/* * expand_array: convert an array Datum into an expanded array * * The expanded object will be a child of parentcontext. * * Some callers can provide cache space to avoid repeated lookups of element * type data across calls; if so, pass a metacache pointer, making sure that * metacache->element_type is initialized to InvalidOid before first call. * If no cross-call caching is required, pass NULL for metacache. */ Datum expand_array(Datum arraydatum, MemoryContext parentcontext, ArrayMetaState *metacache) { ArrayType *array; ExpandedArrayHeader *eah; MemoryContext objcxt; MemoryContext oldcxt; ArrayMetaState fakecache; /* * Allocate private context for expanded object. We start by assuming * that the array won't be very large; but if it does grow a lot, don't * constrain aset.c's large-context behavior. */ objcxt = AllocSetContextCreate(parentcontext, "expanded array", ALLOCSET_START_SMALL_SIZES); /* Set up expanded array header */ eah = (ExpandedArrayHeader *) MemoryContextAlloc(objcxt, sizeof(ExpandedArrayHeader)); EOH_init_header(&eah->hdr, &EA_methods, objcxt); eah->ea_magic = EA_MAGIC; /* If the source is an expanded array, we may be able to optimize */ if (VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(arraydatum))) { ExpandedArrayHeader *oldeah = (ExpandedArrayHeader *) DatumGetEOHP(arraydatum); Assert(oldeah->ea_magic == EA_MAGIC); /* * Update caller's cache if provided; we don't need it this time, but * next call might be for a non-expanded source array. Furthermore, * if the caller didn't provide a cache area, use some local storage * to cache anyway, thereby avoiding a catalog lookup in the case * where we fall through to the flat-copy code path. */ if (metacache == NULL) metacache = &fakecache; metacache->element_type = oldeah->element_type; metacache->typlen = oldeah->typlen; metacache->typbyval = oldeah->typbyval; metacache->typalign = oldeah->typalign; /* * If element type is pass-by-value and we have a Datum-array * representation, just copy the source's metadata and Datum/isnull * arrays. The original flat array, if present at all, adds no * additional information so we need not copy it. */ if (oldeah->typbyval && oldeah->dvalues != NULL) { copy_byval_expanded_array(eah, oldeah); /* return a R/W pointer to the expanded array */ return EOHPGetRWDatum(&eah->hdr); } /* * Otherwise, either we have only a flat representation or the * elements are pass-by-reference. In either case, the best thing * seems to be to copy the source as a flat representation and then * deconstruct that later if necessary. For the pass-by-ref case, we * could perhaps save some cycles with custom code that generates the * deconstructed representation in parallel with copying the values, * but it would be a lot of extra code for fairly marginal gain. So, * fall through into the flat-source code path. */ } /* * Detoast and copy source array into private context, as a flat array. * * Note that this coding risks leaking some memory in the private context * if we have to fetch data from a TOAST table; however, experimentation * says that the leak is minimal. Doing it this way saves a copy step, * which seems worthwhile, especially if the array is large enough to need * external storage. */ oldcxt = MemoryContextSwitchTo(objcxt); array = DatumGetArrayTypePCopy(arraydatum); MemoryContextSwitchTo(oldcxt); eah->ndims = ARR_NDIM(array); /* note these pointers point into the fvalue header! */ eah->dims = ARR_DIMS(array); eah->lbound = ARR_LBOUND(array); /* Save array's element-type data for possible use later */ eah->element_type = ARR_ELEMTYPE(array); if (metacache && metacache->element_type == eah->element_type) { /* We have a valid cache of representational data */ eah->typlen = metacache->typlen; eah->typbyval = metacache->typbyval; eah->typalign = metacache->typalign; } else { /* No, so look it up */ get_typlenbyvalalign(eah->element_type, &eah->typlen, &eah->typbyval, &eah->typalign); /* Update cache if provided */ if (metacache) { metacache->element_type = eah->element_type; metacache->typlen = eah->typlen; metacache->typbyval = eah->typbyval; metacache->typalign = eah->typalign; } } /* we don't make a deconstructed representation now */ eah->dvalues = NULL; eah->dnulls = NULL; eah->dvalueslen = 0; eah->nelems = 0; eah->flat_size = 0; /* remember we have a flat representation */ eah->fvalue = array; eah->fstartptr = ARR_DATA_PTR(array); eah->fendptr = ((char *) array) + ARR_SIZE(array); /* return a R/W pointer to the expanded array */ return EOHPGetRWDatum(&eah->hdr); }