Datum variant_image_eq(PG_FUNCTION_ARGS) { Variant l = (Variant) PG_GETARG_DATUM(0); Variant r = (Variant) PG_GETARG_DATUM(1); int cmp; /* * To avoid detoasting we use _ANY variations on VAR*, but that means we must * make sure to use VARSIZE_ANY_EXHDR, *not* VARSIZE_ANY! */ if(VARSIZE_ANY_EXHDR(l) != VARSIZE_ANY_EXHDR(r)) PG_RETURN_BOOL(false); /* * At this point we need to detoast. We could theoretically leave data * compressed, but since there's no direct support for that we don't bother. */ l = (Variant) PG_DETOAST_DATUM_PACKED(l); r = (Variant) PG_DETOAST_DATUM_PACKED(r); cmp = memcmp(VARDATA_ANY(l), VARDATA_ANY(r), VARSIZE_ANY_EXHDR(l)); PG_FREE_IF_COPY(l, 0); PG_FREE_IF_COPY(r, 1); PG_RETURN_BOOL( cmp == 0 ? true : false); }
Datum variant_hash(PG_FUNCTION_ARGS) { Variant v = (Variant) PG_DETOAST_DATUM_PACKED(PG_GETARG_DATUM(0)); char *data; int len; Datum result; Assert(fcinfo->flinfo->fn_strict); /* Must be strict */ data = VARDATA_ANY(v); len = VARSIZE_ANY_EXHDR(v); result = hash_any((unsigned char *) data, len); /* Avoid leaking memory for toasted inputs */ PG_FREE_IF_COPY(v, 0); return result; }
/* * Create an external variant from our internal representation */ static Variant make_variant(VariantInt vi, FunctionCallInfo fcinfo, IOFuncSelector func) { VariantCache *cache; Variant v; bool oid_overflow=OID_TOO_LARGE(vi->typid); long variant_length, data_length; /* long because we subtract */ Pointer data_ptr = 0; uint flags = 0; cache = get_cache(fcinfo, vi, func); Assert(cache->typid = vi->typid); #ifdef VARIANT_TEST_OID vi->typid += OID_MASK; oid_overflow=OID_TOO_LARGE(vi->typid); #endif if(vi->isnull) { flags |= VAR_ISNULL; data_length = 0; } else if(cache->typlen == -1) /* varlena */ { /* * Short varlena is OK, but we need to make sure it's not external. It's OK * to leave compressed varlena's alone too, but detoast_packed will * uncompress them. We'll just follow rangetype.c's lead here. */ vi->data = PointerGetDatum( PG_DETOAST_DATUM_PACKED(vi->data) ); data_ptr = DatumGetPointer(vi->data); /* * Because we don't store varlena aligned or with it's header, our * data_length is simply the varlena length. */ data_length = VARSIZE_ANY_EXHDR(vi->data); data_ptr = VARDATA_ANY(data_ptr); } else if(cache->typlen == -2) /* cstring */ { data_length = strlen(DatumGetCString(vi->data)); /* We don't store NUL terminator */ data_ptr = DatumGetPointer(vi->data); } else { Assert(cache->typlen >= 0); if(cache->typbyval) { data_length = VHDRSZ; /* Start with header size to make sure alignment is correct */ data_length = (long) VDATAPTR_ALIGN(data_length, cache->typalign); data_length += cache->typlen; data_length -= VHDRSZ; } else /* fixed length, pass by reference */ { data_length = cache->typlen; data_ptr = DatumGetPointer(vi->data); } } /* If typid is too large then we need an extra byte */ variant_length = VHDRSZ + data_length + (oid_overflow ? sizeof(char) : 0); if( variant_length < 0 ) elog(ERROR, "Negative variant_length %li", variant_length); v = palloc0(variant_length); SET_VARSIZE(v, variant_length); v->pOid = vi->typid; v->typmod = vi->typmod; if(oid_overflow) { flags |= VAR_OVERFLOW; /* Reset high pOid byte to zero */ v->pOid &= 0x00FFFFFF; /* Store high byte of OID at the end of our structure */ *((char *) v + VARSIZE(v) - 1) = vi->typid >> 24; } /* * Be careful not to overwrite the valid OID data */ v->pOid |= flags; Assert( get_oid(v, &flags) == vi->typid ); if(!vi->isnull) { if(cache->typbyval) { Pointer p = VDATAPTR_ALIGN(v, cache->typalign); store_att_byval(p, vi->data, cache->typlen); } else memcpy(VDATAPTR(v), data_ptr, data_length); } return v; }
/* * record_image_eq : * compares two records for identical contents, based on byte images * result : * returns true if the records are identical, false otherwise. * * Note: we do not use record_image_cmp here, since we can avoid * de-toasting for unequal lengths this way. */ Datum record_image_eq(PG_FUNCTION_ARGS) { HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0); HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1); bool result = true; Oid tupType1; Oid tupType2; int32 tupTypmod1; int32 tupTypmod2; TupleDesc tupdesc1; TupleDesc tupdesc2; HeapTupleData tuple1; HeapTupleData tuple2; int ncolumns1; int ncolumns2; RecordCompareData *my_extra; int ncols; Datum *values1; Datum *values2; bool *nulls1; bool *nulls2; int i1; int i2; int j; /* Extract type info from the tuples */ tupType1 = HeapTupleHeaderGetTypeId(record1); tupTypmod1 = HeapTupleHeaderGetTypMod(record1); tupdesc1 = lookup_rowtype_tupdesc(tupType1, tupTypmod1); ncolumns1 = tupdesc1->natts; tupType2 = HeapTupleHeaderGetTypeId(record2); tupTypmod2 = HeapTupleHeaderGetTypMod(record2); tupdesc2 = lookup_rowtype_tupdesc(tupType2, tupTypmod2); ncolumns2 = tupdesc2->natts; /* Build temporary HeapTuple control structures */ tuple1.t_len = HeapTupleHeaderGetDatumLength(record1); ItemPointerSetInvalid(&(tuple1.t_self)); tuple1.t_tableOid = InvalidOid; tuple1.t_data = record1; tuple2.t_len = HeapTupleHeaderGetDatumLength(record2); ItemPointerSetInvalid(&(tuple2.t_self)); tuple2.t_tableOid = InvalidOid; tuple2.t_data = record2; /* * We arrange to look up the needed comparison info just once per series * of calls, assuming the record types don't change underneath us. */ ncols = Max(ncolumns1, ncolumns2); my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL || my_extra->ncolumns < ncols) { fcinfo->flinfo->fn_extra = MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, offsetof(RecordCompareData, columns) + ncols * sizeof(ColumnCompareData)); my_extra = (RecordCompareData *) fcinfo->flinfo->fn_extra; my_extra->ncolumns = ncols; my_extra->record1_type = InvalidOid; my_extra->record1_typmod = 0; my_extra->record2_type = InvalidOid; my_extra->record2_typmod = 0; } if (my_extra->record1_type != tupType1 || my_extra->record1_typmod != tupTypmod1 || my_extra->record2_type != tupType2 || my_extra->record2_typmod != tupTypmod2) { MemSet(my_extra->columns, 0, ncols * sizeof(ColumnCompareData)); my_extra->record1_type = tupType1; my_extra->record1_typmod = tupTypmod1; my_extra->record2_type = tupType2; my_extra->record2_typmod = tupTypmod2; } /* Break down the tuples into fields */ values1 = (Datum *) palloc(ncolumns1 * sizeof(Datum)); nulls1 = (bool *) palloc(ncolumns1 * sizeof(bool)); heap_deform_tuple(&tuple1, tupdesc1, values1, nulls1); values2 = (Datum *) palloc(ncolumns2 * sizeof(Datum)); nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool)); heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2); /* * Scan corresponding columns, allowing for dropped columns in different * places in the two rows. i1 and i2 are physical column indexes, j is * the logical column index. */ i1 = i2 = j = 0; while (i1 < ncolumns1 || i2 < ncolumns2) { /* * Skip dropped columns */ if (i1 < ncolumns1 && tupdesc1->attrs[i1]->attisdropped) { i1++; continue; } if (i2 < ncolumns2 && tupdesc2->attrs[i2]->attisdropped) { i2++; continue; } if (i1 >= ncolumns1 || i2 >= ncolumns2) break; /* we'll deal with mismatch below loop */ /* * Have two matching columns, they must be same type */ if (tupdesc1->attrs[i1]->atttypid != tupdesc2->attrs[i2]->atttypid) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot compare dissimilar column types %s and %s at record column %d", format_type_be(tupdesc1->attrs[i1]->atttypid), format_type_be(tupdesc2->attrs[i2]->atttypid), j + 1))); /* * We consider two NULLs equal; NULL > not-NULL. */ if (!nulls1[i1] || !nulls2[i2]) { if (nulls1[i1] || nulls2[i2]) { result = false; break; } /* Compare the pair of elements */ if (tupdesc1->attrs[i1]->attlen == -1) { Size len1, len2; len1 = toast_raw_datum_size(values1[i1]); len2 = toast_raw_datum_size(values2[i2]); /* No need to de-toast if lengths don't match. */ if (len1 != len2) result = false; else { struct varlena *arg1val; struct varlena *arg2val; arg1val = PG_DETOAST_DATUM_PACKED(values1[i1]); arg2val = PG_DETOAST_DATUM_PACKED(values2[i2]); result = (memcmp(VARDATA_ANY(arg1val), VARDATA_ANY(arg2val), len1 - VARHDRSZ) == 0); /* Only free memory if it's a copy made here. */ if ((Pointer) arg1val != (Pointer) values1[i1]) pfree(arg1val); if ((Pointer) arg2val != (Pointer) values2[i2]) pfree(arg2val); } } else if (tupdesc1->attrs[i1]->attbyval) { switch (tupdesc1->attrs[i1]->attlen) { case 1: result = (GET_1_BYTE(values1[i1]) == GET_1_BYTE(values2[i2])); break; case 2: result = (GET_2_BYTES(values1[i1]) == GET_2_BYTES(values2[i2])); break; case 4: result = (GET_4_BYTES(values1[i1]) == GET_4_BYTES(values2[i2])); break; #if SIZEOF_DATUM == 8 case 8: result = (GET_8_BYTES(values1[i1]) == GET_8_BYTES(values2[i2])); break; #endif default: Assert(false); /* cannot happen */ } } else { result = (memcmp(DatumGetPointer(values1[i1]), DatumGetPointer(values2[i2]), tupdesc1->attrs[i1]->attlen) == 0); } if (!result) break; } /* equal, so continue to next column */ i1++, i2++, j++; } /* * If we didn't break out of the loop early, check for column count * mismatch. (We do not report such mismatch if we found unequal column * values; is that a feature or a bug?) */ if (result) { if (i1 != ncolumns1 || i2 != ncolumns2) ereport(ERROR, (errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("cannot compare record types with different numbers of columns"))); } pfree(values1); pfree(nulls1); pfree(values2); pfree(nulls2); ReleaseTupleDesc(tupdesc1); ReleaseTupleDesc(tupdesc2); /* Avoid leaking memory when handed toasted input. */ PG_FREE_IF_COPY(record1, 0); PG_FREE_IF_COPY(record2, 1); PG_RETURN_BOOL(result); }
/* * Convert a HeapTuple into a byte-sequence, and store it directly * into a chunklist for transmission. * * This code is based on the printtup_internal_20() function in printtup.c. */ void SerializeTupleIntoChunks(GenericTuple gtuple, SerTupInfo *pSerInfo, TupleChunkList tcList) { TupleChunkListItem tcItem = NULL; MemoryContext oldCtxt; TupleDesc tupdesc; int i, natts; AssertArg(tcList != NULL); AssertArg(gtuple != NULL); AssertArg(pSerInfo != NULL); tupdesc = pSerInfo->tupdesc; natts = tupdesc->natts; /* get ready to go */ tcList->p_first = NULL; tcList->p_last = NULL; tcList->num_chunks = 0; tcList->serialized_data_length = 0; tcList->max_chunk_length = Gp_max_tuple_chunk_size; if (natts == 0) { tcItem = getChunkFromCache(&pSerInfo->chunkCache); if (tcItem == NULL) { ereport(FATAL, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("Could not allocate space for first chunk item in new chunk list."))); } /* TC_EMTPY is just one chunk */ SetChunkType(tcItem->chunk_data, TC_EMPTY); tcItem->chunk_length = TUPLE_CHUNK_HEADER_SIZE; appendChunkToTCList(tcList, tcItem); return; } tcItem = getChunkFromCache(&pSerInfo->chunkCache); if (tcItem == NULL) { ereport(FATAL, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("Could not allocate space for first chunk item in new chunk list."))); } /* assume that we'll take a single chunk */ SetChunkType(tcItem->chunk_data, TC_WHOLE); tcItem->chunk_length = TUPLE_CHUNK_HEADER_SIZE; appendChunkToTCList(tcList, tcItem); AssertState(s_tupSerMemCtxt != NULL); if (is_memtuple(gtuple)) { MemTuple mtuple = (MemTuple) gtuple; addByteStringToChunkList(tcList, (char *) mtuple, memtuple_get_size(mtuple), &pSerInfo->chunkCache); addPadding(tcList, &pSerInfo->chunkCache, memtuple_get_size(mtuple)); } else { HeapTuple tuple = (HeapTuple) gtuple; HeapTupleHeader t_data = tuple->t_data; TupSerHeader tsh; unsigned int datalen; unsigned int nullslen; datalen = tuple->t_len - t_data->t_hoff; if (HeapTupleHasNulls(tuple)) nullslen = BITMAPLEN(HeapTupleHeaderGetNatts(t_data)); else nullslen = 0; tsh.tuplen = sizeof(TupSerHeader) + TYPEALIGN(TUPLE_CHUNK_ALIGN,nullslen) + datalen; tsh.natts = HeapTupleHeaderGetNatts(t_data); tsh.infomask = t_data->t_infomask; addByteStringToChunkList(tcList, (char *)&tsh, sizeof(TupSerHeader), &pSerInfo->chunkCache); /* If we don't have any attributes which have been toasted, we * can be very very simple: just send the raw data. */ if ((tsh.infomask & HEAP_HASEXTERNAL) == 0) { if (nullslen) { addByteStringToChunkList(tcList, (char *)t_data->t_bits, nullslen, &pSerInfo->chunkCache); addPadding(tcList,&pSerInfo->chunkCache,nullslen); } addByteStringToChunkList(tcList, (char *)t_data + t_data->t_hoff, datalen, &pSerInfo->chunkCache); addPadding(tcList,&pSerInfo->chunkCache,datalen); } else { /* We have to be more careful when we have tuples that * have been toasted. Ideally we'd like to send the * untoasted attributes in as "raw" a format as possible * but that makes rebuilding the tuple harder . */ oldCtxt = MemoryContextSwitchTo(s_tupSerMemCtxt); /* deconstruct the tuple (faster than a heap_getattr loop) */ heap_deform_tuple(tuple, tupdesc, pSerInfo->values, pSerInfo->nulls); MemoryContextSwitchTo(oldCtxt); /* Send the nulls character-array. */ addByteStringToChunkList(tcList, pSerInfo->nulls, natts, &pSerInfo->chunkCache); addPadding(tcList,&pSerInfo->chunkCache,natts); /* * send the attributes of this tuple: NOTE anything which allocates * temporary space (e.g. could result in a PG_DETOAST_DATUM) should be * executed with the memory context set to s_tupSerMemCtxt */ for (i = 0; i < natts; ++i) { SerAttrInfo *attrInfo = pSerInfo->myinfo + i; Datum origattr = pSerInfo->values[i], attr; /* skip null attributes (already taken care of above) */ if (pSerInfo->nulls[i]) continue; if (attrInfo->typlen == -1) { int32 sz; char *data; /* * If we have a toasted datum, forcibly detoast it here to avoid * memory leakage: we want to force the detoast allocation(s) to * happen in our reset-able serialization context. */ oldCtxt = MemoryContextSwitchTo(s_tupSerMemCtxt); attr = PointerGetDatum(PG_DETOAST_DATUM_PACKED(origattr)); MemoryContextSwitchTo(oldCtxt); sz = VARSIZE_ANY_EXHDR(attr); data = VARDATA_ANY(attr); /* Send length first, then data */ addInt32ToChunkList(tcList, sz, &pSerInfo->chunkCache); addByteStringToChunkList(tcList, data, sz, &pSerInfo->chunkCache); addPadding(tcList, &pSerInfo->chunkCache, sz); } else if (attrInfo->typlen == -2) { int32 sz; char *data; /* CString, we would send the string with the terminating '\0' */ data = DatumGetCString(origattr); sz = strlen(data) + 1; /* Send length first, then data */ addInt32ToChunkList(tcList, sz, &pSerInfo->chunkCache); addByteStringToChunkList(tcList, data, sz, &pSerInfo->chunkCache); addPadding(tcList, &pSerInfo->chunkCache, sz); } else if (attrInfo->typbyval) { /* * We send a full-width Datum for all pass-by-value types, regardless of * the actual size. */ addByteStringToChunkList(tcList, (char *) &origattr, sizeof(Datum), &pSerInfo->chunkCache); addPadding(tcList, &pSerInfo->chunkCache, sizeof(Datum)); } else { addByteStringToChunkList(tcList, DatumGetPointer(origattr), attrInfo->typlen, &pSerInfo->chunkCache); addPadding(tcList, &pSerInfo->chunkCache, attrInfo->typlen); attr = origattr; } } MemoryContextReset(s_tupSerMemCtxt); } } /* * if we have more than 1 chunk we have to set the chunk types on our * first chunk and last chunk */ if (tcList->num_chunks > 1) { TupleChunkListItem first, last; first = tcList->p_first; last = tcList->p_last; Assert(first != NULL); Assert(first != last); Assert(last != NULL); SetChunkType(first->chunk_data, TC_PARTIAL_START); SetChunkType(last->chunk_data, TC_PARTIAL_END); /* * any intervening chunks are already set to TC_PARTIAL_MID when * allocated */ } return; }